[论文阅读&代码]DehazeNet: An End-to-End System for Single Image Haze Removal

文章介绍了DehazeNet,这是一个基于深度卷积神经网络的端到端去雾系统,它利用大气散射模型和多种图像去雾先验(如暗通道、最大对比度和颜色衰减)来估计介质传输图并恢复无雾图像。DehazeNet通过Maxout单元和非线性激活函数BRelu提取和处理雾相关特征,提高了去雾质量。
摘要由CSDN通过智能技术生成

 摘要

  • 现有的单图像去雾方法使用很多约束和先验来获得去雾结果,去雾的关键是根据输入的雾图获得得到介质传输图(medium transmission map)
  • 这篇文章提出了一种端到端的可训练的去雾系统—Dehaze Net,用于估计介质传输图
  • Dehaze Net中,输入为雾图,输出为介质传输图,随后通过大气散射模型恢复无雾图像。
  • Dehaze Net网络采用卷积神经网络深度架构,该网络的每层都经过特殊的设计以应用现有的假设和先验。
  • Maxout单元用于特征提取,几乎可以产生大多数雾相关的特征。
  • 提出了一种非线性激活函数BRelu,其能够提高图像去雾的质量

Introduction

  • 当前的去雾方法:直方图方法;对比度方法;饱和度方法
  • 无雾图像的局部对比度高于有雾图像(最大对比度)、暗通道
  • Dehaze Net的设计受到现有的图像去雾的假设及原理的启发,其每层的参数都能够自动学习训练的雾图的特征
  • 本文的主要工作:设计了端到端的DehazeNet网络,应用现有的图像去雾原理对深度网络进行设计,能够直接得到雾图与其介质透射图之间的映射。
  • 非线性激活函数BRelu,利用双边约束来减小搜索空间,提高收敛性。

相关工作

大气散射模型

大气散射模型描述了雾的形成,该模型可以写作:

上式中,I(x)为雾图,J(x)为需要恢复的无雾图像,t(x)为介质透射率,\alpha为全局大气光。 大气散射模型中有三个未知数J(x)、t(x)及\alpha,要想恢复J(x),需要估计出t(x)及\alpha。 

 上图是雾天的成像过程,在雾霾天气中成像的过程中,反射能量减少引起的透射衰减J (x) t (x),导致亮度强度降低。空气灯α(1−t (x))由环境照明的散射形成,提高亮度并降低饱和度。α(1 − t (x))大气光是由环境光照散射形成的,能够增加亮度降低饱和度。

 介质透射率表示并未散射并到达相机的光,d(x)表示场景与相机的距离,\beta是大气散射系数,根据上面的式子,当距离d(x)无限大的时候,t(x)趋于0,那么相机拍摄的图像I(x)就是全局大气光\alpha。如下式子所示。

 在实际的远距离成像过程中,d(x)不会无限大,但是可能会是一个相当大的数值,因此全局大气光的估计可以根据下面的式子(全局大气光可能是图像中亮度最大的点):

 根据大气散射模型,得到雾图的介质透射率是图像去雾恢复的关键环节。

雾相关的特征 

  • 暗通道

暗通道先验是在对大量室外无雾图片的观察分析得到的,大部分无雾图像中,至少有一个通道的像素点的亮度会非常低,甚至接近于0,暗通道定义为局部所有通道像素颜色的最小值。

 公式5中,I^{c}表示图像I的RGB三个颜色通道,y属于像素点x的局部邻域,暗通道特征与图片中雾的浓度密切相关,能够被用于直接计算介质透射率t(x)。

  • 最大对比度

根据大气散射,雾会造成图像对比度的下降,局部对比度是指邻域中像素强度的方差与中心像素有关,邻域内局部对比度的最大值定义如下:

 在该邻域内,对比度特征与介质透射率之间的关系非常明显,可以通过最大化局部对比度增强图像的可见性。

  • 颜色衰减先验(HSV)

受雾霾影响,场景颜色发生褪色,同时patch的饱和度也剧烈下降,亮度也同时增加,产生一个较大的差值(亮度与饱和度的差),根据颜色衰减先验,这个差值可用于估计雾的浓度。

  •  色差(HSV)

