简介:超分辨率图像重建(Super-Resolution)是计算机视觉中的经典任务,目标是从低分辨率输入中恢复高频细节,在视频监控、医学影像、老照片修复等场景中具有广泛需求。传统插值方法虽快但效果模糊,而基于深度学习的卷积神经网络(CNN)通过端到端学习低分辨率到高分辨率的映射,在PSNR、SSIM等客观指标上表现稳定,成为工程落地的优选方案。本文以Pytorch框架为依托,系统拆解超分项目的完整Pipeline:从DIV2K数据集的组织与预处理、随机裁剪与数据增强,到SRCNN核心网络结构的设计、亚像素卷积上采样技巧,再到L1损失函数、学习率策略及训练调参经验,并涵盖pth模型权重的加载、推理验证与ONNX导出部署等关键环节。通过循序渐进的工程实践讲解,帮助初学者快速跑通一个可复现的超分项目,同时为开发者提供从模型训练到实际部署的完整参考。 最近把手上这个超分辨率图像重建项目重新整理了一遍,源码和训练好的pth模型一起打包,放到某个公开分享里。基于Pytorch框架 + CNN卷积网络这套方案,整个链路从数据准备、模型训练到推理验证都是完整的,不是那种只有半截代码的演示项目。这篇文章把项目的设计思路、核心网络结构、训练细节和避坑经验完整写出来,适合三种人看:一是刚接触图像超分、想跑通一个完整Pipeline的初学者,二是想用Pytorch + CNN做实际项目、但不知道怎么组织代码和调参的开发者,三是手头有pth模型、想快速做推理复现或者二次开发的朋友。

我尽量把“为什么这么做”也讲清楚。网上很多超分教程只给网络结构和训练脚本,但数据怎么切、学习率怎么调、加载预训练权重时有哪些坑,这些才是实战里真正卡人的地方。

1. 项目整体设计与方案选型

1.1 超分辨率重建的任务定义与主流方案对比

超分辨率图像重建,英文叫Super-Resolution,简称SR,目标很直接:给一张低分辨率图,生成一张对应的高分辨率图。注意这里不是简单放大,而是要把放大后缺失的高频细节尽可能恢复出来。比如一张96x96的模糊人脸图,重建后要变成384x384且五官边缘清晰,这在视频监控、医学影像、卫星遥感、老照片修复里都有实际需求。

这个任务有几个主流技术方向,先摆出来对比一下:

  • 传统插值法:双线性插值、双三次插值(Bicubic)。速度快,但结果天然模糊,因为本质上只是对现有像素做平滑估计,没有引入任何先验信息。
  • 重建模型:比如稀疏表达、字典学习这类方法,比插值好一些,但结构复杂,恢复能力有限。
  • 深度学习:这又分CNN系和生成对抗网络(GAN)系。GAN系(比如SRGAN、ESRGAN)用感知损失和对抗训练,生成的纹理更逼真,但训练不稳定,且容易产生伪纹理;CNN系直接用卷积网络学习低分辨率到高分辨率的映射,结构简单、训练稳定,虽然极端纹理恢复不如GAN,但PSNR和SSIM这类客观指标通常更高。

这个项目选的是CNN路线,原因很实际:稳定性优先,训练成本低,而且很多落地场景看重的是客观画质而非“看起来像真的”。如果连CNN都跑不顺,直接上GAN会非常痛苦。

1.2 为什么选CNN而不是Transformer

近几年Transformer在视觉领域很火,但超分这种密集预测任务,CNN依然是性价比很高的选择。这里有几个关键理由:

  • 超分任务的核心是局部纹理建模。图像放大时,一个像素的新值主要由周围像素决定,这正好是卷积操作天然擅长的——局部感受野、权值共享、平移不变性。Transformer虽然能建模长距离依赖,但超分更依赖近距离的上下文,长距离信息反而没那么关键。
  • CNN的参数量和计算量对硬件更友好。在相同精度下,CNN模型可以做到很小,跑得很快,适合部署。即便用GPU训练,SEU的Transformer模型(如SwinIR)显存占用也会明显高于CNN模型。
  • Pytorch对CNN的支持极其成熟。卷积、池化、批归一化、亚像素卷积这些算子都有现成API,调试也方便。做超分项目,CNN是入门和落地最稳的路线。

