github地址:https://github.com/2209520576/Image-Processing-Algorithm

图像的缩放很好理解,就是图像的放大和缩小。传统的绘画工具中,有一种叫做“放大尺”的绘画工具,画家常用它来放大图画。当然,在计算机上,我们不再需要用放大尺去放大或缩小图像了,把这个工作交给程序来完成就可以了。下面就来讲讲计算机怎么来放大缩小图象;在本文中,我们所说的图像都是指点阵图,也就是用一个像素矩阵来描述图像的方法,对于另一种图像:用函数来描述图像的矢量图,不在本文讨论之列。

越是简单的模型越适合用来举例子,我们就举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source):
234   38    22
67     44    12
89     65    63

这个矩阵中,元素坐标(x,y)是这样确定的,x从左到右,从0开始,y从上到下,也是从零开始,这是图象处理中最常用的坐标系,就是这样一个坐标:

  ---------------------->X
  |
  |
  |
  |
  |
∨Y

如果想把这副图放大为 4X4大小的图像,那么该怎么做呢?那么第一步肯定想到的是先把4X4的矩阵先画出来再说,好了矩阵画出来了,如下所示,当然,矩阵的每个像素都是未知数,等待着我们去填充(这个将要被填充的图的叫做目标图,Destination):
?        ?        ?       ?
?        ?        ?       ?
?        ?        ?       ?
?        ?        ?       ? 
               
        然后要往这个空的矩阵里面填值了,要填的值从哪里来来呢?是从源图中来,好,先填写目标图最左上角的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出:  

 Sx=srcWidth/dstWidth     ;  Sy=srcHeight/dstHeight  ; 这是两个缩放因子。                 
srcX=dstX* Sx , srcY = dstY *  Sy
好了,套用公式,就可以找到对应的原图的坐标了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0),找到了源图的对应坐标,就可以把源图中坐标为(0,0)处的234象素值填进去目标图的(0,0)这个位置了。

接下来,如法炮制,寻找目标图中坐标为(1,0)的象素对应源图中的坐标,套用公式:
(1*0.75,0*0.75)=>(0.75,0)
结果发现,得到的坐标里面竟然有小数,这可怎么办?计算机里的图像可是数字图像,像素就是最小单位了,象素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是采用四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,好,那么按照四舍五入的方法就得到坐标(1,0),完整的运算过程就是这样的:
(1*0.75,0*0.75)=>(0.75,0)=>(1,0)
那么就可以再填一个象素到目标矩阵中了,同样是把源图中坐标为(1,0)处的像素值38填入目标图中的坐标。
         
依次填完每个象素,一幅放大后的图像就诞生了,像素矩阵如下所示:
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 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。

下面是代码:

//最近邻插值算法
//sx,xy -- 缩放因子
 void nearest(cv::Mat& src, cv::Mat& dst , float sx, float sy){
	 //由缩放因子计算输出图像的尺寸(四舍五入)
	int dst_cols = round(src.cols * sx);
	int dst_rows = round(src.rows * sy);
	//创建输出图像
	dst = cv::Mat(dst_rows,dst_cols,src.type());
	//灰度图像处理
	if (src.channels() == 1){
		for (int i = 0; i < dst.rows; i++){
			for (int j = 0; j < dst.cols; j++){
				//插值计算,输出图像的像素点由原图像对应的最近的像素点得到(四舍五入)
				int i_index = round(i / sy);
				int j_index = round(j / sx);
				if (i_index >src.rows - 1) i_index = src.rows - 1;//防止越界
				if (j_index >src.cols - 1) j_index = src.cols - 1;//防止越界
				dst.at<uchar>(i, j) = src.at<uchar>(i_index, j_index);
			}
		}	
	}
	//彩色图像处理
	else{
		for (int i = 0; i < dst.rows; i++){
			for (int j = 0; j < dst.cols; j++){
				//插值计算,输出图像的像素点由原图像对应的最近的像素点得到(四舍五入)
				int i_index = round(i / sy);
				int j_index = round(j / sx);
				if (i_index >src.rows - 1) i_index = src.rows - 1;//防止越界
				if (j_index >src.cols - 1) j_index = src.cols - 1;//防止越界
				//B
				dst.at<cv::Vec3b>(i, j)[0] = src.at<cv::Vec3b>(i_index, j_index)[0];
				//G
				dst.at<cv::Vec3b>(i, j)[1] = src.at<cv::Vec3b>(i_index, j_index)[1];
				//R
				dst.at<cv::Vec3b>(i, j)[2] = src.at<cv::Vec3b>(i_index, j_index)[2];
			}
		}
	}	
}

效果:

原图:

缩放因子为:1.5

缩放因子为:0.35