Bridge619

Bridge619

Bridge619

命定的局限尽可永在,不屈的挑战却不可须臾或缺!

101 文章数
11 评论数
来首音乐
光阴似箭
今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月

图像去雾算法原理及实现_ACE&DCP

Bridge619
2022-10-02 / 0 评论 / 670 阅读 / 3 点赞

图像去雾

1.1 算法介绍

图像去雾的研究算法有很多,应用比较广泛的有:

  • 直方图均衡化去雾算法
  • Retinex去雾算法
  • 暗通道先验去雾算法
  • 基于卷积神经网络的DehazeNet去雾算法

其主要可以分为 3 类:基于图像增强的去雾算法、基于图像复原的去雾算法和基于 CNN 的去雾算法。

(1) 基于图像增强的去雾算法,通过图像增强技术突出图像细节,提升对比度,使之看起来更加清晰,这类算法的适用性较广。具体的算法有:

  • Retinex 算法 根据成像原理,消除了反射分量的影响,达到了图像增强去雾的效果
  • 直方图均衡化算法 使图像的像素分布更加均匀,放大了图像的细节
  • 偏微分方程算法 将图像视作一个偏微分方程,通过计算梯度场提高对比度
  • 小波变换算法 对图像进行分解,放大有用的部分

此外,在这类算法的基础上出现了众多的基于图像增强原理的改进算法。

(2) 基于图像复原的去雾算法,主要是基于大气散射物理学模型,通过对大量有雾图像和无雾图像进行观察总结,得到其中存在的一些映射关系,然后根据有雾图像的形成过程来进行逆运算,从而恢复清晰图像。其中最经典的要属何恺明提出的:

  • 暗通道先验去雾算法通过对大量无雾图像进行特征分析,找到了无雾图像与大气散射模型中某些参数的先验关系。该算法复杂度低,去雾效果好,因此在其基础上出现了大量基于暗通道先验的改进算法。

(3) 基于CNN的去雾算法 使用 CNN 建立一个端到端的模型,通过有雾图像恢复出无雾图像,目前使用神经网络进行去雾的算法主要有两种思路:

  • 使用 CNN 生成大气散射模型的某些参数,然后再根据大气散射模型来恢复无雾图像
  • 使用 CNN (例如 GAN)直接根据模糊图像生成无雾的清晰图像

CNN 因其强大的学习能力在多个领域得到应用,因此也出现了采用 CNN 进行去雾的算法。2016年CAI等首次提出了一种名为DehazeNet的去雾网络,用于估计有雾图像的透射率。DehazeNet 将有雾的模糊图像作为输入,输出其透射率,基于大气散射模型理论恢复出无雾的清晰图像。


这里采用基于图像增强的自动色彩均衡(Automatic Color Enhancement,ACE) 算法和基于图像复原的暗通道先验(Dark Channel Prior, DCP)去雾算法来实现图像的去雾效果。

1.2 算法原理与实现

1.2.1 ACE去雾算法

1.算法原理&步骤、主要公式

自动色彩均衡(Automatic Color Enhancement,ACE) 算法通过计算图像目标像素点和周围像素点的明暗程度及其关系来对最终的像素值进行校正,实现图像的对比度调整,产生类似人体视网膜的色彩恒常性和亮度恒常性的均衡,具有很好的图像增强效果。

ACE算法包括两个步骤:

  • 一是对图像进行色彩和空域调整,完成图像的色差校正,得到空域重构图像。模仿视觉系统的侧抑制性和区域自适应性,进行色彩的空域调整。侧抑制性是一个生理学概念,指在某个神经元受到刺激而产生兴奋时,再刺激相近的神经元,后者所发生的兴奋对前者产生的抑制作用。
  • 二是对校正后的图像进行动态扩展。对图像的动态范围进行全局调整,并使图像满足灰度世界理论和白斑点假设。算法针对单通道,再延伸应用到RGB彩色空间的3通道图像,即对3个通道分别处理再进行整合完成。

(1) 区域自适应滤波 输入图像 (灰度图为例),该步是对单通道图像 中所有点 的区域自适应滤波,得到完成色差校正,空域重构后的中间结果图像,计算公式如下:

