暗通道去雾 python实现

本文基于何恺明博士的论文,介绍了使用暗通道先验和导向滤波进行单幅图像去雾的方法,并提供了详细的Python代码实现。文章涵盖雾图成像模型、暗通道定义、透光率计算等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文为基于何恺明博士的Single Image Haze Removal Using Dark Channel Prior和Guided Image Filtering两篇论文的去雾算法python代码实现。

1 一些基本的定义

1.1 雾图成像模型

在这里插入图片描述
I(x)为原图,J(x)为无雾图像,A是大气光成分,为一常数。t(x)为透光率。
其含义就是图像I(x)为事物反射的光经过雾气衰减后加上雾气反射的大气光的结合所成的像。

1.2 暗通道定义

在这里插入图片描述

1.3 计算投射图t(x)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里是一个作者统计了大量图片得出的结论,即无雾图片的暗通道是接近于0的。
在这里插入图片描述
在这里插入图片描述
实际上,即使在晴天,大气中也不是完全没有任何粒子。所以当我们看远处的物体时,雾仍然存在。 所以,我们可以通过引入一个常量参数w(0<w<1)论文取值为0.95,为远处的物体保留少量的雾。
这样t(x)会比原来大,大气光的权重会小点,使得图片整体更暗?
在这里插入图片描述
暗通道先验对于天空区域来说不是一个好的先验。幸运的是,在朦胧的图像I中,天空的颜色通常与大气光A非常相似,即下式趋近1。在这里插入图片描述所以,在这些位置t(x)趋于0。因为天空是无限遥远的,它的传输确实接近于零。所以我们不需要预先分离天空区域。

1.4 导向滤波

详见我的另一篇文章导向滤波与opencv python实现

2 代码

2.1 获得暗通道

def get_min_channel(img):
    return np.min(img,axis=2)

最小值滤波器,其实与腐蚀一样:

def min_filter(img,r):
    kernel = np.ones((2*r-1,2*r-1))
    return cv2.erode(img,kernel)#最小值滤波器,可用腐蚀替代

在这里插入图片描述

2.2 大气光照A计算

通常雾最浓地方的颜色被作为大气光的估计值,可以通过暗通道来确定雾气最浓的地方,即暗通道最亮的区域为雾气最浓的地方,此时,大气光为唯一的光源。
首先选择暗通道中最亮的0.1%像素。这些像素通常是最不透明的。在这些像素中,选择输入图像I中强度最高的像素作为大气光。

def get_A(img_haze,dark_channel,bins_l):
    hist,bins = np.histogram(dark_channel,bins=bins_l)#得到直方图
    d = np.cumsum(hist)/float(dark_channel.size)#累加
    # print(bins)
    threshold=0
    for i in range(bins_l-1,0,-1):
        if d[i]<=0.999:
            threshold=i
            break
    A = img_haze[dark_channel>=bins[threshold]].max()
    #候选区域可视化
    show  = np.copy(img_haze)
    show[dark_channel>=bins[threshold]] = 0,0,255
    cv2.imwrite('./most_haze_opaque_region.jpg',show*255)
    return A

暗通道最亮的区域如下图红色区域处:
在这里插入图片描述

2.3 计算t(x)

def get_t(img_haze,A,t0=0.1,w=0.95):
    out = get_min_channel(img_haze)
    out = min_filter(out,r=7)
    t = 1-w*out/A #需要乘上一系数w,为远处的物体保留少量的雾
    t = np.clip(t,t0,1)#论文4.4所提到t(x)趋于0容易产生噪声,所以设置一最小值0.1
    return t

这时候得出来的结果如下图:
在这里插入图片描述
因为没有经过导向滤波细化传输,所以效果有点差。

2.4 导向滤波

以还未进行最小值滤波的暗通道图(用暗通道图效果会很差)作为导向图对t(x)进行导向滤波,细化传输。
输入图像必须得先进行归一化后再处理,否则滤波后会有超过255的值

def guided_filter(I,p,win_size,eps):

    mean_I = cv2.blur(I,(win_size,win_size))
    mean_p = cv2.blur(p,(win_size,win_size))

    corr_I = cv2.blur(I*I,(win_size,win_size))
    corr_Ip = cv2.blur(I*p,(win_size,win_size))

    var_I = corr_I-mean_I*mean_I
    cov_Ip = corr_Ip - mean_I*mean_p

    a = cov_Ip/(var_I+eps)
    b = mean_p-a*mean_I

    mean_a = cv2.blur(a,(win_size,win_size))
    mean_b = cv2.blur(b,(win_size,win_size))

    q = mean_a*I + mean_b
    return q

得到的结果如下图所示:

在这里插入图片描述
与原图相比:
在这里插入图片描述
可见去雾效果还是不错的。

2.5 评估

PSNR

def PSNR(target,ref):
    #必须归一化
    target=target/255.0
    ref=ref/255.0
    MSE = np.mean((target-ref)**2)
    if MSE<1e-10:
        return 100
    MAXI=1
    PSNR = 20*math.log10(MAXI/math.sqrt(MSE))
    return PSNR

SSIM

from skimage.metrics import structural_similarity as sk_cpt_ssim
ssim = sk_cpt_ssim(J,I*255, win_size=11, data_range=255, multichannel=True)

在这里插入图片描述

2.6 完整代码

# -*- coding: utf-8 -*-
# @Time : 2022/10/1 23:08
# @Author : shuoshuo
# @File : main.py
# @Project : 去雾
import cv2
import numpy as np
import matplotlib.pyplot as plt
import math
from skimage.metrics import structural_similarity as sk_cpt_ssim

