基于暗通道先验的单幅图像去雾算法(学习笔记)

1、基本概念

(1)景深:物体离相机的距离。用亮度表示景深,颜色越亮说明深度越大距离越远

(2)图像去雾的目的:恢复清晰度,估计景深

2、去雾模型

基于暗通道先验的单幅图像去雾算法(学习笔记)_第1张图片

这个公式中已知的为I有雾图像,未知数有J、A大气光、t透射率,因为这个公式有多组解,所以此问题称为ill-posed病态问题,加入先验信息后才能成为well-posed良态问题,去雾的目的是得到清晰图像J,确定A和t后才能得到J

 其中 β 是大气散射的参数,d是景深。这个公式表达了scene radiance会随着距离的增大而呈现出指数型的衰减。因为如果我们得到了这个透射率,我们就可以根据这一规律得到景物的深度。

 3、加入先验信息可使不适定问题转换为适定问题

(1)平滑先验

(2)稀疏先验:噪声恢复会用到

(3)低秩先验:正常矩阵简化完后大部分元素为0,低秩先验是用低秩矩阵恢复一个完整的矩阵

4、暗通道先验

(1)r,g,b=0时图像是黑色的,r,g,b=1时图像是白色的,能看到彩色图像是因为r,g,b中有一个值偏小

(2)彩色图像求暗通道的过程:取r,g,b三通道中的最小值,用这个值替代原像素,即最小值滤波

基于暗通道先验的单幅图像去雾算法(学习笔记)_第2张图片

 (3)作者经过统计发现:5000张无雾图片做统计,86%的像素位于[0,16]

基于暗通道先验的单幅图像去雾算法(学习笔记)_第3张图片

由此可以得出的结论是:无雾图像的暗通道趋于0

基于暗通道先验的单幅图像去雾算法(学习笔记)_第4张图片 无雾图像暗通道趋于0的原因:

  1> 阴影

  2> 彩色物体或者表面:反射率较低的物体会导致在暗通道中的低值

  3> 黑色物体或者表面

 (4)有雾图像的暗通道不再是黑的

基于暗通道先验的单幅图像去雾算法(学习笔记)_第5张图片

5、透射率计算过程

基于暗通道先验的单幅图像去雾算法(学习笔记)_第6张图片

基于暗通道先验的单幅图像去雾算法(学习笔记)_第7张图片基于暗通道先验的单幅图像去雾算法(学习笔记)_第8张图片

基于暗通道先验的单幅图像去雾算法(学习笔记)_第9张图片

ω取值为0.95,Ω(x)为像素的15×15邻域,c代表颜色rgb三个通道

在现实生活中,我们看物体时会感觉到近处的清晰,远处的模糊,所以有必要在去雾的时候保留一定程度的雾,ω可以理解为去雾程度,1为完全去雾,0为不去雾。

 6、大气光估算过程:

根据统计,我们发现天空的颜色总是与大气光接近,天空的暗通道会很亮同时透射率很接近0。

求出原始有雾图像的暗通道图像,在暗通道图像中找到像素值最高的、占总像素数目前0.1%的像素点位置,这些位置中原始图像像素值最高的那个像素点,就是大气光A

以下图为例说明一下这么做的理由:如果使用传统的方法,直接选取图像中的亮度值最高的点作为全局大气光值,这样原始有雾图像中的白色物体会对此有影响,使得其值偏高。暗通道的运算可以抹去原始图像中小块的白色物体,所以这样估计的全局大气光值会更准确

基于暗通道先验的单幅图像去雾算法(学习笔记)_第10张图片

 7、大气光A和透射率t都估算出来后,就可以恢复无雾图像

基于暗通道先验的单幅图像去雾算法(学习笔记)_第11张图片

当透射率t的值很小时,会导致J的值偏大,从而使图像整体偏白,因此一般可设置一阈值t0,当t值小于t0时,令t=t0,本文中所有效果图均以t0=0.1为标准计算

8、局限性

(1)暗通道适合大气光均匀的情况,对于环境光不均匀的情况,暗通道先验不适合

(2)对于物体和大气光相近的情况,暗通道先验也不适合 ,比如雪地、白色背景墙

9、python实现

import math
import numpy as np
import cv2


# 用于排序时存储原来像素点位置的数据结构
class Node(object):
    def __init__(self, x, y, value):
        self.x = x
        self.y = y
        self.value = value

    def printInfo(self):
        print
        '%s:%s:%s' % (self.x, self.y, self.value)


# 获取最小值矩阵
# 获取BGR三个通道的最小值
def getMinChannel(img):
    # 输入检查
	#在一副图像中使用shape得到一个3个类别的列表,shape[0] =图像的高,shape[1] =图像的宽,shape[2] = 图像的图像通道数量
    if len(img.shape) == 3 and img.shape[2] == 3:
        pass
    else:
        print
        "bad image shape, input must be color image"
        return None

    imgGray = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)
    localMin = 255

    for i in range(0, img.shape[0]):
        for j in range(0, img.shape[1]):
            localMin = 255
            for k in range(0, 3):
                if img.item((i, j, k)) < localMin:
                    localMin = img.item((i, j, k))
            imgGray[i, j] = localMin

    return imgGray


