单图像去雾

全文摘抄自博主@hyfine_与@just_sort博客,原文地址如下:
https://blog.csdn.net/f290131665/article/details/96404768

https://blog.csdn.net/f290131665/article/details/96404768

本文仅用作个人学习,转载请移步原文作者处。


前言

清华大学刘倩等[1]提出了一种基于均值滤波的去雾算法,算是后续多种基于均值滤波去雾算法的鼻祖。虽然本文于2013提出,当时何凯明的暗通道去雾算法已经非常有名,但是本文算法以其简单、快速、实际去雾效果好的特点,引起广大研究者的关注,本文也算是去雾论文里比较出名的中文论文。

本文将从两个方面介绍该论文。主要是A的求法、T的求法两部分,关于大气散射模型不再赘述,默认各位读者有去雾算法的研究基础(若是没有该基础,建议先仔细研读何凯明于CVPR2009发表的《Single Image Haze Removal Using Dark Channel Prior》)。


一、何凯明算法原理

 之前在介绍何凯明博士的暗通道去雾论文(CVPR 2009最佳论文)的时候已经讲到了这个雾天退化模型,我们这里再来回顾一下。在计算机视觉领域,通常使用雾天图像退化模型来描述雾霾等恶劣天气条件对图像造成的影响,该模型是McCartney首先提出。该模型包括衰减模型和环境光模型两部分。模型表达式为:

其中,x 是图像像素的空间坐标,H是观察到的有雾图像,J(x)是待恢复的无雾图像,r 表示大气散射系数,d代表景物深度,A 是全局大气光,通常情况下假设为全局常量,与空间坐标x无关。

公式(1)中的e^{-r(dx))}表示坐标空间x 处的透射率,我们使用 t(x) 来表示透射率,于是得到公式(2):

由此可见,图像去雾过程就是根据 I(x) 求解 J(x) 的过程。要求解出 J(x),还需要根据 I(x) 求解出透射率 t(x) 和全局大气光A 。实际上,所有基于雾天退化模型的去雾算法就是是根据已知的有雾图像 I(x) 求解出透射率 t(x) 和全局大气光A。

对于暗通道去雾算法来说,先从暗原色通道中选取最亮的0.1%比例的像素点,然后选取原输入图像中这些像素具有的最大灰度值作为全局大气光值A 。RGB三通道中每一个通道都有一个大气光值。

然后根据公式(2)可以得出:

首先可以确定的是 t(x) 的范围是 [0 , 1 ],I(x) 的范围是 [0,255],J(x) 的范围是 [0, 255]。A和 I(x) 是已知的,可以根据 J(x) 的范围从而确定 t(x) 的范围。已知的条件有:

根据(4)和(5)推出:

因此粗略估计透射率的计算公式:

最后为了保证图片的自然性,增加一个参数w来调整透射率 :

好了,上面复习完了何凯明博士的暗通道去雾,我们一起来看看清华大学这篇论文。

二、刘倩单图去雾方法

1.流程

2.原理推导

我们知道去雾的步骤主要就是估计全局大气光值A 和透射率 t(x),因此,本文就是根据输入图像估计A和 L(x)这篇论文使用了 L(x)来代替A(1-t(x))),然后根据雾天退化模型求取去雾后的图像。

2.1 估计透射率t(x)

从第二节的介绍我们知道

然后这篇论文使用了 L(x)来代替A(1-t(x))),即:

 

我们取 H(x)三个通道的最小值并记为:

所以公式2变换为

对公式(4)右边进行均值滤波:

其中s_a代表均值滤波的窗口大小,Ω ( x )表示像素x的s_a \times s_a的邻域。

均值滤波后的结果可以反映 t(x) 的大致趋势,但与真实的 t(x)还差一定的绝对值,因此,我们先得出透射率的粗略估计值:

其中M_{ave}(x)=median_{s_a}(M(x)),δ=1−φ,φ∈[0,1],因此δ ∈ [ 0 , 1 ]。

为了防止去雾后图像出现整体画面偏暗,这里根据图像的均值来调整δ,即:\delta=\rho m_{ave}。其中m_{ave}是M(x)中所有元素的均值,ρ 是调节因子。

因此可以得到透射率的计算公式:

结合公式(1)推出:

2.2 估计全球大气光值

公式(5)中第一个等式左侧的表达式取值范围为[0,1],由此得出

一般情况下又存在

(KaiMing He的暗通道先验理论)。这样就初步确定了全局大气光的范围,为了能快速获取全局大气光,文章直接取两者的平均值作为全局大气光值,即:

然后大气光值A 和L(x)都搞定了,那么带入算法流程中的最后一个公式就可以获取最后的图像了。
————————————————


3.代码实现

C++(OpenCV2 版):

#include <opencv2/opencv.hpp>
#include <iostream>
#include <algorithm>
#include <vector>
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<uchar>(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<Vec3b>(i, j)[0];
			g = src.at<Vec3b>(i, j)[1];
			r = src.at<Vec3b>(i, j)[2];
			temp1 = min(min(r, g), b);
			temp2 = max(max(r, g), b);
			M.at<uchar>(i, j) = temp1;
			M_max.at<uchar>(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<uchar>(i, j) = min((int)(delta * M_ave.at<uchar>(i, j)), (int)M.at<uchar>(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<uchar>(i, j);
			for (int k = 0; k < 3; k++) {
				int val = A * (src.at<Vec3b>(i, j)[k] - temp) / (A - temp);
				if (val > 255) val = 255;
				if (val < 0) val = 0;
				dst.at<Vec3b>(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;
}

Python版

import cv2
import numpy as np


def single_image_defogging(img):
    height,width = img.shape[:2]
    img_min = np.min(img,axis=2)
    img_max = np.max(img,axis=2)
    img_min_sum = np.sum(img_min)
    ave = img_min_sum/(height*width*255)
    eps = 0.85 / ave
    delta = min(0.9, eps * ave)

    img_ave = cv2.boxFilter(img_min, -1, (51, 51))
    img_L = np.minimum((img_ave*delta).astype(np.uint8),img_min)
    A = np.max(img_max) + np.max(img_ave) * 0.5

    temp = np.zeros((height,width,3),dtype=np.uint8)
    temp[:,:,0] = np.subtract(img[:,:,0], img_L)
    temp[:,:,1] = np.subtract(img[:,:,1], img_L)
    temp[:,:,2] = np.subtract(img[:,:,2], img_L)
    temp_AL = np.subtract(A, img_L)
    temp_3AL = temp_AL[:, :,np.newaxis].repeat(3, axis=2)


    img_dst = np.divide(np.multiply(A,temp),temp_3AL).astype(np.uint8)
    img_dst = np.where(img_dst > 255, 255, img_dst)
    img_dst = np.where(img_dst < 0, 0, img_dst)
    return img_dst


if __name__ == '__main__':
    img = cv2.imread("./fog.jpg")
    result_img = single_image_defogging(img)
    cv2.imwrite("./fog22.jpeg", result_img)
    cv2.imshow("result_img",result_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

效果

 

 

参考文献

[1] 刘倩, 陈茂银, 周东华. 基于单幅图像的快速去雾算法[C]// 中国控制与决策会议. 2013.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值