航拍圖像視覺效果提升

全文總結:測試了幾種對比度提升對於航拍彩色圖像的視覺提升效果。總結圖像效果差的原因後,利用去霧算法獲得了絕大多數場景適用的處理效果,CPU單幀處理時間爲160ms(3*1920*1080)。

種種原因,於8.15開始成爲一隻社畜,找的工作呢是關於圖像處理的。入職第一天,我的組長給了我一個任務,就是處理無人機航拍的圖像,主要目的是想提升一下視覺效果。

圖像類似下圖這種,可以看到拍攝環境是光照比較強的,但是圖像看起來似乎有一層灰濛濛的東西。其他光照較弱的圖像中,圖像質量較這張差更多。這樣的圖像主要原因,我認爲是由於公司設計的無人機的飛行高度比較高,當拍攝地面時,空氣中會有較多的散射微粒影響成像,所以出現這樣的效果。

最開始組長讓我試試各種對比度提升的算法,讓圖像看起來好一些,於是我按着這個思路試了一下。

 

直方圖增強

直方圖增強的算法肯定就不必多說了,就是通過擴展像素值的範圍,讓圖像的對比度提升,看起來圖像會有更多細節突出。

在代碼中我使用了openCV自帶的直方圖增強函數,將圖像分爲三個通道,分別使用再將通道合併。

equalizeHist( InputArray src, OutputArray dst )

                                                                          圖1 起飛場地圖

                                                                               圖2 地面植被圖

                                                                                 圖3 天空效果圖

使用直方圖增強時,地面效果圖很差,地面植被場景的效果還不錯,在天空效果圖中,遠處的天空則會出現色塊,說明這種方法不是場景普遍適用的。

總結原因:1. 因爲RGB通道分開進行直方圖均衡化會出現顏色的失真。

                  2.天空有色塊,則是因爲直方圖均衡化對於一些圖像區域灰度範圍不一致時,效果會很差。

基於以上的原因,我還想測試的幾種簡單的圖像增強算法,如伽馬校正,拉普拉斯銳化等等,就被一一否決了。因爲這些算法也大部分是RGB分開處理再融合,避免不了顏色失真的影響。出於對顏色失真的考慮,我決定再使用顏色空間轉換到HSI空間或者YCrCb空間處理,直方圖增強其強度分量。

Mat ycrcb;
Mat result;
vector<Mat> channels;
cvtColor(frame, ycrcb, COLOR_RGB2YCrCb);       //RGB到YCRCB
split(ycrcb, channels);                        //通道分離
equalizeHist(channels[0], channels[0]);        //直方圖增強強度分量
merge(channels, ycrcb);                         //通道合併
cvtColor(ycrcb, result, COLOR_YCrCb2RGB);       //YCRCB到RGB

                                                                                           圖4 地面圖

                                                                                           圖5 植被                                               

圖4 圖5給出了增強強度分量的圖像結果,還是不理想,因爲整個圖像變得像灰度圖一樣,並且天空處仍然有色塊(此處沒圖)。

花了一天測試這些基本的圖像增強算法,效果都不能達到要求。於是我開始另外思考航拍圖像有這種效果的原因,正如我在開頭所講,是空氣中的散射粒子導致了這種現象。我突然覺得這種圖像類似於有霧的圖像,因爲散射粒子就類似於霧對圖像的影響,覺得去霧的算法可能有效果,同時我還需要一種算法處理速度較快,來滿足我後期的需求。

全網搜索了一下,發現了這樣一篇寶藏文章,文章沒有源碼,但是講解十分清楚。看完後,我對於這個算法的流程理解的很好,於是自己用了半天多時間在自己的電腦環境下編寫了程序,並且測試了效果。參考文章點下圖,作者人very nice,我還問了幾個問題都幫我解答了。該文章裏面有的內容我不再贅述,我只寫一下我自己的代碼實現步驟和想法。

一種可實時處理 O(1)複雜度圖像去霧算法的實現。

                                                                                  圖6 算法步驟

1. 求暗通道及圖像最大值(0-255),圖像均值(0-1)

		 //step 1 求出暗通道,以及暗通道均值,圖像最大值
		 for (Y = 0; Y < rows; Y++)
		 {
			 ImgPt = frame.ptr<uchar>(Y);
			 DarkPt = dark.ptr<uchar>(Y);
			 for (X = 0; X < cols; X++)
			 {
				 Min = double(*ImgPt) ;
				 //Max = double(*ImgPt) ;
				 if (Max < (*(ImgPt + 1))) { Max = *(ImgPt + 1); }
				 if (Max < (*(ImgPt)))     { Max = *(ImgPt); }
				 if (Max < (*(ImgPt + 2))) { Max = *(ImgPt + 2); }


				 if (Min > (*(ImgPt + 1))) { Min = *(ImgPt + 1);}
				 if (Min > (*(ImgPt + 2))) { Min = *(ImgPt + 2); }
				 *DarkPt = uint(Min);                                 //    三通道的最小值
				 Sum += Min ;                                     //  累積以方便後面求平均值
				 ImgPt += 3;
				 DarkPt++;


			 }
		 }
		 Mean = (double)Sum / (rows*cols*255);

