暗通道去雾及C++实现

本博文参考和引用:https://www.cnblogs.com/herenzhiming/articles/5280759.html

介绍

图像增强与图像修复二者之间有一定交叉,尽管它们一个强调客观标准,一个强调主观标准,但毕竟最终的结果都改善了图像的质量。图像去雾就是这两种技术彼此交叉领域中最典型的代表。如果将雾霾看作是一种噪声,那么去除雾霾的标准显然是非常客观的,也就是要将图像恢复至没有雾霾下所获取的情况。但是如果将在雾霾环境下拍摄的照片就看作是一种图像本来的面貌,那么去雾显然就是人们为了改善主观视觉质量而对图像所进行的一种增强。早期图像去雾的研究并没有得到应有的重视,很多人认为它的实际意义不大,甚至觉得所谓的去雾算法多是些华而不实的花拳绣腿,缺乏学术上的价值。然而,斗转星移,时易世变。一方面随着大气污染的日益严重,设法改善自动获取的图像质量其意义不言而喻。另一方面,随着数码设备的普及,消费类电子产品的市场也催生出许多新的需求,其中人们对所拍照片质量的修正和优化就是一个显而易见的需求。说到图像去雾,就不得不提到由何恺明博士等人提出的基于暗通道的图像去雾算法。这个算法因其新颖的思路和理想的效果而广受关注,相关论文也曾于2009年荣获CVPR最佳论文奖,同时也是该奖设立以来,首次由亚洲学者获颁此殊荣。

暗通道的概念和意义

暗通道去雾及C++实现
暗通道去雾及C++实现
暗通道去雾及C++实现
暗通道去雾及C++实现
暗通道去雾及C++实现

C++实现源代码

main.cpp:

#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include"function.h"
#include<algorithm>
#include<set>
#include<map>

using namespace std;
using namespace cv;
//导向滤波器

int main()
{
	Mat src = imread("C:/Users/18301/Desktop/1.png");
	Mat dst;
	cvtColor(src, dst, CV_BGR2GRAY);
	Mat dark_channel_mat = dark_channel(src);//输出的是暗通道图像
	int A = calculate_A(src, dark_channel_mat);
	Mat tx = calculate_tx(src, A, dark_channel_mat);
	Mat tx_ = guidedfilter(dst, tx, 30, 0.001);//导向滤波后的tx
	Mat haze_removal_image;
	haze_removal_image= haze_removal_img(src, A, tx_);
	namedWindow("去雾后的图像", 0);
	namedWindow("原始图像", 0);
	imshow("原始图像", src);
	imshow("去雾后的图像", haze_removal_image);
	waitKey(0);
	return 0;
}

function.h

#ifndef FUNCTION_H
#define FUNCTION_H   

#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include<map>

using namespace std;
using namespace cv;
//导向滤波,用来优化t(x),针对单通道
Mat guidedfilter(Mat &srcImage, Mat &srcClone, int r, double eps)
{
	//转换源图像信息
	srcImage.convertTo(srcImage, CV_32FC1,1/255.0);
	srcClone.convertTo(srcClone, CV_32FC1);
	int nRows = srcImage.rows;
	int nCols = srcImage.cols;
	Mat boxResult;
	//步骤一:计算均值
	boxFilter(Mat::ones(nRows, nCols, srcImage.type()),
		boxResult, CV_32FC1, Size(r, r));
	//生成导向均值mean_I
	Mat mean_I;
	boxFilter(srcImage, mean_I, CV_32FC1, Size(r, r));
	//生成原始均值mean_p
	Mat mean_p;
	boxFilter(srcClone, mean_p, CV_32FC1, Size(r, r));
	//生成互相关均值mean_Ip
	Mat mean_Ip;
	boxFilter(srcImage.mul(srcClone), mean_Ip,
		CV_32FC1, Size(r, r));
	Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);
	//生成自相关均值mean_II
	Mat mean_II;
	//应用盒滤波器计算相关的值
	boxFilter(srcImage.mul(srcImage), mean_II,
		CV_32FC1, Size(r, r));
	//步骤二:计算相关系数
	Mat var_I = mean_II - mean_I.mul(mean_I);
	Mat var_Ip = mean_Ip - mean_I.mul(mean_p);
	//步骤三:计算参数系数a,b
	Mat a = cov_Ip / (var_I + eps);
	Mat b = mean_p - a.mul(mean_I);
	//步骤四:计算系数a\b的均值
	Mat mean_a;
	boxFilter(a, mean_a, CV_32FC1, Size(r, r));
	mean_a = mean_a / boxResult;
	Mat mean_b;
	boxFilter(b, mean_b, CV_32FC1, Size(r, r));
	mean_b = mean_b / boxResult;
	//步骤五:生成输出矩阵
	Mat resultMat = mean_a.mul(srcImage) + mean_b;
	return resultMat;
}