式中: 两个像素点间灰度差值,表达拟生物学上的侧抑制性; 表示距离度量函数,使用两点间的欧氏距离,作用上控制点j对 的影响权重,映射出滤波的区域适应性; 是亮度表现函数(奇函数),选择经典 函数。

(2) 色调重整拉伸,对图像动态扩展 将式(1)中得到的中间量拉伸映射到 中,占满动态范围 (8位灰度图像),计算公式如下,式中: 是中间量 的全部定义域,该项使图像达到全局白平衡。

2.主要代码

# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
import math



# 线性拉伸处理
# 去掉最大最小0.5%的像素值 线性拉伸至[0,1]
def stretchImage(data, s=0.005, bins=2000):
    ht = np.histogram(data, bins);
    d = np.cumsum(ht[0]) / float(data.size)
    lmin = 0;
    lmax = bins - 1
    while lmin < bins:
        if d[lmin] >= s:
            break
        lmin += 1
    while lmax >= 0:
        if d[lmax] <= 1 - s:
            break
        lmax -= 1
    return np.clip((data - ht[1][lmin]) / (ht[1][lmax] - ht[1][lmin]), 0, 1)


# 根据半径计算权重参数矩阵
g_para = {}


def getPara(radius=5):
    global g_para
    m = g_para.get(radius, None)
    if m is not None:
        return m
    size = radius * 2 + 1
    m = np.zeros((size, size))
    for h in range(-radius, radius + 1):
        for w in range(-radius, radius + 1):
            if h == 0 and w == 0:
                continue
            m[radius + h, radius + w] = 1.0 / math.sqrt(h ** 2 + w ** 2)
    m /= m.sum()
    g_para[radius] = m
    return m


# 常规的ACE实现
def zmIce(I, ratio=4, radius=300):
    para = getPara(radius)
    height, width = I.shape
    zh = []
    zw = []
    n = 0
    while n < radius:
        zh.append(0)
        zw.append(0)
        n += 1
    for n in range(height):
        zh.append(n)
    for n in range(width):
        zw.append(n)
    n = 0
    while n < radius:
        zh.append(height - 1)
        zw.append(width - 1)
        n += 1
    # print(zh)
    # print(zw)

    Z = I[np.ix_(zh, zw)]
    res = np.zeros(I.shape)
    for h in range(radius * 2 + 1):
        for w in range(radius * 2 + 1):
            if para[h][w] == 0:
                continue
            res += (para[h][w] * np.clip((I - Z[h:h + height, w:w + width]) * ratio, -1, 1))
    return res


# 单通道ACE快速增强实现
def zmIceFast(I, ratio, radius):
    # print(I)
    height, width = I.shape[:2]
    if min(height, width) <= 2:
        return np.zeros(I.shape) + 0.5
    Rs = cv2.resize(I, (int((width + 1) / 2), int((height + 1) / 2)))
    Rf = zmIceFast(Rs, ratio, radius)  # 递归调用
    Rf = cv2.resize(Rf, (width, height))
    Rs = cv2.resize(Rs, (width, height))

    return Rf + zmIce(I, ratio, radius) - zmIce(Rs, ratio, radius)


# rgb三通道分别增强 ratio是对比度增强因子 radius是卷积模板半径
def zmIceColor(I, ratio=4, radius=3):
    res = np.zeros(I.shape)
    for k in range(3):
        res[:, :, k] = stretchImage(zmIceFast(I[:, :, k], ratio, radius))
    return res


# 主函数
if __name__ == '__main__':
    path = "./wutian"  # 文件夹目录
    files = os.listdir(path)  # 得到文件夹下的所有图像文件名称
    for file in files:  # 遍历文件夹
        if not os.path.isdir(file):  # 判断是否是文件夹,不是文件夹才打开
            # 下面为处理图像的过程
            img = cv2.imread(path+"\\"+file)
            H = zmIceColor(img / 255.0)
            Q = img.astype('float64') / 255.0

            arr = np.hstack((Q, H))
            # cv2.imshow('ACE_contrast_%s'%file, arr)
            cv2.imwrite('ACE_dehaze_%s'%file, H * 255.0)
            cv2.imwrite('ACE_contrast_%s'%file, arr * 255.0)
            cv2.waitKey(0)

    print("图像去雾完成,可点击对比图查看ACE去雾算法去雾效果")

