资讯动态

MATLAB实现SRCNN图像超分辨率:从训练到重建的完整指南

发布时间:2026/9/2 1:30:31 来源:尧图企业网站定制
简介这份资源提供的是基于卷积神经网络CNN实现彩色图像超分辨率的MATLAB工程以SRCNN为核心框架解决从低分辨率图像恢复高分辨率清晰图像的问题适合图像处理初学者、深度学习入门者以及需要复现经典超分辨率模型的研究人员。整个压缩包共32个文件其中5个m脚本负责模型定义、训练、演示与评估8个mat文件用于存放网络参数或中间数据19个bmp图像组成Set5、Set14等标准测试集包体约7.39MB。目前已有341人学习该资源。代码覆盖了数据预处理、CNN网络构建、训练到预测的完整流程附有modcrop、shave、compute_psnr等工具可直接运行demo_SR.m完成超分辨率重建并计算PSNR指标方便复现、对比和二次开发。借助这套工程读者能快速理解SRCNN的工作机制并在此基础上进行参数调优或改进实验。 图像超分辨率Super-ResolutionSR这个方向说起来挺老但真正变得“能打”还是深度学习普及之后的事。最近帮人做课题又被问到同一个问题能不能不折腾PyTorch和CUDA直接在MATLAB里用卷积神经网络CNN把彩色图像的超分辨率跑通我的回答是能而且整套流程完全可以在MATLAB里闭环。这篇文章就是我把这个流程完整落地后的记录从训练数据怎么构造、网络怎么搭、训练参数怎么调到重建质量怎么评都会讲到中间还会穿插几个我实际踩过的坑。如果你正在做图像处理相关的课程设计、毕业设计或者只是想在现有工作流里快速验证超分思路这篇文章可以直接参考。1. 超分辨率这种逆问题为什么CNN能派上用场1.1 从插值到学习超分难在哪儿超分辨率解决的问题是给定一张低分辨率图像恢复出清晰的高分辨率图像。这件事麻烦的地方在于低分辨率图像是在下采样过程中丢失了大量高频信息形成的同一张低分辨率图可以对应无数张合理的高分辨率图所以它是个典型的不适定逆问题。传统方法里最常用的是双三次插值bicubic它本质上是利用相邻像素做平滑猜测能把边缘补得比最近邻插值柔和但补不出“确实丢失”的纹理和结构。换句话说插值是在猜测缺失像素的数值而超分辨率要做的是恢复缺失的信息本身这两者的难度完全不是一个量级。1.2 SRCNN三卷积层的工作逻辑CNN之所以能在这个问题上派上用场是因为它可以直接从大量成对的低分辨率/高分辨率样本里学出“低分辨率图像到高分辨率图像”的映射关系。做一个简单的类比插值相当于你拿着古代地图猜现代街道CNN则是看过成千上万组古今对照图之后学会了“看到什么形状就补出什么细节”的规律。我这次实现的是经典的SRCNN结构它用三个卷积层就完成了整套流程第一层9×9卷积输出64个特征图负责从输入图像里提取局部结构特征相当于把各种边缘、角点、纹理模式都激活到对应的特征通道里。第二层1×1卷积输出32个特征图做跨通道的非线性映射。这里的1×1卷积相当于一个轻量级的全连接层把上一层的特征重新组合。第三层5×5卷积输出3个通道把组合后的特征还原成RGB图像。整个网络就是“像素到特征、特征到特征、特征到像素”的映射过程。训练时直接用网络输出和原始高分辨率图像的差异作为损失端到端优化不需要手工设计任何特征提取器。1.3 彩色图和灰度图处理上的分岔灰度超分只需要处理1个通道但彩色图像有RGB三个通道处理时有两条常见路线第一条是直接对RGB三个通道一起预测网络输出3个通道。好处是简单直观网络能自己学习通道之间的相关性代价是卷积核的参数量会增加训练时间相对变长。第二条是先转成YCbCr颜色空间只用CNN对亮度通道Y做超分色度通道Cb、Cr直接用双三次插值放大。理由是人的视觉系统对亮度细节比对色度细节敏感得多而且SRCNN原论文就是这么做的。MATLAB里用rgb2ycbcr和ycbcr2rgb切换很方便但要注意这两组函数对double和uint8类型图像的处理范围不同用double图时YCbCr的范围也是double型0到1用uint8图时是0到255混用很容易出问题。我自己的建议是第一次跑通流程用RGB直连方式后期想做效果优化再切到YCbCr方案。两个版本我都试过后面的“如果想进一步优化”部分会给出结论。2. 开训之前先把训练对和评价指标弄明白2.1 HR-LR训练对怎么生成很多人第一次写超分训练脚本时会犯一个方向性错误直接拿低分辨率图当输入、高分辨率图当标签。这样不是不行但会让网络输出的尺寸和标签对不上损失函数都算不了。SRCNN的经典做法是先把高分辨率图下采样得到低分辨率图再把低分辨率图用双三次插值放大回原来的尺寸用这个“插值放大后的图”作为网络输入原始高清图作为标签。也就是说网络学的是“从双三次插值结果到真实高清细节”之间的映射。核心代码如下scale 3; hr im2double(imread(train01.png)); % 原始高清图 lr imresize(hr, 1/scale, bicubic); % 下采样得到低分辨率图 input imresize(lr, scale, bicubic); % 插值放大作为网络输入 % 此时 input 和 hr 尺寸一致label 就是 hr需要强调的是下采样和上采样都建议用bicubic这样输入和标签在像素位置上是严格对齐的。如果你下采样用bicubic、上采样用bilinear等于在训练时故意给输入引入一个亚像素偏移网络要额外去学这个偏移效果会打折扣。2.2 patch裁剪、数据增强与内存控制完整图片直接丢进网络训练也可以但图片数量少时很容易过拟合而且显存占用大。通常的做法是从每张图里裁剪出固定大小的patch。patchSize我用的是SRCNN论文里的41×41stride也是41也就是相邻patch不重叠。patch太大没有意义因为轻量网络的实际感受野就那么大太大的patch反而让每个batch能容纳的样本数变少。裁剪时会遇到一个内存问题如果把所有patch都拼成一个四维数组几十万patch会非常吃内存。我的做法是先用cell数组按图像存放再统一合并patchSize 41; stride 41; allInputs {}; allLabels {}; for i 1:numel(imageNames) hr im2double(imread(imageNames{i})); lr imresize(hr, 1/scale, bicubic); input imresize(lr, scale, bicubic); [h, w, ~] size(hr); rows 1:stride:h-patchSize1; cols 1:stride:w-patchSize1; numPatches numel(rows) * numel(cols); patchesIn zeros(patchSize, patchSize, 3, numPatches); patchesLabel zeros(patchSize, patchSize, 3, numPatches); idx 0; for r rows for c cols idx idx 1; patchesIn(:,:,:,idx) input(r:rpatchSize-1, c:cpatchSize-1, :); patchesLabel(:,:,:,idx) hr(r:rpatchSize-1, c:cpatchSize-1, :); end end allInputs{end1} patchesIn; allLabels{end1} patchesLabel; end XTrain cat(4, allInputs{:}); YTrain cat(4, allLabels{:});数据增强方面我做了最简单的旋转和翻转对每张patch旋转90/180/270度再做一次水平翻转数据量直接变成原来的8倍。这个操作在超分任务里很有效因为自然图像里边缘方向千变万化旋转翻转能让网络学到更充分的方向不变性。如果你的训练patch数量已经很大也可以跳过增强提高训练速度。2.3 PSNR和SSIM计算中的常见误区训练完成后怎么评价效果最常见的是PSNR和SSIM两个指标。PSNR的公式不复杂但计算时容易踩坑。最容易出的问题是图片数值范围不统一如果图像是uint8格式MAXI取255如果是im2double转换后0到1的double格式MAXI必须取1。混用会导致PSNR虚高或虚低。function psnrVal computePSNR(hr, rec) mse mean((hr(:) - rec(:)).^2); if max(hr(:)) 1.5 % double型图像 psnrVal 10 * log10(1 / mse); else psnrVal 10 * log10(255^2 / mse); end endSSIM可以用MATLAB自带的ssim函数它内部会处理数据范围但要注意它默认对图像加上了高斯窗口窗口大小是11×11。如果你的图像很小SSIM可能不太稳定最好用比较大的测试图或者在多个patch上分别计算后取平均。另外无论是PSNR还是SSIM都必须保证比较的双方尺寸相同而且不要包含padding之后的黑色边界。3. 用Deep Learning Toolbox搭SRCNN3.1 网络结构定义MATLAB的Deep Learning Toolbox里定义这个网络非常简单用layer数组就能搭出来patchSize 41; layers [ imageInputLayer([patchSize patchSize 3], Name, input) convolution2dLayer(9, 64, Padding, same, Name, conv1) reluLayer(Name, relu1) convolution2dLayer(1, 32, Name, conv2) reluLayer(Name, relu2) convolution2dLayer(5, 3, Padding, same, Name, conv3) regressionLayer(Name, output) ];这里有几个细节值得说明第一convolution2dLayer默认Padding是0输出尺寸会比输入小。SRCNN原版在Caffe里确实是这样但MATLAB的Pytorch-like接口配合trainNetwork时如果你希望最终输出和输入尺寸一样最好显式指定Padding为same省去后面自己裁剪的麻烦。第二第二层1×1卷积的Padding不需要设置因为它不会改变空间尺寸。第三最后的输出层是regressionLayer而不是softmaxLayer因为我们要预测的是连续的像素值不是分类概率。这里不需要再单独加激活层regressionLayer前接卷积层的线性输出就足够了。第四我在这个网络里没有加BatchNormalization层。超分任务里BN不是必须的SRCNN当年的网络里也没有加了反而可能让轻量网络收敛变慢。如果你的训练数据质量很高、数量很大可以考虑加但至少要单独做几组对比再决定。3.2 训练选项配置trainNetwork的训练选项用trainingOptions配置。我自己跑通的配置如下options trainingOptions(adam, ... InitialLearnRate, 1e-4, ... MaxEpochs, 30, ... MiniBatchSize, 64, ... Shuffle, every-epoch, ... L2Regularization, 1e-4, ... Verbose, true, ... VerboseFrequency, 20, ... Plots, training-progress, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 50, ... ExecutionEnvironment, auto);关于优化器SRCNN原文用的是SGD加momentum但我在MATLAB里实测下来adam收敛更稳对学习率的敏感度也更低。直接用adamInitialLearnRate设1e-4基本不会出现loss飞掉的情况。如果你的训练集非常小学习率可以再降到5e-5。MiniBatchSize这里需要根据显存来调。GTX 1660级别的显卡64是安全的如果是纯CPU跑建议降到16或者32否则一个epoch的时间会让人失去耐心。ValidationData是可选的但强烈建议设置这样trainNetwork会在训练过程中自动输出验证集上的loss变化比训练loss更能反映是否过拟合。3.3 推理时固定输入尺寸的坑与重叠patch方案这是我在MATLAB里做超分时踩过的最深的一个坑。用trainNetwork训练出来的网络imageInputLayer会锁定训练时的输入尺寸[patchSize patchSize 3]。你如果直接拿一张任意尺寸的完整测试图去predictMATLAB会报维度不匹配错误。解决办法有两个方向。一个是R2022a之后可以用dlnetwork并在输入层用[NaN NaN 3]这种带NaN的尺寸让网络支持任意输入尺寸。但我实测下来不同小版本对NaN尺寸的支持行为不完全一致有的版本在初始化dlnetwork时就需要你提供输入样例不够省心。我更推荐的重建方式是把测试图切成patch逐个predict再拼回去。为了避免拼接处出现明显的块边界相邻patch之间要有重叠区域重叠部分做平均function rec srFullImage(net, img, patchSize, overlap) [h, w, ~] size(img); step patchSize - overlap; rec zeros(h, w, 3, like, img); weight zeros(h, w); for r 1:step:h-patchSize1 rr r:rpatchSize-1; for c 1:step:w-patchSize1 cc c:cpatchSize-1; patch predict(net, img(rr, cc, :)); patch squeeze(patch); rec(rr, cc, :) rec(rr, cc, :) patch; weight(rr, cc) weight(rr, cc) 1; end end mask weight 0; rec(mask) rec(mask) ./ weight(mask); endoverlap设2到4个像素就够了太小会出接缝太大则耗时成倍上升。这个patch拼接方法虽然朴素但稳定可靠不依赖版本特性也是我在项目里最终用的方案。4. 从训练到重建的完整闭环4.1 训练主脚本把前面几个部分串起来训练脚本大概是这个样子scale 3; patchSize 41; stride 41; % 准备数据 imageNames {train01.png, train02.png, train03.png}; [XTrain, YTrain] preparePatchData(imageNames, scale, patchSize, stride); [XVal, YVal] preparePatchData({val01.png, val02.png}, scale, patchSize, stride); % 定义网络 layers [ imageInputLayer([patchSize patchSize 3], Name, input) convolution2dLayer(9, 64, Padding, same, Name, conv1) reluLayer(Name, relu1) convolution2dLayer(1, 32, Name, conv2) reluLayer(Name, relu2) convolution2dLayer(5, 3, Padding, same, Name, conv3) regressionLayer(Name, output) ]; % 训练 options trainingOptions(adam, ... InitialLearnRate, 1e-4, ... MaxEpochs, 30, ... MiniBatchSize, 64, ... Shuffle, every-epoch, ... ValidationData, {XVal, YVal}, ... Plots, training-progress); net trainNetwork(XTrain, YTrain, layers, options); % 保存模型 save(srcnn_rgb_scale3.mat, net);其中preparePatchData就是上一节那段裁剪逻辑的封装建议单独写成一个函数文件方便训练和测试复用。图像数据集如果只有几十张每张抽出来的patch数量就会很多这时候训练脚本跑起来会比较慢但总时间控制在20分钟到1小时是正常的。4.2 重建脚本重建阶段读入测试图把它归一化、放大到目标尺寸后用srFullImage逐块预测最后用imresize的bicubic结果作为baseline对比hr im2double(imread(test01.png)); lr imresize(hr, 1/scale, bicubic); bicubicUp imresize(lr, scale, bicubic); rec srFullImage(net, bicubicUp, patchSize, 2); rec min(max(rec, 0), 1); % 裁剪到合理范围 psnrBicubic computePSNR(hr, bicubicUp); psnrRec computePSNR(hr, rec); ssimBicubic ssim(bicubicUp, hr); ssimRec ssim(rec, hr);predict返回的结果理论上应该在0到1附近但卷积输出的数值偶尔会略微超出这个范围所以最后要clip一下否则PSNR会被几个异常像素拉低。这个细节看起来小但实际影响还挺大的不clip的话PSNR可能会低0.5dB甚至更多。4.3 一次典型效果对比我自己的一组测试结果3倍放大数据集是20张自然图像val集不参与训练大致是方法PSNR (dB)SSIM双三次插值27.310.8732SRCNN训练后29.860.9115从数值上看提升了2.5dB左右这在超分领域算是一个比较合理的提升幅度。主观视觉上的差别更明显bicubic结果边缘有明显的模糊和振铃SRCNN重建出来的边缘更锐利纹理细节也更自然。当然如果你的训练集更大、网络做了数据增强或者改用YCbCr亮度通道方案PSNR还会再往上走一点但不会离谱到从27跳到35毕竟模型容量摆在那里。5. 训练超分网络踩过的坑5.1 图像数据范围不一致导致loss漂移最常见的问题是uint8和double混用。如果你从imread拿到的图是uint8像素范围0到255而im2double转换后的图范围是0到1那么XTrain和YTrain必须保持同一类型。我一开始没注意训练到一半发现loss跳来跳去检查半天才发现一张图用im2double另一张图直接用了uint8的原始值导致网络在不同尺度之间反复横跳。解决方案很简单所有图像统一走im2doubleloss和梯度都会稳定很多。5.2 显存不够batch和patch都要降训练过程中最容易出现的运行时错误就是Out of Memory。如果显存不够先把MiniBatchSize从64降到32再不行降到16。还有一种情况是patchSize设得太大比如我用过61×61显存占用肉眼可见地涨。对于轻量SRCNNpatchSize用41已经足够增大patchSize对精度的提升非常有限但对内存的压力是实打实的。如果你的数据特别多建议用combinedDatastore流式读取不要让所有patch都常驻内存。5.3 学习率设不好loss就卡住用adam时初始学习率1e-4是很好的起点。如果loss一直不下降或者下降几轮后卡住不动先不要急着改网络结构把学习率降到5e-5或1e-5再看看。还有一个常见的坑是L2Regularization设置太大我试过默认的1e-2结果网络学出来的重建图像整体发灰因为正则项把输出往0的方向压缩了。超分任务里L2正则设到1e-4左右就够有时干脆设为0也行模型本身轻量不太容易过拟合太多。5.4 拼接接缝和边界伪影patch拼接方法虽然实用但如果overlap设得太小边界处会出现可感知的亮度不连续。我试过overlap1时放大图片后接缝处明显发暗。把overlap提高到2到4个像素之后接缝基本消失了。如果想进一步消除边界效应可以在拼接时给每个patch加一个hann窗权重靠近patch边缘的像素权重低、中心权重高效果会更好代价是代码复杂一点。另外一个让重建图像边缘出现伪影的小原因是测试时图像边缘像素缺少上下文。patch拼接时边缘部分本身就是被clip掉的所以正常来说不会有大问题但如果你用整图缩放方式去跑就会出现图片四边奇怪的颜色偏移。所以我始终建议用patch重叠拼接而不是强行改网络输入尺寸。6. 如果想进一步优化6.1 YCbCr亮度通道超分效果好一个档在前面基础上我最推荐的改进是把RGB预测改成YCbCr亮度通道预测。具体做法是把训练对都转成YCbCr取第一通道Y作为网络输入和标签Cb、Cr通道用bicubic插值放大。训练时网络输出只有1个通道参数量下降收敛更快而且由于人眼对亮度更敏感最终PSNR往往比RGB直连略高。我这里没有放出完整代码因为实现上就是把preparePatchData里的3通道改成1通道再把回归层的输出通道从3改成1其余逻辑完全一样。如果你做的是工程应用而不是论文复现我强烈建议直接上这个方案。6.2 更轻量或更强的结构怎么走SRCNN是超分领域的入门网络但不是最先进的。后续可以考虑的两个方向是FSRCNNFast Super-Resolution CNN把超分从“先放大再提特征”改成了“提特征后放大”用反卷积或亚像素卷积在最后一层完成上采样速度和精度都更好如果追求视觉效果可以上SRGAN这种带感知损失和对抗训练的模型输出看起来更“真实”。这些网络在MATLAB里都能用layerGraph或自定义层实现只是代码量比SRCNN大不少。除非你有明确的论文复现需求否则先用SRCNN跑通全流程、理解数据流再往上加复杂度是比较稳妥的路线。最后分享一个我自己的习惯模型训练好之后不要只存网络一定要连同训练参数、数据集文件名、scale这些元信息一起保存比如save(srnet_scale3_y.mat, net, scale, patchSize)。因为模型文件过了一两个月再打开你很可能已经忘了这个模型当初是几倍放大练出来的重新测试时会少很多不必要的困惑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价