//计算暗通道图像矩阵,针对三通道彩色图像
Mat dark_channel(Mat src)
{
	int border = 7;
	std::vector<cv::Mat> rgbChannels(3);
	Mat min_mat(src.size(), CV_8UC1, Scalar(0)), min_mat_expansion;
	Mat dark_channel_mat(src.size(), CV_8UC1, Scalar(0));
	split(src, rgbChannels);
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			int min_val = 0;
			int val_1, val_2, val_3;
			val_1 = rgbChannels[0].at<uchar>(i, j);
			val_2 = rgbChannels[1].at<uchar>(i, j);
			val_3 = rgbChannels[2].at<uchar>(i, j);

			min_val = std::min(val_1, val_2);
			min_val = std::min(min_val, val_3);
			min_mat.at<uchar>(i, j) = min_val;

		}
	}
	copyMakeBorder(min_mat, min_mat_expansion, border, border, border, border, BORDER_REPLICATE);

	for (int m = border; m < min_mat_expansion.rows - border; m++)
	{
		for (int n = border; n < min_mat_expansion.cols - border; n++)
		{
			Mat imageROI;
			int min_num = 256;
			imageROI = min_mat_expansion(Rect(n - border, m - border, 2*border+1, 2*border+1));
			for (int i = 0; i < imageROI.rows; i++)
			{
				for (int j = 0; j < imageROI.cols; j++)
				{
					int val_roi = imageROI.at<uchar>(i, j);
					min_num = std::min(min_num, val_roi);
				}
			}
			dark_channel_mat.at<uchar>(m - border, n - border) = min_num;
		}
	}
	return dark_channel_mat;
}


int calculate_A(Mat src,Mat dark_channel_mat)
{
	std::vector<cv::Mat> rgbChannels(3);
	split(src, rgbChannels);
	map<int, Point> pair_data;
	map<int, Point>::iterator iter;
	vector<Point> cord;
	int max_val = 0;
	//cout << dark_channel_mat.rows << " " << dark_channel_mat.cols << endl;
	for (int i = 0; i < dark_channel_mat.rows; i++)
	{
		for (int j = 0; j < dark_channel_mat.cols; j++)
		{
			int val = dark_channel_mat.at<uchar>(i, j);
			Point pt;
			pt.x = j;
			pt.y = i;
			pair_data.insert(make_pair(val, pt));
		}
	}

	for (iter = pair_data.begin(); iter != pair_data.end(); iter++)
	{
		//cout << iter->first << endl;
		cord.push_back(iter->second);
	}
	for (int m = 0; m < cord.size(); m++)
	{
		Point tmp = cord[m];
		int val_1, val_2, val_3;
		val_1 = rgbChannels[0].at<uchar>(tmp.y, tmp.x);
		val_2 = rgbChannels[1].at<uchar>(tmp.y, tmp.x);
		val_3 = rgbChannels[2].at<uchar>(tmp.y, tmp.x);
		max_val = std::max(val_1, val_2);
		max_val = std::max(max_val, val_3);
	}
	return max_val;
}


Mat calculate_tx(Mat &src, int A, Mat &dark_channel_mat)
{
	Mat dst;//是用来计算t(x)
	Mat tx;
	float dark_channel_num;
	dark_channel_num = A / 255.0;
	dark_channel_mat.convertTo(dst, CV_32FC3, 1 / 255.0);//用来计算t(x)
	dst = dst / dark_channel_num;
	tx = 1 - 0.95*dst;//最终的tx图

	return tx;
}


Mat haze_removal_img(Mat &src, int A, Mat &tx)
{
	Mat result_img(src.rows, src.cols, CV_8UC3);
	vector<Mat> srcChannels(3), resChannels(3);
	split(src, srcChannels);
	split(result_img, resChannels);

	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			for (int m = 0; m < 3; m++)
			{
				int value_num = srcChannels[m].at<uchar>(i, j);
				float max_t = tx.at<float>(i, j);
				if (max_t < 0.1)
				{
					max_t = 0.1;
				}
				resChannels[m].at<uchar>(i, j) = (value_num - A) / max_t + A;
			}
		}
	}
	merge(resChannels, result_img);

	return result_img;
}
#endif

实现结果:
暗通道去雾及C++实现
原图像
暗通道去雾及C++实现
暗通道图像
暗通道去雾及C++实现
t(x)图像
暗通道去雾及C++实现
去雾之后的图像

从结果上来看,结果图像仍有一些边缘存在未去的雾,这里是因为t(x)在计算的时候比较粗糙,何凯明在2013年提出了一篇导向滤波可以很好的解决这个问题,关于导向滤波具体算法请参考:导向滤波算法解析

在function.h中已经实现导向滤波,主要针对tx,guidedfilter(Mat &srcImage, Mat &srcClone, int r, double eps)函数,实现结果如下所示:
暗通道去雾及C++实现
导向滤波之后的tx
暗通道去雾及C++实现
基于导向滤波后的去雾图像

相对于上述未进行导向滤波的结果图像,导向滤波后的结果效果有很大改善。