摘要
- 现有的单图像去雾方法使用很多约束和先验来获得去雾结果,去雾的关键是根据输入的雾图获得得到介质传输图(medium transmission map)
- 这篇文章提出了一种端到端的可训练的去雾系统—Dehaze Net,用于估计介质传输图
- Dehaze Net中,输入为雾图,输出为介质传输图,随后通过大气散射模型恢复无雾图像。
- Dehaze Net网络采用卷积神经网络深度架构,该网络的每层都经过特殊的设计以应用现有的假设和先验。
- Maxout单元用于特征提取,几乎可以产生大多数雾相关的特征。
- 提出了一种非线性激活函数BRelu,其能够提高图像去雾的质量
Introduction
- 当前的去雾方法:直方图方法;对比度方法;饱和度方法
- 无雾图像的局部对比度高于有雾图像(最大对比度)、暗通道
- Dehaze Net的设计受到现有的图像去雾的假设及原理的启发,其每层的参数都能够自动学习训练的雾图的特征
- 本文的主要工作:设计了端到端的DehazeNet网络,应用现有的图像去雾原理对深度网络进行设计,能够直接得到雾图与其介质透射图之间的映射。
- 非线性激活函数BRelu,利用双边约束来减小搜索空间,提高收敛性。
相关工作
大气散射模型
大气散射模型描述了雾的形成,该模型可以写作:
上式中,I(x)为雾图,J(x)为需要恢复的无雾图像,t(x)为介质透射率,为全局大气光。 大气散射模型中有三个未知数J(x)、t(x)及,要想恢复J(x),需要估计出t(x)及。
上图是雾天的成像过程,在雾霾天气中成像的过程中,反射能量减少引起的透射衰减J (x) t (x),导致亮度强度降低。空气灯α(1−t (x))由环境照明的散射形成,提高亮度并降低饱和度。α(1 − t (x))大气光是由环境光照散射形成的,能够增加亮度降低饱和度。
介质透射率表示并未散射并到达相机的光,d(x)表示场景与相机的距离,是大气散射系数,根据上面的式子,当距离d(x)无限大的时候,t(x)趋于0,那么相机拍摄的图像I(x)就是全局大气光。如下式子所示。
在实际的远距离成像过程中,d(x)不会无限大,但是可能会是一个相当大的数值,因此全局大气光的估计可以根据下面的式子(全局大气光可能是图像中亮度最大的点):
根据大气散射模型,得到雾图的介质透射率是图像去雾恢复的关键环节。
雾相关的特征
- 暗通道
暗通道先验是在对大量室外无雾图片的观察分析得到的,大部分无雾图像中,至少有一个通道的像素点的亮度会非常低,甚至接近于0,暗通道定义为局部所有通道像素颜色的最小值。
公式5中,表示图像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
被使用了多次,但是它们所指代的是不同的东西:
- 第一个
i
的使用是在最外层的循环中,它用来迭代图像中行数的数量(不包括边界):for i in range(1, num_w-1)
。 - 第二个
i
的使用是在保存图像补丁的文件名中:picname = '%s'%i+'%s'%j+'%s'%k+image_name
。在这里,i
用来表示图像中补丁的行索引。 - 第三个
i
的使用是在将文件路径写入“path_train.txt”文件的循环中:for i in range(len(path_train))
。在这里,i
用作循环计数器,用来迭代文件路径列表。
同样,代码中也有多个变量j
和k
的使用,它们也分别指代不同的东西。
在上述代码中,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
,再加上一个常数来模拟图像受到大气光影响的模糊效果。文章来源:https://www.toymoban.com/news/detail-714093.html
因此,num_t
的值越大,每个图像块生成的模糊图像数量就越多。这样可以增加训练数据集的多样性,提高模型的泛化能力。但同时也会增加数据集的大小和训练时间。文章来源地址https://www.toymoban.com/news/detail-714093.html
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")
到了这里,关于[论文阅读&代码]DehazeNet: An End-to-End System for Single Image Haze Removal的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!