暗通道先验理论的图像去雾
通常彩色图像都包括三个通道,即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