原图与半反转(半逆)图像之间的色差可用于检测雾,对于无雾图像,其半逆图像的三个通道中的像素值不会全部翻转,导致原图与其半逆图像之间的色调变化较大。

 上式中,上标h表示HSV颜色空间的色度通道,根据公式8,介质透射率是色度差的反向传播。

DehazeNet

DehazeNet是一种可训练的端到端系统,可以明确地学习原始模糊图像及其相关介质传输图之间的映射关系。本部分介绍DehazeNet的层设计,并讨论这些设计如何与现有图像去雾方法中的思想(暗通道、最大对比度、颜色衰减先验、色差)相关联。

  •  特征提取

密集地提取这些与雾霾相关的特征相当于用适当的滤波器对输入的雾霾图像进行卷积,然后进行非线性映射。

Maxout激活函数用于降维的非线性映射,Maxout是一种简单的前馈非线性激活函数,用于多层感知或卷积神经网络。在CNN中使用时,通过对k个仿射特征映射进行像素最大化操作来生成一个新的特征映射。根据Maxout,设计了DehazeNet的第一层:

 W代表滤波器,B代表权重,*表示卷积操作

  • 多尺度映射

多尺度特征是有效的去雾霾方法,它密集地计算输入图像在多个空间尺度上的特征。多尺度特征提取也能有效地实现尺度不变性。例如,GoogLeNet中的初始架构使用了具有不同过滤器大小的并行卷积,并更好地解决了在输入图像中对齐对象的问题。

在DehazeNet的第二层使用并行卷积运算,其中任何卷积滤波器的大小都在3 × 3、5 × 5和7× 7之间,并且我们对这三个尺度使用相同数量的滤波器。

  •  局部极值

 相对于cnn的max-pooling通常会降低特征图的分辨率,局部极值操作被密集地应用于每个特征图像素,并且能够保留分辨率用于图像恢复。

  • 非线性回归

深度网络中非线性激活函数的标准选择包括Sigmoid和整流线性单元(ReLU)。前者更容易出现梯度消失的问题,导致网络训练收敛速度慢或局部最优性差。

为了克服梯度消失的问题,提出了提供稀疏表示的ReLU。然而,ReLU是为分类问题而设计的,并不完全适合图像恢复等回归问题。特别是,ReLU仅在值小于零时才抑制值。

 以上四层级联(串联)在一起形成了一个基于CNN的可训练端到端系统,其中与卷积层相关的滤波器和偏差是要学习的网络参数。这些层的设计可以与现有图像去雾方法的专业知识相联系。

 

import os
# import random
import cv2
import numpy as np
import random


def create_dataset(img_dir, data_dir, num_t=10, patch_size=16):
	# img_dir: dir of haze-free images
	# num_t: number of t(x)
	# patch_size: size of image patch
	
	img_path = os.listdir(img_dir)
	# 读取img_dir文件夹下所有文件

	path_train = []
	label_train = []

    # 包含训练样本的文件路径
    # 对应每个训练样本的标签,用于指示该样本属于哪个类别或者是什么类型
    # 通常在训练机器学习模型的过程中,需要同时提供训练数据集的特征和标签,以便模型能够学习到输入特征和相应标签之间的关系,并进行泛化预测
    
	
	for image_name in img_path:
		fullname = os.path.join(img_dir, image_name)
		img = cv2.imread(fullname)
		
		w, h, _ = img.shape
		
		num_w = int(w / patch_size)
		num_h = int(h / patch_size)
    # 将图像按照指定的大小(patch_size)划分成若干个小块,并计算图像中可以划分出多少个这样的小块。具体来说,num_w和num_h分别表示图像宽度方向和高度方向上,能够划分出的小块的数量。w和h分别是图像的宽度和高度,除以patch_size后,用Python的int()函数向下取整,得到的结果就是能够划分的小块的数量。
		for i in range(1, num_w-1):
			for j in range(1, num_h-1):
    # 循环变量 i 和 j 分别从1开始,而不是从0开始,可能是因为要避免处理图像的边缘区域。在图像处理中,通常会将边缘部分的像素值进行特殊处理,因为边缘处的像素往往不完整,可能会导致处理结果出现异常。因此,为了避免处理边缘像素的情况,通常会在处理图像时忽略边缘部分。在这个例子中,使用 range(1, num_w-1) 和 range(1, num_h-1) 作为循环变量,是为了避免处理图像的左、右、上、下四个边缘像素				

				free_patch = img[0 + i * patch_size:patch_size + i * patch_size,
					0 + j * patch_size:patch_size + j * patch_size, :]
				
				for k in range(num_t):
					t = random.random()
					hazy_patch = free_patch * t + 255 * (1 - t)
					picname = '%s'%i+'%s'%j+'%s'%k+image_name
					x = random.random()
					if x > 0.5:
						cv2.imwrite(os.path.join(data_dir, picname), hazy_patch)
						path_train.append(os.path.join(data_dir, picname))
						label_train.append(t)
	
	file = open('path_train.txt', mode='a')
	for i in range(len(path_train)):
		file.write(str(path_train[i])+'\n')
	file.close()
	file = open('label_train.txt', mode='a')
	for i in range(len(label_train)):
		file.write(str(label_train[i])+'\n')
	file.close()


