图像处理之基于暗通道先验理论的图像去雾

暗通道先验理论的图像去雾


通常彩色图像都包括三个通道,即RGB三通道,也可以理解而成一张图片又三层同样大小的RGB(光学三原色:红绿蓝)三色堆叠而成,而图片实质上是由一个个像素组成的,对应于RGB三色来说,每一种颜色都是由这三原色组合而成,比如红色为(255,0,0),绿色为(0,255,0),粉红为(255,192,203),也就是说一张彩色图片中的每个像素都是以这种形式来表示的。

那么我们通常所说的暗通道,即清晰无雾的图片中除天空区域(因为天空区域或者白色区域和雾的特征较为接近)外的任一局部区域像素至少有一个通道值很低,几乎趋近于零。那如何求暗通道呢,我们在上面的代码中已经做了详细的说明,便是先求每个像素在三个通道中的最小值,然后得到一张与原始图像大小一致的灰度图,然后再使用最小值滤波进行平滑处理,即在得到的灰度图中,以每一个像素为中心取一定大小的矩形窗口(本文为15x15),取矩形窗口中灰度值的最小值代替中心像素的值,从而得到原始图像对应的暗通道图像。(求暗通道过程中要计算两次最小值)

为了更加直观地展示暗通道图像,我们先看一下结果,即雾图和其对应的暗通道图:

在这里插入图片描述

为什么要计算暗通道呢,学界对于有雾图像的定义是这样的:
在这里插入图片描述

有雾图像=无雾图像*透射率+全球大气光成分(1-透射率)
现在要已知有雾图,求出无雾图
我们将J(x)写在等式左边,那么这个方程有无数解,因此,我们需要用一些先验知识来约束方程,将原式可以写作:
在这里插入图片描述
如上所述,上标C表示R/G/B三个通道的意思。首先假设在每一个窗口内透射率t(x)为常数,定义它为 ~t(x),并且A值已经给定,然后对式(4)两边求两次最小值运算,得到下式:
在这里插入图片描述
上式中,J是待求的无雾的图像,根据前述的暗原色先验理论有:
在这里插入图片描述
因此,可推导出:
在这里插入图片描述
在这里插入图片描述
这就是透射率 ~t(x) 的预估值。

在现实生活中,即使是晴天白云,空气中也存在着一些颗粒,因此,看远处的物体还是能感觉到雾的影响,另外,雾的存在让人类感到景深的存在,因此,有必要在去雾的时候保留一定程度的雾,这可以通过在式中引入一个在[0,1]之间的因子,修正为:

在这里插入图片描述
本文中,double w = 0.75;

上述推论中都是假设全球大气光A值时已知的,在实际中,我们可以借助于暗通道图来从有雾图像中获取该值。具体步骤如下:

1) 从暗通道图中按照亮度的大小取前0.1%的像素。

2) 在这些位置中,在原始有雾图像I中寻找对应的具有最高亮度的点的值,作为A值。

到这一步,我们就可以进行无雾图像的恢复了。

#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<iostream>
#include<vector>
#include <algorithm>

using namespace cv;
using namespace std;


Mat darkChannel(Mat src)//求暗通道
{
    
    
	Mat rgbmin = Mat::zeros(src.rows, src.cols, CV_8UC1);
	Mat dark = Mat::zeros(src.rows, src.cols, CV_8UC1);
	Vec3b intensity;


	//找出每个点三通道中最小的通道,建立rgbmin图
	for (int m = 0; m < src.rows; m++)
	{
    
    
		for (int n = 0; n < src.cols; n++)
		{
    
    
			intensity = src.at<Vec3b>(m, n);
			rgbmin.at<uchar>(m, n) = min(min(intensity.val[0], intensity.val[1]), intensity.val[2]);
		}
	}

	//模板尺寸
	int scale = 7;
	//cout << "Please enter the mask scale: " << endl;
	//cin >> scale;

	//边界扩充
	int radius = (scale - 1) / 2;
	//border是填充后的图像
	Mat border;
	//由于要求最小值,所以扩充的边界可以用复制边界填充,填充范围上下左右为radius
	copyMakeBorder(rgbmin, border, radius, radius, radius, radius, BORDER_REPLICATE);

	//最小值滤波
	for (int i = 0; i < src.cols; i++)
	{
    
    
		for (int j = 0; j < src.rows; j++)
		{
    
    
			//选取兴趣区域
			Mat roi;
			roi = border(Rect(i, j, scale, scale));

			//求兴趣区域的最小值
			double minVal = 0;
			double maxVal = 0;
			Point minLoc(0, 0);
			Point maxLoc(0, 0);
			minMaxLoc(roi, &minVal, &maxVal, &minLoc, &maxLoc, noArray());

			dark.at<uchar>(Point(i, j)) = (uchar)minVal;           //对填充后的图像以scale作为范围进行最小值滤波,得到的结果存dark
		}
	}
	return dark;
}


