Pytorch搭建卷积自编码对图像去噪

本篇文章介绍基于卷积层的自编码去噪网络。利用卷积层进行图像的编码和解码,是因为卷积操作在提取图像的信息上有较好的效果,而且可以对图像中隐藏的空间信息等内容进行较好的提取。该网络可用于图像去噪、分割等。

在基于卷积的自编码图像去噪网络中,其作用过程如下图所示。在网络中输入图像带有噪声,而输出图像则为去噪的原始图像,在编码器阶段,会经过多个卷积、池化、激活层和BatchNorm层等操作,逐渐降低每个特征映射的尺寸,如将每个特征映射编码的尺寸降低到24×24,即图像的大小缩为原来的1/16;而特征映射编码的解码阶段,则可以通过多个转置卷积、激活层和BatchNorm层等操作,逐渐将其解码为原始图像的大小并且包含3个通道的图像,即96×96的RGB图像。

 1.去噪自编码网络数据准备

先简单介绍一下训练网络使用到的图像数据集——STL10,该数据集可以通过torchvision.datasets模块中的STL10()函数进行下载,该数据集共包含三种类型数据,分别是带有标签的训练集和验证集,分别包含5000张和8000张图像,共有10类数据,还有一个类型包含10万张的无标签图像,均是96×96的RGB图像,可用于无监督学习。虽然使用STL10()函数可直接下载该数据集,但数据大小仅约2.5GB,且下载的数据是二进制数据,故建议直接到数据网址下载,并保存到指定的文件夹。
为了节省时间和增加模型的训练速度,在搭建的卷积自编码网络中只使用包含5000张图像的训练集,其中使用4000张图像用来训练模型,剩余1000张图像作为模型的验证集。
在定义网络之前,首先准备数据,并对数据进行预处理。定义一个从.bin文件中读取数据的函数,并且将读取的数据进行预处理,便于后续的使用,程序如下所示:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from skimage.util import random_noise
from skimage.metrics import peak_signal_noise_ratio as compare_psnr
import torch
from torch import nn
import torch.nn.functional as F
import torch.utils.data as Data
import torch.optim as optim
from torchvision import transforms
from torchvision.datasets import STL10

def read_image(file_path):
    with open(file_path,"rb") as f:
        data=np.fromfile(f,dtype=np.uint8)
        #图像[数量、通道、宽、高]
        images=np.reshape(data,(-1,3,96,96))
        #图像转化为RGB格式
        images=np.transpose(images,(0,3,2,1))
        #输出的图像范围取值在0-1之间
        return  images/255.0
file_path=r"E:\PythonWorkSpace\pytorch_project\pytorch_demo\Dataset\stl10\train_X.bin"
images=read_image(file_path)

在上面读取图像数据的函数read_image()中,只需要输入数据的路径即可,在读取数据后会将图像转化为[数量,通道,宽,高]的形式。为了方便图像可视化使用np.transpose()函数将图像转化为RGB格式,最后输出的像素值是在0~1之间的四维数组,第一维表示图像的数量,后面的三维表示图像的RGB像素值。

下面定义一个为图像数据添加高斯噪声的函数,为每一张图像添加随机噪声,并可视化原始图像与添加噪声之后的图像

def gaussian_noise(images,sigma):
    sigma2=sigma**2/(255**2)#噪声方差
    images_noisy=np.zeros_like(images)
    for i in range(images.shape[0]):
        image=images[i]
        #使用skimage中的random_noise添加噪声
        noise_im=random_noise(image,mode="gaussian",var=sigma2,clip=True)
        images_noisy[i]=noise_im
    return images_noisy
images_noise=gaussian_noise(images,30)
print("images_noise:",images_noise.min(),"~",images_noise.max())
#不带噪声的图像
plt.figure(figsize=(6,6))
for i in np.arange(36):
    plt.subplot(6,6,i+1)
    plt.imshow(images[i,...])
    plt.axis("off")
plt.show()
#带噪声的数据
plt.figure(figsize=(6,6))
for i in np.arange(36):
    plt.subplot(6,6,i+1)
    plt.imshow(images_noise[i,...])
    plt.axis("off")
plt.show()

在gaussian_noise()函数中,通过random_noise()函数为每张图像添加指定方差为sigma2的噪声,并且将带噪图像的像素值范围处理在0~1之间,使用gaussian_noise()函数后,可得到带有噪声的数据集images_noise。并且从输出可知,所有像素值的最大值为1,最小值为0。 

原始图像

带噪声的图像

比较上面的两幅图,可见带噪声的图像更加模糊,通过卷积自编码网络降噪器的目的是要去掉图像中的噪声,获取“干净”的图像。接下来将图像数据集切分为训练集和验证集,并处理为络可用的数据形式

#数据准备为PyTorch可用的形式,转化为[样本,通道,高,宽]的数据形式
data_Y=np.transpose(images,(0,3,2,1))
data_X=np.transpose(images_noise,(0,3,2,1))
#将数据切分成训练集与验证集
X_train,X_val,y_train,y_val=train_test_split(data_X,data_Y,test_size=0.2,random_state=123)
#将图像数据转化为向量
X_train=torch.tensor(X_train,dtype=torch.float32)
y_train=torch.tensor(y_train,dtype=torch.float32)
X_val=torch.tensor(X_val,dtype=torch.float32)
y_val=torch.tensor(y_val,dtype=torch.float32)
#将X和Y转化为数据集合
train_data=Data.TensorDataset(X_train,y_train)
val_data=Data.TensorDataset(X_val,y_val)