做SR这几年,我的体会是:深度学习的选型别追着潮流走,要看数据量和任务本身。几万张图像的数据规模,CNN完全够用。

1.3 为什么框架选Pytorch

这个项目用Pytorch,而非TensorFlow或PaddlePaddle,核心原因有三点:

  • 动态计算图让调试极其方便。训练过程中可以随时打断点查看中间特征图、梯度值,改起来也灵活。超分任务的网络结构经常要调(比如加个残差连接、换一下上采样方式),动态图改起来基本是零成本。
  • 学术界和工业界都在用Pytorch。SR方向的主流论文,比如EDSR、RCAN,官方实现几乎都是Pytorch。这意味着我们可以直接参考前人代码,不用自己从头搭。迁移某个卷积模块、换个上采样层的实现,网上都有现成资料。
  • 模型导出和部署生态完整。pth模型可以直接转ONNX,再转TensorRT或者CoreML,端侧部署的路都是通的。

2. 数据集管理与预处理:超分训练的地基

2.1 公开数据集怎么选、怎么组织

训练超分模型离不开标准数据集。这个项目主要使用DIV2K(DIVerse 2K)作为训练集,它有800张2K分辨率的高清图,内容覆盖自然景观、城市建筑、人物、动物,非常适合超分模型学习。验证集用Set5、Set14、BSD100、Urban100这四个经典基准,测试时对结果做客观评价。

数据集目录我按下面的方式组织:

dataset/
├── DIV2K/
│   ├── train/
│   │   ├── HR/          # 高分辨率原图
│   │   └── LR/          # 低分辨率图
│   └── valid/
│       ├── HR/
│       └── LR/
├── Set5/
├── Set14/
└── BSD100/

这里有个关键点:低分辨率图必须统一生成方式。如果训练和测试时低分辨率图的生成方法不一致,模型性能会大打折扣。因为这个项目做的是X4超分(即放大4倍),所以我用Pytorch的 F.interpolate 统一做Bicubic下采样,生成对应的低分辨率图。

注意:低分辨率图的生成过程要固化为一个函数,放在单独的 utils.py 里,训练、验证、推理时都调用同一个函数。项目里很多人效果差,查到最后往往是低分辨率图的尺寸计算不统一,上下采样倍数对不上。

2.2 训练数据切块与在线增强

DIV2K的800张图都是2K分辨率,直接整图训练不现实——显存扛不住,也没必要。我采用随机裁剪的方式,每次从HR原图中裁剪一块96x96的图块,对应的LR图块是24x24(96除以4)。每轮迭代从全部数据中随机采样,相当于做了天然的数据增强。

代码大概是这样:

import random
import torch
from torch.utils.data import Dataset
import torch.nn.functional as F

