算法优化-1bit转8bit-NEON处理

概述

使用IVE可以加速sobel滤波器和canny滤波器,但是IVE生成的滤波器结果格式不满足实际需求。为此,需要通过软件方式将IVE的输出结果转化成需要的形式,可以使用c语言、NEON Intrinscis和NEON汇编等方式去实现。

大连

Sobel滤波

Sobel滤波器的结果形式如下,使用16比特存储xy方向的梯度值,其中低8位表示x轴方向梯度,高8位表示y轴方向梯度。

IVE-sobel_result_form

实际需求要将xy方向的梯度拆分为两个字节,具体形式如下:

sobel_result_form_xy

NfsU16ToU8WithTwo函数实现

该函数实现将16bit的高低位拆分为两个独立的字节,并将拆分后的字节单独输出。

C语言代码

使用C语言编写代码如下:

1
2
3
4
5
for (int32_t i = 0; i < IMG_BUF_SIZE; i++)
{
sobel_x[i] = data_sobel[i] & 0xff;
sobel_y[i] = (data_sobel[i] >> 8) & 0xff;
}

Running Time : 14.3ms

NfsU16ToU8Square 函数实现

该函数实现将16bit的高低位拆分为两个独立的字节(x,y),并计算\(x^2 + y^2\),将xy轴的梯度求平方和后按照16bit输出结果。

C语言代码

使用C语言编写代码如下:

1
2
3
4
5
6
7
uint8_t temp_x, temp_y; 
for (int32_t i = 0; i < IMG_BUF_SIZE; i++)
{
temp_x = data_sobel[i] & 0xff;
temp_y = (data_sobel[i] >> 8) & 0xff;
output_sobel[i] = temp_x * temp_x + temp_y * temp_y;
}

Running Time : 18.2ms

Intrinsics优化

首先想到的优化方式是采用NEON Intrinsics方式对上述C语言代码进行重构,该方式相比于编写汇编代码更加简单,且便于移植。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int32_t i = 0;
uint16x8_t v_in;
uint8x8_t x, y;
uint16x8_t x2;
uint16x8_t xy_square;
for (i = 0; i + 7 < size; i += 8)
{
// load the input data
v_in = vld1q_u8(input + i);

// seprate the u16 to two u8
x = vmovn_u16(v_in); // mov low 8bit to the x
y = vshrn_n_u16(v_in, 8); // the element right shift 8bit

// calculate the x^2 + y^2
x2 = vmull_u8(x, x);
xy_square = vmlal_u8(x2, y, y);

// store the result
vst1q_u16(output + i, xy_square);
}

Running Time : 3.42ms

数据加载优化

随着对NEON指令集理解的深入,可以直接使用VLD2指令直接在加载数据时就实现xy通道的拆分,从而进一步提升了处理的速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int32_t i = 0;
uint8x8x2_t v_in;
uint16x8_t x2;
uint16x8_t xy_square;
for (i = 0; i + 7 < size; i += 8)
{
// load the input data
v_in = vld2_u8((uint8_t*)input + i);

// calculate the x^2 + y^2
x2 = vmull_u8(v_in.val[0], v_in.val[0]);
xy_square = vmlal_u8(x2, v_in.val[1], v_in.val[1]);

// store the result
vst1q_u16(output + i, xy_square);
}

通过该方式,处理器时可以减少一次mov操作和一次shift操作。如下图所示,VLD2.8直接加载sobel_xD8,同时加载sobel_yD9

IVE-u16totwobyte_load2

Running Time: 2.64ms

使能向量优化

在使用NEON Intinsics时,编译选项中需要使能向量化,具体编译参数如下:

1
gcc -mcpu=cortex-a9 -mfloat-abi=hard -ftree-vectorize -O2 

Canny滤波

Canny滤波器的结果使用一个字节表示8个像素信息,具体形式如下:

IVE-one_bit_to_byte

因此,需要通过软件的方式将字节中的每一个bit像素拆分为一个字节表示,将上图中的两个字节数据进行转换,结果如下图所示:

one_bit_to_byte_split

NfsOneBitToU8函数实现

C语言代码

使用C语言编写代码如下:

1
2
3
4
for (i = 0; i < IMG_BUF_SIZE; i++)
{
data_canny_8bit[i] = ((data_canny[i/8] >> (i % 8)) & 0x01) << 7;
}

汇编语言代码

算法思路

