1. 程式人生 > >OpenCV-最近鄰插值及雙線性插值實現

OpenCV-最近鄰插值及雙線性插值實現

最近鄰插值及雙線性插值的基本原理

舉個簡單的影象: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<cv::Vec3b>(i, j) = image.at<cv::Vec3b>(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;
}

執行結果: