讲插值之前,首先讲像素重采样的概念。假设有图像A和图像B,其中A为源图像,B为目标图像,A与B的坐标具有对应关系f:
(xa, ya)=f(xb, yb)
(相关资料图)
通过关系f,把A的像素值赋值给B中对应像素点的过程,叫做图像A的像素重采样,图像B为重采样之后的图像。比如对于B的任意像素点(x, y),其对应的A的像素点为(x’, y’),那么则把A中点(x’, y’)的像素值A(x’, y’)赋值给B中点(x, y)的像素值B(x, y)。
(x‘, y’)=f(x, y)
B(x, y)=A(x’, y’)
像素重采样的常见应用场景为图像缩放和图像配准。在实际应用过程中,源图像A的对应坐标往往不是整数,而是浮点型数据(如下图所示),因此不能直接取其像素值赋值给目标图像上的对应点。此时要获取点A(x’, y’)的像素值,插值算法就派上用场了。
所谓插值,就是使用浮点型坐标点的周围整型点的像素值来计算该浮点型坐标点的像素值。比如上图中,点(x’, y’)为浮点型坐标点,使用其周围整型点p0、p1、p2、p3的像素值来计算其像素值A(x’, y’),这个过程就是插值。
常见的插值算法有最邻近插值、双线性插值、双三次插值,不管什么插值算法,其本质都是取浮点型坐标点周围的n*n个整型坐标点的像素值进行加权和,从而得到该浮点型坐标点的像素值,如下式:
不同插值算法的区别在于权重W的计算不一样,下文将分别详细讲解最邻近插值、双线性插值、双三次插值的计算原理与实现。
1. 最邻近插值
最邻近插值取离浮点型坐标点的最近点的像素值作为其像素值,也可以看成使用浮点型坐标点周围2*2个整型点的像素值来计算其像素值,不过只有最靠近的那个点权重为1,其余3个点权重系数都为0。
插值的计算如下式:
其权重计算如下式:
最邻近插值的代码实现最简单,直接对浮点型坐标进行四舍五入取整即可,假设浮点坐标为(x_float, y_float),那么使用最邻近插值计算A(x_float, y_float)的代码实现如下:
intx=(int)(x_float+0.5);//加0.5再截断取整,与四舍五入取整等效inty=(int)(y_float+0.5);ucharinner_value=A.ptr(y)[x]; //A(x",y")=A(x,y)
2. 双线性插值
双线性插值与最邻近插值类似,同样使用浮点型坐标点周围2*2个整型点的像素值来计算其像素值,不过其周围每个整型点的权重都不为0,也就是说,其权重计算与最邻近插值不一样:
浮点坐标点(x_float, y_float)双线性插值的代码实现如下:
int x0 = floor(x_float);int x1 = x0 + 1;int y0 = floor(y_float);int y1 = y0 + 1;floatfracRow=y_float-y0;//求浮点坐标的小数部分floatfracCol=x_float-x0;float k0 = 1.0 - fracRow;float k1 = 1.0 - fracCol;floatw0=k0*k1;floatw1=fracRow*k1;floatw2=k0*fracCol;floatw3=fracRow*fracCol;ucharinner_value=(uchar)(w0*A.ptr(y0)[x0]+w1*A.ptr(y1)[x0]+w2*A.ptr(y0)[x1]+w3*A.ptr(y1)[x1]);
3. 双三次插值
双三次插值使用浮点型坐标点周围4*4个整型点的像素值来计算其像素值,如下图所示:
浮点型坐标点的插值为其周围4*4整型坐标点像素值的加权和:
其中权重W(i,j)的计算如下式,其中a取值范围-1~0之间,一般取固定值-0.5。
双三次插值的实现代码如下。
首先是权重函数的实现:
floatcubic_coeff(floatx,floata){ if(x <= 1) { return 1-(a+3)*x*x+(a+2)*x*x*x; } else if(x < 2) { return -4*a+8*a*x-5*a*x*x+a*x*x*x; }return0.0;}
接着是权重系数的计算实现:
voidcal_cubic_coeff(floatx,floaty,float*coeff){ /*calc the coeff*/floatu=x-floor(x);floatv=y-floor(y); u += 1; v += 1;floata=-0.15; float a_mul_4 = (a + a) + (a + a); float a_mul_5 = a_mul_4 + a; float a_mul_8 = a_mul_4 + a_mul_4; float a_add_3 = a + 3; floata_add_2=a+2; float A[4]; A[0] = cubic_coeff(abs(u), a); A[1] = cubic_coeff(abs(u-1), a); A[2] = cubic_coeff(abs(u-2), a); A[3] = cubic_coeff(abs(u-3), a); for (int s = 0; s < 4; s++) { float C = cubic_coeff(abs(v-s), a); coeff[s*4] = A[0]*C; coeff[s*4+1] = A[1]*C; coeff[s*4+2] = A[2]*C; coeff[s*4+3] = A[3]*C; }}
最后,是双三次插值代码:
ucharcubic_inner(Mat A, floatx_float,floaty_float,float a){ float coeff[16]; cal_cubic_coeff(x_float, y_float, coeff); //计算权重系数 float sum = 0.0; int x0 = floor(x_float) - 1;inty0=floor(y_float)-1; for(int i = 0; i < 4; i++) {for(intj=0;j<4;j++) {sum+=coeff[i*4+j]*A.ptr(y0+i)[x0+j]; } } uchar inner_value = (uchar)sum;returninner_value;}
从插值效果来说:双三次插值>双线性插值>最邻近插值,从计算复杂度来说,同样是:双三次插值>双线性插值>最邻近插值。所以实际使用时,根据自己的需要选择合适的插值算法。