上述功能可以使用NEON汇编进行加速,具体思路如下:

  1. 加载操作 使用VLD1指令从内存空间加载图像数据到Q6寄存器,该指令可以一次加载16个字节数据,对应图像的128个像素值;
one_bit_to_byte_neon1
  1. 位与操作 使用VAND指令,对Q6Q4寄存器的元素进行位与操作,并将结果存入Q7寄存器,其中Q4寄存器可使用DUP指令全部置为0x01;此步骤将每个元素的低位像素值取出放入Q7寄存器中;
one_bit_to_byte_neon2
  1. 比较操作 使用VCGT指令对Q7Q5寄存器的元素逐个进行比较,判断Q7寄存器的元素是否大于对应Q5中的元素,其计算结果保存在Q8寄存器中。如果Q7的元素大于Q5的元素,则对应Q8元素设置为255,否则设置为0
one_bit_to_byte_neon3
  1. 移位操作 使用VSHR指令,将Q6中的元素整体向右移动1比特位,即将下一组像素移到元素最低位,方便循环取出图像像素值。
one_bit_to_byte_neon4
  1. 循环操作 循环上述2、3、4步骤,将Q6寄存器中的像素值分别取出,放入Q8~Q15寄存器中。
one_bit_to_byte_neon5
  1. 打包操作 使用VZIP指令每隔4个Q寄存器进行一次打包操作,如下图所示,使像素索引号间隔4排列。该步骤的目的是未了方便下一步的存储操作。
one_bit_to_byte_neon6
  1. 存储操作 使用VST4指令将上述打包好的数据,按照像素循序存储相应的内存地址。

one_bit_to_byte_neon7

3.1.2.2 汇编代码

使用NEON Assembly编写上述功能代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void NfsOneBitToU8Asm(uint8_t* restrict input, uint8_t* restrict output, uint32_t size) {
uint8_t* input_temp = input;
uint8_t* output_temp = output;
uint32_t size_tmp = size;

asm volatile("vmov.i8 q4, #0x01 \n"
"vmov.i8 q5, #0x00 \n"
"100: \n"
"vld1.8 {q6}, [%0]! \n"
"pld [%0, #128] \n"
"vshr.u8 q9, q6, #1 \n"
"vshr.u8 q10, q6, #2 \n"
"vshr.u8 q11, q6, #3 \n"
"vshr.u8 q12, q6, #4 \n"
"vshr.u8 q13, q6, #5 \n"
"vshr.u8 q14, q6, #6 \n"
"vshr.u8 q15, q6, #7 \n"
"vand.i8 q8, q6, q4 \n"
"vand.i8 q9, q9, q4 \n"
"vand.i8 q10, q10, q4 \n"
"vand.i8 q11, q11, q4 \n"
"vand.i8 q12, q12, q4 \n"
"vand.i8 q13, q13, q4 \n"
"vand.i8 q14, q14, q4 \n"
"vand.i8 q15, q15, q4 \n"
"vcgt.s8 q8, q8, q5 \n"
"vcgt.s8 q9, q9, q5 \n"
"vcgt.s8 q10, q10, q5 \n"
"vcgt.s8 q11, q11, q5 \n"
"vcgt.s8 q12, q12, q5 \n"
"vcgt.s8 q13, q13, q5 \n"
"vcgt.s8 q14, q14, q5 \n"
"vcgt.s8 q15, q15, q5 \n"
"vzip.8 q8, q12 \n"
"vzip.8 q9, q13 \n"
"vzip.8 q10, q14 \n"
"vzip.8 q11, q15 \n"
"vst4.8 {d16, d18, d20, d22}, [%1]! \n"
"vst4.8 {d17, d19, d21, d23}, [%1]! \n"
"vst4.8 {d24, d26, d28, d30}, [%1]! \n"
"vst4.8 {d25, d27, d29, d31}, [%1]! \n"
"subs %2, #16 \n"
"bgt 100b \n"
: "=r"(input_temp), "=r"(output_temp), "=r"(size_tmp)
: "0"(input_temp), "1"(output_temp), "2"(size_tmp)
: "memory",
"cc",
"q4",
"q5",
"q6",
"q7",
"q8",
"q9",
"q10",
"q11",
"q12",
"q13",
"q14",
"q15");
}

性能对比

如下表所示,上述代码在联咏NT96565A平台运行时间如下:

Function C language Intrinsics Assembly
NfsU16ToU8WithTwo 14.3 3.2 3.56
NfsU16ToU8Square 18.6 2.64 2.82
NfsOneBitToU8 10.2 2.13 1.78