class SRDataset(Dataset):
    def __init__(self, hr_dir, lr_dir, scale=4, patch_size=96):
        self.hr_paths = sorted(glob(hr_dir + '/*.png'))
        self.lr_paths = sorted(glob(lr_dir + '/*.png'))
        self.scale = scale
        self.patch_size = patch_size

    def __len__(self):
        return len(self.hr_paths)

    def __getitem__(self, idx):
        hr = load_image(self.hr_paths[idx])    # [C,H,W],0~1
        lr = load_image(self.lr_paths[idx])    # [C,H/4,W/4]

        # 随机裁剪
        h, w = hr.shape[-2:]
        top = random.randint(0, h - self.patch_size)
        left = random.randint(0, w - self.patch_size)
        hr_patch = hr[:, top:top + self.patch_size, left:left + self.patch_size]
        lr_patch = lr[:, top // self.scale:(top + self.patch_size) // self.scale,
                      left // self.scale:(left + self.patch_size) // self.scale]

        # 随机翻转和旋转
        if random.random() < 0.5:
            hr_patch = torch.flip(hr_patch, dims=[2])
            lr_patch = torch.flip(lr_patch, dims=[2])
        if random.random() < 0.5:
            hr_patch = torch.flip(hr_patch, dims=[1])
            lr_patch = torch.flip(lr_patch, dims=[1])
        k = random.randint(0, 3)
        hr_patch = torch.rot90(hr_patch, k, dims=[1, 2])
        lr_patch = torch.rot90(lr_patch, k, dims=[1, 2])

        return lr_patch, hr_patch

切块大小需要根据放大倍数调整。X4超分时HR patch是96x96,LR patch是24x24;如果做X2,建议直接用48x48的HR patch,保证LR patch经过多次下采样后尺寸不为0。

2.3 标准化与图像读取的细节

超分任务有个惯例:图像归一化到0~1之间即可,不需要像分类任务那样做均值方差标准化。原因很简单,超分模型输出的是图像像素值,输出范围要能直接映射回RGB空间,如果做了复杂的标准化,输出还得反归一化,反而多一步且容易出问题。

图像读取我用 PIL.Image.open ,转成RGB,再转成Tensor。注意训练时要把图像数值归一化到0~1, torchvision.transforms.ToTensor() 会自动做这件事,不需要额外操作。但推理时如果加载的图像是0~255的uint8,需要手动除以255。

实操心得:加载图像最好统一用PIL,不要混用OpenCV的BGR和PIL的RGB,否则颜色通道顺序错误会导致训练出的模型输出颜色异常。这个问题我踩过两次,每次都是重新检查数据流程才发现。

3. 核心网络结构与代码实现

3.1 SRCNN网络结构拆解

这个项目的基准网络是SRCNN(Super-Resolution Convolutional Neural Network),它是超分领域深度学习开山之作。结构非常简洁,仅三层卷积:

  • 第一层:9x9卷积,64个特征图,负责从低分辨率图像中提取特征块。这步相当于做“特征提取”。
  • 第二层:1x1卷积,32个特征图,负责把特征映射到非线性空间。这步相当于“非线性映射”。
  • 第三层:5x5卷积,3个特征图(对应RGB三通道),负责重建高分辨率图像。这步相当于“重建”。

如果直接用SRCNN处理X4放大,有两种做法:一是先把低分辨率图用Bicubic插值放大4倍,再送入网络学习残差;二是在网络内部做上采样。经典SRCNN用的是前者,简单但前期插值会带入部分模糊信息。我在这个项目里做了一点优化:在SRCNN第三层卷积之前加入一个亚像素卷积(PixelShuffle)层,将特征图直接从低分辨率空间上采样到高分辨率空间,省去外部插值步骤。

网络实现如下:

import torch.nn as nn

class SRCNN(nn.Module):
    def __init__(self, num_channels=3, scale=4):
        super(SRCNN, self).__init__()
        self.conv1 = nn.Conv2d(num_channels, 64, kernel_size=9, padding=4)
        self.relu1 = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(64, 32, kernel_size=1, padding=0)
        self.relu2 = nn.ReLU(inplace=True)

        # 亚像素卷积上采样,特征维度从32变为32 // scale^2
        self.conv3 = nn.Conv2d(32, 32 * (scale ** 2), kernel_size=3, padding=1)
        self.pixel_shuffle = nn.PixelShuffle(scale)
        self.conv4 = nn.Conv2d(32, num_channels, kernel_size=5, padding=2)

    def forward(self, x):
        x = self.relu1(self.conv1(x))
        x = self.relu2(self.conv2(x))
        x = self.conv4(self.pixel_shuffle(self.conv3(x)))
        return x

这个结构比原版SRCNN多了亚像素卷积,但参数量增加不多,效果提升很明显。使用亚像素卷积的好处在于:上采样过程是可学习的,网络自己决定如何生成高频细节,而不是依赖固定的插值核。

3.2 损失函数与评价指标

超分训练的损失函数有几种选择,每个都有适用场景:

  • L1损失(MAE):绝对值误差,训练稳定,收敛后PSNR较高。这是本项目采用的主损失。
  • L2损失(MSE):均方误差,数学上好优化,但对异常值敏感,容易导致重建图像过于平滑。早期超分论文多用MSE,后来发现L1效果往往更好。
  • 感知损失(Perceptual Loss):通过预训练的VGG网络提取特征计算损失,更关注视觉感知。但需要额外加载VGG权重,训练开销大。项目里作为可选项保留。

评价指标方面,超分领域最常用的是PSNR和SSIM:

  • PSNR(峰值信噪比):计算重建图与真实图之间的像素误差,数值越高越好。X4超分在Set5上,SRCNN大约能到30~31dB,好的模型能到33dB以上。
  • SSIM(结构相似性):衡量两图在亮度、对比度、结构上的相似度,范围0~1,越接近1越好。

训练时,损失值和PSNR要同时看。如果损失在下降但PSNR没提升,可能是数据加载有bug;如果PSNR在涨但SSIM不涨,可能是高频纹理过度锐化。

3.3 模型选型的扩展方向

SRCNN只是起点。如果你的硬件资源允许,可以往这几个方向升级:

  • FSRCNN:在SRCNN基础上把上采样放在网络末端,速度更快。
  • ESPCN:用亚像素卷积作为上采样方式,本项目已经用到这个技巧。
  • EDSR、RCAN:深层CNN模型,用了残差学习和注意力机制,效果更好,但训练周期更长。
  • 轻量级模型:如FDLR、LapSRN,适合移动端部署。

我的建议是:先跑通SRCNN,理解整个训练推理流程,再逐步升级网络结构。不要一上来就是几百层的网络,出了问题你根本不知道是数据问题、训练问题还是网络结构问题。

4. 训练过程与参数调优

4.1 训练流程与学习率策略

超分项目的完整训练流程大致如下:

  1. 初始化模型,加载训练集和验证集。
  2. 前向传播,计算L1损失。
  3. 反向传播,更新参数。
  4. 每隔若干个epoch在验证集上计算PSNR和SSIM。
  5. 保存效果最好的模型权重。

训练循环核心代码:

import torch.optim as optim
from torch.utils.data import DataLoader

model = SRCNN(scale=4).cuda()
optimizer = optim.Adam(model.parameters(), lr=1e-4)
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[30, 60, 90], gamma=0.5)
criterion = nn.L1Loss()

