简介这份资源面向图像超分方向的初学者与PyTorch入门者用经典SRCNN模型打通从数据准备到训练、测试的完整流程帮助理解超分辨率的基本原理与工程实现。压缩包共28个文件、约74.6MB包含6个Python脚本负责数据预处理、模型定义、训练与测试7个pth权重文件与2个h5数据集文件可直接加载另有bmp图像用于对比超分与双三次插值效果以及README说明与运行结果记录。已有2977人学习下载说明其上手门槛较低、参考价值较高。资源内置数据集与预训练权重解压后按说明即可运行训练或测试参数中num_works一般电脑改为0即可适合想快速复现SRCNN、观察重建效果并在此基础上做改进实验的读者。1. 从一张糊图说起SRCNN 为什么值得你亲手跑一遍手里有一批低分辨率素材放大后边缘发虚、纹理糊成一团这是做图像超分时最常遇到的场景。SRCNNSuper-Resolution Convolutional Neural Network是深度学习做超分里绕不开的起点它用三层卷积就把「低清到高清」的映射学了出来结构简单到可以在单卡上几十分钟跑完一轮训练。这份 PyTorch 实现适合两类人刚接触图像超分、想搞懂 CNN 怎么做像素级回归的新手以及想拿一个干净基线去对比后续 SR 模型比如 FSRCNN、ESPCN的开发者。它不依赖复杂框架数据准备、模型定义、训练、推理四块拆得清楚改起来没有心理负担。2. 项目结构与技术选型三层卷积怎么撑起超分任务2.1 目录组织与模块职责拿到这份源码先别急着敲命令把目录扫一遍能省很多事。常见的组织方式是这样srcnn/ ├── data/ │ ├── train/ # 训练集高清图 │ └── val/ # 验证集 ├── datasets.py # 数据集加载与预处理 ├── model.py # SRCNN 网络定义 ├── train.py # 训练入口 ├── inference.py # 单图/批量推理 ├── utils.py # PSNR、图像读写、下采样 └── checkpoints/ # 权重保存目录datasets.py负责把高清图用双三次插值下采样成低清图再和原图配对返回。model.py只做一件事定义三层卷积。train.py串起数据、模型、损失、优化器。inference.py加载权重后对任意图片做超分。这种拆法的好处是你想换数据集只动datasets.py想换网络结构只动model.py互不干扰。2.2 为什么是三层卷积SRCNN 的核心判断是超分本质上是像素级的回归问题不需要池化、不需要全连接卷积层足够提取局部纹理和边缘。三层各司其职第一层从输入通道通常是 YCbCr 的 Y 通道1 通道映射到 64 或 128 维特征卷积核 9×9负责捕捉大范围结构。第二层64→32 维卷积核 1×1 或 5×5做非线性映射把低维特征压到更抽象的空间。第三层32→1 维卷积核 5×5重建输出像素。激活函数用 ReLU只有前两层加最后一层不加因为输出是连续像素值不需要非线性截断。这个设计放到今天看很朴素但它把「超分 端到端回归」这件事讲明白了。2.3 数据准备的关键参数训练前必须确认两件事输入是单通道还是三通道以及上采样倍数。SRCNN 原论文在 YCbCr 空间的 Y 通道上训练推理时再把 Cb、Cr 用双三次插值放大后合并。如果你直接用 RGB 三通道训练参数量会涨收敛也慢一些。常见做法是# utils.py 中的下采样逻辑 import cv2 import numpy as np def downsample_image(img, scale3): 用双三次插值把高清图缩小模拟低清输入 h, w img.shape[:2] low cv2.resize(img, (w // scale, h // scale), interpolationcv2.INTER_CUBIC) # 再放大回原尺寸作为网络输入保持尺寸一致 low cv2.resize(low, (w, h), interpolationcv2.INTER_CUBIC) return low这里有个容易忽略的点低清图放大回原尺寸后网络学的是「从模糊到清晰」的残差式映射而不是直接学放大。scale参数决定超分倍数训练时用 2 或 3 比较稳4 倍以上边缘恢复会明显吃力。提示如果显存吃紧把训练图块裁成 128×128 或 96×96比整图塞进去更实际。3. 核心模块拆解模型定义、训练循环与损失函数3.1 SRCNN 网络定义逐行讲model.py是整个项目的心脏代码不长但每一层都有讲究import torch import torch.nn as nn class SRCNN(nn.Module): def __init__(self, num_channels1): super(SRCNN, self).__init__() # 第一层9x9 卷积提取大范围结构特征 self.conv1 nn.Conv2d(num_channels, 64, kernel_size9, padding4) # 第二层1x1 卷积做非线性映射压缩特征维度 self.conv2 nn.Conv2d(64, 32, kernel_size1, padding0) # 第三层5x5 卷积重建输出像素 self.conv3 nn.Conv2d(32, num_channels, kernel_size5, padding2) self.relu nn.ReLU(inplaceTrue) def forward(self, x): x self.relu(self.conv1(x)) # 第一层后激活 x self.relu(self.conv2(x)) # 第二层后激活 x self.conv3(x) # 最后一层不加激活 return xpadding的设置保证每层输出尺寸和输入一致9×9 卷积配 padding45×5 配 padding21×1 配 0。num_channels默认 1对应 Y 通道如果你用 RGB改成 3 即可。inplaceTrue省一点显存但如果你要做特征可视化建议关掉否则 ReLU 会覆盖输入。3.2 训练循环与损失选择训练脚本的核心逻辑是取一批低清图前向传播得到超分结果和对应高清图算损失反向传播更新权重。损失函数用 MSE 最直接# train.py 关键片段 import torch.optim as optim from model import SRCNN from datasets import SRCNNDataset from torch.utils.data import DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) model SRCNN(num_channels1).to(device) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-4) train_loader DataLoader(SRCNNDataset(data/train), batch_size16, shuffleTrue) for epoch in range(100): model.train() running_loss 0.0 for low, high in train_loader: low, high low.to(device), high.to(device) optimizer.zero_grad() output model(low) loss criterion(output, high) loss.backward() optimizer.step() running_loss loss.item() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.6f})lr1e-4是 Adam 在超分任务上的常用起点太大容易震荡太小收敛慢。batch_size16在 8GB 显存上跑 128×128 图块没问题。每轮打印平均损失如果损失卡在某个值不降先检查数据配对是否正确——低清和高清图有没有错位这是血泪经验里排第一的翻车点。3.3 推理与 PSNR 评估训练完要验证效果inference.py负责加载权重并输出超分图utils.py里的 PSNR 计算用来量化# utils.py 中的 PSNR 计算 import math import numpy as np def calculate_psnr(img1, img2): 计算两张图的 PSNR输入为 0-255 的 uint8 数组 mse np.mean((img1.astype(np.float64) - img2.astype(np.float64)) ** 2) if mse 0: return float(inf) return 20 * math.log10(255.0 / math.sqrt(mse))PSNR 越高说明像素误差越小但注意它和人眼感知不完全一致。SRCNN 在 Set5 这类小数据集上3 倍超分通常能到 30dB 以上具体数值取决于训练轮数和数据量。推理时记得把模型切到eval()模式并加上torch.no_grad()否则显存会白白浪费。4. 避坑与排查训练不收敛、显存爆炸、颜色偏移4.1 损失不降反升现象训练几个 epoch 后 loss 突然变成 NaN 或大幅波动。原因通常是学习率过大或者数据里有异常值比如全黑图、损坏文件。解决把lr降到 1e-5 试一轮同时在datasets.py里加一层过滤跳过尺寸过小或读取失败的图片。4.2 显存不够用现象报CUDA out of memory。原因可能是 batch_size 太大、图块尺寸太大或者忘了在推理时加torch.no_grad()。解决先把 batch_size 降到 8 或 4再把训练图块裁到 96×96推理阶段务必用with torch.no_grad():包住前向传播。4.3 输出图颜色发灰或偏色现象超分结果整体发灰或者颜色和原图对不上。原因多半是只处理了 Y 通道合并时 Cb、Cr 没有正确放大或者 RGB 和 BGR 通道顺序搞反了。解决确认cv2.imread读进来是 BGR如果按 RGB 处理要先转换YCbCr 合并时用cv2.merge把三个通道拼回去再转回 BGR。4.4 验证集 PSNR 远低于训练集现象训练 loss 很低但验证集 PSNR 上不去。原因是过拟合训练集太小或和验证集分布差异大。解决加数据增强随机裁剪、翻转或者把验证集换一批不同来源的图别用训练集的子集当验证。4.5 推理速度慢得离谱现象单张图要好几秒。原因可能是模型没放到 GPU或者输入图尺寸太大。解决确认model.to(device)和输入 tensor 都在同一设备大图可以先分块推理再拼接避免一次性塞进显存。5. 二次开发与改造从 SRCNN 到你的超分基线5.1 换损失函数MSE 之外的选择MSE 优化的是像素平均误差结果偏平滑纹理细节容易丢。想提升感知质量可以换成 L1 损失或组合损失# 用 L1 感知损失的组合示例 class CombinedLoss(nn.Module): def __init__(self, alpha0.8): super().__init__() self.alpha alpha self.l1 nn.L1Loss() self.mse nn.MSELoss() def forward(self, pred, target): return self.alpha * self.l1(pred, target) (1 - self.alpha) * self.mse(pred, target)alpha控制 L1 和 MSE 的权重0.8 是我试过比较稳的起点。L1 对异常值更鲁棒训练初期 loss 下降会慢一点但边缘更锐。5.2 加深网络或引入残差连接SRCNN 只有三层感受野有限。想提升效果最直接的改法是加残差连接让网络学残差而不是完整映射class SRCNNResidual(nn.Module): def __init__(self, num_channels1): super().__init__() self.conv1 nn.Conv2d(num_channels, 64, 9, padding4) self.conv2 nn.Conv2d(64, 32, 1) self.conv3 nn.Conv2d(32, num_channels, 5, padding2) self.relu nn.ReLU(inplaceTrue) def forward(self, x): identity x out self.relu(self.conv1(x)) out self.relu(self.conv2(out)) out self.conv3(out) return out identity # 残差连接这样改完网络只需要学「模糊图到高清图的差值」收敛更快PSNR 通常能涨 0.5 到 1dB。注意输入输出通道要一致否则加法对不上。5.3 验证改造是否有效的固定流程每次改完模型或损失别凭感觉判断好坏。我一般固定走这三步第一用同一批验证图跑 PSNR和改之前的基线对比第二抽三张典型图边缘多的、纹理密的、平滑的肉眼对比第三记录训练时间和显存占用确认没有为了涨 0.2dB 把资源翻倍。这套流程能过滤掉大部分「看起来变好了」的错觉。5.4 一个容易忽略的细节权重初始化SRCNN 对初始化比较敏感默认的 PyTorch 初始化有时会让第一层输出方差过大。常见做法是用 Kaiming 初始化def weights_init(m): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, modefan_out, nonlinearityrelu) if m.bias is not None: nn.init.zeros_(m.bias) model.apply(weights_init)加在模型实例化之后、训练之前。这一步不写也能跑但写了之后训练初期的 loss 曲线会平滑很多少走几个 epoch 的弯路。从那以后我每次动网络结构都先把基线 PSNR 和训练曲线存下来改完再跑一遍对比不然很容易把「随机种子带来的波动」当成改进。希望帮到你。本文还有配套的精品资源点击获取