create_dataset("E:/image enhancement/Dense_Haze_NTIRE19/dehaze", "E:/image enhancement/Dense_Haze_NTIRE19/dataset", num_t=10, patch_size=16)

在这段代码中,变量i被使用了多次,但是它们所指代的是不同的东西:

  1. 第一个i的使用是在最外层的循环中,它用来迭代图像中行数的数量(不包括边界):for i in range(1, num_w-1)
  2. 第二个i的使用是在保存图像补丁的文件名中:picname = '%s'%i+'%s'%j+'%s'%k+image_name。在这里,i用来表示图像中补丁的行索引。
  3. 第三个i的使用是在将文件路径写入“path_train.txt”文件的循环中:for i in range(len(path_train))。在这里,i用作循环计数器,用来迭代文件路径列表。

同样,代码中也有多个变量jk的使用,它们也分别指代不同的东西。

在上述代码中,num_t 是指每个图像块(image patch)所生成的模糊图像数量,即每个图像块会通过不同的 t 值来生成多个模糊图像。

在这个函数中,对于每个输入的原始图像,将其分割成多个图像块,每个图像块的大小为 patch_size * patch_size。对于每个图像块,会根据 num_t 生成多个模糊图像。具体地,使用一个随机的 t 值,根据以下公式生成模糊图像:

hazy_patch = free_patch * t + 255 * (1 - t)

其中,free_patch 是原始图像的一个图像块,t 是一个随机值,表示透射率(transmission rate), hazy_patch 是生成的模糊图像。这个公式通过将原始图像的颜色值乘以一个透射率值 t,再加上一个常数来模拟图像受到大气光影响的模糊效果。

因此,num_t 的值越大,每个图像块生成的模糊图像数量就越多。这样可以增加训练数据集的多样性,提高模型的泛化能力。但同时也会增加数据集的大小和训练时间。

import torch
import torch.nn as nn
from torch.utils.data.dataset import Dataset
from PIL import Image
import torchvision
from torchvision import transforms
import torch.utils.data as data
#import torchsnooper
import cv2

BATCH_SIZE = 128
# 每批处理的数据量
EPOCH = 10
# 迭代次数

# BRelu used for GPU. Need to add that reference in pytorch source file.
class BRelu(nn.Hardtanh):
	def __init__(self, inplace=False):
		super(BRelu, self).__init__(0., 1., inplace)

# 调用 nn.Hardtanh 的构造函数,并传递参数 0. 和 1. 作为 min_val 和 max_val,以及参数 inplace 作为布尔值。这样就创建了一个新的 BRelu 类,它继承了 nn.Hardtanh 类,并将其 min_val 和 max_val 参数设置为 0 和 1,相当于将 nn.Hardtanh 类限制在了这个范围内。
		
	def extra_repr(self):
		inplace_str = 'inplace=True' if self.inplace else ''
		return inplace_str