best_psnr = 0.0
for epoch in range(1, 101):
    model.train()
    total_loss = 0.0
    for lr, hr in train_loader:
        lr, hr = lr.cuda(), hr.cuda()
        sr = model(lr)
        loss = criterion(sr, hr)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    scheduler.step()

    # 验证
    psnr = evaluate(model, valid_loader)
    print(f"Epoch {epoch}: loss={total_loss / len(train_loader):.4f}, PSNR={psnr:.2f}dB")

    if psnr > best_psnr:
        best_psnr = psnr
        torch.save(model.state_dict(), "best_srcnn_x4.pth")

学习率策略我用的是 MultiStepLR ,分别在30、60、90个epoch时把学习率减半。初始学习率设为1e-4,这个值对SRCNN这类小网络来说比较稳。如果你用的数据集大、网络深,可以试试初始1e-3加余弦退火。

4.2 训练环境与参数配置参考

我给出一份可以直接参考的训练环境配置:

配置项 参考值
GPU NVIDIA RTX 3060 12GB及以上
训练框架 Pytorch 2.x + CUDA 11.8
批大小 16 ~ 32(取决于显存)
图像块大小 96x96 (HR)
优化器 Adam (beta1=0.9, beta2=0.999)
初始学习率 1e-4
训练轮数 100 epoch
数据增强 随机裁剪 + 水平/垂直翻转 + 90度旋转

