1. 程式人生 > >影象去霧演算法的原理、實現、效果

影象去霧演算法的原理、實現、效果

影象的去霧演算法原理及實現:

本文主要是實現的是基於暗通道處理的去霧。有部分是看論文直接翻譯而來,如有錯誤,歡迎評論區指出,當然您也可以直接閱讀原文。

一、原理

暗通道先驗解釋:

說明:何凱明博士的論文中統計了5000多副影象的特徵,證明了暗通道先驗理論的普遍性,因此我們可以粗略的認為是一條定理。

先看什麼是暗通道先驗:

在絕大多數影象區域性區域裡,有一些畫素總會有至少一個顏色通道具有很低的值,該區域光強度的最小值是個很小的數。暗通道的數學定義,對於任意的輸入影象J,其暗通道可以用下式表達:

                                    

式中Jc表示彩色影象的每個通道 ,Ω(x)表示以畫素X為中心的一個視窗。 

這個公式的意義用程式碼表達也很簡單,就是求出每個畫素RGB分量中的最小值,存入到一副和原始影象大小相同的灰度圖中,然後再對這幅灰度圖進行最小值濾波,濾波的半徑由視窗大小決定,一般有WindowSize = 2 * Radius + 1;          

暗通道先驗的理論指出:

                                                     

這個可以自己去試一下的。

 有了這個先驗,接著就需要進行一些數學方面的推導來最終解決問題。

在計算機視覺中,下述方程所描述的霧圖形成模型被廣泛使用:

                             

其中,I(X)就是我們現在已經有的影象(待去霧的影象),J(x)是我們要恢復出的無霧的影象,A是全球大氣光成分, t(x)為透射率。現在的已知條件就是I(X),要求目標值J(x),顯然,這是個有無數解的方程,因此,就需要一些先驗了。

將其稍作處理,變形為:

                                 

上標C表示R/G/B三個通道的意思。

首先假設在每一個視窗內透射率t(x)為常數,定義他為,並且A值已經給定,然後兩邊求兩次最小值運算,得到下式:

                                        

J是待求的無霧的影象,根據前述的暗原色先驗理論有:

                                                 

   可得:

                                                                            

則透射率的預估值:

                                                     

在現實生活中,即使是晴天白雲,空氣中也存在著一些顆粒,因此,看遠處的物體還是能感覺到霧的影響,另外,霧的存在讓人類感到景深的存在,有必要在去霧的時候保留一定程度的霧,這可以通過在上式中引入一個在[0,1] 之間的因子作為修正:

                                                            

所有的測試結果依賴於:  ω=0.95

上述推論中是假設全球達氣光A值時已知的,在實際中,我們可以藉助於暗通道圖來從有霧影象中獲取該值。

具體步驟如下:

     1) 從暗通道圖中按照亮度的大小取前0.1%的畫素。

          2) 在這些位置中,在原始有霧影象I中尋找對應的具有最高亮度的點的值,作為A值。

     到這一步,我們就可以進行無霧影象的恢復了。由式可知:  J = ( I - A)/t + A  

     現在I,A,t都已經求得了,因此,完全可以進行J的計算。

     當投射圖t 的值很小時,會導致J的值偏大,從而使淂影象整體向白場過度,因此一般可設定一閾值T0,當t值小於T0時,令t=T0,本文中所有效果圖均以T0=0.1為標準計算。

     因此,最終的恢復公式如下:

                                              

去霧的效果:

原圖:

去霧後:

二、各引數對去霧結果的影響

第一:視窗的大小。這個對結果來說是個關鍵的引數,視窗越大,其包含暗通道的概率越大,暗通道也就越黑。建議是視窗大小在11-51之間,即半徑在5-25之間。

第二:ω也有著明顯的意義,其值越小,去霧效果越不明顯。

三、程式碼

小博的環境是vs2017+opencv3.40:

程式碼如下:

#define _CRT_SECURE_NO_WARNINGS
#include "main.h"
#include "guidedfilter.h"
#pragma comment( lib, "opencv_world340d.lib" ) 
using namespace std;
using namespace cv;

int _PriorSize = 15;		//視窗大小 15
double _topbright = 0.001;//亮度最高的畫素比例
double _w = 0.95;			//w0.95
float t0 = 0.1;			//T(x)的最小值   因為不能讓tx小於0 等於0 效果不好   0.1
int SizeH = 0;			//圖片高度
int SizeW = 0;			//圖片寬度
int SizeH_W = 0;			//圖片中的畫素總 數 H*W
Vec<float, 3> a;//全球大氣的光照值
Mat trans_refine;
Mat dark_out1;

