【图像超分】论文复现:新手入门!Pytorch实现SRCNN,数据预处理、模型训练、测试、评估全流程详解,注释详细,简单修改就可以训练你自己的图像数据,有训练好的模型下载地址,随取随用

第一次来请先看这篇文章:【超分辨率(Super-Resolution)】关于【超分辨率重建】专栏的相关说明,包含专栏简介、专栏亮点、适配人群、相关说明、阅读顺序、超分理解、实现流程、研究方向、论文代码数据集汇总等)


前言

论文地址:Image Super-Resolution Using Deep Convolutional Networks

论文精读:【图像超分】论文精读:Image Super-Resolution Using Deep Convolutional Networks(SRCNN)

请配合上述论文精读文章使用,效果更佳!

代码地址:图像超分辨率SRCNN和FSRCNN复现代码,除基本的网络实现外,还有特征图可视化,PSNR曲线图可视化,测试自己的图像数据等

不想理解原理,希望直接跑通然后应用到自己的图像数据的同学,请直接下载上面的代码,有训练好的模型,直接用即可。具体使用方式见代码中的README!有问题来本文评论区留言!

深度学习的模型训练一般遵循以下步骤:

  1. 准备数据集,以及数据预处理
  2. 搭建网络模型
  3. 设置参数并训练
  4. 测试训练好的模型
  5. 用训练好的模型测试自己的数据

下面让我们根据通用流程,一步一步复现SRCNN吧!目标是:输入大小为 h×w 的图像 X,输出为一个 sh×sw 的图像 Y,s 为放大倍数。论文中s取值为2,3,4。

注:放大倍数为1,则图像分辨率不变,只是图像变清晰!如果你只是想将模糊图像通过SRCNN超分后变清晰,那么请将该参数设置为1。

