对灰度图进行向右旋转90度,需要至少遍历访问所有元素一遍,时间复杂度为o(mn),利用NEON加速可以并行读取多个元素,虽然没有改变时间复杂度,但常数因子减小了。
问题描述
针对灰度图进行旋转,假设输入图像尺寸为Height*Width,转换后的图像尺寸为Width*Height。转换效果如下图所示:
转换后的图像坐标对应关系如下:
C代码实现
int GrayImageRotation90(uint8_t * in,uint8_t* out,
int height,int width)
{
uint8_t *p_img = (uint8_t*)in;
uint8_t *p_o = out;
for(int h=0; h<height; h++)
{
for(int w=0; w<width; w++)
{
*(p_o + w*height + height-1-h) = *p_img++;
}
}
return 0;
}
NEON优化
NEON加速思路:将一个输入的大图像划分成8*8大小的图像,分别对每一个8*8的子图像进行旋转,再将其输出到对应的位置处即可。最关键的是对8*8子图像的旋转处理。
NEON的指令vtrn可以解决转置,但针对8*8的图像数据要使用vtrn还需要使用较大的强制转换指令进行配合,总的指令数量较多,最终的计算速度慢,网上通过vtrn实现的旋转图像的加速比只有2.5左右。本文使用查找表指令vtbl进行选择。
step 1. 通过vld指令,将内存数据读取到向量结构体中
step 2. 通过vtbl指令,将列方向的数据组织到一起,如下图所示,A1~D1, A5~D5 4列数据选择输出到一个寄存器中,其它寄存器同理,需要注意的是,在选则寄存器数据是,数字下标相同的数据输出到同一个寄存器,这是一般只有4个相同的,数据,那么另外四个数据该如何选择呢?红色虚线以下的四行数据组合成的寄存器进行同样的选择输出,那么,第一行与第五行能够再一次进行查找表选择时刚好能输出为输出所需要的8个元素的向量为最佳选择。
step 3. 再将vd0与vd4进行查找表选择输出两个连续的寄存器
step 4. 按输出输出存储在对应的内存位置即可。
int GrayRotation90_NEON(uint8_t * in, uint8_t* out, int height, int width)
{
uint8x8_t vone = {1,1,1,1,1,1,1,1};
uint8x8_t index_0 = {28, 20, 12, 4, 24, 16, 8, 0};
uint8x8_t index_1 = vadd_u8(index_0, vone);
uint8x8_t index_2 = vadd_u8(index_1, vone);
uint8x8_t index_3 = vadd_u8(index_2, vone);
uint8x8_t index_4 = {12, 13, 14, 15, 4, 5, 6 ,7};
uint8x8_t index_5 = {8, 9, 10, 11, 0, 1, 2, 3};
uint8x8x4_t mat0;
uint8x8x4_t mat1;
uint8x8x4_t temp0;
uint8x8x4_t temp1;
uint8x8x2_t out0;
uint8x8x2_t out1;
uint8x8x2_t out2;
uint8x8x2_t out3;
int x = 0, y = 0;
for(y=0; y<height; y+=8)
{
for(x=0; x<width; x+=8)
{
mat0.val[0] = vld1_u8(in + y*width+x);
mat0.val[1] = vld1_u8(in + (y+1)*width+x);
mat0.val[2] = vld1_u8(in + (y+2)*width+x);
mat0.val[3] = vld1_u8(in + (y+3)*width+x);
mat1.val[0] = vld1_u8(in + (y+4)*width+x);
mat1.val[1] = vld1_u8(in + (y+5)*width+x);
mat1.val[2] = vld1_u8(in + (y+6)*width+x);
mat1.val[3] = vld1_u8(in + (y+7)*width+x);
temp0.val[0] = vtbl4_u8(mat0, index_0);
temp0.val[1] = vtbl4_u8(mat0, index_1);
temp0.val[2] = vtbl4_u8(mat0, index_2);
temp0.val[3] = vtbl4_u8(mat0, index_3);
temp1.val[0] = vtbl4_u8(mat1, index_0);
temp1.val[1] = vtbl4_u8(mat1, index_1);
temp1.val[2] = vtbl4_u8(mat1, index_2);
temp1.val[3] = vtbl4_u8(mat1, index_3);
out0.val[0] = temp0.val[0];
out0.val[1] = temp1.val[0];
out1.val[0] = temp0.val[1];
out1.val[1] = temp1.val[1];
out2.val[0] = temp0.val[2];
out2.val[1] = temp1.val[2];
out3.val[0] = temp0.val[3];
out3.val[1] = temp1.val[3];
mat0.val[0] = vtbl2_u8(out0, index_4); // line 0
mat0.val[1] = vtbl2_u8(out0, index_5); // line 4
mat0.val[2] = vtbl2_u8(out1, index_4); // line 1
mat0.val[3] = vtbl2_u8(out1, index_5); // line 5
mat1.val[0] = vtbl2_u8(out2, index_4); // line 2
mat1.val[1] = vtbl2_u8(out2, index_5); // line 6
mat1.val[2] = vtbl2_u8(out3, index_4); // line 3
mat1.val[3] = vtbl2_u8(out3, index_5); // line 7
// store out data in order: 0, 4, 1, 5, 2, 6, 3, 7
vst1_u8(out + (x + 0) * height + height-8 - y, mat0.val[0]); // line 0
vst1_u8(out + (x + 1) * height + height-8 - y, mat0.val[2]); // line 1
vst1_u8(out + (x + 2) * height + height-8 - y, mat1.val[0]); // line 2
vst1_u8(out + (x + 3) * height + height-8 - y, mat1.val[2]); // line 3
vst1_u8(out + (x + 4) * height + height-8 - y, mat0.val[1]); // line 4
vst1_u8(out + (x + 5) * height + height-8 - y, mat0.val[3]); // line 5
vst1_u8(out + (x + 6) * height + height-8 - y, mat1.val[1]); // line 6
vst1_u8(out + (x + 7) * height + height-8 - y, mat1.val[3]); // line 7
}
}
return 0;
}
性能分析
输入图像分辨率为640*360*1,在ARMv7处理上运行1000次,C语言函数与NEON函数运行耗时对比如下:
算法 | C | NEON | 加速比 |
耗时 | 2811 | 995 | 2.8 |
由此可见,NEON至少提高了2.8倍的速度。
欢迎关注亦梦云烟的微信公众号: 亦梦智能计算