上述程序首先将两个数据集使用np.transpose()函数转化为[样本,通道,高,宽]的数据形式,然后使用train_test_split()函数将80%的数据用于训练集,20%的数据用于验证集,再使用Data.TensorDataset()函数将数据集中的X和Y数据进行处理,放置到统一的张量中。
接下来使用Data.DataLoader()函数将训练数据集和验证数据集处理为数据加载器train_loader和val_loader,并且每个batch包含32张图像。

train_loader=Data.DataLoader(
    dataset=train_data,
    batch_size=32,
    shuffle=True,
    num_workers=0
)
val_loader=Data.DataLoader(
    dataset=val_data,
    batch_size=32,
    shuffle=True,
    num_workers=0
)

2.基于转置卷积解码的网络搭建

在数据预处理完成之后,开始搭建一个卷积自编码网络

class DenoiseAutoEncoder(nn.Module):
    def __init__(self):
        super(DenoiseAutoEncoder, self).__init__()
        self.Encoder=nn.Sequential(
            nn.Conv2d(in_channels=3,out_channels=64,
                      kernel_size=3,stride=1,padding=1),#[,64,96,96]
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.Conv2d(64,64,3,1,1),#[,64,96,96]
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.Conv2d(64,64,3,1,1),#[,64,96,96]
            nn.ReLU(),
            nn.MaxPool2d(2,2),#[,64,48,48]
            nn.BatchNorm2d(64),
            nn.Conv2d(64,128,3,1,1),#[,128,48,48]
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.Conv2d(128,128,3,1,1),#[,128,48,48]
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.Conv2d(128,256,3,1,1),#[,256,48,48]
            nn.ReLU(),
            nn.MaxPool2d(2,2),#[,256,24,24]
            nn.BatchNorm2d(256)
        )
        self.Decoder=nn.Sequential(
            nn.ConvTranspose2d(256,128,3,1,1),#[,256,24,24]
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.ConvTranspose2d(128,128,3,2,1,1),#[,128,48,48]
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.ConvTranspose2d(128,64,3,1,1),#[,64,48,48]
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.ConvTranspose2d(64,32,3,1,1),#[,32,48,48]
            nn.ReLU(),
            nn.BatchNorm2d(32),
            nn.ConvTranspose2d(32,32,3,1,1),#[,32,48,48]
            nn.ConvTranspose2d(32,16,3,2,1,1),#[,16,96,96]
            nn.ReLU(),
            nn.BatchNorm2d(16),
            nn.ConvTranspose2d(16,3,3,1,1),#[,3,96,96]
            nn.Sigmoid()
        )
    def forward(self,x):
        encoder=self.Encoder(x)
        decoder=self.Decoder(encoder)
        return encoder,decoder

在上述定义的网络类中,主要包含自编码模块Encoder和解码模块Decoder,在Encoder模块中,卷积核均为3×3,并且激活函数为ReLU,池化层使用最大值池化,经过多个卷积、池化和BatchNorm等操作后,图像的尺寸从96×96缩小为24×24,并且通道数会逐渐从3增加到256。但在Decoder模块中,做相反的操作,通过nn.ConvTranspose2d()函数对特征映射进行转置卷积,从而对特征映射进行放大,激活函数除最后一层使用Sigmoid外,其余层则使用ReLU激活函数。经过Decoder后,特征映射会逐渐从24×24放大到96×96,并且通道数也会从256逐渐过渡到3,对应着原始的RGB图像。在网络的forward()函数中会分别输出encoder和decoder的结果。

 3.基于转置卷积解码的网络训练与预测

在网络定义好之后,需要使用训练数据集来优化定义好的自编码网络,以便得到一个自编码降噪器,优化器使用torch.optim.Adam(),损失函数使用均方根nn.MSELoss()函数,并可视化训练过程中损失大小的变化过程,程序如下所示:

model=DenoiseAutoEncoder()
optimizer=torch.optim.Adam(model.parameters(),lr=0.0003)#定义优化器
loss_func=nn.MSELoss()#定义损失函数
history=hl.History()
canvas=hl.Canvas()
train_num=0
val_num=0
#对模型进行迭代训练
for epoch in range(20):
    train_loss_epoch=0
    val_loss_epoch=0
    #对训练数据的加载器进行迭代计算
    for step,(b_x,b_y) in enumerate(train_loader):
        model.train()
        #使用每个batch进行模型训练
        _,output=model(b_x)#CNN在每个Batch上的输出
        loss=loss_func(output,b_y)#均方根误差
        optimizer.zero_grad()#每个迭代步的梯度初始化为0
        loss.backward()
        optimizer.step()
        train_loss_epoch += loss.item() * b_x.size(0)
        train_num=train_num + b_x.size(0)
    #使用每个batch进行模型验证
    for step,(b_x,b_y) in enumerate(val_loader):
        model.eval()
        _,output=model(b_x)
        loss=loss_func(output,b_y)
        val_loss_epoch += loss.item() * b_x.size(0)
        val_num=val_num + b_x.size(0)
    #计算第一个epoch的损失
    train_loss=train_loss_epoch / train_num
    val_loss=val_loss_epoch /val_num
    history.log(epoch,train_loss=train_loss,val_loss=val_loss)
    with canvas:
        canvas.draw_plot([history["train_loss"],history["val_loss"]])

运行上述程序得到下图所示的网络损失函数的变化过程。图示说明在训练集上和测试集上的损失大小均得到了收敛,而且损失函数收敛到一个很小的数值。

猜你喜欢

转载自blog.csdn.net/qq_42681787/article/details/129875226