2013-07-23 113 views
5

我有一個8位的640×480的圖像,我想縮小到320×240的圖像:調整大小的8位圖片

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

會是什麼做的,使用ARM的最佳方式SIMD NEON?任何示例代碼?

作爲一個起點,我只是想這樣做相當於:

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

**最佳**需要定義。最快,最高質量,最小尺寸等?對於*最高質量*,在圖像縮減方面存在不同的折衷。保留低頻內容在某些情況下很重要,在其他情況下保持頻率較高。什麼是* 8位*?灰度,顏色映射,還是別的? –

+0

這是一個灰度輸入。最好=最快。 – gregoiregentil

回答

1

這裏是關於reduce_line的ASM版本@Nils Pipenbrinck建議

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

這是更快的約4倍,則C的版本(在iPhone 5測試)。

5

這是一個一對一的轉換代碼的武裝NEON內在:

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

它處理32個源像素並且每次迭代產生8個輸出像素。

我做了一個快速看看彙編輸出,它看起來沒問題。如果在彙編器中編寫resize_line函數,則可以獲得更好的性能,展開循環並消除管道延遲。這會給你三個性能提升的估計因素。

雖然沒有彙編器更改,但它應該比實現快很多。

注:我沒有測試過的代碼......

+0

太棒了!你認爲在彙編中使用整個resize_image函數會快得多嗎?或者你認爲用你的建議,我已經節省了90%的時間? – gregoiregentil

+0

它會更快......毫無疑問。 –

1

如果你不是太在意精確那麼這個內循環應該給你計算吞吐量的兩倍相比,更精確的算法:

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

它的工作原理是使用vhadd操作,該操作的結果與輸入的大小相同。這樣,您不必將最終總和移回到8位,並且所有算術運算都是8位,這意味着每條指令可以執行兩倍的操作。

然而它不太準確,因爲中間和是量化的,而GCC 4.7做了一個糟糕的代碼生成工作。 GCC 4.8不錯。儘管如此,整個操作有很大的I/O限制的可能性。應該展開循環以最大化負載和算術之間的分離,並且應該使用__builtin_prefetch()(或PLD)在需要之前將傳入數據提升到高速緩存中。