图像的去雾算法原理及实现:
本文主要是实现的是基于暗通道处理的去雾。有部分是看论文直接翻译而来,如有错误,欢迎评论区指出,当然您也可以直接阅读原文。
暗通道先验解释:
说明:何凯明博士的论文中统计了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
//计算暗通道
//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(i, j) = min(
min(img.at>(i, j)[0], img.at>(i, j)[1]),
min(img.at>(i, j)[0], img.at>(i, j)[2])
);//就是两个最小值的过程
}
}
erode(dark, dark_out1, Mat::ones(_PriorSize, _PriorSize, CV_32FC1));
//这个函数叫腐蚀 做的是窗口大小的模板运算 ,对应的是最小值滤波,即 黑色图像中的一块块的东西
return dark_out1;//这里dark_out1用的是全局变量,因为在其它地方也要用到
}
上述推论中是假设全球达气光A值时已知的,在实际中,我们可以借助于暗通道图来从有雾图像中获取该值。
具体步骤如下:
1) 从暗通道图中按照亮度的大小取前0.1%的像素。
2) 在这些位置中,在原始有雾图像I中寻找对应的具有最高亮度的点的值,作为A值。
在式中,每个通道的数据都需要除以对应的A值,即归一化,对对比度低的图像可以获得很好的去雾高对比度图。但是/A操作可能会导致t的值小于0,这种情况就可以把t的值直接设置为0来解决。
针对上面说的,要值得一提的是,严格的来说是要对原始图像的每个通道进行归一化后,再取每个通道R/G/B值的最小值得到中间图,然后对这个中间图进行指定半径的最小值滤波后,通过式得到粗糙的透射率图。那么这样就需要增加不少计算,我在实际中发现如果直接用前面的暗通道图/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 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(i, j) = min(
min(img.at>(i, j)[0], img.at>(i, j)[1]),
min(img.at>(i, j)[0], img.at>(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;
}
另外,直接去雾后的图像会比原始的暗,因此在处理完后需要进行一定的曝光增强。一般在去雾处理后再用自动色剂之类的算法增强下会获得比较满意的结果。
实现代码:https://download.csdn.net/download/m0_37407756/10740384