# 这段代码实现了一个BRelu类,它是nn.Hardtanh类的子类。nn.Hardtanh类是一个激活函数,用于将输入张量的值截断在指定的最小值和最大值之间。在这个BRelu类中,最小值为0,最大值为1,即将输入张量的负值截断为0,将大于1的值截断为1,其余值保持不变。




class DehazeNet(nn.Module):
	def __init__(self, input=16, groups=4):
		super(DehazeNet, self).__init__()
		self.input = input
		self.groups = groups
		self.conv1 = nn.Conv2d(in_channels=3, out_channels=self.input, kernel_size=5)
		self.conv2 = nn.Conv2d(in_channels=4, out_channels=16, kernel_size=3, padding=1)
		self.conv3 = nn.Conv2d(in_channels=4, out_channels=16, kernel_size=5, padding=2)
		self.conv4 = nn.Conv2d(in_channels=4, out_channels=16, kernel_size=7, padding=3)
		self.maxpool = nn.MaxPool2d(kernel_size=7, stride=1)
		self.conv5 = nn.Conv2d(in_channels=48, out_channels=1, kernel_size=6)
		self.brelu = nn.ReLU6()
		for name, m in self.named_modules():
			# lambda : 定义简单的函数    lambda x: 表达式
			# map(func, iter)  iter 依次调用 func
			# any : 有一个是true就返回true
			if isinstance(m, nn.Conv2d):
				# 初始化 weight 和 bias
				nn.init.normal_(m.weight, mean=0,std=0.001)
				if m.bias is not None:
					nn.init.constant_(m.bias, 0)
	
	def Maxout(self, x, groups):
		x = x.reshape(x.shape[0], groups, x.shape[1]//groups, x.shape[2], x.shape[3])
		x, y = torch.max(x, dim=2, keepdim=True)
		out = x.reshape(x.shape[0],-1, x.shape[3], x.shape[4])
		return out
	#BRelu used to CPU. It can't work on GPU.
	def BRelu(self, x):
		x = torch.max(x, torch.zeros(x.shape[0],x.shape[1],x.shape[2],x.shape[3]))
		x = torch.min(x, torch.ones(x.shape[0],x.shape[1],x.shape[2],x.shape[3]))
		return x
	
	def forward(self, x):
		out = self.conv1(x)
		out = self.Maxout(out, self.groups)
		out1 = self.conv2(out)
		out2 = self.conv3(out)
		out3 = self.conv4(out)
		y = torch.cat((out1,out2,out3), dim=1)
		# print(y.shape[0],y.shape[1],y.shape[2],y.shape[3],)
		y = self.maxpool(y)
		# print(y.shape[0],y.shape[1],y.shape[2],y.shape[3],)
		y = self.conv5(y)
		# y = self.relu(y)
		# y = self.BRelu(y)
		# y = torch.min(y, torch.ones(y.shape[0],y.shape[1],y.shape[2],y.shape[3]))
		y = self.brelu(y)
		y = y.reshape(y.shape[0],-1)
		return y


loader = torchvision.transforms.Compose([
	transforms.ToTensor(),
	transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
augmentation = torchvision.transforms.Compose([
	transforms.RandomHorizontalFlip(0.5),
	transforms.RandomVerticalFlip(0.5),
	transforms.RandomRotation(30),
	transforms.ToTensor(),
	transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])


class FogData(Dataset):
	# root:图像存放地址根路径
	# augment:是否需要图像增强
	def __init__(self, root, labels, augment=True):
		# 初始化 可以定义图片地址 标签 是否变换 变换函数
		self.image_files = root
		self.labels = torch.cuda.FloatTensor(labels)
		self.augment = augment   # 是否需要图像增强
		# self.transform = transform

	def __getitem__(self, index):
		# 读取图像数据并返回
		if self.augment:
			img = Image.open(self.image_files[index])
			img = augmentation(img)
			img = img.cuda()
			return img, self.labels[index]
		else:
			img = Image.open(self.image_files[index])
			img = loader(img)
			img = img.cuda()
			return img, self.labels[index]

	def __len__(self):
		# 返回图像的数量
		return len(self.image_files)


path_train = []
file = open('path_train.txt', mode='r')
content = file.readlines()
for i in range(len(content)):
	path_train.append(content[i][:-1])

label_train = []
file = open('label_train.txt', mode='r')
content = file.readlines()
for i in range(len(content)):
	label_train.append(float(content[i][:-1]))
	#print(float(content[i][:-1]))

train_data = FogData(path_train, label_train, False)
train_loader = data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True, )

net = DehazeNet()
net.load_state_dict(torch.load(r'defog4_noaug.pth', map_location='cpu'))

#@torchsnooper.snoop()
def train():
	lr = 0.00001
	optimizer = torch.optim.Adam(net.parameters(), lr=0.0000005)
	loss_func = nn.MSELoss().cuda()
	for epoch in range(EPOCH):
		total_loss = 0
		for i, (x, y) in enumerate(train_loader):
	# 输入训练数据
	# 清空上一次梯度
			optimizer.zero_grad()
			output = net(x)
	# 计算误差
			loss = loss_func(output, y)
			total_loss = total_loss+loss
	# 误差反向传递
			loss.backward()
	# 优化器参数更新
			optimizer.step()
			if i % 10 == 5:
				print('Epoch', epoch, '|step ', i, 'loss: %.4f' % loss.item(), )
		print('Epoch', epoch, 'total_loss', total_loss.item())
	torch.save(net.state_dict(), r'defog4_noaug.pth')


#train()


def defog(pic_dir):
	img = Image.open(pic_dir)
	img1 = loader(img)
	img2 = transforms.ToTensor()(img)
	c, h, w = img1.shape
	patch_size = 16
	num_w = int(w / patch_size)
	num_h = int(h / patch_size)
	t_list = []
	for i in range(0, num_w):
		for j in range(0, num_h):
			patch = img1[:, 0 + j * patch_size:patch_size + j * patch_size,
				0 + i * patch_size:patch_size + i * patch_size]
			patch = torch.unsqueeze(patch, dim=0)
			t = net(patch)
			t_list.append([i,j,t])
	
	t_list = sorted(t_list, key=lambda t_list:t_list[2])
	a_list = t_list[:len(t_list)//100]
	a0 = 0
	for k in range(0,len(a_list)):
		patch = img2[:, 0 + a_list[k][1] * patch_size:patch_size + a_list[k][1] * patch_size,
				0 + a_list[k][0] * patch_size:patch_size + a_list[k][0] * patch_size]
		a = torch.max(patch)
		if a0 < a.item():
			a0 = a.item()
	for k in range(0,len(t_list)):
		img2[:, 0 + t_list[k][1] * patch_size:patch_size + t_list[k][1] * patch_size,
			0 + t_list[k][0] * patch_size:patch_size + t_list[k][0] * patch_size] = (img2[:,
			0 + t_list[k][1] * patch_size:patch_size + t_list[k][1] * patch_size,
			0 + t_list[k][0] * patch_size:patch_size + t_list[k][0] * patch_size] - a0*(1-t_list[k][2]))/t_list[k][2]
	defog_img = transforms.ToPILImage()(img2)
	defog_img.save('E:/image enhancement/Dense_Haze_NTIRE19/result/test.jpg')


defog("E:/image enhancement/demoes/DehazeNet-master/DehazeNet-master/data/girls.jpg")

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 单图像去雾是一种常见的计算机视觉问题,它旨在从含有雾霾的图像中恢复出清晰的图像。"Dark Channel Prior"是一种常用的算法,在单图像去雾中有着广泛的应用。 Dark Channel Prior(暗通道先验)是通过观察自然景物在局部区域中深度最小像素的强度来估计雾霾浓度和传播距离的先验知识。Dark Channel可以简单地通过在输入图像的每个像素位置上采用最小值操作来计算得到。这样,我们可以仿佛窥视很多景物的背后,包括雾霾中那些没有雾的真实景物。 Dark Channel Prior算法分为三个步骤: 1. 估计暗通道:对输入图像的每个像素计算出最小通道值,得到每个像素位置的暗通道。由于雾霾导致图像亮度改变,这一步估计了景物中的最小透射率。 2. 估计大气光:通过在暗通道图像中找到最亮的像素值,得到估计的大气光。这是因为大气光对于雾霾图像中的亮度改变有关键作用。 3. 去雾恢复:基于估计的暗通道和大气光,我们可以在图像中进行退化模型的恢复,以消除雾霾效应。这可以通过以下公式实现:R = (I - A) / t + A,其中R是去雾后的图像,I是输入图像,A是估计的大气光,t是透射率。 代码实现上,我们可以通过使用基于块的方法来减小计算量,同时在去雾后对结果进行细化和增强,以获得更好的效果。在此基础上,还可以采用其他技术和方法来进一步改进去雾结果,例如引入图像边缘信息和增加颜色校正。 总之,单图像去雾是一项具有挑战性和广泛应用的任务。"Dark Channel Prior" 算法被证明是一种有效的方法,能够在一定程度上还原出清晰的图像,提升雾霾图像的质量。 ### 回答2: Single image haze removal using dark channel prior是一种用于去除图片中雾霾的算法。该算法通过分析图像的暗通道先验信息来估计图像中的雾霾程度,并进行相应的去雾操作。 暗通道先验是指图像中的某些区域在某个颜色通道上的像素值较低。这是因为雾霾会使得图像中的物体颜色变浅,而远处的物体通常更加受到雾霾的影响。基于此,算法通过寻找图像中的暗通道来估计雾霾强度。 具体实现方案如下: 1. 对于给定的输入图像,算法首先计算图像的暗通道。这可以通过对图像的每个像素点在RGB颜色空间中选择最小值来实现。 2. 通过暗通道估计得到的雾霾强度,算法可以计算出每个像素点在雾霾下的透射率。透射率越高,表示该像素受到的雾霾影响越小。 3. 基于透射率,算法可以计算出未被雾霾遮挡的场景亮度。这可以通过选择图像中的最大值来实现。 4. 最后,算法通过去除透射率和场景亮度对图像进行去雾操作。这可以通过对每个像素点应用去雾公式来实现。去雾公式将图像中的像素值重新映射,以减少雾霾的影响。 通过单一图像和暗通道先验的使用,该算法能够较好地去除图像中的雾霾,提高图像的可视性和质量。它在计算机视觉和图像处理领域有着广泛的应用,例如景观摄影和无人驾驶等。 ### 回答3: Single image haze removal using dark channel prior是一种用于去除图像雾霾的算法。这个算法的代码实现逻辑是基于一个称为“暗通道先验”的概念。 在这个算法中,我们首先计算图像的暗通道图像。暗通道图像是指在图像的每个局部区域内,选择像素值最小的通道作为该区域的暗通道像素值。通过计算暗通道图像,我们可以得到整个图像的暗通道图像。 接下来,通过观察我们发现,大部分的非天空区域的暗通道像素值都是接近于0的,而天空区域的暗通道像素值通常会高于0。而这些高于0的像素值正是由于雾霾所导致的。 因此,我们可以通过选取每个局部区域内暗通道像素值最小的像素点作为参考点,来估算出雾霾的浓度。进一步地,我们可以根据这个浓度值来消除图像的雾霾。 具体地,我们可以使用以下公式来计算去除雾霾后的像素值: t(x) = 1 - w * min(R/G, R/B) 其中,t(x)表示去除雾霾后的像素值,w表示雾霾浓度(衡量雾霾的程度),R/G和R/B分别表示图像红色通道和绿色通道以及蓝色通道的比值。这个公式可以将原始像素值转换为去除雾霾后的像素值。 最后,我们可以根据去除雾霾后的像素值和原始图像的亮度值来还原最终的去雾图像。 Single image haze removal using dark channel prior代码实现了上述算法的具体步骤和细节。通过使用这个代码,我们可以方便地对图像进行去雾处理,使得图像更加清晰和真实。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值