暗通道去雾

暗通道去雾

序言

毕业设计选了用fpga做图像处理,之前在做车牌识别的时候,碰巧发现做去雾处理的,发现这个蛮有意思的,当时考研没有时间呢,现在正好借毕设把这个想法实现一下。其中图像去雾处理,网上有好多方法,比较了一下,何恺明的暗通道去雾应该是效果最好的,后来的人也对他的算法进行了改进,基本能达到比较不错的结果。

原理

暗通道先验理论

暗通道图像就是三个颜色分量的最小值组成一副灰度图像,

对于没有雾的图像J_dark趋于零,这一结果是何博士分析了5000多张正常图片的暗通道结果,而对于有雾图像并不满足之一验证。

#####公式

在去雾处理中,下面去雾模型被广泛应用

其中I(x)代表输入的雾图,J(x)代表去雾后的图像,t(x)为透射图,A为大气参数。现在只有I(x)知道,想要求出来J(x),还要把tx,A求出来才可以

对公式两边求最小值操作,因为正常图像的暗通道趋于零,所以可以化简为

因为在实际景物中存在一定雾气,因此在前面加入一个参数w,这个因子一般取0.95

而大气参数A,可以根据暗通道求出,最后的去雾公式为

为了防止J(x)过大超过阈值,tx不能太小,因此对tx设置一个最小值t0=0.1

opencv实现

求暗通道

求三个通道中的最小值即可,用at遍历,然后输出为单通道图片,最后进行最小值滤波,最小值滤波在

int FindDarkChannel(Mat* scr_img, Mat* dst_img)
{
	uchar r, g, b;
	for (int row = 0; row < scr_img->rows; row++)
	{
		for (int col = 0; col < scr_img->cols; col++)
		{
			r = scr_img->at<Vec3b>(row, col)[0];
			g = scr_img->at<Vec3b>(row, col)[1];
			b = scr_img->at<Vec3b>(row, col)[2];
			if (r < g&&r < b)
			{
				dst_img->at<uchar>(row,col) = r;
			}
			else if (g < r&&g < b)
			{
				dst_img->at<uchar>(row, col) = g;
			}
			else
			{
				dst_img->at<uchar>(row, col) = b;
			}
		}
	}
	return 0;
}

获取大气光值A

1、首先把暗通道分成一千份,然后找到这千分之一最亮的像素区域。

2、从这块区域中找到最亮的点就是大气光强。

分成1000份是为了防止把一些非天空区域的白色物体当成大气光值

int GetMosphereLight(Mat* dark_img, Mat* scr_img, int* A)
{
	int top_size = dark_img->cols*dark_img->rows / 1000;
	//cout << dark_img->cols*dark_img->rows << endl;
	//cout << "top_size::" << top_size << endl;
	int sum_pxiel[1010] = {0};  //多余的储存剩下的像素
	int max_num = 0,cnt_num=0,cnt_pixel=0;//0开始

	for (int row = 0; row < dark_img->rows; row++)
	{
		for (int col = 0; col < dark_img->cols; col++)
		{
			sum_pxiel[cnt_num] += dark_img->at<uchar>(row, col);
			cnt_pixel++;
			if (cnt_num == 0)
				cout << cnt_pixel << endl;

			if (cnt_pixel == top_size)
			{
				//cout << "cnt_num" << cnt_num <<"  "<<sum_pxiel[cnt_num]<< endl;
				cnt_pixel = 0;
				cnt_num++;

			}
		}
	}
	max_num = 0;

	//debug
	for (int i = 0; i < 100; i++)
	{
		cout << i << "   " << sum_pxiel[i] << endl;
	}

	for (int i = 0; i < 1000; i++)
	{
	
		if (sum_pxiel[i] > sum_pxiel[max_num])
		{
			max_num = i;
		}
	}
	cout << "max_num         " << max_num << endl;

	int row = max_num * top_size / dark_img->cols;
	int col = max_num * top_size % dark_img->cols;

	cout << "row  " << row << "col  " << col << endl;

	int max_row = row, max_col = col;

	for (int i = 0; i < top_size; i++)
	{
		//cout << "i==" << i << "  " << endl;
		int row_temp = row + i / dark_img->cols;
		int col_temp = col + i % dark_img->cols;
		if (dark_img->at<uchar>(row_temp,col_temp) > dark_img->at<uchar>(max_row, max_col))
		{
			max_row = row_temp;
			max_col = col_temp;
		}

	}

	Mat a_img = scr_img->clone();
	Point a_point;
	a_point.x = max_col;
	a_point.y = max_row;
	circle(a_img, a_point, 10, (0, 255, 0),5);
	imshow("mark大气光值", a_img);

	for (int i = 0; i < 3; i++)
	{
		A[i] = scr_img->at<Vec3b>(max_row, max_col)[i];
	}

	return 0;
}

获取透射图

这个大气光值A是取得三个通道的平均值,这样可以加快运算速度,另外需要注意图像的类型

