1. 程式人生 > >C/C++ 影象處理(7)------影象の球面貼合算法

C/C++ 影象處理(7)------影象の球面貼合算法

    關於影象的球面貼合,是全景應用中比較常見的技術,而現有的一些資源大多不太好,比較晦澀。在經過一段時間的摸索之後,發現了這個部落格寫的相對可以,本文的實現也將其作為重要的參考,如果看過本文之後有什麼不明白或者覺得不好的地方可以去看看。

    在展開本文之前,先來看看下面的兩張圖片:

   

    左邊的影象被貼合到球面上後,其正檢視為右邊的影象。而我們要研究的是,如何去貼合的過程。

    可以想象,左邊的影象是一張極薄的紗,將其蒙到一個大小正好的球面上(薄紗過中點的橫軸正好覆蓋球赤道的半周),然後正看過去會怎麼樣。我們將看到一個接近於上面右邊影象的影象。為什麼說接近而不說相同呢,因為大小是不一樣的。左邊影象的赤道如果剛剛好覆蓋掉球的半個平面,則球的周長將為左邊影象的寬度的兩倍。假定左邊影象的寬度為W,而球的半徑為R,則有

    πR=W

    所以球的正檢視的原直徑應該為

    2R=2*W/π<W

    即球的正檢視不會觸碰到影象邊緣,而為了我們看起來舒服一點,我們希望球的正檢視剛剛好就觸碰到影象邊緣,是故球做完貼合之後其正檢視需要做一個等比例的放大,該放大係數為π/2。經過放大,我們就得到了右邊的影象。之後,我們把放大係數設為k(注意這裡的k值跟影象貼合的球體半徑有關)。

    下面我們正式進入影象貼合的研究,還是遵循上面的設想,把左邊影象設想成一張極薄的紗,然後將其蒙到一個大小正好的球面上。那麼,左邊影象的中點將是右邊影象的中點,也就是球正面頂點的位置。

    接著,我們將影象的中心點記為點0,然後在左影象上隨便取一個點即為點A,假設點A是落在過中點的橫軸上的,很容易想象經過貼合後A也將落在右圖過中心點的赤道上。進一步想,左圖繞中心旋轉一個角度之後,原來不在過中點的橫軸上的點可能變為其上的點,相應的貼合後的點也會落在右圖過中心點的赤道上。也就是說,影象上的任意一點經過貼合之後,將落在原圖與中心點連線的線上。接著我們沿著連線切下將得到下面的切面:


    這便是這篇文章推導的關鍵,其中最為關鍵的是弧長等於OA*k這句話(由上面的薄紗模型很容易得出該弧為左圖中的OA經過彎曲而來,長度自然相同,而*k是因為影象經過了放大),有了上面這些條件,我們可以列出以下這些公式:

    推導到最後我們可以看到原影象座標(X,Y)和貼合後影象座標(X',Y')的換算關係,其跟上面提到的部落格最大不同的地方是將X,Y寫在左邊而X',Y'寫在右邊。在本人看來,這樣才是合理的,因為後面我們需要去原影象找到對應的畫素點取畫素值,而對映後圖像遍歷時座標是知道的,應該為已知條件。詳細的可參考我之前寫過的 

    OK,到這裡推導過程就全部結束了,按照上面的公式便可完成影象的對映。其主要的程式碼如下:

