C++——bmp影象縮放(插值)
本文要實現的功能是使用最近鄰插值以及雙線性插值完成bmp影象的縮放。
1、最近鄰插值
不需要計算,在待求象素的四鄰象素中,將距離待求象素最近的鄰象素灰度賦給待求象素:
設i+u, j+v(i, j為正整數, u, v為大於零小於1的小數,下同)為待求象素座標,則待求象素灰度的值 f(i+u, j+v);如果(i+u, j+v)落在A區,即u<0.5, v<0.5,則將左上角象素的灰度值賦給待求象素,同理,落在B區則賦予右上角的象素灰度值,落在C區則賦予左下角象素的灰度值,落在D區則賦予右下角象素的灰度值。
最鄰近演算法計算量較小,但可能會造成插值生成的影象灰度上的不連續,在灰度變化的地方可能出現明顯的鋸齒狀。
程式碼如下:
void image_scaling_nearest() { char readPath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\lunpan.bmp"; readBmp(readPath); unsigned char *imagedata = NULL; //動態分配儲存原圖片的畫素資訊的二維陣列 unsigned char *imagedataScal = NULL;//動態分配儲存縮放後的圖片的畫素資訊的二維陣列 imagedata = pBmpBuf; float ExpScalValue = 0; ////期望的縮放倍數(允許小數) int FloatToIntwidth, FloatToIntheight;/////小數變成整數(float To Int) int RotateAngle = 90;//要縮放的角度,預設90 //圖片縮放處理 cout << "請輸入要縮放的倍數:" << endl; cin >> ExpScalValue; ///如果ExpScalValue含有小數,需要整數化 ///對期望的縮放結果取整 FloatToIntwidth = (int)(ExpScalValue*bmpWidth); FloatToIntheight = (int)(ExpScalValue*bmpHeight); //影象每一行的位元組數必須是4的整數倍 int lineByte2 = (FloatToIntwidth * biBitCount / 8 + 3) / 4 * 4; imagedataScal = new unsigned char[lineByte2 * FloatToIntheight];///為縮放後圖像分配儲存空間 int pre_i, pre_j, after_i, after_j;//縮放前後對應的畫素點座標 for (int i = 0; i<FloatToIntheight; i++) { for (int j = 0; j<FloatToIntwidth; j++) { for (int k = 0; k < 3;k++) { after_i = i; after_j = j; pre_i = (int)(after_i / ExpScalValue);/////取整,插值方法為:最鄰近插值(近鄰取樣法) pre_j = (int)(after_j / ExpScalValue); if (pre_i >= 0 && pre_i < bmpHeight && pre_j >= 0 && pre_j < bmpWidth)//在原圖範圍內 *(imagedataScal + i * lineByte2 + j*3+k) = *(imagedata + pre_i * bmpWidth*3 + pre_j*3+k); } } } //儲存bmp圖片 char writePath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\111.bmp"; saveBmp(writePath, imagedataScal, FloatToIntwidth, FloatToIntheight, biBitCount, pColorTable); printf("縮放變換完成,請檢視Scalresult.bmp檔案。\n\n"); //釋放記憶體 delete[] imagedata; delete[] imagedataScal; }
2、雙線性插值
雙線性插值是有兩個變數的插值函式的線性插值擴充套件,其核心思想是在兩個方向分別進行一次線性插值,線性插值的結果與插值的順序無關。(下圖從https://blog.csdn.net/zhangla1220/article/details/41014541截圖所得)
目標圖(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;
為更加形象地說明該原理,我們給出下圖:
如上面所言,我們需要算出目標影象畫素跟原影象畫素的對映關係,我們從遍歷目標影象畫素點開始,對每一個畫素點進行比例計算,算出其如果投影到原影象應該是處於原影象的哪個位置,通常所得到的結果正如上圖的所示,求得的點P處於原影象的四個真實畫素點中。
我們假定原影象畫素點間的RGB(或灰度)值是呈線性變化的,則我們可以通過A、B兩點RGB(或灰度)值算出AB線段上面的E點的RGB(或灰度)值,同理亦可算出F點的RGB(或灰度)值。得到了E、F兩點的RGB(或灰度)值後可經由相同的方法得到P點的RGB(或灰度)值。到此,我們就知道該如何通過對映關係去求得目標影象的RGB(或灰度)值了。
我們把點A、B、C、D、E、F、P的RGB(或灰度)值分別記為F_A、F_B、F_C、F_D、F_E、F_F、F_P(注意由於RGB是三個值,這個記法其實不嚴謹,可以理解為是FA等是RGB的其中一個值,然後另外兩個也可同理得到),則有
F_E=(1-0.7)*F_A+(1-0.3)*F_B,
F_F=(1-0.7)*F_C+(1-0.3)*F_D,
最終 得到目標點的值:
F_P=(1-0.7)*F_E+(1-0.3)*F_F=0.3*0.3*F_A+0.3*0.7*F_B+0.7*0.3*F_C+0.7*0.7*F_D。
程式碼如下:
void image_scaling_doubleline()
{
char readPath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\lunpan.bmp";
readBmp(readPath);
unsigned char *imagedata ; //動態分配儲存原圖片的畫素資訊的二維陣列
unsigned char *imagedataScal ;//動態分配儲存縮放後的圖片的畫素資訊的二維陣列
imagedata = pBmpBuf;
float ExpScalValue = 0; ////期望的縮放倍數(允許小數)
int FloatToIntwidth, FloatToIntheight;/////小數變成整數(float To Int)
int RotateAngle = 90;//要縮放的角度,預設90
//圖片縮放處理
cout << "請輸入要縮放的倍數:" << endl;
cin >> ExpScalValue;
///如果ExpScalValue含有小數,需要整數化
///對期望的縮放結果取整
FloatToIntwidth = (int)(ExpScalValue*bmpWidth);
FloatToIntheight = (int)(ExpScalValue*bmpHeight);
//影象每一行的位元組數必須是4的整數倍
int lineByte = (bmpWidth * biBitCount / 8 + 3) / 4 * 4;
int lineByte2 = (FloatToIntwidth * biBitCount / 8 + 3) / 4 * 4;
imagedataScal = new unsigned char[lineByte2 * FloatToIntheight];///為縮放後圖像分配儲存空間
/*******************影象處理部分******************/
/*******************雙線性插值******************/
for (int i = 0; i < FloatToIntheight; i++)
for (int j = 0; j < FloatToIntwidth; j++)
{
float d_original_img_hnum = i / ExpScalValue;
float d_original_img_wnum = j / ExpScalValue;
int i_original_img_hnum = d_original_img_hnum;
int i_original_img_wnum = d_original_img_wnum;
float distance_to_a_x = d_original_img_wnum - i_original_img_wnum;//在原影象中與a點的水平距離
float distance_to_a_y = d_original_img_hnum - i_original_img_hnum;//在原影象中與a點的垂直距離
int original_point_a = i_original_img_hnum*lineByte + i_original_img_wnum * 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點A
int original_point_b = i_original_img_hnum* lineByte + (i_original_img_wnum + 1) * 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點B
int original_point_c = (i_original_img_hnum + 1)* lineByte + i_original_img_wnum * 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點C
int original_point_d = (i_original_img_hnum + 1)* lineByte + (i_original_img_wnum + 1) * 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點D
if (i_original_img_hnum == bmpHeight - 1)
{
original_point_c = original_point_a;
original_point_d = original_point_b;
}
if (i_original_img_wnum == bmpWidth - 1)
{
original_point_b = original_point_a;
original_point_d = original_point_c;
}
int pixel_point = i*lineByte2 + j * 3;//對映尺度變換影象陣列位置偏移量
for (int k = 0; k < 3; k++)
{
imagedataScal[pixel_point + k] =
imagedata[original_point_a + k] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
imagedata[original_point_b + k ]* distance_to_a_x*(1 - distance_to_a_y) +
imagedata[original_point_c + k ]* distance_to_a_y*(1 - distance_to_a_x) +
imagedata[original_point_d + k ]* distance_to_a_y*distance_to_a_x;
/*assert((pixel_point + k)<(lineByte2 * FloatToIntheight));
assert((original_point_a + k)<(lineByte * bmpHeight));
assert((original_point_b + k)<(lineByte * bmpHeight));
assert((original_point_c + k)<(lineByte * bmpHeight));
assert((original_point_d + k)<(lineByte * bmpHeight));*/
}
}
/*******************雙線性插值******************/
/*******************影象處理部分******************/
//儲存bmp圖片
char writePath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\11-1.bmp";
saveBmp(writePath, imagedataScal, FloatToIntwidth, FloatToIntheight, biBitCount, pColorTable);
printf("縮放變換完成,請檢視Scalresult.bmp檔案。\n\n");
//釋放記憶體
delete[] imagedata;
delete[] imagedataScal;
}