简介:Pix2Pix对抗网络的Matlab实现包,覆盖生成对抗网络中最经典的图像到图像翻译任务,适合高校本科、硕士阶段进行深度学习、计算机视觉方向的教研学习,可在matlab2014/2019a中直接运行,内置完整运行结果。资源共5个文件,包含2个m脚本(主程序PIX2PIX.m与数据集加载LoadFacadeDatabase.m)、1个说明文档、1张训练结果图(jpg)和1个动态演示(gif),压缩包整体仅28.78MB,轻量易部署。已有148人学习浏览。代码将主程序与数据加载分离,结构清晰,并附有facade数据集上的生成结果与动态效果,能直观展示Pix2Pix模型如何通过对抗训练实现建筑立面图像到标签图的转换。配套说明文档可帮助快速理解关键参数与执行流程,适用于课程实验、毕业设计及论文复现,也可作为入门GAN的进阶案例。
1. 用Matlab复现Pix2Pix之前,先想清楚它在解决什么问题
做图像处理的人多半有过这种时刻:看到论文里Pix2Pix把边缘图变成逼真鞋子、把分割色块变成街景照片,第一反应是“我也要跑一个”,然后被 Python 环境、CUDA 版本、模型权重下载劝退。如果你手头的语言是 Matlab,这份“Pix2Pix对抗网络附matlab代码+运行结果.zip”就是一条更顺的路。它是条件生成对抗网络(cGAN)的经典落地实现,核心任务是图像到图像的翻译:输入一张结构图,输出一张内容细节完整的目标图。适合两类人:一类是课程设计或毕业论文需要可运行代码的学生,另一类是已经在用 Matlab 做图像处理、想验证生成对抗网络效果的研究者。这篇博文不给你念原理,直接讲清楚怎么把代码跑起来、参数怎么调、哪些地方会翻车。
2. 条件生成对抗网络详解:Pix2Pix能出活的三个关键设计
2.1 从普通GAN到条件GAN:判别器必须“看着输入”来打分
普通生成对抗网络里,生成器输入一段随机噪声,输出一张图;判别器只看这张图是真还是假。这个设定对“生成一张像猫的图”没问题,但对“把这张边缘图变成一双鞋”完全没用——因为你不仅要图好看,还要图与输入的结构严格对应。Pix2Pix 用的对抗生成网络是条件版本,核心改动只有一处:生成器和判别器都把输入图像作为附加条件。
生成器这边输入不再是纯噪声,而是“输入图 A”和随机噪声拼接在一起;判别器这边也不再只看一张图,而是同时看“输入图 A + 生成图 G(A)”或“输入图 A + 真实图 B”,然后判断这一对是否匹配。这个“匹配”二字是条件GAN的灵魂:判别器不仅要判断图真不真,还要判断图与输入是否对应。对应不上的高分图会被判假,这就逼着生成器去忠实还原结构,而不是自由发挥。
用工程语言说,普通GAN的优化目标是分布距离,条件GAN的优化目标是“条件分布 + 对应关系”,后者的约束强得多。所以在调参时你要有心理准备:Pix2Pix 的判别器损失很难降到接近 0,因为它的任务比普通 GAN 更难,降得太低反而说明判别器“偷懒”了,没在认真检查对应关系。
2.2 生成器用U-Net而不是普通编解码器:跳连是细节还原的关键
Pix2Pix 的生成器网络结构沿用的是 U-Net,而不是常见的自动编码器结构。自动编码器是一个对称的沙漏:编码器把 256×256 的图像逐步降采样到很小的特征图,解码器再从小特征图恢复到大图。问题在于,中间这个小特征图是“信息瓶颈”,边缘信息、纹理细节在前面几层就被压缩丢了,恢复出来总是模糊的。
U-Net 在编码器和解码器之间加了跳连(skip connection):第 i 层编码器的输出,直接拼接到第 n-i 层解码器的输入上。这样解码器在恢复图像时,不仅能拿到瓶颈处的高级语义,还能拿到浅层的边缘、颜色、位置细节。对 Pix2Pix 来说,输入和输出在宏观结构上高度一致(边缘图对应鞋子、分割图对应街景),跳连让生成器把主要精力放在“补细节”而不是“记结构”上,训练效率完全不同。
我在实际使用中观察到一个现象:如果把 U-Net 换成纯编解码器,同样训练 100 轮,生成图的结构不会太离谱,但边缘锯齿和块状伪影非常明显,尤其在小物体周围。这是因为细节信息需要通过瓶颈重建,而瓶颈根本记不住那么多像素。所以当你看到生成图像“轮廓对但细节糊”时,先检查用的网络结构是不是带跳连的 U-Net,而不是急着加训练轮数。
2.3 PatchGAN判别器:70×70感受野够用,全局图反而容易崩
Pix2Pix 的判别器叫 PatchGAN,它不像普通判别器那样输出一个 0 到 1 的全局标量,而是输出一个 N×N 的矩阵,矩阵里的每个值代表图像上一个局部区域的真假。论文里用的是 70×70 感受野的 PatchGAN:每隔一定步长对原图采样若干小 patch,对每个 patch 分别判断真假,最后取平均作为整体损失。
为什么不用更大的感受野甚至全局图?经验和论文结论都指向一个事实:Pix2Pix 这类任务里,局部纹理是否真实、边缘是否自然,比“整张图看起来整体像不像”更重要。PatchGAN 把注意力集中在局部,能有效防止生成图出现全局模糊但局部纹理平坦的问题。而且 patch 数量多,等于给判别器提供了更多训练样本,判别器更容易收敛,不太容易出现生成器和判别器互相甩开的情况。
这个设计对调参的直接影响是:如果.imageSize 设置得很大(比如 512×512),而判别器的下采样层数不变,感受野相对整张图的比例会变小。此时要不要加大 patch 尺寸,需要你根据生成图的伪影分布来判断,而不是无脑追求大感受野。我一般习惯先用 256×256 训练稳定,再往大分辨率试,否则很容易在 512 分辨率下遇到判别器过强、生成器梯度消失的问题。
3. 运行这个Matlab代码包:从路径设置到跑完一轮训练
3.1 先检查环境:Matlab版本、工具箱、显卡驱动
拿到“Pix2Pix对抗网络附matlab代码+运行结果.zip”,先别急着双击主脚本。第一步是确认你的环境满足运行条件。根据我帮人排查此类代码包的经验,90% 的启动失败不是代码问题,是环境问题。Pix2Pix 的 Matlab 实现普遍依赖 Deep Learning Toolbox,如果你的版本比较老(R2019b 之前),很多写法兼容不了;如果连工具箱都没装,打开就是一堆“未定义函数或变量”。
一个很典型的坑是:Matlab 加载模型时用了dlnetwork和minibatchqueue这套新 API,这是 R2020b 之后才引入的。老版本跑不动,并不是代码包故意不兼容,而是维护者通常按新版本写。建议用 R2020b 及以上版本,R2023a 最好。显卡方面,Pix2Pix 默认开着 GPU 训练,需要确认parallel.gpu.GPUDevice.isAvailable返回 1;没有独显或显存不足的机器,别硬开 GPU,可以把executionEnvironment参数改成'cpu',256×256 分辨率下 CPU 训练虽然慢,但能跑通。
% 环境自检脚本,建议在运行训练前先执行 disp(version); % 查看Matlab版本 % 检查深度学习工具箱 try net = dlnetwork(); disp('Deep Learning Toolbox OK'); catch error('缺少Deep Learning Toolbox,请先安装工具箱'); end % 检查GPU是否可用(没有GPU就改用CPU训练) if gpuDeviceCount > 0 gpu = gpuDevice(1); fprintf('检测到GPU: %s,显存 %.1f GB\n', gpu.Name, gpu.AvailableMemory/1e9); else warning('未检测到GPU,训练将使用CPU,速度会明显变慢'); end这段代码做了三件事:确认版本号、确认工具箱、确认 GPU。特别注意gpuDeviceCount的返回值,有些电脑装了显卡驱动但 CUDA 计算能力不够(低于 3.0),Matlab 会直接报“请求的 GPU 不支持”,此时不要纠结,直接走 CPU。实际经验是:一张 6GB 显存的卡,256×256、batchSize=1 可以勉强跑;低于 4GB 很可能会在训练中途被“out of memory”打断,后面我会讲怎么降配。
3.2 跑通最小训练流程:目录结构、数据预处理、主循环
这个压缩包里的目录结构,和大多数 Matlab 深度学习项目一致:data文件夹存训练数据,checkpoints存中间模型,results存每个 epoch 结束时的生成图。如果你打开压缩包发现目录名不一样,别慌,先找main或train开头的脚本,那就是入口。下面这段代码是训练流程的最小骨架,覆盖了数据读取、网络构建和训练循环三个环节:
% pix2pix_train.m —— 训练主流程骨架(基于dlnetwork,R2020b+) % 数据约定:data/train 下每张图是“输入图 | 目标图”水平拼接而成 % 1. 初始化超参数 opts.epochs = 200; opts.batchSize = 1; opts.lr = 2e-4; opts.lambdaL1 = 100; % L1损失权重,论文经典值 opts.imageSize = [256, 256]; opts.executionEnvironment = 'auto'; % 有GPU用GPU,没有自动回退CPU % 2. 读取训练数据(imageDatastore自动扫描目录下所有图片) imds = imageDatastore('data/train', 'FileExtensions', {'.png','.jpg'}); fprintf('加载了 %d 张训练图片\n', numel(imds.Files)); % 3. 构建生成器与判别器 [G, D] = createPix2PixNetworks(opts); G = initialize(G); D = initialize(D); % 4. 训练循环 for epoch = 1:opts.epochs % 每个epoch重新打乱数据顺序 imds = shuffle(imds); while hasdata(imds) imgPair = read(imds); % 读一张拼接图 [256 512 3] [imgA, imgB] = splitPair(imgPair, opts.imageSize); [G, D, gLoss, dLoss] = trainStep(G, D, imgA, imgB, opts); end fprintf('第 %d 个epoch完成\n', epoch); end这段骨架代码的作用是帮你建立“Pix2Pix 训练就是从数据到损失到梯度更新”的整体脉络,实际使用时你要对照包里的函数名做替换。参数里lambdaL1=100是 Pix2Pix 论文给的经典值,意思是 L1 像素损失在总损失里占绝对主导;想突出细节锐利可以降到 50,想更稳重建结构可以升到 150,后面第 4 章会细说。batchSize=1也是论文标配,Pix2Pix 对 batch 大小异常敏感,Batch Normalization 在 batch=1 时退化为 Instance Normalization,这反而是出好图的关键,不要轻易改成 4 或 8。
3.3 数据预处理为什么要靠“切列”:Matlab数组操作的核心作用
上面代码里的splitPair是个容易被忽略但非常关键的函数。Pix2Pix 的需求是把一张 256×512 的拼接图从中间切成左右两半:左半是输入图 A,右半是目标图 B。在 Matlab 里这就是一次数组索引操作,但很多人栽在“维度顺序”上,因为图像数组是高×宽×通道×样本数,不是 Python 的样本数×高×宽×通道。
function [imgA, imgB] = splitPair(imgPair, imageSize) % 将拼接图切分为输入图A和目标图B % imgPair: 尺寸为 [H 2W 3] 的数组(单张图没有样本维) h = imageSize(1); w = imageSize(2); % 取左半部分作为A,右半部分作为B imgA = imgPair(:, 1:w, :); imgB = imgPair(:, w+1:2*w, :); % 统一转成 single 且范围在 [-1, 1],这是GAN训练的基本要求 imgA = single(imgA) * 2 / 255 - 1; imgB = single(imgB) * 2 / 255 - 1; end这里本质上做的事情是一次“Matlab数组取出多列”的操作:imgPair(:, 1:w, :)取出前 w 列,imgPair(:, w+1:2*w, :)取出后 w 列。注意代码末尾的归一化步骤,很多人在这一步翻车——直接把 0 到 255 的 uint8 数据送进网络,损失曲线像过山车一样乱跳,还误以为是学习率问题。Pix2Pix 的生成器最后用 tanh 激活函数输出,输出范围是 [-1, 1],所以输入和目标图都必须归一化到 [-1, 1],两边对不上,训练必然不稳定。
另一个常见问题是:从本地图片读进来的数据是H×W×C,而网络层需要的是H×W×C×Batch。如果你的代码报“维度不匹配”,检查是不是少了permute或reshape。用minibatchqueue的版本通常会自动补上 batch 维,但手写循环里必须自己处理。
4. 必调参数解读:训练轮数、Batch Size、L1权重怎么配才不翻车
4.1 四个核心参数的合理区间与调参顺序
跑通一轮训练只是开始,真正折磨人的是参数调优。Pix2Pix 最需要关注的参数是下面四个,我把它们整理成一个表,方便你对照着查。调参顺序建议从上往下:先确定 imageSize 和 batchSize 这两个“硬约束”,再动 lr 和 lambdaL1 两个“软约束”。
| 参数 | 经典默认值 | 建议范围 | 调参倾向 |
|---|---|---|---|
| epochs | 200 | 50-300 | 数据量小就少跑,数据量大先跑20轮看趋势 |
| batchSize | 1 | 1-4 | 超过4容易让图像模糊,显存不够就保持1 |
| lr(学习率) | 2e-4 | 1e-4 ~ 1e-3 | 用Adam优化器时2e-4是GAN的黄金起点 |
| lambdaL1 | 100 | 50-150 | 想更“像真的”就降低,想更“忠实输入”就升高 |
先说batchSize。很多人习惯把它调大来加速训练,但在 Pix2Pix 里这是个反直觉的坑。batch=1 时,Batch Normalization 层相当于对单张图做归一化,等于 Instance Norm 的效果,能保留更多图像个体特征;batch 调大后,归一化变成了跨样本的统计,生成图会趋于“平均化”,细节损失明显。实测经验是 batch=4 时边缘已经有点肉了,batch=8 时手和脚这种精细结构基本糊成一团。所以不要为了显卡利用率牺牲图像质量,特别是你只有一张入门卡时,老老实实 batch=1。
再说lr。I只能在训练刚启动时观察到明显下降,后面就一直在小范围震荡。判别器损失同理。看曲线时有三个“不健康状态”要警惕:
D_loss一路掉到接近 0 且G_loss猛涨:判别器太强,把生成器杀死了。降低 lr 或减小 lambdaL1。G_loss持续下降但D_loss始终很高:判别器太弱,生成器在“骗过瞎子”,生成的图细节可能一塌糊涂。增大 lambdaL1 或增加判别器训练步数。- 两个损失都发散到 NaN:最常见原因是归一化没做好或数据里有损坏图片,先检查数据管道。
看生成图的方法更直接:把每个 epoch 生成的图和上一轮对比,重点关注边缘是否锐利、颜色是否溢出、纹理是否重复。我给一个可落地的做法——每 100 次迭代打印一次损失,每 5 个 epoch 保存一组生成结果,这样你事后想查“到底第几轮开始变好”就有据可依。
% 训练过程中的打印与保存设置 iter = 0; for epoch = 1:opts.epochs while hasdata(mbq) iter = iter + 1; [G, D, gLoss, dLoss] = trainStep(G, D, imgA, imgB, opts); % 每100次迭代打印一次损失 if mod(iter, 100) == 0 fprintf('epoch %d | iter %d | G_loss %.4f | D_loss %.4f\n', ... epoch, iter, gLoss, dLoss); end end % 每个epoch结束时保存生成图与检查点 saveResults(G, opts, epoch); if mod(epoch, 50) == 0 save(fullfile('checkpoints', sprintf('G_epoch_%d.mat', epoch)), 'G'); end end这段代码解决的是“黑匣子”问题——训练过程看不到中间状态,出了问题只能干瞪眼。注意saveResults要同时保存“输入图 A / 真实图 B / 生成图 G(A)”三张并排的对比图,而不是只存生成图。没有真实图做对比,你根本判断不了生成结果到底准不准。检查点每 50 轮保存一次,训练中途断电或内存爆掉时,至少能从最近一个 checkpoint 恢复,不用从头再来。
4.3 训练多久能停:边看指标边停的训练习惯
Pix2Pix 不是训练越久越好——跑过的人都有这种血泪经验:第 80 轮的生成图干净利落,第 150 轮反而出现颜色斑块和伪影,这叫做“过拟合到判别器”。提前停止(early stopping)的策略在 GAN 训练里同样重要,但判断标准不能只看损失值,要以生成图质量为第一依据。
一个实用的做法是:每 10 个 epoch 用固定的测试输入跑一次生成,把这些结果按顺序排成序列来回放,找到“视觉质量最好的那个 epoch”。这个 epoch 不一定是损失最低的,很多时候损失还在降,但图已经开始崩坏。我习惯把保存间隔设小一点(比如每 10 轮存一次),训练完对比所有中间结果,选最佳权重,而不是死等 200 轮跑完。如果你想用一个量化指标做粗筛,可以用ssim函数计算生成图与真实图的结构相似度,SSIM 超过 0.6 说明结构已基本正确,超过 0.8 说明细节相当不错。
5. 常见问题排查:中文注释乱码、路径错误、训练崩坏三个坑
5.1 打开代码满屏中文注释乱码:编码冲突不是代码问题
现象:用 Matlab 打开.m文件,中文注释全部变成“鍝堝搱”之类的乱码,直接导致无法阅读。原因:代码包作者在 Windows 下用 GBK 编码保存,而你用的 Matlab 2023 默认按 UTF-8 读取。这是最典型的中文注释乱码场景,和代码本身无关。
解决:在 Matlab 命令窗口执行prefdir打开 preferences 目录,用文本编辑器打开matlab.prf,找到EditorLanguage或编码相关配置,将其改为zh_CN或 UTF-8。更快的办法是在编辑器里重新指定文件编码:打开乱码文件,右键选择“另存为”,编码选 UTF-8 后关闭再重新打开。如果文件太多,干脆写一个批量转码脚本,用fileread配合fwrite统一转成 UTF-8。注意这个过程要备份原文件,我见过有人转码转出半个文件丢失的。
5.2 报错“未定义函数或变量 createPix2PixNetworks”:不是代码问题
现象:运行主脚本,提示createPix2PixNetworks未定义,但压缩包里明明有这个文件。原因:这个自定义函数和主脚本不在同一目录,而且主脚本没有把函数所在目录加入搜索路径。Matlab 只会搜索当前目录和 path 列表里的目录,不会自动递归搜索子文件夹。
解决:这是最容易被新手误判的一类坑。你别急着找代码 bug,先确认主脚本最开头有没有addpath(genpath('utils'))或等效语句。没有就手动执行addpath(genpath(pwd)),把整个项目目录加进去。另外检查函数文件名是否和函数名一致——Matlab 规定函数名与文件名必须完全一致,大小写也不能错。如果文件名是createPix2PixNetwworks.m多打一个 w,Matlab 照样找不到。
5.3 损失出现 NaN 或者 loss 剧烈震荡:归一化与学习率的锅
现象:训练没跑几步,损失直接变成 NaN,或者 loss 在正常值附近剧烈抖动,生成图全是雪花噪点。原因:最常见的是输入数据没有归一化到 [-1, 1]。Pix2Pix 的生成器输出层用 tanh,值域必须是 [-1, 1],如果你直接喂 [0, 255] 的 uint8 数据,损失计算时梯度会爆炸。另一个原因是学习率太高,Adam 算法在 lr 超过 1e-3 时很容易发散。
解决:先检查数据预处理部分的归一化代码,确认读图后是single(img) * 2 / 255 - 1。确认归一化没问题后,再看 loss 是不是在第一个 epoch 就 NaN,如果是,把 lr 降到 1e-4 再试。还有个隐蔽点:训练图里混入了一张损坏的图片或全黑图,这会让 batch 的统计量异常,也会 NaN。我排查时会写脚本扫描所有训练图,算一下每张图的均值和方差,方差为 0 的图直接删掉。
5.4 报显存不足 out of memory:不要直接换显卡,先降配置
现象:训练到几百个 iter 时报out of memory on device,或者直接整个 Matlab 卡死。原因:Pix2Pix 在 256×256 分辨率下,即使 batch=1,生成器和判别器加上中间特征图,显存占用也在 4GB 左右,这还没算反向传播的梯度缓存。
解决:三个降配方案依次试。第一,把图像分辨率从 256×256 降到 192×192 甚至 128×128,显存占用是按面积降的,降到 128 直接省 3/4;第二,确认executionEnvironment是'auto'而不是强制'gpu',避免 GPU 装不下时还要硬跑;第三,查看代码里是否有不必要的中间变量缓存,比如在 trainStep 里临时存了所有层的中间输出用于调试,这非常吃显存。如果以上都做了还是不够,那就只能用 CPU 训练了,白天写代码,晚上挂着跑,200 轮大概要二三十个小时,勉强能接受。
6. 进阶:把Pix2Pix用到自己的数据上,并用SSIM验证效果
跑通内置数据集之后,真正有价值的动作是换成自己的数据。做这件事的关键在数据准备:Pix2Pix 要求每张训练样本是一对“输入-目标”图,你要把它们水平拼成一张图。比如你想做“去阴影”任务,就把带阴影的图和同场景无阴影图并排保存。拼接时一定要保证左右两半的分辨率、通道数完全一致,否则预处理切列时直接错位。我自己做自建数据集时,习惯用 Matlab 写一个批量脚本扫描两个文件夹,按文件名匹配后拼接,存成 png 格式,顺便打个标签确认对齐。
训练自建数据时,数据量是最现实的问题。Pix2Pix 不像分类网络那样几千张就能学个大概,它需要学习像素级别的映射关系,经验上至少准备 200 对以上才有可看的结果,500 到 1000 对能达到论文里比较稳定的效果。如果你只有几十对数据,建议用数据增强:随机翻转、旋转 90 度、轻微缩放。注意翻转要对左右两半同步做,否则会破坏对应关系。
验证环节我建议用两个指标配合看。第一个是 Matlab 自带的ssim函数,计算生成图与真实图的结构相似度,取值 0 到 1,越高越好;第二个是峰值信噪比 PSNR。这里有个容易犯的错误:直接对整张图算指标,结果会虚高,因为背景区域太好算了。正确做法是先用蒙版圈出目标区域,只在 ROI 内算 SSIM 和 PSNR。下面给一段验证代码:
% 验证生成质量:计算SSIM与PSNR I_gen = imread('results/epoch_200_generated.png'); I_real = imread('data/test/real.png'); % 转为灰度并统一尺寸 I_gen = im2gray(imresize(I_gen, size(I_real, 1:2))); % SSIM接近1说明结构相似,PSNR高于30dB说明像素偏差小 ssimVal = ssim(I_gen, I_real); psnrVal = psnr(I_gen, I_real); fprintf('SSIM = %.4f, PSNR = %.2f dB\n', ssimVal, psnrVal);这段代码里im2gray把彩色图转灰度,是因为 SSIM 在单通道上计算更稳定;imresize保证两张图尺寸一致。注意测试集图像不能是训练集里的图,否则指标虚高到你不敢相信。
我最后想分享一个习惯:Pix2Pix 调参没有银弹,但我每次拿到别人的代码,第一件事永远是改路径、缩数据、减轮数,跑通一个 10 轮的迷你实验,确认整套链路没问题,再放大到完整训练。这样做的好处是能把“环境问题”和“算法问题”隔离开,避免跑了一整夜发现是数据路径写错。希望这些经验能帮到你,让你的对抗网络训练少走点弯路。
本文还有配套的精品资源,点击获取