# 获取暗通道
def getDarkChannel(img, blockSize=3):
    # 输入检查
    if len(img.shape) == 2:
        pass
    else:
        print
        "bad image shape, input image must be two demensions"
        return None

    # blockSize检查
    if blockSize % 2 == 0 or blockSize < 3:
        print
        'blockSize is not odd or too small'
        return None

    # 计算addSize
    addSize = (blockSize - 1) // 2

    newHeight = img.shape[0] + blockSize - 1
    newWidth = img.shape[1] + blockSize - 1

    # 中间结果
    imgMiddle = np.zeros((newHeight, newWidth))
    imgMiddle[:, :] = 255

    imgMiddle[addSize:newHeight - addSize, addSize:newWidth - addSize] = img

    imgDark = np.zeros((img.shape[0], img.shape[1]), np.uint8)
    localMin = 255

    for i in range(addSize, newHeight - addSize):
        for j in range(addSize, newWidth - addSize):
            localMin = 255
            for k in range(i - addSize, i + addSize + 1):
                for l in range(j - addSize, j + addSize + 1):
                    if imgMiddle.item((k, l)) < localMin:
                        localMin = imgMiddle.item((k, l))
            imgDark[i - addSize, j - addSize] = localMin

    return imgDark


# 获取全局大气光强度
def getAtomsphericLight(darkChannel, img, meanMode=False, percent=0.001):
    size = darkChannel.shape[0] * darkChannel.shape[1]
    height = darkChannel.shape[0]
    width = darkChannel.shape[1]

    nodes = []

    # 用一个链表结构(list)存储数据
    for i in range(0, height):
        for j in range(0, width):
            oneNode = Node(i, j, darkChannel[i, j])
            nodes.append(oneNode)

    # 排序
    nodes = sorted(nodes, key=lambda node: node.value, reverse=True)

    atomsphericLight = 0

    # 原图像像素过少时,只考虑第一个像素点
    if int(percent * size) == 0:
        for i in range(0, 3):
            if img[nodes[0].x, nodes[0].y, i] > atomsphericLight:
                atomsphericLight = img[nodes[0].x, nodes[0].y, i]

        return atomsphericLight

    # 开启均值模式
    if meanMode:
        sum = 0
        for i in range(0, int(percent * size)):
            for j in range(0, 3):
                sum = sum + img[nodes[i].x, nodes[i].y, j]

        atomsphericLight = int(sum / (int(percent * size) * 3))
        return atomsphericLight

    # 获取暗通道前0.1%(percent)的位置的像素点在原图像中的最高亮度值
    for i in range(0, int(percent * size)):
        for j in range(0, 3):
            if img[nodes[i].x, nodes[i].y, j] > atomsphericLight:
                atomsphericLight = img[nodes[i].x, nodes[i].y, j]

    return atomsphericLight


# 恢复原图像
# Omega 去雾比例 参数
# t0 最小透射率值
def getRecoverScene(img, omega=0.95, t0=0.1, blockSize=15, meanMode=False, percent=0.001):
    imgGray = getMinChannel(img)
    imgDark = getDarkChannel(imgGray, blockSize=blockSize)
    atomsphericLight = getAtomsphericLight(imgDark, img, meanMode=meanMode, percent=percent)

    imgDark = np.float64(imgDark)
    transmission = 1 - omega * imgDark / atomsphericLight

    # 防止出现t小于0的情况
    # 对t限制最小值为0.1
    for i in range(0, transmission.shape[0]):
        for j in range(0, transmission.shape[1]):
            if transmission[i, j] < 0.1:
                transmission[i, j] = 0.1

    sceneRadiance = np.zeros(img.shape)

    for i in range(0, 3):
        img = np.float64(img)
        sceneRadiance[:, :, i] = (img[:, :, i] - atomsphericLight) / transmission + atomsphericLight

        # 限制透射率 在0~255
        for j in range(0, sceneRadiance.shape[0]):
            for k in range(0, sceneRadiance.shape[1]):
                if sceneRadiance[j, k, i] > 255:
                    sceneRadiance[j, k, i] = 255
                if sceneRadiance[j, k, i] < 0:
                    sceneRadiance[j, k, i] = 0

    sceneRadiance = np.uint8(sceneRadiance)

    return sceneRadiance


# 调用示例

def sample():
    img = cv2.imread('./NTIRE-2021-Dehazing-DWGAN-main/test_image/21-1.jpg', cv2.IMREAD_COLOR)
    sceneRadiance = getRecoverScene(img)

    cv2.imshow('original', img)
    cv2.imshow('test', sceneRadiance)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


sample()

效果图:

基于暗通道先验的单幅图像去雾算法(学习笔记)_第12张图片

结论:可以看出暗通道先验对不均匀的雾,去雾效果不是很好 

你可能感兴趣的:(计算机视觉学习笔记,python)