随着ARKit发布,手机端的图像识别及追踪功能越来越普及,诸多app都纷纷开动脑筋去做一些好玩的场景,这其中就离不开手机端的指令加速问题。最近刚开始研究arm neon,打算从Opencv现有的代码来扒一扒neon加速都被放在哪些地方,具体怎么实现的
Opencv的imgproc模块中有一个pyramids的文件,图像金字塔是图像多尺度的一种表达,将图片进行上采样或下采样得到一系列不同大小的图片集合。我们就挑其中用的最多的高斯下采样来举例。Sample里面调用的入口函数是pyrDown,它里面三个参数,src, dst, size,分别是输入图片,输出图片,输出图片大小,输出图片大小的size计算公式一般是rows/2 +1, cols/2 +1。
pyrDown函数里面,有一些并行加速的逻辑,忽略掉,可以直接看pyrDown_
进入PyrDownVec_模板函数里面,里面有一些channel, border的处理,这些跟neon加速关系不大,暂时不细讲,主要的逻辑在一个大循环这,按照行来遍历,里面有一个循环,是遍历当前这行和前后两行(总共5行)的每列数据,根据高斯核的系数(1,4,6,4,1)做水平的卷积操作。然后把这5行的数据,分别带入VecOp里面进行neon加速的操作。
VecOp对应了PyrDownVec_32s8u方法,这里开始了neon相关的逻辑
uint16x8_t v_r0 = vcombine_u16(vqmovn_u32(vld1q_u32(row0 + x)), vqmovn_u32(vld1q_u32(row0+ x + 4)));
vld1q_u32把row0的x列数据读取4个到q寄存器,然后vqmovn_u32把每个元素的u32类型强转成u16类型,x+4列的数据也同样操作,再通过vcombine_u16的操作把这8个元素连接起来,变成uint16*8_t的类型。
v_r0 =vaddq_u16(vaddq_u16(v_r0, v_r4), vaddq_u16(v_r2, v_r2));
v_r1 =vaddq_u16(vaddq_u16(v_r1, v_r2), v_r3);
uint16x8_t v_dst0 = vaddq_u16(v_r0, vshlq_n_u16(v_r1, 2));
之前已经说过我们在VecOp外面做过水平的卷积,现在这里开始做竖直的卷积
1 4 6 4 1
1 4 6 4 1
1 4 6 4 1 水平卷积的效果,相当于每列乘以系数
1 4 6 4 1
1 4 6 4 1
在水平卷积的基础上再做一次竖直卷积1, 4, 6, 4, 1,相当于每行分别乘以这个系数
1 4 6 4 1 * 1 1 4 64 1
1 4 6 4 1 * 4 4 1624 16 4
1 4 6 4 1 * 6 6 2436 24 6
1 4 6 4 1 * 4 4 1624 16 4
1 4 6 4 1 * 1 1 4 64 1
看到这个核,想必大家很熟悉了,这就是高斯卷积核了
vst1q_u8(dst + x, vcombine_u8(vqmovn_u16(vshrq_n_u16(vaddq_u16(v_dst0, v_delta), 8)),
vqmovn_u16(vshrq_n_u16(vaddq_u16(v_dst1, v_delta), 8))));
把最后结果转成unsigned char,16个结果一个批次
右移8位,类似除以256,256是高斯核的scale系数,保证Gi之和=1
加上delta128,不是确定原因,猜测类似四舍五入的感觉,要除以256之前,先加128,如果尾数后8位大于128,那么除以256之后,就进1,否则,就等于0,主要还是保证精度。