1. 程式人生 > >最近鄰內插值與雙線性插值

最近鄰內插值與雙線性插值

前一陣想改進個影象處理的演算法,發現基礎太差了,還是要從基礎開始,畢竟一口出不成胖子大笑

於是自己學習一下影象處理的基本知識。看到兩個最簡單的演算法 一個是最近鄰內插值和雙線性插值,決定自己實現以下,順便也能瞭解一下數字影象內部的儲存結構。

以下為簡單整理

by the way,基礎知識逐漸明朗的感覺還是不錯滴~程式設計師最大的開心應該就是程式無bug跑通的時候,無論是多簡單的程式。

一個Width*Height  nChannel個通道的影象 共具有Width * Height 個畫素點。每個畫素點有nChannel個通道元素。

以3通道為例,3通道為B G R

如下:

如上所示,每一行有效元素共Width*nChannel個元素,實際上,有時候比這個數要多。

為了提高速度,需要按4位元組對齊,因此如果每行元素個數不是4的倍數,需要補齊,這個即為平時總提到的widthstep。

基於以上的瞭解,實現兩種演算法

1 最近領內插值演算法

就是令變換後像素的灰度值等於距它最近的輸入畫素的灰度值

如上圖 按照比例一致 可知道

Y = W / w * y;

X = H / h * x;

則目標圖(x, y)處的灰度值與原圖(X, Y )處的灰度值一致: f(x, y) = f(X, Y)

程式碼:

//最近鄰內插
void NearestInsert(const char *OriImagePath, char *OutImagePath, int Width, int Height)
{
	IplImage * OriImage = cvLoadImage(OriImagePath, 1);
	IplImage * OutImage = cvCreateImage(cvSize(Width, Height), 8, 3);

	unsigned char *OriImage_c = (unsigned char *)OriImage->imageData;
	unsigned char *OutImage_c = (unsigned char *)OutImage->imageData;

	double fx = (double)OriImage->height / (double)Height;//height方向上的比值
	double fy =  (double)OriImage->width / (double)Width;//width方向上的比值
	int channel = OriImage->nChannels;//通道數 一般為RGB 3通道
	
	for (int x = 0; x < Height; x++)
	{
		for(int y = 0; y < Width; y++)
		{
			for (int k = 0; k <channel; k++)
			{
				int out =  x * OutImage->widthStep + y * channel + k;
				int ori = (int)(fx * x) * OriImage->widthStep + (int)(fy * y)  * channel + k;
				OutImage_c[out] = OriImage_c[ori];
			}

		}
			
	}
	
	//test
	cvNamedWindow("OriImage",1);
	cvShowImage("OriImage",OriImage);
	cvWaitKey(0);
	cvNamedWindow("NearestInsert",1);
	cvShowImage("NearestInsert",OutImage);
	cvWaitKey(0);
	//cvSaveImage(OutImagePath,OutImage);
	cvReleaseImage(&OriImage);
	cvReleaseImage(&OutImage);
}


2 雙線性插值

在x y兩個方向上分別進行一次線性插值,方向先後不影響順序。

如上圖所示

目標圖(x, y) 對映到原圖是(X + u, Y + v)(計算方法同最鄰近插值)。設u與v分別為X + u,Y + v的小數部分。

由於下標都是整數,因此原圖其實並不存在該點。

則取其附近四個領域點為(X, Y) (X, Y + 1) (X + 1, Y) (X + 1, Y + 1)

則目標圖(x, y)處的值為 f(x , y) = f(X + u, Y + v) =f (X, Y)  * (1 - u) * (1 - v) + f(X, Y + 1) * (1 - u) * v + f(X + 1, Y) * u * (1 - v) + f (X + 1, Y + 1) * u * v;

程式碼如下:

//雙線性內插
void BilinearInsert(const char *OriImagePath, char *OutImagePath, int Width, int Height)
{
	IplImage * OriImage = cvLoadImage(OriImagePath, 1);
	IplImage * OutImage = cvCreateImage(cvSize(Width, Height), 8, 3);

	unsigned char *OriImage_c = (unsigned char *)OriImage->imageData;
	unsigned char *OutImage_c = (unsigned char *)OutImage->imageData;

	double fx = (double)OriImage->height / (double)Height;//height方向上的比值
	double fy =  (double)OriImage->width / (double)Width;//width方向上的比值
	int channel = OriImage->nChannels;//通道數 一般為RGB 3通道

	for (int x = 0; x < Height; x++)
	{
		for(int y = 0; y < Width; y++)
		{
			//對於目標影象(x, y),實際對映到原圖的座標為(fx * x, fy * y),但是若為小數,原圖並不存在該店,因此近似為(i, j)
			int i = fx * x;
			int j = fy * y;
			int out =  x * OutImage->widthStep + y * channel;
			//找到四個領域的下標
			int ori1 = i * OriImage->widthStep + j * channel;
			int ori2 = i * OriImage->widthStep + (j + 1)  * channel;
			int ori3 = (i + 1) * OriImage->widthStep + j  * channel;
			int ori4 = (i + 1) * OriImage->widthStep + (j + 1)  * channel;
			//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)  
			for (int k = 0; k <channel; k++)
			{
				float u = (float)x * fx - i;//
				float v = (float)y * fy - j;
				OutImage_c[out + k] = OriImage_c[ori1 + k] * (1 - u) * (1 - v) 
					+ OriImage_c[ori2 + k] * (1 - u) * v
					+ OriImage_c[ori3 + k] * u * (1 - v)
					+ OriImage_c[ori4 + k] * u * v;
					
			}

		}

	}

	//test
	cvNamedWindow("BilinearInsert",1);
	cvShowImage("BilinearInsert",OutImage);
	cvWaitKey(0);
	//cvSaveImage(OutImagePath,OutImage);
	cvReleaseImage(&OriImage);
	cvReleaseImage(&OutImage);

}