NEON向量数据类型根据以下模式命名:
<type><size>x<number_of_lanes>_t
int16x4_t
是一个向量,其中包含4个16位的短整型变量(4个lane,每个lane存16位的数)。
float32x4_t
是一个向量,其中包含4个32位的浮点型变量(4个lane,每个lane存32位的数)。
uint8x16x3_t pixel_rgb = vld3q_u8(pRGB);
这里vld
是加载操作,3
表示3个元素的解交错(deinterleaving),Q
表示使用Q寄存器,u8
表示数据类型,也就是向量的一个通道(lane)为8位无符号数据。
所以它从rgb
指针指向的地址开始,加载16个像素的RGB数据到3个Q寄存器里,同时解交错,使3个Q寄存器分别存放16个R值,16个G值,16个B值。(Q寄存器刚好128位,16*8=128),如下图所示:
注意现在这里每个lane
是u8
类型的(8bit),如果直接对它做乘法,乘数较大时,结果可能超出8Bit。要是把结果存回原来的lane
,宽度不够会导致结果损失。
因此这里用vget_high_u8
和vget_low_u8
分别取出高位和低位,分别对高位和低位做操作,按照公式Y = ( ( 65*R + 129*G + 25*B ) >> 8 ) + 16
逐步计算,用长指令(指令加L
后缀)将宽度增倍存储。
最后用vqmovn_u16
将宽度减半,这是一条饱和指令(指令加Q
前缀),超出8Bit的数据将被 saturated 到8Bit,即大于255的值结果都是255。然后用vcombine_u8
将高位和低位合并,用vst1q_u8
存储结果。

这样Y就计算完了,大概思路是这样,具体计算过程看代码。
用汇编对指令进行Schedule后,把对相同寄存器的操作错开,让流水线马力全开,速度更快。但是汇编代码不利于移植,不同的ARM架构之间指令集或有不同,切换平台后需要重新审查代码。而NEON Intrinsic C代码是经过封装了的,比较固定和统一,代码写好后由编译器生成汇编代码并适当地Optimize,能以较低的成本享受NEON带来的加速。
本文代码是基于ARMv7架构的,在64位的ARMv8上速度应该会更快。ARMv8有32个128位NEON寄存器,数量翻了一倍,可以对更多数据并行处理。但是它的NEON寄存器访问形式和支持的数据类型有所改变,代码也要对应修改,目前还没有进行探究。
对齐问题: 本文代码每次取16个像素,如果图片的宽不是16的倍数,就会出问题。有一种方法,把原始数据每行用零padding到16的倍数,就能应对这种问题。OpenCV的IplImage
类里面就有这个属性:widthStep
。本文代码未对此进行处理。
文中若有不当之处请指出😬