def guided_filter(I,p,win_size,eps):

    mean_I = cv2.blur(I,(win_size,win_size))
    mean_p = cv2.blur(p,(win_size,win_size))

    corr_I = cv2.blur(I*I,(win_size,win_size))
    corr_Ip = cv2.blur(I*p,(win_size,win_size))

    var_I = corr_I-mean_I*mean_I
    cov_Ip = corr_Ip - mean_I*mean_p

    a = cov_Ip/(var_I+eps)
    b = mean_p-a*mean_I

    mean_a = cv2.blur(a,(win_size,win_size))
    mean_b = cv2.blur(b,(win_size,win_size))

    q = mean_a*I + mean_b
    return q
def get_min_channel(img):
    return np.min(img,axis=2)
def min_filter(img,r):
    kernel = np.ones((2*r-1,2*r-1))
    return cv2.erode(img,kernel)#最小值滤波器,可用腐蚀替代
def get_A(img_haze,dark_channel,bins_l):
    hist,bins = np.histogram(dark_channel,bins=bins_l)#得到直方图
    d = np.cumsum(hist)/float(dark_channel.size)#累加
    # print(bins)
    threshold=0
    for i in range(bins_l-1,0,-1):
        if d[i]<=0.999:
            threshold=i
            break
    A = img_haze[dark_channel>=bins[threshold]].max()
    #候选区域可视化
    show  = np.copy(img_haze)
    show[dark_channel>=bins[threshold]] = 0,0,255
    cv2.imwrite('./most_haze_opaque_region.jpg',show*255)
    return A
def get_t(img_haze,A,t0=0.1,w=0.95):
    out = get_min_channel(img_haze)
    out = min_filter(out,r=7)
    t = 1-w*out/A #需要乘上一系数w,为远处的物体保留少量的雾
    t = np.clip(t,t0,1)#论文4.4所提到t(x)趋于0容易产生噪声,所以设置一最小值0.1
    return t
def PSNR(target,ref):
    #必须归一化
    target=target/255.0
    ref=ref/255.0
    MSE = np.mean((target-ref)**2)
    if MSE<1e-10:
        return 100
    MAXI=1
    PSNR = 20*math.log10(MAXI/math.sqrt(MSE))
    return PSNR

if __name__ == '__main__':
    I = cv2.imread('test.jpg')/255.0
    dark_channel = get_min_channel(I)
    dark_channel_1 = min_filter(dark_channel,r=7)
    # cv2.imwrite("./dark_channel.jpg", dark_channel_1*255)

    A = get_A(I,dark_channel_1,bins_l=2000)

    t = get_t(I,A)
    t = guided_filter(dark_channel,t,81,0.001)
    t = t[:,:,np.newaxis].repeat(3,axis=2)#升维至(r,w,3)

    J = (I-A)/t +A

    J = np.clip(J,0,1)
    J = J*255
    J =np.uint8(J)

    cv2.imwrite("./result.jpg",J)

    #评估
    PSNR = PSNR(J,I*255)
    print(f"PSNR:{PSNR}")
    ssim = sk_cpt_ssim(J,I*255, win_size=11, data_range=255, multichannel=True)
    print(f"ssim:{ssim}")

3 参考文献

何恺明的两篇论文:
Single Image Haze Removal Using Dark Channel Prior
Guided Image Filtering
论文链接
Dehazing for Image and Video Using Guided Filter

### 实现通道算法 对于实现基于通道先验的算法,该算法由He等人提出并广泛应用于图像处理领域[^1]。此方法通过估计大气光和透射率来恢复无图像。 #### 主要步骤概述: - **输入含图**:读取待处理的含图片。 - **估算大气光A**:计算全局最大值作为大气光强度。 - **计算通道图J_dark**:构建原始图像的通道。 - **求解透射率t(x)**:利用公式 \( t(x)=1-\omega\cdot \frac{I_{dark}(x)}{A} \),其中ω为调节参数,默认设为0.95。 - **精炼透射率map**:采用导向滤波优化得到更平滑的结果。 - **重建清晰图像**:按照定义霾影响后的像素颜色模型重构最终输出。 以下是Python环境下使用OpenCV库的一个简单示例代码片段用于演示如何编写上述过程中的部分功能模块: ```python import cv2 import numpy as np def dark_channel_prior(img, w=15): """Compute Dark Channel Prior""" img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (w, w)) dark_img = cv2.erode(img_gray, kernel) return dark_img def estimate_atmospheric_light(image, dark): """Estimate Atmospheric Light A""" h, w = image.shape[:2] flat_image = image.reshape([h * w, 3]) flat_dark = dark.flatten() search_num = int(h * w / 1000) array_idx = flat_dark.argsort()[-search_num:] a = {} for i in range(3): tmp = [] for idx in array_idx: tmp.append(flat_image[idx][i]) a[i] = sum(tmp) / len(tmp) atmospheric_light = [a[2], a[1], a[0]] return atmospheric_light # Load Image and preprocess it. img = cv2.imread('foggy.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) dc = dark_channel_prior(img) atm_light = estimate_atmospheric_light(img, dc) print(atm_light) ``` 这段程序仅展示了两个核心函数——`dark_channel_prior()` 和 `estimate_atmospheric_light()` 的基本框架;完整的流程还需要加入更多细节调整以及后续步骤如透射率细化与色彩校正等操作。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值