1、基本概念
(1)景深:物体离相机的距离。用亮度表示景深,颜色越亮说明深度越大距离越远
(2)图像去雾的目的:恢复清晰度,估计景深
2、去雾模型
这个公式中已知的为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三通道中的最小值,用这个值替代原像素,即最小值滤波
(3)作者经过统计发现:5000张无雾图片做统计,86%的像素位于[0,16]
由此可以得出的结论是:无雾图像的暗通道趋于0
1> 阴影
2> 彩色物体或者表面:反射率较低的物体会导致在暗通道中的低值
3> 黑色物体或者表面
(4)有雾图像的暗通道不再是黑的
5、透射率计算过程
ω取值为0.95,Ω(x)为像素的15×15邻域,c代表颜色rgb三个通道
在现实生活中,我们看物体时会感觉到近处的清晰,远处的模糊,所以有必要在去雾的时候保留一定程度的雾,ω可以理解为去雾程度,1为完全去雾,0为不去雾。
6、大气光估算过程:
根据统计,我们发现天空的颜色总是与大气光接近,天空的暗通道会很亮同时透射率很接近0。
求出原始有雾图像的暗通道图像,在暗通道图像中找到像素值最高的、占总像素数目前0.1%的像素点位置,这些位置中原始图像像素值最高的那个像素点,就是大气光A
以下图为例说明一下这么做的理由:如果使用传统的方法,直接选取图像中的亮度值最高的点作为全局大气光值,这样原始有雾图像中的白色物体会对此有影响,使得其值偏高。暗通道的运算可以抹去原始图像中小块的白色物体,所以这样估计的全局大气光值会更准确
7、大气光A和透射率t都估算出来后,就可以恢复无雾图像
当透射率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()
效果图:
结论:可以看出暗通道先验对不均匀的雾,去雾效果不是很好