3.实现结果图像

ACE_contrast_bench
ACE_contrast_cones
ACE_contrast_cones
ACE_contrast_dubai
ACE_contrast_dubai
ACE_contrast_forest
ACE_contrast_forest
ACE_contrast_herzeliya
ACE_contrast_herzeliya
ACE_contrast_neighbour
ACE_contrast_neighbour
ACE_contrast_pumpkins
ACE_contrast_pumpkins
ACE_contrast_road
ACE_contrast_road
ACE_contrast_tiananmen
ACE_contrast_tiananmen
ACE_contrast_train
ACE_contrast_train

1.2.2 暗通道先验去雾算法

1.算法原理&步骤、主要公式

暗通道先验(Dark Channel Prior, DCP)去雾算法依赖大气散射模型进行去雾处理,通过对大量有雾图像和无雾图像进行观察总结,得到其中存在的一些映射关系,然后根据有雾图像的形成过程来进行逆运算,从而恢复清晰图像。

(1) 大气散射模型在计算机视觉和计算机图形学中,方程所描述的大气散射模型被广泛使用。参数解释如下:

  • 是图像的空间坐标
  • 代表有雾图像(待去雾图像)
  • J(x)代表无雾图像(待恢复图像)
  • 代表全球大气光值
  • 代表透射率

方程右边第一项为场景直接衰减项,第二项为环境光项。

(2) 暗通道定义:在绝大多数非天空的局部区域中,某些像素总会至少有一个颜色通道的值很低。对于一幅图像 ,其暗通道的数学定义表示如下:

其中, 表示以 为中心的局部区域,上标 表示 三个通道。该公式的意义用代码表达也很简单,首先求出每个像素 分量中的最小值,存入一副和原始图像大小相同的灰度图中,然后再对这幅灰度图进行最小值滤波,滤波的半径由窗口大小决定。

(3) 暗通道先验理论 暗通道先验理论指出:对于非天空区域的无雾图像 的暗通道趋于0​,即:

(4) 公式变形根据大气散射模型,将第一个公式稍作处理,变形为下式:

假设每一个窗口的透射率 为常数,记为 ,并且 值已给定,对式两边同时进行两次最小值运算,可得:

其中,J(x)是要求的无雾图像,根据前述的暗通道先验理论可知:

因此可推导出:

(5) 透射率计算:将上式带入可得到透射率t’(x)的预估值,如下所示:

现实生活中,即便晴空万里,空气中也会存在一些颗粒,在眺望远处的景物时,人们还是能感觉到雾的存在。另外,雾的存在让人们感受到景深,因此在去雾的同时有必要保留一定程度的雾。可以通过引入一个 之 间 的 因 子 (一 般取0.95)对预估透射率进行修正,如式所示:

以上的推导过程均假设大气光值A是已知的,在实际中,可以借助暗通道图从原始雾图中求取。具体步骤如下:

  • 先求取暗通道图,在暗通道图中按照亮度的大小提取最亮的前 的像素
  • 在原始雾图I(x)中找对应位置上具有最高亮度的点的值,作为大气光值

此外,由于透射率t偏小时,会造成J偏大,恢复的无雾图像整体向白场过度,因此有必要对透射率设置一个下限值 (一般取值为 ),当t值小于 时,取 。将以上求得的透射率和大气光值代入公式,最终整理得到图像的恢复公式如下:

2.主要代码

# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
def zmMinFilterGray(src, r=7):
    '''最小值滤波,r是滤波器半径'''
    return cv2.erode(src, np.ones((2 * r + 1, 2 * r + 1)))