uchar light(vector<uchar> inputIamgeMax)//找出全局最亮的点作为估计大气光
{
    
    
	uchar maxA = 0;
	for (int i = 0; i < inputIamgeMax.size() - 1; i++)
	{
    
    

		if (maxA < inputIamgeMax[i + 1])
		{
    
    
			maxA = inputIamgeMax[i + 1];
		}
	}
	return maxA;
}


vector<uchar> calLight(Mat temp)
{
    
    
	Mat image;
	Mat dark = darkChannel(image);
	dark.copyTo(temp);
	vector<Point> darkMaxPoint;
	vector<uchar> inputMax;
	for (long i = 0; i < ((dark.rows * dark.cols) / 1000); i++)
	{
    
    
		double minVal = 0;
		double maxVal = 0;
		Point minLoc(0, 0);
		Point maxLoc(0, 0);
		minMaxLoc(temp, &minVal, &maxVal, &minLoc, &maxLoc, noArray());

		darkMaxPoint.push_back(maxLoc);//找到最亮点位置
		inputMax.push_back(image.at<uchar>(maxLoc));
		circle(temp, maxLoc, 5, Scalar(0), 1, 8, 0);
		temp.at<uchar>(maxLoc) = temp.at<uchar>(minLoc);
	}
	return inputMax;
}


int main(int argc, char* argv[])
{
    
    

	Mat image = imread("E:\\picture\\wutian.jpg");
	//imshow("image", image);
	Mat darkChannel1 = darkChannel(image);
	//imshow("darkChannel", darkChannel1);

	namedWindow("dehazed");

	//估计大气光
	Mat temp;
	darkChannel1.copyTo(temp);
	vector<Point> darkMaxPoint;
	vector<uchar> inputMax;
	for (long i = 0; i < ((darkChannel1.rows * darkChannel1.cols) / 1000); i++)
	{
    
    
		double minVal = 0;
		double maxVal = 0;
		Point minLoc(0, 0);
		Point maxLoc(0, 0);
		minMaxLoc(temp, &minVal, &maxVal, &minLoc, &maxLoc, noArray());

		darkMaxPoint.push_back(maxLoc);//找到最亮点位置
		inputMax.push_back(image.at<uchar>(maxLoc));
		circle(temp, maxLoc, 5, Scalar(0), 1, 8, 0);
		temp.at<uchar>(maxLoc) = temp.at<uchar>(minLoc);
	}

	uchar A = light(inputMax);


	double w = 0.75;

	//createTrackbar("w1", "dehazed", &w1, 100, NULL);

	//求折射率
	Mat T = Mat::zeros(image.rows, image.cols, CV_8UC3);
	Scalar intensity;

	for (int m = 0; m < image.rows; m++)
	{
    
    
		for (int n = 0; n < image.cols; n++)
		{
    
    
			intensity = darkChannel1.at<uchar>(m, n);
			T.at<Vec3b>(m, n)[0] = (1 - w * intensity.val[0] / A) * 255;
			T.at<Vec3b>(m, n)[1] = (1 - w * intensity.val[0] / A) * 255;
			T.at<Vec3b>(m, n)[2] = (1 - w * intensity.val[0] / A) * 255;
		}
	}


	//去雾
	Mat J(image.rows, image.cols, CV_8UC3, Scalar(180, 120, 50));
	Mat temp1(image.rows, image.cols, CV_8UC3, Scalar(180, 120, 50));

	//subtract(image, Scalar(A, A, A), temp1);
	temp1 = abs(image - Scalar(A, A, A));
	double t0 = 0.1;

	Scalar T1;
	Vec3b intsrc;
	
	for (int i = 0; i < image.cols; i++)
	{
    
    
		for (int j = 0; j < image.rows; j++)
		{
    
    
			T1 = T.at<uchar>(Point(i, j));
			intsrc = image.at<Vec3b>(Point(i, j));
			double tmax = (T1.val[0] / 255) < t0 ? t0 : (T1.val[0] / 255);

			for (int k = 0; k < 3; k++)
			{
    
    
				J.at<Vec3b>(Point(i, j))[k] = abs((intsrc.val[k] - A) / tmax + A) > 255 ? 255 : abs((intsrc.val[k] - A) / tmax + A);
			}
		}
	}

	imshow("dehazed", J);
	while (char(waitKey(1)) != 'q') {
    
    }




	return 0;
}

结果如下:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36587495/article/details/108580940