1. 程式人生 > >C++——bmp影象縮放(插值)

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;
}