最近邻插值及双线性插值的基本原理
举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source):
234 38 22
67 44 12
89 65 63
这个矩阵中,元素坐标(x,y)是这样确定的,x从左到右,从0开始,y从上到下,也是从零开始,这是图象处理中最常用的坐标系。
如果想把这副图放大为 4X4大小的图像,如下所示,矩阵的每个像素都是未知数,等待着我们去填充(这个将要被填充的图的叫做目标图,Destination):
? ? ? ?
? ? ? ?
? ? ? ?
? ? ? ?
然后要往这个空的矩阵里面填值了,要填的值从源图中来,先填写目标图最左上角的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出:
srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)
就可以找到对应的原图的坐标了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0),找到了源图的对应坐标,就可以把源图中坐标为(0,0)处的象素值填进去目标图的(0,0)这个位置了。
接下来,如法炮制,寻找目标图中坐标为(1,0)的象素对应源图中的坐标(1*0.75,0*0.75)=>(0.75,0),结果发现,得到的坐标里面有小数,计算机里的图像可是数字图像,象素就是最小单位了,象素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是采用四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,那么按照四舍五入的方法就得到坐标(1,0)
那么就可以再填一个象素到目标矩阵中了,同样是把源图中坐标为(1,0)处的像素值填入目标图中的坐标。
依次填完每个象素,一幅放大后的图像就诞生了,像素矩阵如下所示:
234 38 22 22
67 44 12 12
89 65 63 63
89 65 63 63
这种放大图像的方法叫做最临近插值算法,这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真。效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。
双线性插值算法描述如下:
对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)
其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。
现在假如目标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是一个概念上的虚拟象素,实际在源图中并不存在这样一个象素,那么目标图的象素(1,1)的取值不能够由这个虚拟象素来决定,而只能由源图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)离(1,1)要更近一些,那么(1,1)所起的决定作用更大一些,这从公式1中的系数uv=0.75×0.75就可以体现出来,而(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作用就要小一些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这一特点。
代码实现:
1.最近邻插值图像缩放
//最近邻插值图像缩放
cv::Mat nNeighbourInterpolation(cv::Mat image)
{
//判断输入有效性
CV_Assert(image.data != NULL);
int rows = image.rows, cols = image.cols;
//构建目标图像
cv::Mat result = cv::Mat(cv::Size(150, 150), image.type(), cv::Scalar::all(0));
int resultrows = result.rows, resultcols = result.cols;
//坐标转换,求取缩放倍数
float cx = (float)cols / resultcols;
float cy = (float)rows / resultrows;
//遍历图像,完成缩放操作
for (int i = 0; i < resultrows; i++)
{
//取整,获取目标图像在源图像对应坐标
int yy = cvFloor(cy*i);
for (int j = 0; j < resultcols; j++)
{
int xx = cvFloor(j*cx);
//边界处理,防止指针越界
if (xx >= cols) xx = rows - 1;
if (yy >= rows) yy = cols - 1;
//映射矩阵
result.at(i, j) = image.at(yy, xx);
}
}
return result;
}
运行结果:
2.双线性插值缩放
//双线性插值
cv::Mat BilinearInterpolation(cv::Mat image)
{
CV_Assert(image.data != NULL);
int rows = image.rows, cols = image.cols, step = image.step;
//构建目标图像
cv::Mat result = cv::Mat(cv::Size(150, 150), image.type(), cv::Scalar::all(0));
int resultrows = result.rows, resultcols = result.cols, resultstep = result.step;
IplImage src = image, dst = result;
for (int i = 0; i < resultrows - 1; i++)
{
for (int j = 0; j < resultcols - 1; j++)
{
float srcx = (j + 0.5)*((float)cols) / resultcols - 0.5;
float srcy = (i + 0.5)*((float)rows) / resultrows - 0.5;
int xx = (int)srcx, yy = (int)srcy;
//3通道求邻域加权值1
float t1x = ((uchar*)(src.imageData + step * yy))[xx * 3] * (1 - (srcx - xx)) +
((uchar*)(src.imageData + step * yy))[(xx + 1) * 3] * (srcx - xx);
float t1y= ((uchar*)(src.imageData + step * yy))[xx * 3 + 1] * (1 - (srcx - xx)) +
((uchar*)(src.imageData + step * yy))[(xx + 1) * 3 + 1] * (srcx - xx);
float t1z= ((uchar*)(src.imageData + step * yy))[xx * 3 + 2] * (1 - (srcx - xx)) +
((uchar*)(src.imageData + step * yy))[(xx + 1) * 3 + 2] * (srcx - xx);
//3通道求邻域加权值2
float t2x = ((uchar*)(src.imageData + step * (yy + 1)))[xx * 3] * (1 - (srcx - xx)) +
((uchar*)(src.imageData + step * (yy + 1)))[(xx + 1) * 3] * (srcx - xx);
float t2y = ((uchar*)(src.imageData + step * (yy + 1)))[xx * 3 + 1] * (1 - (srcx - xx)) +
((uchar*)(src.imageData + step * (yy + 1)))[(xx + 1) * 3 + 1] * (srcx - xx);
float t2z = ((uchar*)(src.imageData + step * (yy + 1)))[xx * 3 + 2] * (1 - (srcx - xx)) +
((uchar*)(src.imageData + step * (yy + 1)))[(xx + 1) * 3 + 2] * (srcx - xx);
//根据公式求解目标图像加权
((uchar*)(dst.imageData + resultstep * i))[j * 3] = t1x * (1 - (srcy - yy)) + t2x * (srcy - yy);
((uchar*)(dst.imageData + resultstep * i))[j * 3 + 1] = t1y * (1 - (srcy - yy)) + t2y * (srcy - yy);
((uchar*)(dst.imageData + resultstep * i))[j * 3 + 2] = t1z * (1 - (srcy - yy)) + t2z * (srcy - yy);
}
//列操作
((uchar*)(dst.imageData + resultstep * i))[(resultcols - 1) * 3] = ((uchar*)(dst.imageData + resultstep * i))[(resultcols - 2) * 3];
((uchar*)(dst.imageData + resultstep * i))[(resultcols - 1) * 3 + 1] = ((uchar*)(dst.imageData + resultstep * i))[(resultcols - 2) * 3 + 1];
((uchar*)(dst.imageData + resultstep * i))[(resultcols - 1) * 3 + 2] = ((uchar*)(dst.imageData + resultstep * i))[(resultcols - 2) * 3 + 2];
}
//行操作
for (int i = 0; i < resultcols * 3; i++)
((uchar*)(dst.imageData + resultstep * (resultrows - 1)))[i] = ((uchar*)(dst.imageData + resultstep * (resultrows - 2)))[i];
cv::Mat result1 = cv::cvarrToMat(&dst, true);
return result1;
}
运行结果: