ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch图像超分辨率重建实战:从SRCNN到训练避坑全解析

PyTorch图像超分辨率重建实战:从SRCNN到训练避坑全解析 简介一份基于Python的图像超分辨率重建源码包面向想学习或复现SR算法的开发者覆盖数据预处理、模型定义、训练与测试完整流程。包内共5个文件全部为Python脚本分别承担工具函数、数据扩展、主流程、模型构建和测试评估等角色压缩包仅10KB轻量便于快速下载分析。已有619人学习/下载。源码对应超分辨率技术中的核心流程可结合SRCNN、VDSR等深度学习模型思路查看数据加载、模型搭建与训练评估的具体实现。通过阅读和运行这些脚本能直观理解低分辨率图像到高分辨率重建的工程细节也可基于现有模块进行二次修改与优化。整体适合配合理论文章进行动手实践是入门图像超分辨率的实用参考。1. 图像超分辨率重建源码先搞清这份 Python 工程到底解决什么问题监控截图里看不清车牌、老照片放大后人脸发虚、电商图一放大边缘全是锯齿——图像超分辨率重建SR就是把低分辨率LR图恢复成高分辨率HR图的技术不是简单插值填像素。这份 Python 源码包含 utils.py、expand_data.py、main.py、model.py、test.py 五个文件从数据增强、模型定义到训练、测试覆盖一条用深度学习做超分的完整链路。适合课程设计、毕业设计或已会用 OpenCV 插值放大但嫌效果不够好的从业者。它能帮你把从零训练一个超分模型完整跑通也是你换数据集、换倍率时的改造起点。2. 算法选型与源码阅读SRCNN、VDSR、ESPCN、SRGAN 怎么分辨超分辨率重建本质是一个病态问题ill-posed problem同一个低分辨率像素块可以对应无数种高分辨率纹理。算法干的不是查表还原而是在约束条件下猜出最可能的高频细节。重建质量高说的就是这个猜测足够接近真实。这也是为什么插值方法一旦放大超过 2 倍就露馅——它假设图像局部平滑而真实世界的纹理恰恰充满高频跳变。看源码之前我习惯先把几条主路线在脑子里过一遍不然拿到 model.py 根本不知道自己在看什么。超分方法分两大类基于插值的方法和基于学习的方法。插值方法不依赖数据拿到一张图就能算速度快但补不出新细节学习方法依赖大量 LR-HR 配对数据训练阶段费时费力但推理时能把训练中学到的纹理先验用上效果上限高得多。这份源码里出现了 model.py 和 main.py显然是学习方法的工程形态具体是哪一条路线打开 model.py 三分钟就能判断。2.1 插值方法与学习式方法的本质区别插值方法里最常碰见的是最近邻、双线性和双三次三种。最近邻直接复制邻近像素放大后锯齿感明显基本只用来做速度优先的预览双线性对周围 2x2 像素做加权平均图像被抹得发糊双三次对周围 4x4 像素做多项式拟合是 OpenCV 里cv2.INTER_CUBIC的实现也是超分领域生成 LR 图最常用的降采样方式。插值的本质是平滑假设它只能把已有信息抹匀无法凭空生成新的高频纹理所以放大倍数一高边缘和纹理都撑不住。学习式方法走的是另一条路。传统机器学习做法先手工提取特征再用稀疏编码或近邻检索把 LR 块映射到 HR 块特征工程和字典学习是两套独立流程工程上很绕。深度学习方法把特征提取、非线性映射、图像重建揉进一个端到端网络里输入 LR、输出 HR中间过程全部自动学这也是 2014 年 SRCNN 之后的主流形态。方法核心思路优点局限最近邻插值复制最近的像素计算开销最低锯齿严重双线性插值2x2 邻域加权平滑过渡细节全部抹平双三次插值4x4 邻域多项式拟合边缘比双线性好无法恢复真实纹理深度学习端到端学习 LR→HR 映射能生成新细节依赖数据与算力这张表是选型的第一层判断如果你的需求是放大后别太糊就行、不能等 GPU插值就够了如果追求恢复出纹理细节才需要往下看学习式方法。这份源码既然训练、测试脚本齐全目标显然在后者。2.2 四个经典网络架构SRCNN、VDSR、ESPCN、SRGAN 的选型逻辑SRCNN 是 2014 年第一篇把 CNN 引入超分的文章。结构上只有三层卷积特征提取、非线性映射、重建。注意它的输入不是原始 LR而是先双三次插值放大到目标尺寸再进网络所以卷积计算全部发生在高分辨率空间算力开销大感受野也小。作为教学基线它非常合格——结构简单、调试容易、CPU 上也能跑通很多课程源码都以它为底。VDSR 解决的问题是深浅。SRCNN 只有三层学到的高频映射能力有限VDSR 把卷积层数加到 20 层并引入残差学习网络只学 LR 和 HR 之间的差值而不是直接输出整幅图像。输入尺寸做不到太大所以 VDSR 一般要求输入 LR 图有一定宽度的边界上下文训练也得用小学习率慢慢磨。它的意义在于示范了深网络 残差在超分里的价值边缘恢复比 SRCNN 明显更利落。ESPCN 是冲着效率去的。它提出亚像素卷积sub-pixel convolution对应 PyTorch 里的nn.PixelShuffle网络先在低分辨率特征图上做卷积最后一层把多个通道重新排列成高分辨率图像。这样计算量集中在分辨率最低的阶段推理速度很快适合实时视频超分这类场景。代价是训练时对通道数设置比较敏感写错排列逻辑会出现周期性伪影。SRGAN 关注的不再是 PSNR而是人眼感受。前几个方法都用像素级损失L2 或 L1训练这种损失天然偏向平滑结果——PSNR 高但放大后皮肤、毛发像被磨过。SRGAN 引入生成对抗结构生成器输出 HR 图判别器判断它是真图还是生成图再叠加感知损失逼迫网络生成纹理逼真的结果。视觉效果确实更强但训练不稳定、调参成本高而且 PSNR 常常不如前面几个。架构核心机制优点适合场景SRCNN三层卷积先放大后重建结构简单教学友好入门、CPU 训练VDSR20 层卷积 残差学习边缘恢复好有 GPU 的常规训练ESPCN亚像素卷积重排推理快、省显存实时/低功耗场景SRGAN生成对抗 感知损失视觉纹理真实追求观感而非指标拿到 model.py 的时候先对着这张表找特征看到三层左右的卷积没有残差大概率是 SRCNN 系看到多个相同结构的卷积块和残差连接往 VDSR/残差网络方向想看到PixelShuffle就是 ESPCN 系看到两个网络生成器和判别器和两个损失那训练脚本里必然有 GAN 的训练循环。这套源码的文件列表里只有一个 model.py 和一个 main.py最可能的是 SRCNN 或轻量残差变体——这类教学源码用 SRCNN 的概率最高因为显存友好、容易跑通。2.3 拿到源码先别急着跑五分钟反推 model.py 的路线我拿到任何一份超分源码第一件事不是配环境而是花五分钟读 model.py判断项目水平、路线和潜在坑位。在终端执行grep -n class\|def forward\|Conv2d\|PixelShuffle\|Upsample\|ConvTranspose2d\|ReLU\|ResidualBlock model.py这条命令把所有类定义、前向函数、卷积层、上采样操作带行号列出来。看到Conv2d的密集程度可以判断网络深浅看到PixelShuffle说明走了子像素路线看到Upsample(scale_factor, modebilinear)说明上采样放在网络前段看到带残差连接的 Block 类说明借鉴了 VDSR 或更深结构。光看这个 grep 输出模型的骨架基本就清楚了。再看损失函数和优化器配置这决定训练行为grep -n Loss\|optimizer\|lr\|weight_decay\|scheduler main.pyMSELoss对应 PSNR 导向的训练目标L1Loss在近几年的超分实现里更常见训练更稳Adam配1e-4左右的学习率是常规组合SGD的话一般要配更低的学习率和动量。这一轮看完心里就有底了这是教学型基线还是可扩展工程训练大概要多少显存跑通一个能看结果的实验需要多久。提示如果 grep 出来的结构非常简单比如只有两三个卷积模块、没有残差也没有 PixelShuffle不要失望。教学源码的价值在于链路完整而不是指标刷新。先用它把流程跑通再逐步替换模型文件是性价比最高的用法。从这份源码的文件组织来看——五个文件分工明确expand_data.py独立负责数据增强test.py独立负责推理评测——它是典型的教学工程不追 SOTA目标是把超分的完整物链路在两三百行代码里走通。这也是它值得下载的原因你改的不是一个黑匣子而是每一步都能看懂、能改动的白盒子。3. 环境搭建与数据准备从零把五个文件的工程跑通拿到源码先别急着双击 main.py。超分训练依赖 PyTorch 环境和配对数据这两步没准备好跑起来之后排除错的成本会翻倍。下面按我自己的操作顺序来写。3.1 Python 环境配置torch 版本、CUDA 和 opencv 的搭配如果你之前已经装过 python跳过安装那一步直接建虚拟环境。我一般用 conda因为方便切换 pytorch 和 cuda 的版本组合翻车后也容易整个删掉重来。网上各种 python 安装教程都行但环境隔离这一步别省直接在 base 环境里装 torch早晚会因为版本冲突后悔conda create -n superres python3.8 conda activate superres pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy tqdmPython 3.8 是兼容性最稳的选择太新的版本偶尔会和旧代码里的语法或依赖冲突。torch 版本不用追求最新按你机器上 CUDA 的版本来如果没装 CUDA 或者只是想先跑通逻辑把--index-url换成 CPU 版即可SRCNN 这种小网络在 CPU 上也能完成一次小规模训练。opencv 负责读图和双三次缩放numpy 负责矩阵运算tqdm 用来显示训练进度。装完验证一下 GPU 是否可用python -c import torch; print(torch.__version__, torch.cuda.is_available())输出True说明 GPU 可用False也不影响先把流程跑通只是训练速度慢一个量级。如果这里报了 CUDA 相关的版本错误多半是 torch 和驱动不匹配去 PyTorch 官网按 CUDA 版本重新选 wheel 就行不要自己在终端里乱设环境变量硬凑。3.2 数据目录怎么组织LR/HR 配对是超分训练的命根子超分是监督学习训练数据必须成对出现一张 HR 原图配一张由它降采样得到的 LR 图。常见的目录结构是这样data/ train/ hr/ # 高清原图png 或 jpg lr/ # 对应降采样图文件名与 hr 一一对应 val/ hr/ lr/LR 图怎么来最常见也是论文默认的做法是用双三次插值把 HR 图缩小到目标倍率import cv2 import os def make_lr(hr_path, lr_path, scale2): # 用双三次插值把 HR 缩小 scale 倍生成配对的 LR img cv2.imread(hr_path) h, w img.shape[:2] lr cv2.resize(img, (w // scale, h // scale), interpolationcv2.INTER_CUBIC) cv2.imwrite(lr_path, lr) # 对 train/hr 下所有原图生成 2x 的 LR for name in os.listdir(data/train/hr): make_lr(fdata/train/hr/{name}, fdata/train/lr/{name.replace(.png, _x2.png)})这段代码的关键在cv2.INTER_CUBIC超分领域约定俗成用双三次降采样来生成 LR因为大多数论文、预训练模型和评价基准比如 Set5、Set14都默认这个退化方式。你在训练和测试时必须用同一种退化方式否则训练时模型学的是双三次降采样后的恢复测试时喂给它一张别的方式降采样的图效果必然打折。val 集不用太大十张覆盖不同场景的图就够用来观察训练趋势真正的指标评测放到训练结束后的测试阶段。注意不要用 PIL 的Image.resize默认插值或 OpenCV 的INTER_NEAREST生成 LR得到的结果会让模型学到错误的映射。低倍率下差异不大4x 时差异非常明显。3.3 expand_data.py 在做什么数据增强的常见写法数据集通常不大几百张 HR 图直接训练容易过拟合所以这份源码专门把数据增强抽成了expand_data.py。超分里的增强和分类不一样不能随便改变像素内容只能做几何变换随机裁剪、水平翻转、垂直翻转、90 度旋转。这些变换不改变图像的退化关系——HR 和 LR 必须做完全相同的变换配对关系才成立。一个典型的实现长这样import random def get_patch(hr, lr, patch_size96, scale2): # 从同一位置裁出 HR/LR 小方块位置满足 scale 倍率关系 ih, iw lr.shape[:2] iy random.randint(0, ih - patch_size) ix random.randint(0, iw - patch_size) lr_patch lr[iy:iy patch_size, ix:ix patch_size] hr_patch hr[iy * scale:(iy patch_size) * scale, ix * scale:(ix patch_size) * scale] return hr_patch, lr_patch def augment(hr, lr): # 水平和垂直翻转增加样本量90 度旋转提高网络对方向的鲁棒性 if random.random() 0.5: hr, lr hr[:, ::-1], lr[:, ::-1] if random.random() 0.5: hr, lr hr[::-1], lr[::-1] if random.random() 0.25: hr hr.transpose(1, 0, 2) lr lr.transpose(1, 0, 2) return hr, lr这两段函数是超分训练里最标准的增强写法。注意get_patch里的坐标换算LR 图的坐标乘上 scale 才是 HR 图里对应的坐标这一步算错网络对不齐输入输出损失函数永远降不下去。patch_size 一般取 64 到 96太大显存压力大太小模型学不到足够的上下文。expand_data.py在你的源码里大概率就是这套逻辑你可以直接改 patch_size 观察训练速度和效果的变化。角度旋转一次就能让样本量乘 4配合翻转几百张原图就能撑起一个像样的训练集。3.4 utils.py 里的 PSNR 与 SSIM先搞懂评测口径再谈效果utils.py里通常放着评测指标最常见的是 PSNR 和 SSIM。PSNR 的公式看着简单实现里全是细节超分评测一般只算 Y 通道亮度因为人眼对亮度最敏感RGB 三通道全算反而会稀释指标。import cv2 import numpy as np def psnr_y(img1, img2, shave4): # 输入是 0-255 的 RGB 图先转 YCbCr 再取 Y 通道 y1 cv2.cvtColor(img1, cv2.COLOR_BGR2YCrCb)[..., 0].astype(np.float64) y2 cv2.cvtColor(img2, cv2.COLOR_BGR2YCrCb)[..., 0].astype(np.float64) # 裁掉边缘 shave 像素避免边界效应对指标的影响 y1 y1[shave:-shave, shave:-shave] y2 y2[shave:-shave, shave:-shave] mse np.mean((y1 - y2) ** 2) if mse 0: return float(inf) return 10.0 * np.log10(255.0 * 255.0 / mse)三个细节值得记住第一必须裁边shave 通常取 scale 或者 scale 的整数倍因为网络边缘的插值/反卷积结果天然不可靠第二范围是 255 而不是 1说明输入图是 0-255 的 uint8如果你代码里归一化到了 [0,1]公式里的 255 要相应改成 1第三channel 顺序是 BGR 是 OpenCV 的默认换成 PIL 读图就是 RGB转换函数要对应。SSIM 直接调skimage.metrics.structural_similarity就行注意设对data_range255和channel_axis参数同样只算 Y 通道、同样裁边参数保持一致才有可比性。这套评测口径直接决定了你在 test.py 里看到的数字是否有意义。如果一篇博客说 PSNR 33dB另一篇说 29dB先别急着下结论先确认它们的评测口径是否一致——shave 大小、是否只算 Y 通道、LR 生成方式任何一个不同指标就差出两三个 dB这是超分领域最大的数字陷阱之一。4. 训练主循环与参数解读main.py 里最值得改的五个超参数数据准备好之后训练就是 main.py 的事。很多初学者拿到代码直接python main.py然后盯着 loss 发呆跑完一轮发现效果不对也不知道该动哪儿。这一章把训练主循环拆开再给你一张参数调整的对照表。4.1 训练主循环在做什么一次 forward-backward 的完整链条无论哪份超分源码main.py 的训练骨架都长一个样# 训练主循环的通用骨架对应 main.py 的核心逻辑 for epoch in range(args.epochs): model.train() for lr_img, hr_img in train_loader: lr_img lr_img.to(device) # 输入搬到 GPU hr_img hr_img.to(device) # 标签搬到 GPU out model(lr_img) # 前向LR 经过网络得到重建 HR loss criterion(out, hr_img) # 与真实 HR 算差距 optimizer.zero_grad() # 清空上一步的梯度 loss.backward() # 反向传播计算梯度 optimizer.step() # 更新网络权重 torch.save(model.state_dict(), fcheckpoints/epoch_{epoch}.pth)逐行看model.train()把网络切到训练模式影响 BatchNorm 和 Dropout 的行为lr_img, hr_img来自 DataLoader 的 batch形状是(B, C, H, W)out是网络重建的高分辨率图和hr_img尺寸必须严格一致不一致说明模型输出层或数据配对出错了loss.backward()之后的optimizer.step()才是真正更新权重的地方。每轮结束存一个 checkpoint是防止训练中断白烧 GPU 的最简单手段也方便你回溯哪个 epoch 的效果最好。如果你的 main.py 里还有验证环节一般会在每个 epoch 结束或每 N 个 epoch 跑一次 val_loader记录验证集 PSNR只在验证指标更好时覆盖保存模型。这份源码里 test.py 单独存在意味着评测默认在训练完成后做你需要自己注意别把最后一次保存的 checkpoint 当成最优——训练后期 loss 可能在震荡最后一步未必是效果最好的一步。4.2 五个关键超参数选错一个等于白烧几小时 GPU训练超参数决定了收敛速度和最终效果五个参数优先关注参数推荐起始值怎么调典型翻车点scale与 LR 生成倍率一致2x 跑通后再试 4x数据是 2x、模型输出是 4x尺寸对不上直接报错patch_size64~96显存不足先降这个调太小32模型学不到上下文batch_size16配合 patch_size 一起降单独把 batch 降到 1BN 层会失效learning_rate1e-4Adam不收敛降到 3e-55e-3 起手loss 直接震荡epochs50~100看验证指标是否 plateau只看训练 loss 不看验证容易过拟合scale是全局一致性最强的参数数据生成用它、数据加载裁剪用它、模型输出尺寸校验也用它。改 scale 不是改一个数字那么简单牵一发动全身具体怎么联动我在最后一章细说。patch_size和batch_size是显存的两个旋钮优先降 patch_size 而不是 batch_size因为 patch_size 影响一次看到的内容范围batch_size 只影响梯度估计的稳定性。学习率用 Adam 配1e-4起步这是超分领域被验证过无数次的组合换成 SGD 的话一般还要降一个量级并配 momentum。epochs 先定 50 跑一轮看趋势验证 PSNR 还在涨就继续涨不动了就收。4.3 数据加载和训练命令从 Dataset 到跑起来训练入口的 DataLoader 靠 Dataset 类支撑超分 Dataset 的核心工作是把 LR-HR 配对、裁剪、增强、归一化串起来# 对应 main.py 里 Dataset 的核心逻辑 class SRDataset(Dataset): def __init__(self, hr_dir, lr_dir, scale, patch_size): self.hr_paths sorted(glob.glob(hr_dir /*.png)) self.lr_paths sorted(glob.glob(lr_dir /*.png)) # HR 和 LR 文件数量、排序必须一一对应 assert len(self.hr_paths) len(self.lr_paths), HR/LR 数量不一致 def __getitem__(self, idx): hr cv2.imread(self.hr_paths[idx]) lr cv2.imread(self.lr_paths[idx]) hr, lr get_patch(hr, lr, self.patch_size, self.scale) hr, lr augment(hr, lr) # HWC - CHWBGR - RGB并归一化到 [0, 1] hr torch.from_numpy(hr.transpose(2, 0, 1)[::-1].copy()).float() / 255.0 lr torch.from_numpy(lr.transpose(2, 0, 1)[::-1].copy()).float() / 255.0 return lr, hr注意两个隐藏细节一是路径排序sorted保证 HR 和 LR 按文件名顺序对齐如果文件名规则不一致比如 HR 叫001.png、LR 叫001_x2.pngsorted 之后可能错位训练时 loss 不降先检查这一步二是[::-1]的 BGR 转 RGB 操作OpenCV 读进来是 BGRPyTorch 的图像模型普遍按 RGB 约定转不转直接影响最终效果。虽然超分训练一般不用预训练权重但评测时和别人的结果对比通道顺序不一致会差出明显数字。数据链路准备好之后训练命令长这样python main.py --scale 2 --patch-size 96 --batch-size 16 \ --lr 1e-4 --epochs 50 --gpu 0 --checkpoint-dir ./checkpoints每个参数的落点--scale 2告诉数据加载和模型输出你要做 2x 超分--patch-size 96对应上面 Dataset 里的裁剪尺寸--batch-size和--lr直接传给 DataLoader 和优化器--checkpoint-dir是权重保存目录建议每次实验换一个目录别覆盖掉上一轮的对比结果。如果你机器上没有 GPU把--gpu 0去掉或者写--gpu -1代码里一般会退回 CPU小 patch 小规模训练也能在可接受的时间内完成。训练完成后用 test.py 验证python test.py --checkpoint ./checkpoints/epoch_50.pth --input data/val/lr/demo.png --scale 2。它会加载权重、重建图像并把结果写到指定目录。第一次跑通之前建议先用一张小图验证输出尺寸是不是正确的 scale 倍数再做正式的指标评测。注意训练前先确认代码里torch.manual_seed(seed)是否固定了随机种子。不固定的话同样的命令每次跑出来的曲线会有差异排错时很难判断改动是否有效。固定 seed 后复现性会好很多对比实验才有意义。5. 避坑与排查五个高频翻车现场及对应处理办法超分训练里翻车是常态我自己第一次跑 SRCNN 就交了整晚 GPU 的学费。下面五个问题是我在课程源码和实际项目里见过最多的基本覆盖了从训练到测试的主要坑位。每一条都按现象 → 原因 → 解决来写方便你对照排查。5.1 训练 loss 不降反升曲线像心电图**现象**日志里 loss 前几个 epoch 还在 0.1 左右后面开始上下乱跳甚至越来越大验证 PSNR 原地不动。**原因**最常见的是学习率太大超分网络的损失面很陡Adam 配 5e-3 以上的学习率很容易在最优解附近弹跳其次是输入数据没有归一化raw 的 0-255 像素值喂进网络梯度过大导致训练发散还有一种隐蔽的情况是 batch 内图像尺寸不一致DataLoader 把不同 H/W 的图硬拼要么报错要么产生异常张量。**解决**先把学习率降到 1e-4 甚至 3e-5确认预处理里图像除以 255 归一化到 [0,1]在 Dataset 的__getitem__里打印一下返回张量的 shape保证每张图都是(3, patch, patch)。这三步做完绝大多数震荡问题会消失。如果还震荡检查 loss 是否把 HR 和 LR 的输入输出弄反了这种低级错误在改代码时很容易混进来。5.2 显存 OOMbatch 从 32 降到 4 还是炸**现象**训练一开始就报CUDA out of memory把 batch_size 一路降到 4 甚至 2仍然撑不住。**原因**你一直在降 batch但 patch_size 没动。显存占用主要由输入张量、中间特征图和梯度三部分组成patch 尺寸的平方增长直接放大特征图占用另外如果网络把上采样放在前面卷积全部在高分辨率空间计算显存消耗会翻好几倍这是 SRCNN 这类先放大后卷积结构的通病。**解决**先把 patch_size 从 96 降到 48用nvidia-smi观察显存占用再来考虑 batch。如果还不行检查模型输出尺寸——有的实现会在网络末端用nn.Upsample把图放大到 4x而你以为自己在跑 2x白白浪费显存。还有一个现代解法是自动混合精度训练循环里加torch.cuda.amp.autocast()FP16 计算能把显存占用压到一半左右代价是少数算子可能溢出需要GradScaler配合。5.3 训练 PSNR 很高测试图却糊成一团**现象**验证集 PSNR 能到 32dB 以上但拿一张新的测试图跑 test.py输出明显发糊细节像是被平均过。**原因**训练和测试的数据分布不一致这是超分项目里最典型的隐性翻车。常见的有三类一是测试图本身分辨率远大于训练 patch网络没见过这么大尺寸的输入二是测试图的降采样方式和训练时不一致你训练用双三次生成的 LR测试却拿一张本来就小的图直接放大退化方式根本不匹配三是训练和测试的归一化方式不一致训练时 [0,1]测试时忘了除 255网络输出的分布完全不对。**解决**先把测试流程调成和训练严格一致——同一套退化函数生成 LR、同一个通道顺序、同一个归一化范围如果测试图比训练图大太多测试时把图切成多块重叠 patch 分别重建再拼回去避免一次性整图进网络。改完这三处再看效果多数训练好测试差的问题出在这而不是模型本身。5.4 重建结果出现棋盘格状伪影**现象**放大后的图像网格状纹理明显尤其在边缘和文字周围像蒙了一层棋盘格纱窗。**原因**棋盘伪影几乎都是反卷积转置卷积ConvTranspose2d或 PixelShuffle 使用不当产生的。转置卷积在某些配置下输出在中心位置权重高、边缘权重低叠加起来形成不均匀的重叠花纹PixelShuffle 如果通道排列顺序写错重排后也会产生周期性错位。**解决**先查 model.py 里用的是什么上采样方式。如果是ConvTranspose2d优先换成nn.PixelShuffle或先nn.Upsample(scale_factor, modebilinear)再接一层卷积如果是 PixelShuffle确认它的输入通道数等于输出通道数乘 scale 的平方并直接用torch.nn.functional.pixel_shuffle不要自己手写 reshape。改完重训一版棋盘纹通常在几个 epoch 内就能看到明显改善。5.5 有 GPU 但训练速度和 CPU 差不多现象torch.cuda.is_available()输出 True但一个 epoch 的时间没有明显加速GPU 利用率在 nvidia-smi 里一直在个位数徘徊。**原因**数据加载成了瓶颈。最常见的是num_workers0DataLoader 在 GPU 训练时用单线程在主进程里读图和处理GPU 大部分时间在等数据另一个是每次迭代都在 Dataset 里重新调用cv2.imread没有做缓存几百张图反复读盘。**解决**DataLoader 里把num_workers设为 CPU 核数的一半比如 4 或 8pin_memoryTrue打开如果数据量不大在 epoch 开始前把全部训练集提前读进内存做缓存能省掉反复读盘的开销。改完之后 GPU 利用率通常会从个位数涨到 80% 以上训练速度的提升比换显卡还明显。6. 进阶验证把源码从 2x 改成 4x 的三个联动动作2x 跑通之后最常见的下一步需求是 4x——监控画面放大、老照片修复、卫星图切片增强基本都是 4x。直接把 main.py 里的 scale 改成 4 并不会工作因为超分项目里 scale 是一个贯穿全局的约束至少三处必须一起动。6.1 三处必须联动修改第一处是数据生成。训练用的 LR 必须改成双三次降采样 4 倍测试时生成 LR 的函数也要同步。最好把降采样封装成一个函数训练和 test.py 共用同一个入口def degrade(hr, scale): # 训练和测试共用的退化函数保证 LR 生成方式完全一致 h, w hr.shape[:2] lr cv2.resize(hr, (w // scale, h // scale), interpolationcv2.INTER_CUBIC) return lr第二处是网络输出。如果你的模型末端原本是 2x 的上采样4x 时要么把上采样倍数改成 4要么在 ESPCN 系模型里把最后一个卷积的输出通道数从out_ch * 4改成out_ch * 16——PixelShuffle 的通道数要求是输出通道数乘 scale 的平方这个数字算错运行到最后一层必然报 shape 错误。第三处是评测裁边PSNR 计算里的 shave 参数一般要跟着 scale 放大2x 时裁 4 像素4x 时可以裁 8 像素左右否则边界效应会污染指标。6.2 改完怎么验证固定三件套我自己的验证流程是固定的三件事一张自然图像、一条退化函数、一份对比记录。先拿一张包含文字和边缘的清晰照片用degrade生成 4x LR再分别用插值放大和训练好的模型重建并排对比然后记录重建图的 PSNR、SSIM 和推理耗时最后把同样的流程用在不同风格图片上跑一遍确认不是只在某一张图上效果好。文字边缘的锐利程度和是否有伪影比 PSNR 数字更能反映模型真实水平。改 scale 最容易忽略的其实是评估习惯。从那以后我每次改倍率都强制自己先把退化函数、网络输出、评测裁边三处同时改掉再拿一张固定测试图跑通首轮确认指标和视觉都正常之后才开始大规模训练——这个习惯帮我避免了好几次训了一夜、起来发现 scale 没改全的浪费。希望帮到你。本文还有配套的精品资源点击获取
返回列表