点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
1.前言
今天为大家带来一篇之前看到的用于单幅图像去雾的算法,作者来自清华大学,论文原文见附录。
2.雾天退化模型
在计算机视觉领域,通常使用雾天图像退化模型来描述雾霾等恶劣天气条件对图像造成的影响,该模型是McCartney首先提出。该模型包括衰减模型和环境光模型两部分。模型表达式为:
其中,是图像像素的空间坐标,是观察到的有雾图像,是待恢复的无雾图像,表示大气散射系数,代表景物深度,是全局大气光,通常情况下假设为全局常量,与空间坐标无关。
公式(1)中的表示坐标空间处的透射率,我们使用来表示透射率,于是得到公式(2):
由此可见,图像去雾过程就是根据求解的过程。要求解出,还需要根据求解出透射率和全局大气光。实际上,所有基于雾天退化模型的去雾算法就是是根据已知的有雾图像求解出透射率和全局大气光。
对于暗通道去雾算法来说,先从暗原色通道中选取最亮的0.1%比例的像素点,然后选取原输入图像中这些像素具有的最大灰度值作为全局大气光值。RGB三通道中每一个通道都有一个大气光值。
然后根据公式(2)可以得出:
首先可以确定的是的范围是,的范围是,的范围是。和是已知的,可以根据的范围从而确定的范围。已知的条件有:
根据(4)和(5)推出:
因此初略估计透射率的计算公式:
最后为了保证图片的自然性,增加一个参数来调整透射率 :
好了,上面复习完了何凯明博士的暗通道去雾,我们一起来看看清华大学这篇论文。
3.算法流程
算法流程实际上有了这个算法流程就可以写出代码了,不过为了加深理解可以看下面的一些推导。
4.一些推导
我们知道去雾的步骤主要就是估计全局大气光值和透射率,因此,本文就是根据输入图像估计和(这篇论文使用了来代替),然后根据雾天退化模型求取去雾后的图像。
从第二节的介绍我们知道
然后这篇论文使用了来代替,即:
。
我们取三个通道的最小值并记为:
所以公式2变换为
对公式(4)右边进行均值滤波:
其中代表均值滤波的窗口大小,表示像素的的邻域。
均值滤波后的结果可以反映的大致趋势,但与真实的还差一定的绝对值,因此,我们先得出透射率的粗略估计值:
其中,因此。
为了防止去雾后图像出现整体画面偏暗,这里根据图像的均值来调整,即:
其中是中所有元素的均值,是调节因子。
因此可以得到透射率的计算公式:
结合公式(1)推出:
。
公式(5)中第一个等式左侧的表达式取值范围为,由此得出
一般情况下又存在
(KaiMing He的暗通道先验理论)。这样就初步确定了全局大气光的范围,为了能快速获取全局大气光,文章直接取两者的平均值作为全局大气光值,即:
...(9)。
然后大气光值和都搞定了,那么带入算法流程中的最后一个公式就可以获取最后的图像了。
5.代码实现
下面是代码实现。
#include
#include
#include
#include
using namespace cv;
using namespace std;
int getMax(Mat src) {
int row = src.rows;
int col = src.cols;
int temp = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
temp = max((int)src.at(i, j), temp);
}
if (temp == 255) return temp;
}
return temp;
}
Mat dehaze(Mat src) {
double eps;
int row = src.rows;
int col = src.cols;
Mat M = Mat::zeros(row, col, CV_8UC1);
Mat M_max = Mat::zeros(row, col, CV_8UC1);
Mat M_ave = Mat::zeros(row, col, CV_8UC1);
Mat L = Mat::zeros(row, col, CV_8UC1);
Mat dst = Mat::zeros(row, col, CV_8UC3);
double m_av, A;
//get M
double sum = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
uchar r, g, b, temp1, temp2;
b = src.at(i, j)[0];
g = src.at(i, j)[1];
r = src.at(i, j)[2];
temp1 = min(min(r, g), b);
temp2 = max(max(r, g), b);
M.at(i, j) = temp1;
M_max.at(i, j) = temp2;
sum += temp1;
}
}
m_av = sum / (row * col * 255);
eps = 0.85 / m_av;
boxFilter(M, M_ave, CV_8UC1, Size(51, 51));
double delta = min(0.9, eps*m_av);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
L.at(i, j) = min((int)(delta * M_ave.at(i, j)), (int)M.at(i, j));
}
}
A = (getMax(M_max) + getMax(M_ave)) * 0.5;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int temp = L.at(i, j);
for (int k = 0; k < 3; k++) {
int val = A * (src.at(i, j)[k] - temp) / (A - temp);
if (val > 255) val = 255;
if (val < 0) val = 0;
dst.at(i, j)[k] = val;
}
}
}
return dst;
}
int main() {
Mat src = imread("F:\\fog\\1.jpg");
Mat dst = dehaze(src);
cv::imshow("origin", src);
cv::imshow("result", dst);
cv::imwrite("F:\\fog\\res.jpg", dst);
waitKey(0);
return 0;
}
6.结果
7.结论
算法里面有2个参数可以自己调节,滤波的半径和。具体如何调节?我就不放在这里说了,这个算法后面会在我的新专题里面进行一遍优化,到时候再来回答这个问题。如果你迫切需要这个算法的实现或者对它感兴趣,可以自己尝试调整这两个参数获得想要的效果。这里的均值滤波也可以换成我们之前讲的Side Window Filter说不定可以获得更好的效果。
8.参考
https://blog.csdn.net/u013684730/article/details/76640321
https://www.cnblogs.com/Imageshop/p/3410279.html
下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。
下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。
下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。
交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~