资讯详情

SRCNN图像超分复现:三层卷积与PyTorch工程要点

📅 2026/10/26 2:06:13 | 华诺云谱 👁 阅读
SRCNN图像超分复现:三层卷积与PyTorch工程要点
简介面向深度学习入门者与图像超分初学者的SRCNN PyTorch实现包围绕经典超分辨率模型的搭建、训练与测试流程组织内容适合希望从零理解卷积网络在图像重建中应用的人群对小白较为友好并提供可运行工程。压缩包共28个文件、约74.6MB包含6个py脚本、7个pth权重、2个h5数据集文件以及9张bmp结果图、1张png示意图和md、txt说明文档覆盖模型代码、数据准备、预训练参数与可视化结果等部分。已有2978人学习下载可作为课程实验、毕业设计原型或自学练手素材。读者可据此了解SRCNN的网络结构、数据组织方式与训练测试入口借助权重和对比图观察双三次插值与超分输出的差异并参考说明文档排查环境与进程数等参数设置问题减少从零搭框架的成本。1. 从一张糊掉的截图说起SRCNN 到底动了哪一步某开发者手上有一批低分辨率的产品图想放大两倍放进详情页。双三次插值跑完边缘发虚、细文字糊成一片放大带来的“软”几乎没法看。SRCNN 就是在这个位置切进来的它不改「先放大」这一步放大依旧交给双三次而是在放大后的那张软图上再叠三层卷积把高频细节重建回来。这也是超分SR里最经典的 baseline。适合谁想搞清超分模型演进起点的、显存不多想先跑通一个 demo 的、需要一个便宜对照组来验证自己新模型的。整套实现的权重就八千多个单卡入门级显卡几分钟能跑一轮翻车成本很低。2. 三层卷积的账9-1-5 结构为什么能顶住超分2.1 稀疏编码那条手工流水线被折叠成了三层 CNNSRCNN 出现之前图像超分的主流路线是稀疏编码把 LR 图切成重叠 patch每个 patch 到学好的低分辨率字典里求一组稀疏系数再用高分辨率字典把系数还原成 HR patch最后把重叠 patch 按权重叠回去。整条流程有三个明确组件——patch 提取与表示、非线性映射、重建彼此割裂字典和系数还得分开优化。SRCNN 干的事情说穿了就是把这三步各换成一个卷积层端到端一起训。第一层 9×9 卷积对应 patch 提取与特征表示输出的 64 个通道就是每个位置的“表示向量”第二层 1×1 卷积对应非线性映射1×1 在空间上不跨像素本质是对每个位置独立做全连接把 64 维压成 32 维承接的正是稀疏编码里那一步非线性变换第三层 5×5 卷积对应重建把 32 张特征图加权叠回图像空间。前两层带 ReLU第三层不加因为重建输出要能取负值——这点后面会专门踩一次坑。理解了这层映射关系参数就不需要死记了层数少是因为原始流水线只有这三个阶段核尺寸大是因为字典的“作用域”本来就覆盖一个 patch9×9 和 5×5 就是那个作用域的近似。后面 FSRCNN 把 9×9 拆成 5×5 加 1×1、把上采样挪到网络末尾本质上都是在动这三个组件的位置和形状。2.2 感受野与参数量13×13 的窗口8129 个权重三个卷积层堆下来每个输出像素实际看到的输入范围是 13×13算法是 1 (9−1) (1−1) (5−1)。也就是说重建一个像素时网络能参考它周围 13×13 邻域的信息这个窗口比常用的 3×3 小核堆叠要大在放大倍数为 24 时基本够覆盖一个笔画或一条边缘的上下文。按 Y 通道单通道输入输出算三层的参数账如下层核尺寸输入→输出通道参数量激活conv19×91→649×9×1×64 64 5248ReLUconv21×164→321×1×64×32 32 2080ReLUconv35×532→15×5×32×1 1 801无合计8129如果走 RGB 三通道直训conv1 变成 9×9×3×64 64 15616conv3 变成 5×5×32×3 3 2403总量涨到 20099是单通道方案的 2.5 倍左右。推理开销也按每像素乘加数算单通道是 9×9×64 64×32 32×5×5 8032 MACs/像素一张 500×500 的图大约 2 GFLOPs 量级乘加算两次放到今天的 GPU 上基本是免费。所以「SRCNN 慢」这个印象主要来自原论文那个不走 padding、逐 patch 推理的实现不是模型本身的问题。2.3 PyTorch 骨架与 padding 的取法原论文为了严格对齐卷积输出尺寸是先把大图切成小块送进去的。实际复现时更常见的做法是给每层补 padding让输出尺寸与输入完全一致训练和推理都省事import torch import torch.nn as nn class SRCNN(nn.Module): def __init__(self, num_channels1): super().__init__() # padding kernel // 2保证 H、W 不变 self.conv1 nn.Conv2d(num_channels, 64, kernel_size9, padding4) self.conv2 nn.Conv2d(64, 32, kernel_size1, padding0) self.conv3 nn.Conv2d(32, num_channels, kernel_size5, padding2) self.relu nn.ReLU(inplaceTrue) self._init_weights() def _init_weights(self): # 前两层接 ReLU用 He 初始化 for m in (self.conv1, self.conv2): nn.init.kaiming_normal_(m.weight, modefan_out, nonlinearityrelu) nn.init.zeros_(m.bias) # 最后一层无激活方差压小避免初始输出幅度过大 nn.init.normal_(self.conv3.weight, mean0.0, std1e-3) nn.init.zeros_(self.conv3.bias) def forward(self, x): x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) return self.conv3(x) # 不能加激活否则输出的负值被截断逻辑说明padding4对应 9//2padding2对应 5//2这样三层都不改变特征图尺寸网络的输入和输出可以逐像素做 MSE训练侧不用做任何裁剪对齐。num_channels在 Y 通道方案里填 1模型直接吃灰度图RGB 方案填 3但要先想清楚色度怎么处理这一点在第 5 章会展开。参数说明kaiming_normal_的modefan_out是按输出通道数缩放方差配合 ReLU 是标准做法第三层不用 He是因为它后面没有激活函数用大方差会让初始输出幅度远超 [0,1] 区间前期 loss 会剧烈震荡。.conv3的std1e-3是个经验值换成 1e-2 也能收敛只是前几十步 loss 曲线不那么好看。写完先打印一下尺寸和参数量做自检m SRCNN(num_channels1) x torch.randn(2, 1, 64, 64) print(m(x).shape) # torch.Size([2, 1, 64, 64]) print(sum(p.numel() for p in m.parameters())) # 8129尺寸对得上、参数量等于 8129说明结构没写错。这两个数字记住了后面排查时会反复用到。3. 数据管线从 HR 图造出成对样本的三个关键抉择3.1 双三次下采样、scale 因子与 YCbCr 通道取舍SRCNN 的训练样本是这样造出来的从一张 HR 图里裁一个 patch用双三次插值把它缩小 scale 倍得到 LR再把这张 LR 用双三次插值放大回原尺寸作为网络的输入而原始 patch 就是标签。注意这里的关键点——网络学的是「从双三次放大图到 HR 图」的残差式补偿不是「从 LR 直接到 HR」。这句理解错了后面所有输入输出尺寸都会对不上。scale 因子的选择直接影响任务难度。scale2 时双三次放大已经相当接近 HR网络只需要补一点点细节PSNR 提升空间小但容易训scale4 时放大图已经糊得明显提升空间大但 SRCNN 这种浅层结构能补回来的也有限指标会显得难看。新手建议先跑 scale2 建立基线再往 3、4 推。通道处理是第二个抉择。人眼对亮度Y的敏感度远高于色度Cb、Cr所以超分领域的常规做法是转 YCbCr只对 Y 通道训练和推理Cb、Cr 直接双三次放大再拼回去。SRCNN 原论文也是这么做的。这样做有两个好处模型参数砍掉三分之二训练快色度不参与训练天然规避了色彩漂移。代价是处理链路里多一次颜色空间转换如果转换用的库和计算 PSNR 用的库不一致指标会有微妙的偏差这个坑在第 5 章单列。3.2 Dataset 与 DataLoader 的落地写法数据集类要做的事就三件读图、转 Y 通道、裁 patch 造对。下面这版是能直接跑的import os import cv2 import numpy as np import torch from torch.utils.data import Dataset class SRPatchDataset(Dataset): 从 HR 图目录构造 (双三次放大图, HR) 成对样本只使用 Y 通道。 def __init__(self, hr_dir, scale2, patch_size96, repeat24, augmentTrue): self.files sorted( os.path.join(hr_dir, f) for f in os.listdir(hr_dir) if f.lower().endswith((.png, .jpg, .jpeg, .bmp)) ) assert self.files, f{hr_dir} 下没有找到图像 assert patch_size % scale 0, patch_size 必须能被 scale 整除 self.scale scale self.patch_size patch_size self.repeat repeat # 每张图重复采样次数撑大 epoch self.augment augment def __len__(self): return len(self.files) * self.repeat def __getitem__(self, idx): path self.files[idx % len(self.files)] img cv2.imread(path, cv2.IMREAD_COLOR) # BGR, uint8 y cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)[:, :, 0] # 只取 Y 通道 y y.astype(np.float32) / 255.0 p self.patch_size h, w y.shape if h p or w p: # 小图先放大到可裁 y cv2.resize(y, (max(p, w), max(p, h)), interpolationcv2.INTER_CUBIC) h, w y.shape top np.random.randint(0, h - p 1) left np.random.randint(0, w - p 1) hr y[top:top p, left:left p] if self.augment: if np.random.rand() 0.5: hr hr[:, ::-1] if np.random.rand() 0.5: hr hr[::-1, :] hr np.ascontiguousarray(hr) # 关键先下采样再上采样得到与 hr 同尺寸的网络输入 small cv2.resize(hr, (p // self.scale, p // self.scale), interpolationcv2.INTER_CUBIC) lr_up cv2.resize(small, (p, p), interpolationcv2.INTER_CUBIC) return (torch.from_numpy(lr_up).unsqueeze(0), torch.from_numpy(hr).unsqueeze(0))逻辑说明__getitem__里做了两次 resize第一次缩到p // scale第二次放回p这两步不能省也不能合并——第一次是模拟低分辨率退化第二次才是网络的真实输入。翻转增强只做水平和垂直镜像不做 90 度旋转因为自然图像有明确的重力方向旋转会引入不真实样本如果你的数据集全是俯拍或航拍图旋转可以打开。参数说明patch_size96是个折中太小则每个 batch 覆盖的内容少、收敛慢太大则单图能采的样本数少、容易过拟合scale必须整除patch_size否则第二次 resize 的目标尺寸不是整数代码会直接抛异常。repeat24表示每张 HR 图在一个 epoch 里被采 24 次图像数量少于几百张时这个值要拉高否则一个 epoch 的步数太少学习率调度会失去意义。3.3 损失、优化器与两段式学习率损失函数没有悬念用 MSE。超分领域后来普遍用 L1 换更好的感知质量但 SRCNN 时代 MSE 就是标配而且 MSE 直接对应 PSNR 这个评价指标训练目标和汇报指标是一致的省得解释。优化器用 SGD momentum0.9 是原论文的配置但原论文给前两层和最后一层设了不同的学习率前两层 1e-4最后一层 1e-5。这个设置的动机是最后一层直接输出像素值梯度幅度大学习率高了容易在 [0,1] 边界附近震荡。复现时第一版建议照抄跑通之后再尝试统一学习率或者换 Adam 做对比。optimizer torch.optim.SGD([ {params: model.conv1.parameters(), lr: 1e-4}, {params: model.conv2.parameters(), lr: 1e-4}, {params: model.conv3.parameters(), lr: 1e-5}, ], momentum0.9, weight_decay0) # 每 30 个 epoch 学习率减半跑 80 个 epoch scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.5)逻辑说明参数分组param groups是 PyTorch 给不同层设不同超参的标准写法scheduler.step()会对所有组统一缩放所以前两层和第三层始终维持 10:1 的比例不会因为衰减而打乱。参数说明weight_decay0是刻意的SRCNN 参数量小正则化过头反而欠拟合如果你的训练集只有几十张图并且指标在训练集上远高于验证集再考虑加 1e-5 量级的权重衰减。step_size30、gamma0.5这组值跑 80 个 epoch 刚好衰减两次属于保守配置想快一点可以把step_size降到 15但要注意别衰减太早导致收敛不完全。4. 训练循环与评估PSNR 到底怎么算才不虚高4.1 训练循环骨架与断点续训训练循环本身没什么花样但有几个细节决定了这轮训练是不是可复现的import torch import torch.nn as nn from torch.utils.data import DataLoader device cuda if torch.cuda.is_available() else cpu train_set SRPatchDataset(data/train_hr, scale2, patch_size96, repeat24) train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue) model SRCNN(num_channels1).to(device) criterion nn.MSELoss() optimizer torch.optim.SGD([ {params: model.conv1.parameters(), lr: 1e-4}, {params: model.conv2.parameters(), lr: 1e-4}, {params: model.conv3.parameters(), lr: 1e-5}, ], momentum0.9, weight_decay0) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.5) for epoch in range(80): model.train() running 0.0 for lr_img, hr_img in train_loader: lr_img lr_img.to(device, non_blockingTrue) hr_img hr_img.to(device, non_blockingTrue) pred model(lr_img) loss criterion(pred, hr_img) optimizer.zero_grad(set_to_noneTrue) loss.backward() # 浅层网络梯度偶尔会爆加一道保险 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() running loss.item() * lr_img.size(0) scheduler.step() avg running / len(train_set) print(fepoch {epoch1:03d} | mse {avg:.6f} | lr {optimizer.param_groups[0][lr]:.1e}) # 每个 epoch 存一次方便回溯是哪一轮开始指标掉头的 torch.save({ epoch: epoch, model: model.state_dict(), optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), }, srcnn_ckpt.pth)逻辑说明zero_grad(set_to_noneTrue)比默认的zero_grad()少一次写零操作在浅层小模型上影响不大但养成习惯没坏处。clip_grad_norm_的max_norm1.0对 SRCNN 来说基本不会触发但一旦数据里有异常像素比如全黑图触发的除零式梯度没这行会直接训飞。checkpoint 里连 optimizer 和 scheduler 一起存是为了断点续训时学习率能接上——只存模型权重、重开训练时学习率从 1e-4 重新开始前几十个 epoch 等于白跑。参数说明batch_size32是按 96×96 patch、单通道、8 GB 显存估算的实际占用不到 1 GB可以往 64 推。num_workers4要看机器 CPU 核数设太大会因为每个 worker 都持有 OpenCV 的独立上下文而变慢4 到 8 是常见区间。4.2 PSNR 的四个虚高来源PSNR 的计算公式很简单10 * log10(MAX² / MSE)实操里出错的从来不是公式本身而是数据准备阶段import torch def psnr_y(pred, target, data_range1.0): pred/target 都是 (N,1,H,W) 的 float tensor范围 [0, data_range] pred pred.clamp(0.0, data_range) # 必须先裁剪否则越界值会污染 MSE mse torch.mean((pred - target) ** 2).item() if mse 0: return float(inf) return 10.0 * torch.log10(torch.tensor(data_range ** 2 / mse)).item()第一个坑是没做 clamp。网络输出可以超过 1 也可以为负这些越界值会拉高 MSE让 PSNR 偏低如果越界值恰好和 target 同向偏移又会反过来让 MSE 变小PSNR 虚高。不规范的做法是两个方向都存在导致同样的模型在不同脚本里报出相差零点几 dB 的数字。第二个坑是在 uint8 上算 PSNR。先把浮点结果乘 255 四舍五入成 uint8再算 MSE会丢掉小于 1/255 的误差项scale2 这种小倍率任务上差值能到 0.10.3 dB。一致的约定是模型输出保持 float、clamp 到 [0,1]、在这个精度上算 MSE只有 SSIM 计算才转成 uint8因为不少 SSIM 实现要求整数输入。第三个坑是通道口径不统一。用 Y 通道训练却拿 RGB 三通道的平均 PSNR 去汇报两组数字根本不是一个东西跨论文对比时会凭空多出 12 dB。汇报时写清楚「Y 通道、data_range1.0、是否裁边」把口径固定下来。第四个坑是拿 bicubic 上采样结果当预测值去算。如果你的网络输入就是 bicubic 放大图而预测分支某条路径直接输出了输入PSNR 会非常接近 bicubic 基线看起来“没学坏”其实“也没学到”。验证方法是把 bicubic 基线单独算一遍和模型结果并排打印差值小于 0.1 dB 说明网络基本在摆烂。4.3 SSIM 的边界与权值初始化的诊断SSIM 的计算建议直接用成熟实现不要手写from skimage.metrics import structural_similarity as ssim import numpy as np def ssim_y(pred, target): p (pred.clamp(0, 1)[0, 0].cpu().numpy() * 255).round().astype(np.uint8) t (target[0, 0].cpu().numpy() * 255).round().astype(np.uint8) return ssim(p, t, data_range255, win_size11, gaussian_weightsTrue, sigma1.5, use_sample_covarianceFalse)参数说明win_size11、sigma1.5是 SSIM 的经典配置和多数论文口径一致use_sample_covarianceFalse也是为对齐论文口径设成 True 会改变 0.001 量级的数值跨论文比较时要注意。data_range255配 uint8 输入如果传浮点输入就要改成 1.0两者混用会得到负数或大于 1 的荒谬结果。收敛诊断方面SRCNN 只要初始化对了loss 曲线应该在前 2000 步内快速下降然后在 1e-3 量级缓慢磨。如果看到 loss 在前 100 步就跳到 0.5 以上并持续震荡八成是第三层初始化方差太大如果 loss 平稳但一直下不去先查第一层有没有被正确初始化——把conv1.weight.std()打印出来He 初始化下应该在 0.030.05 之间如果接近 0 说明初始化被后面的代码覆盖了。5. 排查SRCNN 复现里最容易翻车的五个点5.1 输出尺寸比输入少一圈现象训练能跑通但计算 loss 时pred和hr形状对不上或者推理出来的图比原图小一圈。原因忘了给卷积层加 padding或者 padding 算错了。9×9 不加 padding 会缩 8 像素5×5 会缩 4 像素三层叠起来输出比输入小 12 像素正好是感受野减一。原论文的 patch 化推理就是这么干的但现代实现基本都是全图进全图出。解决确认每层 padding 等于kernel_size // 2即 conv1 加 4、conv2 加 0、conv3 加 2。写个assert pred.shape hr.shape放在 loss 前面比事后对着报错猜要快得多。5.2 RGB 直训导致色彩漂移现象用 RGB 三通道直接训完放大后的图像整体偏色天空发紫、肤色发红而且换一张图偏的方向还不一样。原因三个通道共享同一组卷积核网络没有能力学出「亮度通道更该被重建、色度通道更该被平滑」这种差异化策略训练数据里色度分布的方差又远大于亮度梯度被色度主导亮度反而学不好。同时 MSE 在 RGB 空间里对色度误差的惩罚被放大进一步把网络往错误方向推。解决回到 YCbCr 分开处理。训练和推理都只让网络碰 Y 通道Cb、Cr 用双三次插值放大到目标尺寸最后cv2.cvtColor拼回 BGR。如果确实需要 RGB 端到端至少把第一层的输入通道从 3 改成 1 再用 1×1 卷积扩展让网络自己学通道加权别让 9×9 卷积直接吃三通道。5.3 PSNR 很高但肉眼看着糊现象验证集 PSNR 比 bicubic 基线高 1 dB 以上但把结果图放大看边缘和纹理并没有变锐甚至有点“塑料感”。原因MSE 优化的目标是逐像素平均误差最小对高频的惩罚权重被低频稀释了网络学到的是「把所有像素往均值拉」而不是「把边缘推回去」。这不是训练出错是 MSE 这个损失函数本身的取向。SRCNN 这类浅层模型在高倍率下尤其明显。解决汇报指标时同时报 PSNR 和 SSIMSSIM 对结构变化更敏感能看出网络是不是真的补了结构肉眼看图要看局部裁剪比如文字边缘、栏杆、头发整图缩略图根本看不出差别。想改善观感就得换损失L1 或者加感知损失但那已经超出 SRCNN 的范畴了作为 baseline 保留 MSE 就行。5.4 最后一层加了 ReLU输出全是非负现象loss 下降到一个平台后就死活下不去验证集 PSNR 卡在某个值附近震荡输出的图像整体偏亮。原因第三层后面误加了 ReLU所有负值被截成 0。重建层输出的是像素值负值代表「比均值暗」被截断后网络只能往亮的方向补偿暗部细节全丢。而且 ReLU 在 0 处的梯度是 0一旦某个位置的输入落到负半轴梯度就再也回不来了这部分权重等于死掉。解决第三层绝不能加激活。前两层加 ReLU 是为了非线性第三层是线性重建加激活就把模型变成了只能表达非负函数的受限模型。改完之后 loss 应该能再往下走一截暗部细节也会回来。5.5 显存爆在大图上现象训练时 batch 设 32 没问题一换到测试集整图推理就 OOM明明只跑一张图。原因显存占用和像素数成正比和 batch 关系不大。训练时 96×96 的 patch 只有 9216 个像素一张 4000×3000 的图有 1200 万像素是第一层的 64 个通道、9×9 卷积核全部展开中间特征图能到几个 GB。解决推理必须分块。把大图切成 192×192 或 256×256 的 tile边缘留 16 像素重叠逐块过网络再按权重拼回去具体写法见下一章。另一个思路是torch.no_grad()加model.half()半精度推理显存直接减半SRCNN 这种小模型对精度不敏感PSNR 差异在 0.01 dB 量级。6. 推理与延伸把 SRCNN 接到实际流程上6.1 大图分块与重叠拼接直接整图送进网络的写法在 demo 上图能跑图上到 2K 以上就会翻车。分块推理的关键是重叠区域要加权融合否则块与块之间会出现明显的接缝。用汉宁窗做权重是最省事的方案import numpy as np import torch torch.no_grad() def infer_tiled(model, y_img, patch192, overlap16, devicecuda): y_img: HxW float32 [0,1]返回同尺寸 Y 通道重建结果 model.eval() h, w y_img.shape out np.zeros_like(y_img, dtypenp.float32) weight np.zeros_like(y_img, dtypenp.float32) def win(n): # 汉宁窗 极小值避免边缘权重为 0 导致除零 return np.hanning(n).astype(np.float32) 1e-3 stride patch - overlap for i in range(0, h, stride): for j in range(0, w, stride): i1, j1 min(i patch, h), min(j patch, w) i0, j0 max(0, i1 - patch), max(0, j1 - patch) tile y_img[i0:i1, j0:j1] t torch.from_numpy(tile).unsqueeze(0).unsqueeze(0).to(device) pred model(t).clamp(0, 1).cpu().numpy()[0, 0] wgt win(pred.shape[0])[:, None] * win(pred.shape[1])[None, :] out[i0:i1, j0:j1] pred * wgt weight[i0:i1, j0:j1] wgt return out / np.maximum(weight, 1e-8)逻辑说明i0 max(0, i1 - patch)这行保证最后一块紧贴边界而不是越界代价是最后一块和倒数第二块的重叠会比overlap大融合时靠权重归一化自动处理。汉宁窗在块中心权重接近 1、在块边缘衰减到接近 0两块重叠区域的权重互补加权平均后接缝不可见。参数说明patch192是按 8 GB 显存在 scale4 下估算的显存宽裕可以拉到 256 或 384越大接缝越少但显存占用按平方涨overlap至少要大于感受野的一半也就是 7 像素以上取 16 是留了余量。如果忘了model.eval()BatchNorm 的统计量会按每个 tile 单独更新同一张图不同块的输出风格会不一致这种 bug 肉眼很难定位。6.2 和后续模型的对照什么时候该换掉 SRCNNSRCNN 作为 baseline 的价值在于它足够简单简单到任何异常都能归因到具体某一行。但它有两个绕不过去的结构性限制上采样前置意味着所有卷积都在高分辨率特征图上算计算量随放大倍数的平方增长三层结构感受野固定 13×13对高倍率重建的上下文不够。模型上采样位置参数量量级相对 SRCNN 的变化SRCNN前置双三次8K三层直筒MSE 端到端FSRCNN后置十余 K末端用反卷积放大计算量前移ESPCN后置更小亚像素卷积实时性更好VDSR前置数十万20 层 全局残差指标明显更高EDSR后置百万级残差块堆叠去掉 BN指标强但慢选型逻辑很直接要实时、要嵌到端侧往 ESPCN 方向走只要指标、算力不限VDSR 起步想理解超分模型演进SRCNN 到 FSRCNN 这一步是必看的——上采样位置从网络前挪到网络后带来的计算量变化是数量级的这个道理后面所有模型都在用。6.3 一个验收入口复现 SRCNN 的过程中我踩过最深的坑不是结构写错是「以为跑通了实际上没学到东西」。后来形成一个习惯每训完一个 baseline先在标准测试集Set5、Set14 这类通用超分评测集上把 bicubic、模型输出、HR 三张图并排裁同一块局部存下来人工扫一眼再对齐打印 PSNR 和 SSIM。这一步花不到五分钟但能在指标看起来正常的情况下截住「网络只是复读了输入」这类隐性失败。指标是黑匣子局部裁剪图才是后悔药。从那以后每次开新数据集前我都强制先把这三张图跑一遍再动超参调优希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