char img_name[100] = "11.jpg";//檔名


							   //讀入圖片
Mat ReadImage()
{
	Mat img = imread(img_name);

	SizeH = img.rows;
	SizeW = img.cols;
	SizeH_W = img.rows*img.cols;

	Mat real_img(img.rows, img.cols, CV_32FC3);
	img.convertTo(real_img, CV_32FC3);

	real_img = real_img / 255;

	return real_img;

	//讀入圖片 並其轉換為3通道的矩陣後 
	//除以255 將其RBG確定在0-1之間
}


//計算暗通道
//J^{dark}(x)=min( min( J^c(y) ) )
Mat DarkChannelPrior(Mat img)
{
	Mat dark = Mat::zeros(img.rows, img.cols, CV_32FC1);//新建一個所有元素為0的單通道的矩陣

	for (int i = 0; i<img.rows; i++)
	{
		for (int j = 0; j<img.cols; j++)
		{
			dark.at<float>(i, j) = min(
				min(img.at<Vec<float, 3>>(i, j)[0], img.at<Vec<float, 3>>(i, j)[1]),
				min(img.at<Vec<float, 3>>(i, j)[0], img.at<Vec<float, 3>>(i, j)[2])
				);//就是兩個最小值的過程
		}
	}
	erode(dark, dark_out1, Mat::ones(_PriorSize, _PriorSize, CV_32FC1));
	//這個函式叫腐蝕 做的是視窗大小的模板運算 ,對應的是最小值濾波,即 黑色影象中的一塊塊的東西

	return dark_out1;//這裡dark_out1用的是全域性變數,因為在其它地方也要用到
}

void printMatInfo(char * name, Mat m)
{
	cout << name << ":" << endl;
	cout << "\t" << "cols=" << m.cols << endl;
	cout << "\t" << "rows=" << m.rows << endl;
	cout << "\t" << "channels=" << m.channels() << endl;
}


//Main Function
int main(int argc, char * argv[])
{
	Mat dark_channel;
	Mat trans;
	Mat img;
	Mat free_img;
	char filename[100];



	while (_access(img_name, 0) != 0)//檢測圖片是否存在
	{
		std::cout << "The image " << img_name << " don't exist." << endl << "Please enter another one:" << endl;
		cin >> filename;
	}

	clock_t start, finish;
	double duration1, duration3, duration4, duration7;

	//讀入圖片
	cout << "讀入圖片 ..." << endl;

	start = clock();
	img = ReadImage();
	imshow("原圖", img);
	//printMatInfo("img", img);
	finish = clock();
	duration1 = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << "Time Cost: " << duration1 << "s" << endl;//輸出這一步的時間
	cout << endl;

	//計算暗通道
	cout << "計算暗通道 ..." << endl;

	start = clock();
	dark_channel = DarkChannelPrior(img);
	imshow("Dark Channel Prior", dark_channel);
	printMatInfo("dark_channel", dark_channel);
	finish = clock();
	duration3 = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << "Time Cost: " << duration3 << "s" << endl;
	cout << endl;

	//計算全球光照值
	cout << "計算A值 ..." << endl;
	start = clock();
	a = Airlight(img, dark_channel);
	cout << "Airlight:\t" << " B:" << a[0] << " G:" << a[1] << " R:" << a[2] << endl;
	finish = clock();
	duration4 = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << "Time Cost: " << duration4 << "s" << endl;
	cout << endl;

	//計算tx
	cout << "Reading Refine Transmission..." << endl;
	trans_refine = TransmissionMat(DarkChannelPrior_(ReadImage()));
	//printMatInfo("trans_refine", trans_refine);
	//imshow("Refined Transmission Mat",trans_refine);
	cout << endl;

	Mat tran = guidedFilter(img, trans_refine, 60, 0.0001);//導向濾波 得到精細的透射率圖
														   //imshow("fitler", tran);

														   //去霧

	cout << "Calculating Haze Free Image ..." << endl;
	start = clock();
	free_img = hazefree(img, tran, a, 0);//此處 如果用tran的話就是導向濾波部分
										 //如果是trans_refine 就沒有用導向濾波 效果不是那麼						的好
										 /*
										 上面第四個引數是用來增加亮度的,0.1比較好
										 */
	imshow("去霧後", free_img);


	finish = clock();
	duration7 = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << "Time Cost: " << duration7 << "s" << endl;
	cout << "Total Time Cost: " << duration1 + duration3 + duration4 + duration7 << "s" << endl;

	//儲存圖片的程式碼

	//imwrite("output.jpg", free_img * 255);

	waitKey();
	cout << endl;

	return 0;
}

還有幾個函式有時間整理。