批大小的选择要结合显存。12GB显存跑SRCNN这种轻量网络,批大小32完全没问题;如果换成EDSR那种深层网络,批大小可能得降到8甚至4。显存不足时优先减小批大小,别动图像块尺寸,因为图像块太小会影响超分效果。

4.3 训练速度与收敛速度的调优心得

训练过程中有几个关键观察点:

  • 前10个epoch损失会快速下降,这是正常的,说明模型在学习基本映射。之后下降会变慢,需要耐心。
  • 如果第50个epoch时PSNR还没超过28dB(X4超分,Set5验证集),大概率是数据有问题或者学习率设置不当。
  • 用 torch.cuda.amp 混合精度训练可以缩短训练时间,但要注意梯度缩放,避免精度溢出。

我实际测试过,在RTX 3060上,用SRCNN训练100个epoch大约需要3~4小时。如果换更深网络,建议至少准备8小时以上的训练时间预算。实时监控训练日志非常关键,可以用TensorBoard记录loss和PSNR曲线。

实操心得:训练时候别只盯着loss,PSNR曲线才是真正反映效果的指标。loss波动正常,但PSNR持续下降或者震荡剧烈,就要停下来检查学习率是不是太大了,或者数据加载是否有误。

5. 源码结构与pth模型文件使用

5.1 项目的目录结构说明

打包的zip解压之后,目录结构如下:

super_resolution_project/
├── data/
│   └── dataset.py          # 数据集定义与加载
├── models/
│   └── srcnn.py            # SRCNN网络定义
├── utils/
│   ├── metrics.py          # PSNR/SSIM计算
│   └── img_utils.py        # 图像读取、保存、下采样工具
├── train.py                # 训练脚本
├── inference.py            # 单张图像超分推理脚本
├── eval.py                 # 验证脚本,计算PSNR、SSIM
├── weights/
│   ├── srcnn_x4_best.pth   # 训练好的模型权重
│   └── README.md           # 权重说明文档
├── requirements.txt
└── README.md

这个结构是超分项目最常见的划分方式,数据、模型、工具、脚本分开,方便维护。如果你拿到的是别人打包的源码,先看README和requirements.txt,确认环境依赖,再跑inference.py。

5.2 requirements.txt与依赖安装

requirements.txt内容如下:

torch>=1.13.0
torchvision>=0.14.0
numpy>=1.21.0
Pillow>=9.0.0
opencv-python==4.8.0.74
scikit-image>=0.19.0
tqdm>=4.64.0

安装命令:

pip install -r requirements.txt

注意Pytorch的安装需要匹配你的CUDA版本。如果设备上有NVIDIA GPU,建议去官网生成对应的安装命令,例如:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

如果没有GPU,直接用CPU版本也能跑推理,只是速度慢很多。

5.3 推理脚本:如何加载pth模型

pth文件是Pytorch保存模型权重的标准格式。常见有两种保存方式:一是只保存 state_dict (权重字典),二是保存完整的模型对象。这个项目使用的是 state_dict 方式,加载时需要先实例化网络,再加载权重。

推理脚本核心代码:

import torch
import torch.nn.functional as F
from PIL import Image
import numpy as np
from models.srcnn import SRCNN

def load_model(weight_path):
    model = SRCNN(scale=4)
    # PyTorch 2.6+ 加载时建议指定 weights_only=True
    state_dict = torch.load(weight_path, map_location='cpu', weights_only=True)
    model.load_state_dict(state_dict)
    model.eval()
    return model