硬件环境:windows11+RTX 2060(比这个高肯定没问题,我这个配置本机跑500个epoch一点问题没有,一会就跑完。但超分自己的图像时,如果图像很大,可能有内存溢出的错误
运行环境:jupyter notebook/pycharm(前者好处是分代码段运行,测试方法,适合学习用;后者适合跑完整项目用
pytorch环境:torch1.9.1+cuda11.1(其他版本没测试过,应该问题不大)

1. 准备数据集和数据预处理

1.1 数据集选择

简单回顾论文中出现过的数据集:

  • 训练集(Train dataset):91 images、ILSVRC 2013 ImageNet
  • 测试集(Test dataset):Set5、Set14、BSD200

由于91 images的数据处理有些繁琐,对于新手不太友好,而ImageNet数据集过大,可能不利于硬件设备不好的同学,所以本文使用折中的BSD200数据集,包含200张图像训练集和200张图像测试集

1.2 数据预处理

根据论文中第4节实验部分开头所述,RGB 颜色模式色调、色度、饱和度三者混在一起难以分开,超分只用于YCbCr的Y通道,即亮度分量。因此,需要将图像数据由RGB模型转换成 YCbCr 颜色模式,Y 是指亮度分量,Cb 表示 RGB 输入信号蓝色部分与 RGB 信号亮度值之间的差异,Cr 表示 RGB 输入信号红色部分与 RGB 信号亮度值之间的差异。

此外,通过观察BSD200数据集中的图像可知,数据集中的图像宽高并不一致,每张图像的大小也千差万别。而SRCNN要求输入图像的长宽一致,所以在训练之前,需要将图像裁剪成宽高一致的图像。采用的策略是按图像中心外扩,resize成300×300的图像。

数据预处理的代码如下:

# 判断某个文件是否是图像
# enswith判断是否以指定的.png,.jpg,.jpeg结尾的字符串
# 可以根据情况扩充图像类型,加入.bmp、.tif等
def is_image_file(filename):
    return any(filename.endswith(extension) for extension in [".png", ".jpg", ".jpeg"])

# 读取图像转为YCbCr模式,得到Y通道
def load_img(filepath):
    img = Image.open(filepath).convert('YCbCr')
    y, _, _ = img.split()
    return y

# 裁剪大小,宽高一致为300
# 如果想训练自己的数据集,请根据情况修改裁剪大小
CROP_SIZE = 300

# 封装数据集,适配后面的torch.utils.data.DataLoader中的dataset,定义成类似形式
# 类参数为图像文件夹路径和放大倍数
# __len__(self) 定义当被len()函数调用时的行为(返回容器中元素的个数)
#__getitem__(self) 定义获取容器中指定元素的行为,相当于self[key],即允许类对象可以有索引操作。
#__iter__(self) 定义当迭代容器中的元素的行为
# 返回输入图像和标签,传入DataLoader的dataset参数
class DatasetFromFolder(Dataset):
    def __init__(self, image_dir, zoom_factor):
        super(DatasetFromFolder, self).__init__()
        self.image_filenames = [join(image_dir, x) for x in listdir(image_dir) if is_image_file(x)] # 图像路径列表
        crop_size = CROP_SIZE - (CROP_SIZE % zoom_factor) # 处理放大倍数,防止用户瞎设置,本例只能设置为2,3,4,大小不变
        # 数据集变换
        # 还有一些其他的变换操作,如归一化等,遇到一个积累一个
        self.input_transform = transforms.Compose([transforms.CenterCrop(crop_size), # 从图片中心裁剪成300*300
                                                   transforms.Resize(
                                                       crop_size // zoom_factor),    # Resize, 输入应该是缩放倍数后的图像,因为先缩小后放大
                                                   transforms.Resize(
                                                       crop_size, interpolation=Image.BICUBIC), # 双三次插值
                                                   transforms.ToTensor()]) # 图像转成tensor
        # label标签,超分不是分类问题,定义成一样的就行
        self.target_transform = transforms.Compose(
            [transforms.CenterCrop(crop_size), transforms.ToTensor()])

    def __getitem__(self, index):
        input = load_img(self.image_filenames[index]) # 输入是图像的Y通道,即亮度通道
        target = input.copy()
        input = self.input_transform(input)
        target = self.target_transform(target)
        return input, target

    def __len__(self):
        return len(self.image_filenames) # 图像个数

至此,数据预处理完成,训练之前就可以传入DataLoader的dataset参数中。

1.3 评估指标PSNR和SSIM

训练过程中,每个epoch会得到一个SRCNN的模型。那么如何评价该模型的性能呢?需要两个评价指标:PSNR和SSIM。由于训练时需要用到,所以需要提前将计算PSNR和SSIM的代码准备好。

1.3.1 PSNR

PSNR(Peak Signal to Noise Ratio)为峰值信噪比,计算公式如下:
M S E = 1 H × W ∑ i = 1 H ∑ j = 1 W ( X ( i , j ) − Y ( i , j ) ) 2 M S E=\frac{1}{H \times W} \sum_{i=1}^{H} \sum_{j=1}^{W}(X(i, j)-Y(i, j))^{2} MSE=H×W1i=1Hj=1W(X(i,j)Y(i,j))2
P S N R = 10 log ⁡ 10 ( ( 2 n − 1 ) 2 M S E ) P S N R=10 \log _{10}\left(\frac{\left(2^{n}-1\right)^{2}}{M S E}\right) PSNR=10log10(MSE(2n1)2)
其中,MSE是均方误差。两个图像对应像素位置的值相减再平方和最后取平均。n是每像素的比特数,一般取256。PSNR的单位是dB,该值越大表示图像的失真越小。

通常认为,PSNR在38以上的时候,人眼就无法区分两幅图片了。

PSNR的计算代码如下:

def psnr(loss):
    return 10 * log10(1 / loss.item())

损失函数使用的就是MSE,所以这么定义。log10是python的math库中的包。

做实验的时候,PSNR一般保留两位小数。

1.3.2 SSIM

SSIM(Structural Similarity,结构相似性)由三个对比模块组成:亮度、对比度、结构。
在这里插入图片描述
(1) 亮度对比函数
图像平均灰度:
μ X = 1 H × M ∑ i = 1 H ∑ j = 1 M X ( i , j ) \mu_{X}=\frac{1}{H \times M} \sum_{i=1}^{H} \sum_{j=1}^{M} X(i, j) μX=H×M1i=1Hj=1MX(i,j)
亮度对比函数:
l ( x , y ) = 2 μ x μ y + C 1 μ x 2 + μ y 2 + C 1 l(x, y)=\frac{2 \mu_{x} \mu_{y}+C_{1}}{\mu_{x}^{2}+\mu_{y}^{2}+C_{1}} l(x,y)=μx2+μy2+C12μxμy+C1

(2) 对比度对比函数
图像的标准差:
σ X = ( 1 H + W − 1 ∑ i = 1 H ∑ j = 1 M ( X ( i , j ) − μ X ) 2 ) 1 2 \sigma_{X}=\left(\frac{1}{H+W-1} \sum_{i=1}^{H} \sum_{j=1}^{M}\left(X(i, j)-\mu_{X}\right)^{2}\right)^{\frac{1}{2}} σX=(H+W11i=1Hj=1M(X(i,j)μX)2)21
对比度对比函数:
c ( x , y ) = 2 σ x σ y + C 2 σ x 2 + σ y 2 + C 2 c(x, y)=\frac{2 \sigma_{x} \sigma_{y}+C_{2}}{\sigma_{x}^{2}+\sigma_{y}^{2}+C_{2}} c(x,y)=σx2+σy2+C22σxσy+C2

(3) 结构对比函数
s ( x , y ) = σ x y + C 3 σ x σ y + C 3 s(x, y)=\frac{\sigma_{x y}+C_{3}}{\sigma_{x} \sigma_{y}+C_{3}} s(x,y)=σxσy+C3σxy+C3

综合上述三个部分,得到 SSIM 计算公式:

SSIM ⁡ ( x , y ) = f ( l ( x , y ) , c ( x , y ) , s ( x , y ) ) = [ l ( x , y ) ] α [ c ( x , y ) ] β [ s ( x , y ) ] γ \begin{aligned} \operatorname{SSIM}(x, y) & =f(l(x, y), c(x, y), s(x, y)) \\ & =[l(x, y)]^{\alpha}[c(x, y)]^{\beta}[s(x, y)]^{\gamma} \end{aligned} SSIM(x,y)=f(l(x,y),c(x,y),s(x,y))=[l(x,y)]α[c(x,y)]β[s(x,y)]γ

其中,𝛼, 𝛽, 𝛾 > 0,用来调整这三个模块的重要性。
SSIM取值为[0, 1], 值越大说明图像失真越小,两幅图像越相似。

做实验的时候,SSIM一般保留四位小数。

由于python中没有封装好的SSIM,我们自己实现一个:

"""
计算ssim函数
"""
# 计算一维的高斯分布向量
def gaussian(window_size, sigma):
    gauss = torch.Tensor(
        [exp(-(x - window_size//2)**2/float(2*sigma**2)) for x in range(window_size)])
    return gauss/gauss.sum()


# 创建高斯核,通过两个一维高斯分布向量进行矩阵乘法得到
# 可以设定channel参数拓展为3通道
def create_window(window_size, channel=1):
    _1D_window = gaussian(window_size, 1.5).unsqueeze(1)
    _2D_window = _1D_window.mm(
        _1D_window.t()).float().unsqueeze(0).unsqueeze(0)
    window = _2D_window.expand(
        channel, 1, window_size, window_size).contiguous()
    return window


# 计算SSIM
# 直接使用SSIM的公式,但是在计算均值时,不是直接求像素平均值,而是采用归一化的高斯核卷积来代替。
# 在计算方差和协方差时用到了公式Var(X)=E[X^2]-E[X]^2, cov(X,Y)=E[XY]-E[X]E[Y].
def ssim(img1, img2, window_size=11, window=None, size_average=True, full=False, val_range=None):
    # Value range can be different from 255. Other common ranges are 1 (sigmoid) and 2 (tanh).
    if val_range is None:
        if torch.max(img1) > 128:
            max_val = 255
        else:
            max_val = 1

        if torch.min(img1) < -0.5:
            min_val = -1
        else:
            min_val = 0
        L = max_val - min_val
    else:
        L = val_range

    padd = 0
    (_, channel, height, width) = img1.size()
    if window is None:
        real_size = min(window_size, height, width)
        window = create_window(real_size, channel=channel).to(img1.device)
        
    # 图像卷积后的均值
    mu1 = F.conv2d(img1, window, padding=padd, groups=channel)
    mu2 = F.conv2d(img2, window, padding=padd, groups=channel)
    
    # 均值平方
    mu1_sq = mu1.pow(2)
    mu2_sq = mu2.pow(2)
    mu1_mu2 = mu1 * mu2
    
    # 方差
    sigma1_sq = F.conv2d(img1 * img1, window, padding=padd,
                         groups=channel) - mu1_sq
    sigma2_sq = F.conv2d(img2 * img2, window, padding=padd,
                         groups=channel) - mu2_sq
    sigma12 = F.conv2d(img1 * img2, window, padding=padd,
                       groups=channel) - mu1_mu2
    
    # SSIM默认常数
    C1 = (0.01 * L) ** 2
    C2 = (0.03 * L) ** 2

    v1 = 2.0 * sigma12 + C2
    v2 = sigma1_sq + sigma2_sq + C2
    cs = torch.mean(v1 / v2)  # contrast sensitivity

    ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2)
    
    # 平均SSIM
    if size_average:
        ret = ssim_map.mean()
    else:
        ret = ssim_map.mean(1).mean(1).mean(1)

    if full:
        return ret, cs
    return ret

# 封装成类
class SSIM(torch.nn.Module):
    def __init__(self, window_size=11, size_average=True, val_range=None):
        super(SSIM, self).__init__()
        self.window_size = window_size
        self.size_average = size_average
        self.val_range = val_range

        # Assume 1 channel for SSIM
        self.channel = 1
        self.window = create_window(window_size)

    def forward(self, img1, img2):
        (_, channel, _, _) = img1.size()

        if channel == self.channel and self.window.dtype == img1.dtype:
            window = self.window
        else:
            window = create_window(self.window_size, channel).to(
                img1.device).type(img1.dtype)
            self.window = window
            self.channel = channel

        return ssim(img1, img2, window=window, window_size=self.window_size, size_average=self.size_average)

由于SSIM和PSNR都需要在训练时用于评估模型的好坏,而不是简单的给两张图像根据像素对应来计算。所以,我们将像素计算改为卷积计算,可以评估两个tensor类型的图像。其中,window_size=11、C1和C2的取值都是SSIM论文的matlab源代码的默认参数。

至此,准备工作全部完成。

2. 定义网络结构

简单回顾SRCNN模型的三层结构:
在这里插入图片描述

  • 输入:处理后的低分辨率图像
  • 第一层:特征提取,卷积核大小9×9
  • 第二层:非线性映射,卷积核大小1×1,第一层到第二层卷积核数量由64变为32
  • 第三层:图像重构,卷积核大小5×5
  • 输出:放大倍数后的高分辨图像

层间结构:Conv(1,64,9) —— ReLU —— Conv(64,32,1) —— ReLU —— Conv(32,1,5)

细节:无padding(有padding影响不大,更容易适应图像大小

SRCNN模型实现如下:

class SRCNN(nn.Module):
    def __init__(self, upscale_factor):
        super(SRCNN, self).__init__()

        self.relu = nn.ReLU()
        self.conv1 = nn.Conv2d(1, 64, kernel_size=9, padding=9//2)
        self.conv2 = nn.Conv2d(64, 32, kernel_size=5, padding=5//2)
        self.conv3 = nn.Conv2d(32, 1, kernel_size=5, padding=5//2)
        self.pixel_shuffle = nn.PixelShuffle(upscale_factor)

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu(x)
        x = self.conv2(x)
        x = self.relu(x)
        x = self.conv3(x)
        x = self.pixel_shuffle(x)
        return x

3. 设置参数并训练模型

3.1 参数设置

# 放大倍数
zoom_factor = 3
nb_epochs = 500
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
torch.manual_seed(0)
torch.cuda.manual_seed(0)
BATCH_SIZE = 4
NUM_WORKERS = 0
trainset = DatasetFromFolder(r"./data/images/train", zoom_factor)
valset = DatasetFromFolder(r"./data/images/train", zoom_factor)
trainloader = DataLoader(dataset=trainset, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)
valloader = DataLoader(dataset=valset, batch_size=BATCH_SIZE,
                       shuffle=False, num_workers=NUM_WORKERS)

以放大倍数3为例,训练500轮,使用GPU训练。由于我的设备比较拉,所以BATCH_SIZE = 4,NUM_WORKERS = 0。如果你的设备比较好,可以修改它们。

根据【数据预处理】定义的DatasetFromFolder函数得到训练集和验证集。数据集放在与代码文件同目录下:./data/images/train。最后,使用DataLoader得到训练前所需的训练集和验证集。

如果要训练自己的数据集,将训练图像放在train文件夹下即可。

3.2 模型训练

SRCNN训练部分代码如下:

model = SRCNN(1).to(device) # 模型,net.to(device)保证用GPU训练,本地训练可以不加,服务器上一定要加
criterion = nn.MSELoss() # 损失函数为MSE
optimizer = optim.Adam(              
    [
        {"params": model.conv1.parameters(), "lr": 0.0001},
        {"params": model.conv2.parameters(), "lr": 0.0001},
        {"params": model.conv3.parameters(), "lr": 0.00001},
    ]
) # Adam优化器,设定三个层学习率为论文中的值,最后一层更小
# 一般的优化器传参就是optim.Adam(lr=0.0001), 因为最后一层学习率不同,所以用字典形式传参。


best_psnr = 0.0
for epoch in range(nb_epochs):
    # 训练
    epoch_loss = 0
    for iteration, batch in enumerate(trainloader): # batchsize为4,则trainloader的长度是50,遍历它
        input, target = batch[0].to(device), batch[1].to(device) # 每个trainloader是由图像张量和标签构成
        optimizer.zero_grad() # 训练经典三步第一步:梯度归零
        out = model(input) # 输入模型得到输出
        loss = criterion(out, target) # 计算损失
        loss.backward() # 训练经典三步第二步:反向传播
        optimizer.step() # 训练经典三步第三步:梯度下降,更新一步参数
        epoch_loss += loss.item() # 累加损失
    print(f"Epoch {epoch}. Training loss: {epoch_loss / len(trainloader)}") # 训练完计算损失
    
    # 验证
    sum_psnr = 0.0
    sum_ssim = 0.0
    with torch.no_grad():
        for batch in valloader:
            input, target = batch[0].to(device), batch[1].to(device)
            out = model(input)
            loss = criterion(out, target)
            pr = psnr(loss)
            sm = ssim(input, out)
            sum_psnr += pr
            sum_ssim += sm
    print(f"Average PSNR: {sum_psnr / len(valloader)} dB.")
    print(f"Average SSIM: {sum_ssim / len(valloader)} ")
    avg_psnr = sum_psnr / len(valloader)
    if avg_psnr >= best_psnr:
        best_psnr = avg_psnr  # 用psnr衡量模型,保存最好的
        torch.save(model, r"best_model_SRCNN_3.pth")

训练500个epoch:
在这里插入图片描述

图像张量在传入神经网络之前的格式为:(batch_size, channel, h, w)。

4. 测试训练好的模型

用测试集测试训练好的模型代码如下:

BATCH_SIZE = 4
model_path = "best_model_SRCNN_3.pth"
testset = DatasetFromFolder(r"./data/images/test", zoom_factor) # 用BSD200中的测试图像测试模型
testloader = DataLoader(dataset=testset, batch_size=BATCH_SIZE,shuffle=False, num_workers=NUM_WORKERS)
sum_psnr = 0.0
sum_ssim = 0.0
model = torch.load(model_path).to(device) # 载入模型
criterion = nn.MSELoss()
with torch.no_grad():
    for batch in testloader:
        input, target = batch[0].to(device), batch[1].to(device)
        out = model(input)
        loss = criterion(out, target)
        pr = psnr(loss)
        sm = ssim(input, out)
        sum_psnr += pr
        sum_ssim += sm
print(f"Test Average PSNR: {sum_psnr / len(testloader)} dB")
print(f"Test Average SSIM: {sum_ssim / len(testloader)} ")

取全部测试集计算的总平均PSNR和SSIM,结果为:
在这里插入图片描述

5. 用训练好的SRCNN模型超分自己的图像数据

代码如下:

# 参数设置
zoom_factor = 3 # 放大倍数
model = "best_model_SRCNN_3.pth" # 模型路径
image = "./SEM_image_test/6.jpg" # 自己的图像路径
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 读取图片
img = Image.open(image).convert('YCbCr') # PIL类型,转成YCbCr
img = img.resize((int(img.size[0] * zoom_factor), int(img.size[1] * zoom_factor)), Image.BICUBIC) # 双三次插值放大
y, cb, cr = img.split() # 划分Y,cb,cr三个通道
img_to_tensor = transforms.ToTensor() # 获得一个ToTensor对象

# view中-1的含义是该维度不明,让其自动计算, input是由torch.Tensor([1,h,w])变成torch.Tensor([1,1,h,w])
# 图像Tensor格式:(batch_size, channel, h, w)
input = img_to_tensor(y).view(1, -1, y.size[1], y.size[0]).to(device) # 将y通道变换成网络输入的tensor格式

# 输出图片
model = torch.load(model).to(device) # 载入模型
out = model(input).cpu() # 模型输出
out_img_y = out[0].detach().numpy() # 返回新的三维张量并转成numpy
out_img_y *= 255.0 
out_img_y = out_img_y.clip(0, 255) # 取0-255内的值
out_img_y = Image.fromarray(np.uint8(out_img_y[0]), mode='L') # numpy转成PIL
out_img = Image.merge('YCbCr', [out_img_y, cb, cr]).convert('RGB') # 合并三个通道变成RGB格式

# 绘图显示,上面的操作都是为了plt显示图像
fig, ax = plt.subplots(1, 2, figsize=(22, 10))
ax[0].imshow(img)
ax[0].set_title("原图")
ax[1].imshow(out_img)
ax[1].set_title("SRCNN恢复结果")

# 改dpi大小得到保存的图像清晰度,越大图片文件越大,质量越高;有的办法是保存成pdf,pdf放大不失真
plt.savefig(r"./SEM_image_test/8_result.jpg",dpi=1000,bbox_inches = 'tight')
plt.show()

显示结果为:
在这里插入图片描述
可以看到图像清晰了不少。

6. 其他补充

6.1 特征图可视化

本小节复现论文中的图6,代码如下:

import cv2

model = SRCNN(1) # 模型
image_path = "./SEM_image_test/butterfly_GT.jpg" # 输入
gray_image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
out_paths = ['./SEM_image_test/conv1_feature_maps.jpg', './SEM_image_test/conv2_feature_maps.jpg','./SEM_image_test/conv3_feature_maps.jpg' ]

gray_image_tensor = transforms.ToTensor()(gray_image).unsqueeze(0)

with torch.no_grad():
	conv1_output = model.conv1(gray_image_tensor) # 1,64,256,256 torch.Tensor
conv1_feature = conv1_output[0].detach().numpy() # 64,256,256 numpy
conv1_feature_maps = np.hstack([conv1_feature[0],conv1_feature[1],conv1_feature[2],conv1_feature[3]])
cv2.imshow('Conv1 Output', conv1_feature_maps)
cv2.imwrite(out_paths[0], conv1_feature_maps * 255)

with torch.no_grad():
    conv2_output = model.conv2(conv1_output) # 64,32
conv2_feature = conv2_output[0].detach().numpy() 
conv2_feature_maps = np.hstack([conv2_feature[0],conv2_feature[1],conv2_feature[2],conv2_feature[3]])
cv2.imshow('Conv2 Output', conv2_feature_maps)
cv2.imwrite(out_paths[1], conv2_feature_maps * 255)

    
cv2.waitKey(0)
cv2.destroyAllWindows()

以灰度输入,注意保证图像经过每一层的Tensor大小正确,保存前四个特征图,观察结果。

第一层的特征图:1,64,256,256
请添加图片描述

第二层特征图:1,32,256,256
请添加图片描述

横向来看,从64个特征图中可视化前四个。纵向来看,经过第一层结构不同,第二层强度不同。

6.2 训练过程中PSNR折线图可视化

用一个列表存储训练过程中的平均PSNR:

each_psnr = []
each_psnr.append(avg_psnr) # 保存一轮的psnr,画图用

横轴为epoch,训练138轮停止。epoch和psnr的关系为:
在这里插入图片描述
代码如下:

epochs = range(0,138)

# plot画图,设置颜色和图例,legend设置图例样式,右下,字体大小
plt.plot(epochs,each_psnr,color='red',label='SRCNN(trained on BSD200)')  
plt.legend(loc = 'lower right',prop={'family' : 'Times New Roman', 'size'   : 12})

# 横纵轴文字内容以及字体样式
plt.xlabel("epoch",fontproperties='Times New Roman',size = 12)
plt.ylabel("Average test PSNR (dB)",fontproperties='Times New Roman',size=12)

# 横纵轴刻度以及字体样式
plt.xlim(0,138)
plt.ylim(23,26)
plt.yticks(fontproperties='Times New Roman', size=12)
plt.xticks(fontproperties='Times New Roman', size=12)

# 网格样式
plt.grid(ls = '--')

plt.show()

6.3 以像素计算自己的图像输入输出前后的PSNR和SSIM

代码如下:

import cv2

def psnr1(original, compressed):
    mse = np.mean((original - compressed) ** 2)
    if mse == 0:
        return 100  # PSNR 为无穷大
    max_pixel = 255.0
    psnr = 10 * np.log10((max_pixel ** 2) / mse)
    return psnr

from skimage.metrics import structural_similarity as ssim

def ssim1(original, compressed):
    grayA = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY)
    grayB = cv2.cvtColor(compressed, cv2.COLOR_BGR2GRAY)
    
    score, _ = ssim(grayA, grayB, full=True)
    return score

out_img = np.array(out_img)
img = np.array(img)


print("PSNR:", psnr1(img,out_img))
print("SSIM:", ssim1(img,out_img))

MSE按超分前后两张图像的每个像素计算,进而计算PSNR。SSIM使用skimage库中的结构相似性来计算。将前面的显微图像传入,得到的值为:在这里插入图片描述

至此,SRCNN复现完成。网络还是有些问题,我们后续修改。重点在于深度学习流程的学习,以及快速入门。

如果本文对你有所帮助,点个赞吧!

  • 48
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 35
    评论
评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十小大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值