调整大小的8位图片
我有一个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;
这里是关于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测试)。
这是一个一对一的转换代码的武装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函数,则可以获得更好的性能,展开循环并消除管道延迟。这会给你三个性能提升的估计因素。
虽然没有汇编器更改,但它应该比实现快很多。
注:我没有测试过的代码......
太棒了!你认为在汇编中使用整个resize_image函数会快得多吗?或者你认为用你的建议,我已经节省了90%的时间? – gregoiregentil
它会更快......毫无疑问。 –
如果你不是太在意精确那么这个内循环应该给你计算吞吐量的两倍相比,更精确的算法:
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
)在需要之前将传入数据提升到高速缓存中。
**最佳**需要定义。最快,最高质量,最小尺寸等?对于*最高质量*,在图像缩减方面存在不同的折衷。保留低频内容在某些情况下很重要,在其他情况下保持频率较高。什么是* 8位*?灰度,颜色映射,还是别的? –
这是一个灰度输入。最好=最快。 – gregoiregentil