算法要点
一、基本原理
具体原理可以自行搜索,这里只大致描述一下,目标图像上某一个点按比例映射到原始图像上某一点,不过通常计算得到的原始点坐标不是整数,最近邻的做法是向下取整,而双线性差值的做法是取与该坐标最接近的上下左右四个点来线性加权,在放大的时候不会像最近邻那样锯齿严重,但是计算量会大不少。这里要注意的是加权分为两个维度,横向和纵向。此外,边界的处理也要注意。
二、优化思路
这里核心的优化思路有两点:
- 避免重复计算
- 浮点转定点
避免重复计算
首先要分析哪里存在可能的重复计算。resize是原始图像和目标图像之间的映射,这个映射不一定是一对一的。比如对于目标图像的高大于原始图像的高的情况,目标图像的多个行可能会映射到原始图像的同一行,这样我们就可以利用之前的计算结果,而不用重复计算。
对于目标图像上的某个点,映射到原始图像的坐标为O,相邻的四个点为ABCD,则目标图像的像素值为val = A(1-fx)(1-fy) + Bfx(1-fy) + C(1-fx)fy + Dfxfy
这个公式可以分解为两个过程:HResize和VResize
HResize
val0 = A(1-fx) + Bfx
val1 = C(1-fx) + Dfx
VResize
val = val0*(1-fy) + val1*fy
之所以分解为两步,是因为上面提到的当目标图像的多行映射到原始图像的同一行时,HResize计算的结果是可以复用的,直接拷贝即可。而VResize是无法复用的,因为即便都映射到同一行,但是fy是不会相同的。
浮点转定点
浮点转定点需要乘一个转换系数。这里fx和fy都是浮点,OpenCV默认的做法是将fx和fy都乘以固定系数1<<11转成定点,最后结果再右移22。为什么取11,因为这是保证不溢出的最高的精度。分析上面的val0,假设A和B都取最大255,则val0最大为
2^19
。假设val0和val1都取最大值
2^19
,则val最大为
2^30
。如果这个固定系数取12,则val的最大值为2^32,即便是unsigned也装不下了。
虽然取11能保证精度最大,但是由于val0最大是
2^19
,int16是装不下了,只能进行int32计算,neon一次只能算4个数。因此如果想提升neon向量化计算的并行度,可以给系数降低一点,参考OpenCV的三方库carotene,系数取的是7,同样这是为了保证能进行int16计算的最高精度,因为这样val0的最大值为2^15。此外为了保证VResize也能进行int16计算,val0和val1要转回到int8,这一点和OpenCV不一样。转换系数的降低可以提升向量化计算的并行度,进而提升性能,不过会对像素值造成一定的精度损失,经过测试,像素值的误差不会超过3,而转换系数为11时这个误差不会超过1。
整个过程中计算量最重的部分是HResize,我们分析HResize的向量化过程,由于是int16的计算,因此一次最多算8个点,以8UC3为例,假设映射到原始图像的8个点分别为ABCDEFGH,
A = A0 A1 A2 A3 A4 A5 A6 A7
B = B0 B1 B2 B3 B4 B5 B6 B7
C = C0 C1 C2 C3 C4 C5 C6 C7
D = D0 D1 D2 D3 D4 D5 D6 D7
E = E0 E1 E2 E3 E4 E5 E6 E7
F = F0 F1 F2 F3 F4 F5 F6 F7
G = G0 G1 G2 G3 G4 G5 G6 G7
H = H0 H1 H2 H3 H4 H5 H6 H7
对于A点,我们需要
[A0 A1 A2]*K0 + [A3 A4 A5]*K0’,K0=1-fx,K0’=fx,
后面的B~H点同理。
因此向量化计算为
[A0 B0 C0 D0 E0 F0 G0 H0]*[K0 K1 K2 K3 K4 K5 K6 K7] + [A3 B3 C3 D3 E3 F3 G3 H3]*[K0' K1' K2' K3' K4' K5' K6' K7']
[A1 B1 C1 D1 E1 F1 G1 H1]*[K0 K1 K2 K3 K4 K5 K6 K6] + [A4 B4 C4 D4 E4 F4 G4 H4]*[K0' K1' K2' K3' K4' K5' K6' K7']
[A2 B2 C2 D2 E2 F2 G2 H2]*[K0 K1 K2 K3 K4 K5 K6 K7] + [A5 B5 C5 D5 E5 F5 G5 H5]*[K0' K1' K2' K3' K4' K5' K6' K7']
可见关键是要从ABCDEFGH向量中抽取出类似Ai Bi Ci Di Ei Fi Gi Hi(0<=i<6)这样的向量。
这里已知两种做法,一种是转置,一种是查表。
三、优化结果
输出336x192,测试以下三种输入:
phone | 224x128 | 640x360 | 1280x720 |
---|---|---|---|
MI6 | 0.61ms->0.16ms | 1.46ms->0.3ms | 1.78ms->0.35ms |
魅蓝5S | 1.97ms->0.57ms | 4.9ms->1.38ms | 5.64ms->1.97ms |
IPHONE7 | 0.31ms->0.06ms | 0.7ms->0.12ms | 0.8ms->0.17ms |
输出224x128,测试以下三种输入:
phone | 336x192 | 640x360 | 1280x720 |
---|---|---|---|
MI6 | 0.52ms->0.12ms | 0.72ms->0.15ms | 0.73ms->0.17ms |
魅蓝5S | 1.67ms->0.45ms | 2.33ms->0.64ms | 2.59ms->1.25ms |
IPHONE7 | 0.27ms->0.05ms | 0.35ms->0.07ms | 0.36ms->0.07ms |
输出640x360,测试以下三种输入:
phone | 224x128 | 336x192 | 1280x720 |
---|---|---|---|
MI6 | 1.51ms->0.36ms | 1.84ms->0.45ms | 1.91ms->1.21ms |
魅蓝5S | 4.87ms->1.44ms | 6.15ms->1.77ms | 6.32ms->4.88ms |
IPHONE7 | 0.44ms->0.15ms | 0.65ms->0.15ms | 0.38ms->0.37ms |