最近鄰內插值與雙線性插值
前一陣想改進個影象處理的演算法,發現基礎太差了,還是要從基礎開始,畢竟一口出不成胖子
於是自己學習一下影象處理的基本知識。看到兩個最簡單的演算法 一個是最近鄰內插值和雙線性插值,決定自己實現以下,順便也能瞭解一下數字影象內部的儲存結構。
以下為簡單整理
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);
}