void DealWithImgData(BYTE *srcdata, BYTE *drcdata,int width,int height)//引數一為原影象的資料區首指標,引數二為貼合後影象的資料區首指標,引數三為影象的寬,引數四為影象的高
{
	int l_width = WIDTHBYTES(width* 24);//計算點陣圖的實際寬度並確保它為4byte的倍數 
	double radius1 = height /2;//貼合球面半徑
	double radius2 = radius1*radius1;//半價的平方
	double x1, y1;//目標在球正檢視中的座標位置
	double x, y;//目標在球正檢視中對應原圖的座標位置
	double middle2 = 2 * radius1 / 3.1416;//計算過程式子
	double matan;//目標與圓心連線與x軸的夾角
	int pixel_point;//遍歷影象指標
	int pixel_point_row;//遍歷影象行首指標
	double oa;//點對應弧長度
	
	//雙線性插值演算法相關變數
	int i_original_img_hnum, i_original_img_wnum;//目標點座標
	double distance_to_a_y, distance_to_a_x;//在原影象中與a點的水平距離  
	int original_point_a, original_point_b, original_point_c, original_point_d;
 
	for (int hnum = 0; hnum < height; hnum++)
	{
		pixel_point_row = hnum*l_width;
		for (int wnum = 0; wnum < width; wnum++)
		{
			if ((hnum - height / 2)*(hnum - height / 2) + (wnum - width / 2)*(wnum - width / 2) < radius2)//在球體視場內才處理
			{
				pixel_point = pixel_point_row + wnum * 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點
				/***********球面貼合***********/
				x1 = wnum - width / 2;
				y1 = height / 2 - hnum;

				if (x1 != 0)
				{
					oa = middle2*asin(sqrt(y1*y1 + x1*x1) / radius1);//這裡在確定影象大小的情況下可以用查表法來完成,這樣會大大的提高其效率
					matan = atan2(y1, x1);
					x = cos(matan)*oa;
					y = sin(matan)*oa;
				}
				else
				{
					y = asin(y1 / radius1)*middle2;
					x = 0;
				}
				/***********球面貼合***********/

				/***********雙線性插值演算法***********/
				i_original_img_hnum = (height / 2 - y);
				i_original_img_wnum = (x + width / 2);
				distance_to_a_y = (height / 2 - y) - i_original_img_hnum;
				distance_to_a_x = (x + width / 2) - i_original_img_wnum;//在原影象中與a點的垂直距離  

				original_point_a = i_original_img_hnum*l_width + i_original_img_wnum * 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點A    
				original_point_b = original_point_a + 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點B  
				original_point_c = original_point_a+ l_width;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點C   
				original_point_d = original_point_c + 3;//陣列位置偏移量,對應於影象的各畫素點RGB的起點,相當於點D  

				if (hnum == height - 1)
				{
					original_point_c = original_point_a;
					original_point_d = original_point_b;
				}
				if (wnum == width - 1)
				{
					original_point_a = original_point_b;
					original_point_c = original_point_d;
				}

				drcdata[pixel_point + 0] =
					srcdata[original_point_a + 0] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
					srcdata[original_point_b + 0] * distance_to_a_x*(1 - distance_to_a_y) +
					srcdata[original_point_c + 0] * distance_to_a_y*(1 - distance_to_a_x) +
					srcdata[original_point_c + 0] * distance_to_a_y*distance_to_a_x;
				drcdata[pixel_point + 1] =
					srcdata[original_point_a + 1] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
					srcdata[original_point_b + 1] * distance_to_a_x*(1 - distance_to_a_y) +
					srcdata[original_point_c + 1] * distance_to_a_y*(1 - distance_to_a_x) +
					srcdata[original_point_c + 1] * distance_to_a_y*distance_to_a_x;
				drcdata[pixel_point + 2] =
					srcdata[original_point_a + 2] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
					srcdata[original_point_b + 2] * distance_to_a_x*(1 - distance_to_a_y) +
					srcdata[original_point_c + 2] * distance_to_a_y*(1 - distance_to_a_x) +
					srcdata[original_point_c + 2] * distance_to_a_y*distance_to_a_x;
				/***********雙線性插值演算法***********/
			}
		}
	}
}

    經過本人的測試,如果每次直接這樣算效率是比較低的,所以後來本人改由查表法來完成上面的工作,這份工程和可執行程式都已經打包在一起上傳了(由於是X64編譯的,需要電腦是64位作業系統才可以執行,如果是32位的則可通過修改工程解決,工程利用OpenCV進行解碼,需要自行配置,否則無法執行),如果有興趣的可以去下載。

下面放出一張處理結果圖