int GetTransmission(Mat* scr_img, Mat* trans_img, int* A, float w)
{
	Mat trans_temp = Mat(scr_img->rows, scr_img->cols, CV_32FC3);
	float a_average = (float)(A[0] + A[1] + A[2]) / 3;
	if (a_average > 220)
		a_average = 220;
	cout << "average" << a_average << endl;
	for (int row = 0; row < scr_img->rows; row++)
	{
		for (int col = 0; col < scr_img->cols; col++)
		{
			for (int i = 0; i < 3; i++)
			{
				trans_temp.at<Vec3f>(row, col)[i] = (float)scr_img->at<Vec3b>(row, col)[i] / a_average;
			}
		}
	}
	imshow("t_temp", trans_temp);

	Mat dark_t_img = Mat(scr_img->size(), CV_32FC1);
	FindDarkChannel_f(&trans_temp, &dark_t_img);
	imshow("t暗通道", dark_t_img);
	*trans_img = dark_t_img.clone();
	MinFilter_f(&dark_t_img, trans_img, 6);
	imshow("t_min", *trans_img);

	int a = 0;
	for (int row = 0; row < scr_img->rows; row++)
	{
		for (int col = 0; col < scr_img->cols; col++)
		{
			if ((1 - w * trans_img->at<float>(row, col)) < 0)
			{
				cout << "  ww" << endl;
				trans_img->at<float>(row, col) = 0;
			}
			else
				trans_img->at<float>(row, col) = 1 - w * trans_img->at<float>(row, col);
			if (trans_img->at<float>(row, col) < 0.1)
			{
				//cout << a++ << endl;
				trans_img->at<float>(row, col) = 0.1;
			}
		}

	}


	return 0;
}

导向滤波

直接出来的透射图并不精细,用导向滤波后输出的透射图更加精细,导向滤波的引导图可以是灰度图,也可以是rgb图像。opencv里面有导向滤波的函数,但是需要经过编译后才可以使用,导向滤波的实现并不麻烦,这里可以自己实现

int GuideFilter(Mat* scr_img, Mat* guide_img, Mat* final_img, int radius, float e)
{
	Mat guide_conver = Mat(guide_img->size(), CV_32FC1);
	guide_img->convertTo(guide_conver, CV_32FC1, 1.0 / 255);
	
	int size = radius * 2 + 1;
	//1
	Mat mean_i = Mat(guide_img->size(), CV_32FC1);
	boxFilter(guide_conver, mean_i, CV_32FC1, Size(size, size));
	Mat mean_p = Mat(guide_img->size(), CV_32FC1);
	boxFilter(*scr_img, mean_p, CV_32FC1, Size(size, size));
	Mat mean_ip = Mat(guide_img->size(), CV_32FC1);
	boxFilter(guide_conver.mul(*scr_img), mean_ip, CV_32FC1, Size(size, size));
	Mat mean_ii = Mat(guide_img->size(), CV_32FC1);
	boxFilter(guide_conver.mul(guide_conver), mean_ii, CV_32FC1, Size(size, size));;

	//2
	Mat var_i = Mat(guide_img->size(), CV_32FC1);
	var_i = mean_ii - mean_i.mul(mean_i);
	Mat cov_ip = Mat(guide_img->size(), CV_32FC1);
	cov_ip = mean_ip - mean_i.mul(mean_p);
	//3
	Mat a = Mat(guide_img->size(), CV_32FC1);
	a = cov_ip / (var_i + e);
	Mat b = Mat(guide_img->size(), CV_32FC1);
	b = mean_p - a.mul(mean_i);
	//4
	Mat mean_a = Mat(guide_img->size(), CV_32FC1);
	Mat mean_b = Mat(guide_img->size(), CV_32FC1);
	boxFilter(a, mean_a, CV_32FC1, Size(size, size));
	boxFilter(b, mean_b, CV_32FC1, Size(size, size));
	//5
	*final_img = mean_a.mul(guide_conver) + mean_b;
	return 0;
}

恢复图像

这里需要注意的是,参与运算的图像有浮点型和整数型,整数型要归一化才可以和浮点型运算。

int RecoverPicture(Mat* scr_img, Mat* t_img, Mat* recover_img, int* A)
{
	float average = (float)(A[0] + A[1] + A[2]) / 3;
	cout <<"A"<< average/255 << endl;
	if (average > 220)
		average = 220;
	for (int row = 0; row < scr_img->rows; row++)
	{
		for (int col = 0; col < scr_img->cols; col++)
		{
			for (int i = 0; i < 3; i++)
			{
				recover_img->at<Vec3f>(row, col)[i] = ((float)scr_img->at<Vec3b>(row, col)[i]/255 -average/255) / t_img->at<float>(row, col) + average /255;
			}
		}
	}
	return 0;
}

不足

如果图像中,天空区域过亮或者具有大面积天空区域,去雾图片会出现色斑效应,看网上有人指出可能是t0的最小值为to,当大量t趋于一个值时就会出现色斑效应,另外如果大气光值找的不准确,色彩就会失真,这些问题等后期再解决。