图像进行几何变换的时候,没有办法给一些像素点直接赋值,这才有了插值算法
先说最邻近插值,该算法的原理很简单,仅仅是将原图像的坐标系映射到另一个坐标系上,找到相邻的像素点进行赋值,一个新的像素点仅由原图的一个像素点决定,算法简单,效率高,但是该算法处理后的图像,边缘的马赛克放大后十分明显,双线性插值就很好的解决了这一问题。
双线性插值是线性插值的一种扩展,它是在线性插值的基础上分别进行两次x轴上的单线性和一次y轴上单线性,区别于最临近插值,双线性插值处理后的图像边缘会变得较为柔和,放大后的图像不会出现马赛克,但是细节会大量丢失,图像缩放带来的损失很低(但是任然存在),双线性插值的一个点由它附近的四个点来决定,用这四个点来拟合出需要插入的新像素点的rgb值,计算方法如下。
双线性插值(图片来源百度百科 )
线性插值
四个坐标的表示如下:
双线性就是两次x的线性加一次y方向的线性,有以下 代表某一点的灰度值(参上图)
在x方向上做插值:
在y方向上做插值:
综上:
这里的分母由于都是两个相邻的点计算出来的,那么它一定恒等于1,比如x0=4, x1=5,那么分母就是x1-x0为1,而该公式所有的分母都符合这一规则,故,公式所有的分母都可以简化为1。
其实有了公式以后,我们只需要套公式运算就可以了,但是这里会有一个问题:即,新的坐标系和原图像坐标系的几何中心对称问题,在坐标系变换中,通常的变换是:
这里引用大佬通俗易懂的解释:
原图像的有些点没有参与计算。举个例子,把9∗9的原图像缩小成3∗3,原图像的原点(0,0)和目标图像的原点(0,0)都为左上角,目标图像右上角的坐标为(0,2),对应原图像的坐标为(0∗(9/3),2∗(9/3))=(0,6)。目标图像右边已经没有点了,(0,6)右边的像素点也就用不到了,原图像和目标图像的像素之间的对应关系如下:
图片来源51CTO博客
从图片可以看出,只有圈出来的红色部分参与运算了。目标图像的每个像素点的灰度值相对于原图像偏左上方,右下角的元素实际上没有参与运算。
为了让原图像和目标图像的中心对齐,我们规定另外一种变换方式 :
就是在原来的变换后面加了调节因子:
这种变换下,目标图像的中心点(1,1),对应了原图像的中心点(4,4),两个图像的几何中心重合,能充分利用原图像的点,并且目标图像的每个像素点之间都是等间隔的,也都和两边有一定的边距。实际上,在openCv中也是这种变换方式。
图片来源51CTO博客
代码:
int is_a_negative_number(int i){
if(i < 0){
return 0;
}
return i;
}
Mat Bilinear_interpolation(Mat image, int height, int width){
// initializer matrix
Mat empty = cv::Mat::zeros(height, width, CV_8UC3);
cout << "bilinear empty matrix dimension is " << empty.channels() << endl;
// get scale rate
double destination_rate_h = image.rows / (double)height;
double destination_rate_w = image.cols / (double)width;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
auto &ptr = empty.at<Vec3b>(i, j);
// double src_x = (j + 0.5) * destination_rate_w - 0.5;
// double src_y = (i + 0.5) * destination_rate_h - 0.5;
double src_x = j * destination_rate_w + 0.5 * (destination_rate_w - 1);
double src_y = i * destination_rate_h + 0.5 * (destination_rate_h - 1);
int src_x0 = (int)floor(src_x);
int src_x1 = min(src_x0 + 1, image.cols - 1);
int src_y0 = (int) floor(src_y);
int src_y1 = min(src_y0 + 1, image.rows - 1);
auto &originx1 = image.at<Vec3b>(is_a_negative_number(src_y0), is_a_negative_number(src_x0));
auto &originx2 = image.at<Vec3b>(is_a_negative_number(src_y0), is_a_negative_number(src_x1));
auto &originy1 = image.at<Vec3b>(is_a_negative_number(src_y1), is_a_negative_number(src_x0));
auto &originy2 = image.at<Vec3b>(is_a_negative_number(src_y1), is_a_negative_number(src_x1));
for (int k = 0; k < empty.channels(); k++) {
double temp0 = (src_x1 - src_x) * originx1[k] + (src_x - src_x0) * originx2[k];
double temp1 = (src_x1 - src_x) * originy1[k] + (src_x - src_x0) * originy2[k];
ptr[k] = (int)((src_y1 - src_y) * temp0 + (src_y - src_y0) * temp1);
}
}
}
imshow("bilinear", empty);
return empty;
}