def inference(model, lr_image_path, output_path):
    lr_image = Image.open(lr_image_path).convert('RGB')
    lr_tensor = torch.from_numpy(np.array(lr_image)).float().permute(2, 0, 1).unsqueeze(0) / 255.0

    with torch.no_grad():
        sr_tensor = model(lr_tensor)

    sr_image = sr_tensor.squeeze(0).permute(1, 2, 0).numpy() * 255.0
    sr_image = np.clip(sr_image, 0, 255).astype(np.uint8)
    Image.fromarray(sr_image).save(output_path)

if __name__ == '__main__':
    model = load_model('weights/srcnn_x4_best.pth')
    inference(model, 'test_lr.png', 'test_sr.png')

注意:Pytorch 2.6开始, torch.load 的 weights_only 参数默认值发生了变化。如果你用的是2.6及以上版本,加载不带完整类的state_dict时,建议显式设置 weights_only=True ,这样既能提高安全性,也能避免一些兼容性警告。如果你加载时报错,大概率是保存时用的Pytorch版本和新环境不一致,先试试 weights_only=False ,但不建议在生产环境这么做,因为存在反序列化风险。

5.4 验证脚本的使用方法

eval.py的作用是在标准测试集上计算PSNR和SSIM。使用方法:

python eval.py --test_dir dataset/Set5 --weights weights/srcnn_x4_best.pth --scale 4

脚本会遍历测试目录下的LR图像,逐张重建,并和HR原图计算指标,最终汇总平均PSNR和SSIM。

PSNR的计算要保证两张图像尺寸完全一致。SRCNN直接输出和LR相同尺寸的图(低分辨率空间),所以计算PSNR前需要将SR结果上采样到HR尺寸,或者将HR下采样到SR尺寸。这个项目采用的是前者——先将模型输出做Bicubic上采样4倍,再与HR比较。

6. 常见问题与排查实录

6.1 加载pth权重时的四大高频报错

报错一: size mismatch for conv1.weight

权重字典里的张量尺寸和当前模型不匹配。常见原因是网络结构定义不一致,比如通道数设成了1(灰度图),但权重是在RGB三通道上训练的。解决方法是检查 models/srcnn.py 里的 num_channels 是否为3, scale 是否与训练时一致。

报错二: Missing key(s) in state_dict

加载的权重缺少当前模型需要的键。通常是因为保存权重时模型有额外的层(比如用了 DataParallel ),加载时模型没包 DataParallel 。解决方法是加载时去掉权重键里的 module. 前缀:

state_dict = torch.load(weight_path, map_location='cpu', weights_only=True)
from collections import OrderedDict
new_state = OrderedDict()
for k, v in state_dict.items():
    name = k[7:] if k.startswith('module.') else k
    new_state[name] = v
model.load_state_dict(new_state)

报错三: Can't get attribute 'SRCNN' on <module ...>

这个是加载完整模型对象而不是state_dict时才会出现的问题。如果pth文件是通过 torch.save(model, path) 保存的,加载时需要在相同环境里定义好 SRCNN 类。最好的办法是统一使用 state_dict 方式保存和加载。

报错四: RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False

模型权重是在GPU上保存的,加载时设备没有GPU。解决方案就是加 map_location='cpu' ,或者显式指定 map_location='cuda:0' 。

6.2 重建效果差,PSNR远低于论文值

遇到这种情况,先按优先级排查:

  1. 检查低分辨率图生成方式。训练和推理必须用同一种下采样模式(比如都是Bicubic),否则模型面对的是训练时没见过的退化模式。
  2. 检查图像归一化范围。模型训练时输入输出都是0~1,推理时如果输入是0~255,结果会完全不对。
  3. 检查上采样倍数。X4模型输入24x24,输出应该也是24x24(因为上采样在网络内部完成),如果你把112x112的图直接送进去,模型按“训练时的LR尺度”理解,结果自然不对。
  4. 确认颜色通道顺序。RGB还是BGR,混用会造成颜色偏移,且PSNR大幅下降。

