简介数字图像处理超分辨率算法复现项目面向计算机、数学、电子信息等专业课程设计、期末大作业与毕业设计参考也适合希望深入经典图像复原算法的爱好者学习。资源包含完整源码与项目说明从低分辨率图像读取、插值放大到超分重建均有覆盖并附有对照实验结果可帮助理解算法内部流程及不同阶段的效果差异。压缩包共68个文件以26个m脚本源码为核心辅以41个bmp实验图像与1个md说明文档整体仅13.46MB轻量便捷其中m脚本对应算法主程序与功能函数bmp图像为低分辨率输入、双三次插值及超分输出示例md文档简要说明项目结构与运行要点。目录按code、src、pict、LR、BI、super等模块组织便于按处理环节检索和二次调试。目前已有263人学习使用适合具备基础图像处理知识、希望快速复现算法并完成课程任务或毕业设计的读者。1. 超分辨率算法复现到底在复现什么先搞清楚这 30 分的作业要你交什么每年到了数字图像处理期末超分辨率Super-Resolution, SR几乎都是大作业里的“钉子户”。这个标题里最关键的两个字不是“超分辨率”而是“复现”。复现意味着你不需要提出新算法也不需要把数学推导写到天荒地老你需要做的是把一篇论文里提出的超分模型在本地用代码跑通用公开数据集训练或推理出结果然后把源码、说明文档和实验报告按规范打包成一个 zip 交上去。换句话说这是“照着论文复现 工程化落地”的综合题不是“研究型课题”。现阶段主流课程里能选、能复现、又好写报告的超分算法主要是三类传统插值法双三次插值、经典深度学习模型SRCNN、FSRCNN、ESPCN、VDSR以及生成对抗类模型SRGAN、ESRGAN。对大多数大作业来说SRCNN 是首选ESPCN 是次选SRGAN 系列难度偏高一周内从零复现风险大。这篇文章就围绕“怎么把一个超分算法完整复现出来”这件事展开覆盖选型理由、数据集处理、训练代码、评估指标和踩坑记录最后给一套可以直接写进报告里的验证与可视化方案。如果你是第一次接触这类大作业建议先想清楚你手里有多少时间、机器有没有 GPU、导师对报告篇幅有没有要求。这三个问题直接决定你选哪个算法。2. 选哪个算法复现SRCNN、ESPCN、FSRCNN 的取舍与理由2.1 三个主流算法的核心差异网络结构、参数量和复现难度先看一张思路对比再谈选型。算法核心思路参数量级训练难度报告可写深度SRCNN先用双三次插值放大再用三层卷积网络做映射约 5.7 万低中等FSRCNN不先放大直接在低分辨率空间提取特征最后用反卷积放大约 1.2 万低中等ESPCN不先放大在低分辨率空间提取特征最后一层用亚像素卷积PixelShuffle重排约 3 万低较高VDSR加深网络到 20 层引入残差学习约 66 万中较高SRGAN引入生成对抗网络用感知损失替代像素损失约 100 万高高对大作业来说SRCNN 最稳妥的原因有两条。第一它只有三个卷积层网络结构简单训练时间短CPU 也能在半小时内完成一个初步模型在小数据集上的训练。第二它是超分领域深度学习的开山之作论文中讲清楚了“为什么用插值放大作为前置步骤”“为什么选择 MSE 作为损失函数”这两个关键点报告里能引用的理论内容多。ESPCN 也值得考虑因为“亚像素卷积”这一个点就能在报告里单独开一节展开讲显得你理解了“从低分辨率到高分辨率不是简单反卷积而是特征通道重排”的本质。但 ESPCN 的 PixelShuffle 在 PyTorch 里实现起来比 SRCNN 的 Conv2d 要绕一点部分同学会在维度变换上卡住。如果你平时没怎么写过 PyTorch老老实实选 SRCNN。2.2 模型实现的两种路径从零手写还是用现成架构改复现这件事存在“从零手写”和“基于官方开源代码修改”两条路。大作业评分时老师通常更看重你对代码的理解程度而不是代码是不是完全由你一个字母一个字母敲出来的。常见做法是参考论文作者给出的官方实现或 GitHub 上认可的复现版本理解每一行代码的作用后按自己的结构重写一遍。这种做法下的代码虽然和原版有相似痕迹但你已经掌握了其中逻辑能在答辩时回答“为什么这个 Conv2d 的 kernel_size 是 9”这类追问。一个 SRCNN 的完整模型定义用 PyTorch 写出来大概是这样的import torch.nn as nn class SRCNN(nn.Module): def __init__(self, num_channels1, upscale_factor3): super(SRCNN, self).__init__() # 第一层图像块提取对应论文中的 Patch extraction # 输入是经过双三次插值放大后的低分辨率图像输出 64 个特征图 self.conv1 nn.Conv2d( num_channels, 64, kernel_size9, padding9//2 ) # 第二层非线性映射对应论文中的 Non-linear mapping self.conv2 nn.Conv2d( 64, 32, kernel_size1, padding0 ) # 第三层重建对应论文中的 Reconstruction self.conv3 nn.Conv2d( 32, num_channels, kernel_size5, padding5//2 ) 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 x这里的三个参数需要说明第一层 kernel_size 取 9是因为论文作者实验发现 9×9 的感受野能较好提取局部结构信息第二层用 1×1 卷积做跨通道的信息融合相当于一个非线性映射第三层用 5×5 卷积输出最终的高分辨率图像。激活函数只有前两层用 ReLU最后一层不加因为输出是像素值不需要把负值截断成 0。2.3 预训练权重和“从零训练”的关系什么时候你不用自己训很多大作业的评分标准里“模型是训练出来的”和“模型是加载预训练权重跑通的”是两种评价档次。想让报告有说服力最好自己训哪怕只训几个 epoch。但为了保险起见也可以先加载一份预训练权重跑通推理流程确认整个数据通路没有错误再决定要不要从头训练。这里有个很实用的操作链路先用官方或第三方预训练权重跑通测试集记录一份 PSNR/SSIM 数值作为“基准线”。然后你再从头训练如果训出来的模型效果接近或超过这条基准线说明复现正确如果差得远说明训练超参有问题需要调。这种做法在报告里写出来老师会认为你有工程验证意识。3. 数据集与预处理把公开数据转成模型能吃的格式以及裁剪和归一化的四个坑3.1 常见超分数据集选型DIV2K、Set5、Set14、Urban100 怎么搭配超分复现最常用的数据集搭配是“训练集 测试集”模式。训练集用来拟合模型参数测试集用来评估模型效果。课程大作业中通常不需要追求大规模数据集。常见做法是如果机器配置够用训练集选 DIV2K 的 800 张高清图如果机器配置一般或者时间紧张可以选用 T9191 张图和 General100100 张图这类小型数据集。测试集选 Set5 或 Set14因为这两套数据在超分论文中是“标准答案”你训练出来的 PSNR 可以直接和论文报告值对比。选型建议如下表数据集图片数量用途建议DIV2K800 训练 100 验证训练GPU 环境下首选T9191 张训练CPU 环境或时间紧张时选General100100 张训练和 T91 搭配使用Set55 张测试必选论文对比标准Set1414 张测试可选增加说服力Urban100100 张测试可选含大量纹理细节更能体现模型差异3.2 数据对的生成逻辑先下采样再上采样还是先上采样再下采样超分任务需要的是“低分辨率图-高分辨率图”对。常见做法是把原始高清图先做高斯模糊再按缩放因子下采样得到低分辨率图低分辨率图作为模型输入原始高清图作为监督信号。但实际复现中训练输入往往不是直接拿低分辨率图而是先对它做双三次插值上采样到目标尺寸再送入模型。这是必要的因为 SRCNN 内部不包含放大模块它只做“从插值放大的模糊图到清晰图”的映射。生成数据对的代码可以写成一个工具函数import cv2 import numpy as np def generate_lr_hr_pair(img, upscale_factor3): # 原始高清图必须是 H x W x 3 或者 H x W 的数组 h, w img.shape[:2] # 保证边长能被放大因子整除否则下采样后再上采样尺寸会对不上 h_new h - (h % upscale_factor) w_new w - (w % upscale_factor) img img[:h_new, :w_new] # 先缩小到低分辨率尺寸 lr_h, lr_w h_new // upscale_factor, w_new // upscale_factor lr_img cv2.resize(img, (lr_w, lr_h), interpolationcv2.INTER_CUBIC) # 再把低分辨率图放大回原始尺寸作为 SRCNN 的输入 lr_up cv2.resize(lr_img, (w_new, h_new), interpolationcv2.INTER_CUBIC) # 原始高清图是训练标签 hr_img img return lr_up, hr_img这段代码里的尺寸对齐逻辑值得多说一句。如果不做h - (h % upscale_factor)这一步图片尺寸不能被缩放因子整除时低分辨率图放大回来会和高分辨率图尺寸不一致训练时张量拼接会直接报错。这是新手最常见的翻车点之一。3.3 训练时的 Patch 裁剪策略为什么不能整图直接塞进网络SRCNN 论文中训练时是从训练图像上随机裁剪出一批固定大小的 patch 来训练的而不是把整张大图直接送入网络。两个原因第一GPU 显存有限整张大图比如 2K 分辨率塞不进小显存显卡第二随机裁剪天然起到数据增强的作用能让模型看到更多不同的局部结构。常见参数是patch 大小为 33×33stride 为 14。这里要注意这个 33×33 是裁剪的 HR 图尺寸对应的 LR 输入尺寸也是 33×33因为已经把 LR 图放大到和 HR 一样尺寸了。如果用的是 ESPCN 这类不预先放大的模型裁剪时就要按“LR patch 尺寸 HR patch 尺寸 / 放大因子”来设计。PyTorch 中裁剪逻辑的一个标准实现片段def random_crop(img, size33): h, w img.shape[:2] if h size or w size: raise ValueError(f图片尺寸 {h}x{w} 小于裁剪尺寸 {size}请更换训练集或调大缩放因子) top np.random.randint(0, h - size 1) left np.random.randint(0, w - size 1) return img[top:top size, left:left size]注意前提条件如果裁剪尺寸大于原图直接抛异常终止程序这样可以避免后续训练中断以后你还一头雾水不知道是哪张图出了问题。3.4 归一化范围不统一导致的“伪高分”0-255 还是 0-1 必须一以贯之这是超分复现中涉及分数高低的一个隐藏细节。如果训练时把图片归一化到 [0, 1]评估时却用 [0, 255] 区间的像素值计算 PSNRPSNR 数值会虚高 20 多个 dB。很多同学第一次跑完训练看到 PSNR 高达 45dB 以为模型效果爆表后来才发现是计算时单位没统一这类“虚假繁荣”在答辩时很容易被老师拆穿。正确逻辑是训练阶段把像素值除以 255 变到 [0, 1] 区间评估阶段要么保持 [0, 1] 区间统一计算要么把输出乘回 255 再计算。不能一半一半。建议在整个项目里固定下来比如在项目说明文档里写清楚“所有图像均以 0-1 浮点数格式送入模型PSNR 计算基于 0-1 区间”。这样也能避免代码在不同模块间来回传递时出现单位错乱的问题。4. 训练与评估用 PyTorch 从零跑通 SRCNN 的完整流程4.1 Dataset 类与数据加载器把上一章的预处理逻辑封装成可迭代对象PyTorch 的数据流水线基于torch.utils.data.Dataset类。你需要实现一个自定义 Dataset在__getitem__方法里完成读图、生成 LR-HR 对、裁剪、转 Tensor 这一整套操作。import torch from torch.utils.data import Dataset from PIL import Image class SRCNNDataset(Dataset): def __init__(self, image_paths, upscale_factor3, patch_size33): self.image_paths image_paths self.upscale_factor upscale_factor self.patch_size patch_size def __len__(self): return len(self.image_paths) def __getitem__(self, idx): # 读取高清图并转成 RGB 数组 img np.array(Image.open(self.image_paths[idx]).convert(RGB)) # 生成 LR-HR 对 lr_up, hr generate_lr_hr_pair(img, self.upscale_factor) # 在 HR 图上随机裁剪LR 图用同一位置裁剪 h, w hr.shape[:2] top np.random.randint(0, h - self.patch_size 1) left np.random.randint(0, w - self.patch_size 1) hr_patch hr[top:top self.patch_size, left:left self.patch_size] lr_patch lr_up[top:top self.patch_size, left:left self.patch_size] # 转成 float 张量并归一化到 [0, 1] lr_tensor torch.from_numpy(lr_patch.transpose(2, 0, 1)).float() / 255.0 hr_tensor torch.from_numpy(hr_patch.transpose(2, 0, 1)).float() / 255.0 return lr_tensor, hr_tensor这段代码的关键在于lr_patch和hr_patch使用了同一组(top, left)坐标。因为 LR 图已经通过双三次插值被放大到了与 HR 图相同的尺寸所以两幅图的空间位置是一一对应的可以直接用同一组裁剪参数。如果你不是先用插值放大而是直接送原始小图那裁剪坐标就必须按放大因子换算这又是一个容易出错的细节。4.2 训练主循环学习率、 BatchSize 和 Epoch 该怎么定SRCNN 训练时常见的配置是Adam 优化器初始学习率1e-4BatchSize 16Epoch 50 到 100。如果你的训练集不大比如 T9150 个 epoch 已经足够如果用的是 DIV2K100 个 epoch 会更稳妥。学习率衰减策略可以用StepLR每 30 个 epoch 乘以 0.1或者训练到中期手动降低学习率。以下是一段可以直接跑的训练主循环代码import torch.optim as optim from torch.utils.data import DataLoader from torchvision.transforms import Compose, ToTensor # 假设 train_paths 是所有训练图片路径的列表 dataset SRCNNDataset(train_paths, upscale_factor3, patch_size33) dataloader DataLoader(dataset, batch_size16, shuffleTrue, num_workers2) model SRCNN(num_channels3, upscale_factor3) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.1) num_epochs 50 device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) for epoch in range(num_epochs): model.train() total_loss 0.0 for lr_tensor, hr_tensor in dataloader: lr_tensor lr_tensor.to(device) hr_tensor hr_tensor.to(device) optimizer.zero_grad() sr_tensor model(lr_tensor) loss criterion(sr_tensor, hr_tensor) loss.backward() optimizer.step() total_loss loss.item() * lr_tensor.size(0) avg_loss total_loss / len(dataset) current_lr optimizer.param_groups[0][lr] print(fEpoch {epoch1:03d}, Loss: {avg_loss:.6f}, LR: {current_lr:.2e}) if (epoch 1) % 10 0: torch.save(model.state_dict(), fsrcnn_epoch_{epoch1}.pth)num_workers2表示用两个子进程读取数据能加快数据加载速度但如果你在 Windows 环境下运行发现报错可以把它改成 0问题会立即消失。损失函数固定用MSELoss因为 SRCNN 论文的核心假设就是“像素均方误差最小化等价于最大化 PSNR”这一点要写进报告的损失函数设计说明里。4.3 评估指标 PSNR 和 SSIM 的计算细节以及彩色图该算什么通道超分任务最常用的两个评估指标是 PSNR峰值信噪比和 SSIM结构相似性。计算 PSNR 时如果输入是彩色图常见做法是只计算 Y 通道亮度通道的 PSNR因为人眼对亮度变化最敏感。import math import cv2 import numpy as np def calculate_psnr(img1, img2): # 输入是 [0, 255] 范围的 uint8 或 float 图像 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)) def calculate_psnr_y_channel(sr_img, hr_img): # 转 YCbCr 色彩空间取 Y 通道 sr_y cv2.cvtColor(sr_img, cv2.COLOR_RGB2YCrCb)[:, :, 0] hr_y cv2.cvtColor(hr_img, cv2.COLOR_RGB2YCrCb)[:, :, 0] return calculate_psnr(sr_y, hr_y)SSIM 计算可以直接用skimage.metrics.structural_similarity。但要注意边界处理SRCNN 的输出边缘会有黑边或伪影评估前通常会用shave参数去掉边缘像素一般去掉 6 个像素的边界。这里有一个论文汇报的规律不去边界算出的 PSNR 会比去边界低 0.3 到 0.8dB因为边缘存在卷积边界效应。报告里要说明你用的是哪种方式否则别人复现你结果时会得到不同的数值。5. 复现避坑指南从数据到代码的 5 条血泪经验5.1 坑一验证集 PSNR 很高测试集上效果却一塌糊涂现象训练时每 10 个 epoch 在验证集上算一次 PSNR数值一直在涨且表现得很好但最后在 Set5 上测试生成的高分辨率图边缘模糊PSNR 比论文低很多。原因训练过程中验证集和训练集来自同一数据分布且验证集图片可能参加过训练集的裁剪。模型过拟合到了训练集的统计特征上没有泛化能力。解决训练集和测试集必须严格分开测试集只用 Set5、Set14 这类公开标准数据集这些数据不参与任何形式的训练。同时在报告里说明测试时使用的模型是训练完成后保存的最优权重而不是最后一个 epoch 的权重。保存最优权重的方法每个 epoch 结束算一次验证集 PSNR超过历史最大值就覆盖保存best_model.pth。5.2 坑二使用不同库的双三次插值得到的数据对不一致现象数据预处理时用 OpenCV 的cv2.resize做双三次插值测试阶段加载预处理好的图片时用了 PyTorch 或者 scikit-image 的 resize最终 PSNR 结果比预期低 0.5dB 甚至更多。原因不同图像处理库对双三次插值的实现细节不同虽然名字一样但插值核的近似方式和边界处理策略有差异。你在预处理阶段生成的 LR 图和测试阶段重新缩放的图不是同一套数据导致模型输入分布漂移。解决整个项目统一使用同一个库完成所有 resize 操作。我一般全用cv2.resize因为 OpenCV 的插值实现是标准参考。如果要用torch.nn.functional.interpolate做在线缩放就要保证训练和测试都用它。这条坑在写报告时也值得提一嘴因为你项目说明文档里写的预处理代码和评估代码如果用了不同的库老师一眼就能看出来。5.3 坑三训练 Loss 不降反升或出现 NaN现象训练刚开始几个 epochLoss 从 0.03 降到 0.01 后突然跳到 nan或者全程在振荡不下降。原因最常见的有三类——学习率过大、BatchSize 过小导致梯度方向抖动、输入图像中存在全零或全黑的异常图片这类图片反向传播时容易让权重更新异常。另一种可能是Conv2d参数初始化不当。解决先看数据。加一段检查代码在 Dataset 的__getitem__返回前检查张量方差如果方差为 0 就打日志跳过该样本。再调小学习率到1e-5尝试如果 Loss 能稳定下降说明原学习率大了。最后确保输入张量是float32而不是float64或uint8否则 PyTorch 某些算子上会类型不匹配报错。5.4 坑四显存不足但减小 BatchSize 后效果变差响应变慢现象8GB 显存的显卡BatchSize 设为 16 时直接 Out of Memory改到 4 后能跑但训练速度极慢而且最终效果明显变差。原因BatchSize 过小时Batch Normalization 统计量不准确梯度噪声变大模型收敛不稳定。解决不要只降 BatchSize同时要降输入 patch 尺寸。比如把 patch 从 33×33 降到 21×21显存占用会显著降低模型照样能收敛。效果变差的补偿手段是加大训练 epoch 数量。另外可以把torch.backends.cudnn.benchmark True加上让 cuDNN 自动优化卷积算法这在输入尺寸固定的情况下提升明显。5.5 坑五保存模型后加载推理结果和训练时不一致现象训练结束后在测试集上验证并记录了 PSNR把模型保存为srcnn.pth期末验收现场加载模型再跑一遍测试集结果 PSNR 低了 1dB 多。原因主要是训练和推理阶段的数据预处理路径不一致。训练时 LR 图经过随机裁剪、归一化后直接送进模型推理时却走了另一套预处理代码忘了归一化这一步或者归一化后再astype(np.uint8)丢了精度。解决推理阶段和训练阶段共用同一个预处理函数从写代码的第一天就封装一个preprocess()函数供两边调用。同时推理代码里加载模型之后要调用model.eval()并用with torch.no_grad():包住前向推理否则权重中的 Dropout 或 BatchNorm 行为会变成训练模式输出结果不稳定。6. 进阶与验收把结果做进报告里的技巧、消融实验思路和一次复现的完整验证如果你想在及格的基础上冲高分这里有一个很实用的小技巧让模型“多尺度复现”。现在的 SRCNN 代码里写死了upscale_factor3你可以把模型改成支持任意缩放因子的版本——训练一个 3 倍模型在推理时通过插值降尺度到 2 倍输入来近似测试。具体做法是推理时先对高分辨率图做 2/3 比例的双三次缩放再送进 3 倍模型输出的图相当于原始图的 2 倍放大。这种操作虽然不是严格意义上的多尺度模型但能够在小作业的篇幅下展示你对尺寸关系的理解。消融实验是报告里最容易拉开差距的部分。你可以做三组对比A 组用论文里的 9-1-5 卷积核配置B 组把第二层卷积核从 1×1 改成 3×3C 组去掉 ReLU 激活函数。把三组实验的 PSNR/SSIM 和训练时间做成一张对比表在报告里分析“更大的卷积核带来更高的重建精度但参数量和计算时间也成倍增加”“去掉 ReLU 后模型退化为线性变换效果明显下降”。这是最安全的加分项因为所有数据都来自你自己真实的实验记录。完整验证流程可以按以下三步走第一步用预训练权重跑通推理流程确认结果能够复现论文中预设的 PSNR 数值范围第二步从头训练 50 个 epoch保存 best 模型第三步在 Set5 上做最终测试输出重建结果图和原图对比并计算 PSNR/SSIM。不要只在命令行里打印数值要保存一张对比图——把 LR 放大图、SR 重建图、HR 原图从左到右拼在一张图里标注各自的 PSNR 值。这张图是报告的封面图材料也是答辩时最直观的展示形式。最后说一个我的个人习惯每次复现项目我都会在项目根目录下放一个README.md里面写清楚“用 Python 3.8 和 PyTorch 1.9 以上版本先运行python train.py训练模型再运行python test.py --checkpoint best_model.pth测试”。这份文件不用长但目录结构要清晰——data/放数据集models/放模型定义checkpoints/放训练权重results/放测试输出。老师打开 zip 包的第一印象基本决定你这门课大作业的分数区间而这个印象 50% 由项目说明文档决定。希望这套流程能帮你把这条复现路走得稳一点省下熬夜踩坑的时间。本文还有配套的精品资源点击获取