2013-07-23 16 views
5

Ho un'immagine a 8 bit 640x480 che vorrei a ridursi ad un'immagine 320x240:Ridimensiona un'immagine a 8 bit da 2 con ARM NEON

void reducebytwo(uint8_t *dst, uint8_t *src) 
//src is 640x480, dst is 320x240 

Quale sarebbe il modo migliore per farlo utilizzando ARM SIMD NEON? Qualche esempio di codice da qualche parte?

Come punto di partenza, ho semplicemente vorrebbe fare l'equivalente di:

for (int h = 0; h < 240; h++) 
    for (int w = 0; w < 320; w++) 
     dst[h * 320 + w] = (src[640 * h * 2 + w * 2] + src[640 * h * 2 + w * 2 + 1] + src[640 * h * 2 + 640 + w * 2] + src[640 * h * 2 + 640 + w * 2 + 1])/4; 
+0

** Il migliore ** deve essere definito. Più veloce, alta qualità, dimensioni minime, ecc.? Per * la massima qualità *, ci sono diversi compromessi nella riduzione delle immagini. La conservazione del contenuto a bassa frequenza è importante in alcuni casi e ad alta frequenza in altri. Cosa è * 8-bit *? Una scala di grigi, mappata a colori o qualcos'altro? –

+0

È un input di scala di grigi. Migliore = più veloce. – gregoiregentil

risposta

1

Ecco la versione di ASM sul reduce_line che @Nils Pipenbrinck suggerito

static void reduce2_neon_line(uint8_t* __restrict src1, uint8_t* __restrict src2, uint8_t* __restrict dest, int width) { 
    for(int i=0; i<width; i+=16) { 
     asm (
      "pld [%[line1], #0xc00]  \n" 
      "pld [%[line2], #0xc00]  \n" 
      "vldm %[line1]!, {d0,d1} \n" 
      "vldm %[line2]!, {d2,d3} \n" 
      "vpaddl.u8 q0, q0   \n" 
      "vpaddl.u8 q1, q1   \n" 
      "vadd.u16 q0, q1   \n" 
      "vshrn.u16 d0, q0, #2  \n" 

      "vst1.8 {d0}, [%[dst]]! \n" 

      : 
      : [line1] "r"(src1), [line2] "r"(src2), [dst] "r"(dest) 
      : "q0", "q1", "memory" 
      ); 
    } 
} 

Si tratta di circa 4 volte più veloce versione C (testato su iPhone 5).

5

Questo è un 1-1 traduzione del vostro codice per armare NEON intrinseche:

#include <arm_neon.h> 
#include <stdint.h> 

static void resize_line (uint8_t * __restrict src1, uint8_t * __restrict src2, uint8_t * __restrict dest) 
{ 
    int i; 
    for (i=0; i<640; i+=16) 
    { 
    // load upper line and add neighbor pixels: 
    uint16x8_t a = vpaddlq_u8 (vld1q_u8 (src1)); 

    // load lower line and add neighbor pixels: 
    uint16x8_t b = vpaddlq_u8 (vld1q_u8 (src2)); 

    // sum of upper and lower line: 
    uint16x8_t c = vaddq_u16 (a,b); 

    // divide by 4, convert to char and store: 
    vst1_u8 (dest, vshrn_n_u16 (c, 2)); 

    // move pointers to next chunk of data 
    src1+=16; 
    src2+=16; 
    dest+=8; 
    } 
} 

void resize_image (uint8_t * src, uint8_t * dest) 
{ 
    int h;  
    for (h = 0; h < 240 - 1; h++) 
    { 
    resize_line (src+640*(h*2+0), 
       src+640*(h*2+1), 
       dest+320*h); 
    } 
} 

processi IT 32 sorgenti-pixel e genera 8 pixel in uscita per iterazione.

Ho dato una rapida occhiata all'uscita dell'assemblatore e sembra a posto. È possibile ottenere prestazioni migliori se si scrive la funzione resize_line nell'assembler, srotolare il ciclo ed eliminare le bancarelle della pipeline. Questo ti darebbe un fattore stimato di tre aumenti delle prestazioni.

Dovrebbe essere molto più veloce dell'implementazione senza modifiche di assemblatore.

Nota: non ho ancora testato il codice ...

+0

Grande! Pensi che avere l'intera funzione resize_image in assembler sarebbe molto più veloce o pensi che con il tuo suggerimento, ho già risparmiato il 90% del tempo? – gregoiregentil

+0

Sarebbe più veloce .. non ci sono dubbi. –

1

Se non siete troppo interessati con precisione allora questo ciclo interno dovrebbe darvi il doppio del throughput di calcolo rispetto al algoritmo più preciso:

for (i=0; i<640; i+= 32) 
{ 
    uint8x16x2_t a, b; 
    uint8x16_t c, d; 

    /* load upper row, splitting even and odd pixels into a.val[0] 
    * and a.val[1] respectively. */ 
    a = vld2q_u8(src1); 

    /* as above, but for lower row */ 
    b = vld2q_u8(src2); 

    /* compute average of even and odd pixel pairs for upper row */ 
    c = vrhaddq_u8(a.val[0], a.val[1]); 
    /* compute average of even and odd pixel pairs for lower row */ 
    d = vrhaddq_u8(b.val[0], b.val[1]); 

    /* compute average of upper and lower rows, and store result */ 
    vst1q_u8(dest, vrhaddq_u8(c, d)); 

    src1+=32; 
    src2+=32; 
    dest+=16; 
} 

Funziona utilizzando l'operazione vhadd, che ha un risultato della stessa dimensione dell'input. In questo modo non è necessario riportare la somma finale a 8 bit, e tutta l'aritmetica è di otto bit, il che significa che è possibile eseguire il doppio delle operazioni per istruzione.

Tuttavia è meno preciso, poiché la somma intermedia è quantizzata e GCC 4.7 fa un lavoro terribile di generazione del codice. GCC 4.8 funziona bene.

L'intera operazione ha buone probabilità di essere vincolata all'I/O. Il ciclo dovrebbe essere srotolato per massimizzare la separazione tra i carichi e l'aritmetica, e (o PLD) dovrebbe essere usato per sollevare i dati in entrata in cache prima che sia necessario.