這一步的基本思路還是按參考文章寫得,不同的是,我將求最大值的代碼也加入了這段程序中。我覺得值得注意的一點是,平均值Mean的範圍。算法步驟和其對應論文中,表示圖像的像素值的範圍是0-1,但是因爲openCV的像素都是0-255,我爲了方便,只將平均值算到0-1,而不改變其它的值。如果變量的範圍不注意一下,很有可能下面的計算環境光一步得不到結果。

2. 暗通道圖像模糊化

//step 2 對暗通道均值濾波(0-255)運行時間109ms左右
blur(dark, mdark, Size(64, 64));

這一步就比較簡單了,就是用openCV內置的blur函數,對圖像做了一次均值濾波。這裏的size取決於圖像大小,也看後面的測試效果。因爲我的應用場景比較關心時間,所以測算了一下這個模糊函數的時間在110ms左右(彩色圖像1920*1080)。參考文章的作者有個時間更快的方法,如果讀者對於CPU速度加快有興趣,可以參考。因爲我的代碼最終是要用GPU實現的,所以不再考慮那些優化的方法。

3. 環境光求解及暗通道最大值

         //step 3 求解Lx,環境光(0-255)
		 for (Y = 0; Y < rows; Y++)
		 {
			 ImgPt = mdark.ptr<uchar>(Y);
			 p1 = Lx.ptr<uchar>(Y);
			 DarkPt = dark.ptr<uchar>(Y);
			 for (X = 0; X < cols; X++)
			 {
				 
				 if (dMax <(*(ImgPt))) { dMax = (*(ImgPt)); }

				 Min = 0.9;
				 if ((rou*Mean) < Min) Min = rou * Mean;

				 Min = Min * (*ImgPt);
				 //cout << uint(*ImgPt);
			     if (Min >(*DarkPt)) Min = (*DarkPt);
				 *p1 = uint(Min);

				 ImgPt += 1;
				 DarkPt++;
				 p1++;
			 }
			}

這一步也很簡單,就是根據公式求出環境光,注意環境光的像素值是0到255,這個Lx矩陣是一個單通道矩陣。

4. 計算原始圖像

 //step 4 求解A,大氣指數,爲1*3數組
		 A =(0.5*(Max + dMax));

 //step 5  計算原始圖像,使用LUT方法
//建立查找表
	 unsigned char * Table = (unsigned char  *)malloc(256 * 256 * sizeof(unsigned char));
			 for (Y = 0; Y < 256; Y++)
			 {
				 Index = Y << 8;
				 for (X = 0; X < 256; X++)
				 {
					 Value = (Y -X) / (1 - X * (1 / A));
					 //cout << Value << endl;
					 if (Value > 255)
						 Value = 255;
					 else if (Value < 0)
						 Value = 0;

					 Table[Index++] = Value;

				 }
			 }

 //查表計算原始圖像
			 for (Y = 0; Y < rows; Y++)
			 {
				 ImgPt = frame.ptr<uchar>(Y);
				 p1 = Ipro.ptr<uchar>(Y);
				 p2 = Lx.ptr<uchar>(Y);
				 for (X = 0; X < cols; X++)
				 {
					  
					 *p1= Table[(*ImgPt<< 8) + uint((*p2))];
					 *(p1 + 1) = Table[(*(ImgPt+1) << 8) + uint((*p2))];
					 *(p1 + 2) = Table[(*(ImgPt+2) << 8) + uint((*p2))];
					 ImgPt += 3;
					 p1+=3; 
					 p2++;


				 }
			 }

這裏也是根據公式算出原始圖像,值得注意的是,參考博文作者用的是查表法。就C++寫openCV的程序來說,最費時間的其實就是循環讀取像素的過程,再計算每個像素。那麼有沒有什麼較快的方法呢?

OpenCV官方文檔提供了一些參考,How to scan images, lookup tables and time measurement with OpenCV。最快的就是查表法,這是通過犧牲一部分內存的方法來降低運算量。計算前和計算後每一個像素值,存在一種映射關係,如果找到這種映射關係,就可以直接賦值,而不是還在每個像素點計算。官方文檔也提供了一些解釋,有興趣可以去看看上面的網址。

從以上這一段程序,可以看書來,計算的時候就直接用查表計算,免去了很多計算量。openCV還提供一種內置函數來實現查表法,即。其中I是輸入圖像;lookUpTable就是我們建立的查找表,按照像素一一對應的關係,應該是256*256大小;J就是輸出圖像。

LUT(I, lookUpTable, J);

LUT函數由於是openCV的內置函數,已經實現了加速,我認爲其實就是簡單的放到了GPU下,每個像素並行計算了,消耗的時間也就是GPU和CPU來回讀寫的時間。

以上就完成了去霧算法的全部過程,由於我只是在實現這個算法,並沒有對算法的原理有過多研究,可能沒能在這方面提供幫助。下面是處理完的圖像效果

這個算法的實現就告一段落,效果以滿足公司的要求,由於處理時間還是太長,所以還是要探究加快計算速度的辦法。後面也需要將該算法移植到公司的視頻播放器中,所以最近也在學習D3D11的部分,希望用GPU並行計算實現這部分代碼,還在探索中。這篇博客的內容也是來自兩週前了,今天週五接近下班,所以就寫了寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章