2013-07-23 10 views
5

Ich habe eine 8-Bit-640x480 Bild, das ich auf einem 320x240 Bild schrumpfen würden:Resize 8-Bit-Bild von 2 mit ARM NEON

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

Was wäre der beste Weg, das zu tun ARM mit SIMD NEON? Irgendein Beispielcode irgendwo?

Als Ausgangspunkt, ich möchte einfach das Äquivalent tun:

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

** Das Beste ** muss definiert werden. Schnellste, höchste Qualität, minimale Größe, etc? Für * höchste Qualität * gibt es unterschiedliche Kompromisse bei der Bildreduktion. Es ist wichtig, dass der Inhalt niedriger Frequenzen erhalten bleibt, in einigen Fällen und in anderen Fällen bei hohen Frequenzen. Was ist * 8-Bit *? Eine Graustufen-, Farbabbildung oder etwas anderes? –

+0

Es ist ein Graustufen-Eingang. Am besten = am schnellsten. – gregoiregentil

Antwort

1

ist die asm-Version auf reduce_line die @Nils Pipenbrinck

vorgeschlagen
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" 
      ); 
    } 
} 

Es ist etwa 4-mal schneller als C-Version (getestet auf iPhone 5).

5

Dies ist ein 0.59 Übersetzung des Codes NEON-Spezifika zu bewaffnen:

#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); 
    } 
} 

Verarbeitet 32 Quell-Pixel und erzeugt 8 Ausgangspixel pro Iteration.

Ich habe einen kurzen Blick auf den Assembler-Ausgang und es sieht gut aus. Sie können eine bessere Leistung erzielen, wenn Sie die Funktion resize_line in Assembler schreiben, die Schleife auflösen und Pipeline-Blockierungen beseitigen. Das würde Ihnen einen geschätzten Faktor von drei Leistungssteigerungen geben.

Es sollte viel schneller als Ihre Implementierung ohne Assembler Änderungen sein.

Anmerkung: Ich habe den Code nicht getestet ...

+0

Großartig! Denkst du, dass die gesamte resize_image Funktion in Assembler viel schneller wäre oder denkst du, dass ich mit deinem Vorschlag schon 90% Zeit gespart habe? – gregoiregentil

+0

Es wäre schneller .. kein Zweifel. –

1

Wenn Sie mit Präzision nicht zu befürchten, dann ist diese innere Schleife sollten Sie zweimal geben den Rechendurchsatz im Vergleich zu den genaueren Algorithmus:

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; 
} 

Es funktioniert mit der vhadd Operation, die ein Ergebnis die gleiche Größe wie der Eingang hat. Auf diese Weise müssen Sie die letzte Summe nicht wieder auf 8 Bit reduzieren, und die gesamte Arithmetik ist acht Bit lang, was bedeutet, dass Sie doppelt so viele Operationen pro Anweisung ausführen können.

Allerdings ist es weniger genau, weil die Zwischensumme quantisiert ist, und GCC 4.7 macht eine schreckliche Arbeit, Code zu erzeugen. GCC 4.8 geht gut.

Die gesamte Operation hat jedoch eine gute Chance, I/O gebunden zu sein. Die Schleife sollte entrollt werden, um die Trennung zwischen Lasten und Arithmetik zu maximieren, und __builtin_prefetch() (oder PLD) sollte verwendet werden, um die eingehenden Daten in Caches zu hieven, bevor sie benötigt werden. Hier

Verwandte Themen