6.3 显存不足与训练速度慢

显存不足(OOM)时,按这个顺序调整:

  • 减小 batch_size ,从32降到16,再不行降到8。
  • 用 torch.cuda.amp 混合精度训练。
  • 减小HR patch size,从96降到64。但注意patch太小会影响重建质量,只在显存实在不够时才用这招。

训练速度慢的优化:

  • 开启 num_workers ,在DataLoader里设置 num_workers=4 或更高,利用多进程加载数据。
  • 用 pin_memory=True ,减少CPU与GPU之间的数据传输时间。
  • 数据预处理尽量在 dataset.py 里做,不要在训练循环里重复做。

6.4 部署时pth模型转换的小经验

如果你要把pth模型用到实际产品中,通常要转成ONNX。转换方法:

import torch
from models.srcnn import SRCNN

model = SRCNN(scale=4)
model.load_state_dict(torch.load('weights/srcnn_x4_best.pth', map_location='cpu', weights_only=True))
model.eval()

dummy_input = torch.randn(1, 3, 24, 24)
torch.onnx.export(
    model, dummy_input, "srcnn_x4.onnx",
    input_names=['input'], output_names=['output'],
    opset_version=11
)

转ONNX时有几个坑要注意:一是ONNX的 PixelShuffle 算子在不同opset版本下支持度不一样,opset 11以上比较稳;二是固定输入尺寸会限制动态分辨率推理,如果要做任意尺寸输入,需要设置动态轴:

torch.onnx.export(model, dummy_input, "srcnn_x4.onnx",
                  input_names=['input'], output_names=['output'],
                  dynamic_axes={'input': {0: 'batch', 2: 'height', 3: 'width'},
                                'output': {0: 'batch', 2: 'height', 3: 'width'}},
                  opset_version=11)

7. 扩展:从SRCNN到更先进的超分模型

7.1 残差学习与深层网络

SRCNN虽然经典,但三层卷积的表达能力有限。当训练数据增大、算力增强后,业界开始堆深度,也就是EDSR、RCAN这类深层网络。它们的核心思想是残差学习——网络学习的不是直接输出HR图像,而是输出LR到HR的残差(细节差)。因为LR图像本身已经有大量低频信息,残差学习让网络只需关注高频细节,优化更容易,收敛更快。

这个项目的源码结构已经预留了扩展入口,你只要在 models/ 下新增一个网络类,然后修改 train.py 中的模型实例化部分,就能换用其他网络训练。比如用EDSR的残差块替换SRCNN的卷积层。

7.2 注意力机制与通道加权

最近几年超分领域引入注意力机制(如RCAN中的通道注意力模块),让网络自动关注信息量更大的特征通道。如果你有额外的数据和时间,可以在SRCNN第二层卷积后面加一个简单的SE模块,通常能带来0.1~0.3dB的PSNR提升。代价是参数和计算量增加,是否值得需要自己权衡。

7.3 轻量化与移动端适配

如果目标是移动端部署,可以考虑将卷积替换为深度可分离卷积,或者用更轻量的激活函数。也可以用知识蒸馏——把大模型当老师,小模型当学生,让小型模型学到接近大模型的效果。这个方向在模型压缩领域很成熟,动手前先跑通大模型的完整训练,再尝试蒸馏。


最后再分享一个小技巧:超分模型训练完之后,先不要急着部署。多跑几张不同类型的低分辨率图片——人脸、风景、文字、夜景——看看模型在哪些场景下效果好、哪些场景下效果差。如果发现文字区域重影严重,可能是训练数据里文字图像太少,可以补充数据再微调。做模型和做菜一样,自己尝过才知道盐放得合不合适,训练日志和指标是参考,但最终还是要看实际效果。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