def guidedfilter(I, p, r, eps):
    height, width = I.shape
    m_I = cv2.boxFilter(I, -1, (r, r))
    m_p = cv2.boxFilter(p, -1, (r, r))
    m_Ip = cv2.boxFilter(I * p, -1, (r, r))
    cov_Ip = m_Ip - m_I * m_p

    m_II = cv2.boxFilter(I * I, -1, (r, r))
    var_I = m_II - m_I * m_I

    a = cov_Ip / (var_I + eps)
    b = m_p - a * m_I

    m_a = cv2.boxFilter(a, -1, (r, r))
    m_b = cv2.boxFilter(b, -1, (r, r))
    return m_a * I + m_b

def Defog(m, r, eps, w, maxV1):                 # 输入rgb图像,值范围[0,1]
    '''计算大气遮罩图像V1和光照值A, V1 = 1-t/A'''

    V1 = np.min(m, 2)                           # 得到暗通道图像
    Dark_Channel = zmMinFilterGray(V1, 7)
    V1 = guidedfilter(V1, Dark_Channel, r, eps)  # 使用引导滤波优化
    bins = 2000
    ht = np.histogram(V1, bins)                  # 计算大气光照A
    d = np.cumsum(ht[0]) / float(V1.size)
    lmax: int
    for lmax in range(bins - 1, 0, -1):
        if d[lmax] <= 0.999:
            break
    A = np.mean(m, 2)[V1 >= ht[1][lmax]].max()
    V1 = np.minimum(V1 * w, maxV1)               # 对值范围进行限制
    return V1, A

def deHaze(m, r=81, eps=0.001, w=0.98, maxV1=0.80, bGamma=False):
    Y = np.zeros(m.shape)
    Mask_img, A = Defog(m, r, eps, w, maxV1)             # 得到遮罩图像和大气光照
    for k in range(3):
        Y[:,:,k] = (m[:,:,k] - Mask_img)/(1-Mask_img/A)  # 颜色校正
    Y = np.clip(Y, 0, 1)
    if bGamma:
        Y = Y ** (np.log(0.5) / np.log(Y.mean()))       # gamma校正,默认不进行该操作
    return Y


if __name__ == '__main__':
    path = "./wutian"  # 文件夹目录
    files = os.listdir(path)  # 得到文件夹下的所有图像文件名称
    for file in files:  # 遍历文件夹
        if not os.path.isdir(file):  # 判断是否是文件夹,不是文件夹才打开
            # 下面为处理图像的过程
            img = cv2.imread(path + "\\" + file)
            H = deHaze(img / 255.0)
            Q = img.astype('float64') / 255

            arr = np.hstack((Q, H))
            # cv2.imshow('DCP_contrast_%s'%file, arr)
            cv2.imwrite('DCP_dehaze_%s' % file, H * 255)
            cv2.imwrite('DCP_contrast_%s' % file, arr * 255)
            cv2.waitKey(0)

    print("图像去雾完成,可点击对比图查看暗通道先验去雾算法去雾效果")

3.实现结果图像

DCP_contrast_bench
DCP_contrast_cones
DCP_contrast_cones
DCP_contrast_dubai
DCP_contrast_dubai
DCP_contrast_forset
DCP_contrast_forset
DCP_contrast_herzeliya
DCP_contrast_herzeliya
DCP_contrast_neighbour
DCP_contrast_neighbour
DCP_contrast_pumpkins
DCP_contrast_pumpkins
DCP_contrast_road
DCP_contrast_road
DCP_contrast_tiananmen
DCP_contrast_tiananmen
DCP_contrast_train
DCP_contrast_train

1.3 结果对比分析

采用基于图像增强的自动色彩均衡(Automatic Color Enhancement,ACE) 算法和基于图像复原的暗通道先验(Dark Channel Prior, DCP)去雾算法对不同的雾天图像进行处理,从处理结果可以看出,DCP算法去雾效果不错,但是也还有一些问题,比如对带有天空的雾天图片去雾效果不佳;而ACE算法是通过图像的对比度调整来实现去雾,在实际应用的过程中针对不同的情况会存在一定的失真,需要不断的调整参数来校正。

文章不错,扫码支持一下吧~
上一篇 下一篇
评论