简介Pix2Pix对抗网络Matlab实现包面向图像生成与转换方向的本硕学生及科研人员适合在课程设计、毕业设计或课题复现中学习条件生成对抗网络cGAN的完整流程。资源共5个文件包含两个核心.m源文件PIX2PIX.m主程序与LoadFacadeDatabase.m数据加载脚本以及说明文本、运行结果截图和训练过程动画压缩包整体约28.78MB文件结构清晰。目前已有148人学习下载适用于MATLAB 2014或2019a环境配好数据集即可直接运行。读者可借此复现建筑立面facade图像到图像的转换效果观察不同训练阶段的生成输出理解U-Net生成器与PatchGAN判别器的协作机制同时可依据说明文档调整训练参数、替换数据进一步迁移至边缘图转实物、语义图转街景等任务。博主长期从事优化算法、神经网络、图像处理等方向仿真若运行中遇到环境或代码问题可私信沟通便于本科、硕士教研学习顺利推进。1. 用 MATLAB 跑 Pix2Pix 对抗网络这份压缩包到底能落地什么Pix2Pix 对抗网络附 MATLAB 代码和运行结果这类资源包在工程场景里常被当成黑匣子解压、点运行、看曲线、换自己的数据然后告诉团队能用。实际上处理过图像翻译类项目就会明白Pix2Pix 的实现难点从来不在网络结构而在数据配对、损失配比和训练稳定。跑通和跑好之间差着至少三天调参。这篇文章给手上有 MATLAB 基础、第一次认真跟生成对抗网络代码的人用。我会把网络结构怎么拆、数据怎么组织、训练脚本怎么写、参数怎么调、哪些地方会翻车一次讲完。它是条件生成对抗网络里最简单也最值得先复现的方案值得你把每个字符都过一遍。2. 先把网络结构吃透条件生成对抗网络怎么映射成 MATLAB 代码2.1 条件生成对抗网络详解判别器看的不只是“真”或“假”普通生成对抗网络的生成器输入是随机噪声输出是图像。判别器只需要判断输入图像是真实样本还是生成样本。Pix2Pix 把这件事改了生成器输入的是条件图片 x输出是目标图片 G(x)也就是图像到图像的翻译。判别器输入不再是单独一张图而是“条件图 目标图”拼成的一对图像。如果目标是真实目标 y判别器要给高分如果是生成目标 G(x)判别器要给低分。这样设计的原因很直接如果不把条件图喂给判别器判别器只会判断“目标图是不是一张像样的街景”不会判断“这张街景到底对应语义分割图里的哪一块”。结果就是生成器可以生成任意风格漂亮的图却和输入条件完全对不上。条件生成对抗网络之所以能落地靠的就是把 x 作为约束条件同时送给生成器和判别器。这个问题在 MATLAB 里的体现非常具体。生成器和判别器的输入层都要按 3 通道设计而判别器的输入实际是 6 通道其中 3 个通道来自条件图另外 3 个通道来自真实目标或生成目标。很多人第一次写网络时报维度错误不是层参数写错而是忘了在进入网络之前用cat(3, x, y)拼接。Pix2Pix 的训练损失也可以写成很直观的形式。判别器希望学会区分“条件 x 配真实 y”和“条件 x 配生成 G(x)”生成器则希望骗过判别器同时让自己输出的图尽可能接近真实 y。后者通常用 L1 距离约束因为 L2 距离会鼓励生成模糊结果而 L1 对边缘和纹理的保留要好得多。原始论文里生成器损失由对抗损失和 L1 损失组成这个组合逻辑在 MATLAB 自定义训练循环里要原样写出来不能只靠内置的网络层完成。2.2 用 layerGraph 手写 U-Net 生成器跳连和残差要的是同一件事Pix2Pix 的生成器用 U-Net而不是普通 Encoder-Decoder。Encoder-Decoder 是一条路压到底再展开前面的细节信息很难传到最后。U-Net 在对称位置加跳连把某层编码器的输出直接拼到对应层解码器的输入上相当于给解码器开了一条捷径。用 MATLAB 的layerGraph搭 U-Net关键是把每一段层先用addLayers加进去再用connectLayers把特征图从编码器接到解码器。下面是一个三层下采样示例不是完整 8 层 U-Net但结构和连接逻辑是一样的function lgraph buildUNetLayers() lgraph layerGraph(); % 输入 256x256x3归一化放在数据端做层里不做 lgraph addLayers(lgraph, imageInputLayer([256 256 3], ... Name, input, Normalization, none)); % 编码器第一段下采样到 128x128 enc1 [ convolution2dLayer(4, 64, Stride, 2, Padding, 1, Name, enc1_conv) leakyReluLayer(0.2, Name, enc1_lrelu)]; lgraph addLayers(lgraph, enc1); % 编码器第二段下采样到 64x64 enc2 [ convolution2dLayer(4, 128, Stride, 2, Padding, 1, Name, enc2_conv) batchNormalizationLayer(Name, enc2_bn) leakyReluLayer(0.2, Name, enc2_lrelu)]; lgraph addLayers(lgraph, enc2); % 编码器第三段下采样到 32x32 enc3 [ convolution2dLayer(4, 256, Stride, 2, Padding, 1, Name, enc3_conv) batchNormalizationLayer(Name, enc3_bn) leakyReluLayer(0.2, Name, enc3_lrelu)]; lgraph addLayers(lgraph, enc3); % 解码器第三段上采样回 64x64 dec3 [ transposedConv2dLayer(4, 128, Stride, 2, Cropping, 1, Name, dec3_dconv) batchNormalizationLayer(Name, dec3_bn) reluLayer(Name, dec3_relu)]; lgraph addLayers(lgraph, dec3); % 解码器第二段上采样回 128x128 dec2 [ transposedConv2dLayer(4, 64, Stride, 2, Cropping, 1, Name, dec2_dconv) batchNormalizationLayer(Name, dec2_bn) reluLayer(Name, dec2_relu)]; lgraph addLayers(lgraph, dec2); % 解码器第一段上采样回 256x256 dec1 [ transposedConv2dLayer(4, 3, Stride, 2, Cropping, 1, Name, dec1_dconv) tanhLayer(Name, dec1_tanh)]; lgraph addLayers(lgraph, dec1); % 连接 lgraph connectLayers(lgraph, input, enc1_conv); lgraph connectLayers(lgraph, enc1_lrelu, enc2_conv); lgraph connectLayers(lgraph, enc2_lrelu, enc3_conv); lgraph connectLayers(lgraph, enc3_lrelu, dec3_dconv); % 跳连enc2 与 dec3 输出在通道维拼接 lgraph connectLayers(lgraph, enc2_lrelu, dec3_concat/in2); lgraph connectLayers(lgraph, dec3_relu, dec2_dconv); lgraph connectLayers(lgraph, enc1_lrelu, dec2_concat/in2); lgraph connectLayers(lgraph, dec2_relu, dec1_dconv); end这段代码里有一个concatenationLayer没有写全因为真正的 U-Net 要在指定位置插入“拼接层”而不是直接connectLayers到不存在的端口。更简单的做法是用两个自定义拼接层concat1 concatenationLayer(3, 2, Name, dec3_concat); concat2 concatenationLayer(3, 2, Name, dec2_concat); lgraph addLayers(lgraph, concat1); lgraph addLayers(lgraph, concat2);concatenationLayer(3, 2, ...)的含义是在第 3 维也就是通道维拼接两个输入。编码器特征图的尺寸和解码器特征图的尺寸必须一致否则拼接会直接报错。如果你不想手写也可以直接用 Computer Vision Toolbox 里的unetLayers但我建议第一次跑还是手写一次这样才能理解跳连在哪一层发生后面改网络才有把握。生成器最后一层我用了tanhLayer输出范围是 -1 到 1这是 Pix2Pix 的常见做法。如果你的数据归一化到 0 到 1最后一层就要改成sigmoidLayer或clippedReluLayer否则输出接不上。2.3 PatchGAN 判别器不评整张图而是逐块打分判别器如果只输出一个标量意味着把整张 256×256 图像压成一个真假判断局部细节会被平均掉。PatchGAN 的做法是把输入切分成很多感受野重叠的小块每个块独立打分最后得到一个二维评分网格。训练时对网格所有格子的值取平均再计算损失。这样做的收益有两个。第一判别器被迫关注纹理、边缘、颜色一致性这些局部特征而不是只依赖整体构图。第二评分网格本身保留了空间信息比全连接层压成标量更容易优化。在 MATLAB 里PatchGAN 判别器通常就是几个带步长卷积层叠加不需要池化层也不需要全连接层。function lgraph buildPatchDiscriminator() lgraph layerGraph(); % 输入是 6 通道条件图 3 通道 目标图 3 通道 lgraph addLayers(lgraph, imageInputLayer([256 256 6], ... Name, input, Normalization, none)); % 下采样到 128x128输出 64 张特征图 lgraph addLayers(lgraph, [ convolution2dLayer(4, 64, Stride, 2, Padding, 1, Name, d_conv1) leakyReluLayer(0.2, Name, d_lrelu1)]); % 下采样到 64x64 lgraph addLayers(lgraph, [ convolution2dLayer(4, 128, Stride, 2, Padding, 1, Name, d_conv2) batchNormalizationLayer(Name, d_bn2) leakyReluLayer(0.2, Name, d_lrelu2)]); % 下采样到 32x32 lgraph addLayers(lgraph, [ convolution2dLayer(4, 256, Stride, 2, Padding, 1, Name, d_conv3) batchNormalizationLayer(Name, d_bn3) leakyReluLayer(0.2, Name, d_lrelu3)]); % 最后的评分层输出 31x31x1 lgraph addLayers(lgraph, [ convolution2dLayer(4, 1, Stride, 1, Padding, 1, Name, d_score)]); lgraph connectLayers(lgraph, input, d_conv1); lgraph connectLayers(lgraph, d_lrelu1, d_conv2); lgraph connectLayers(lgraph, d_lrelu2, d_conv3); lgraph connectLayers(lgraph, d_lrelu3, d_score); end最后一层输出 31×31也就是说原始输入被分成了大约 31×31 个 patch 分别打分。这里要注意评分层不要接激活函数直接输出线性分数。配合最小二乘损失时真实样本的分数向量要尽量接近 1生成样本的分数向量要尽量接近 0。训练完成后如果判别器效果好31×31 的评分图上会看到很明显的局部亮块和暗块对应它认为真实的区域和穿帮的区域。3. 从解压到跑通数据配对、训练脚本和运行结果的复现路径3.1 拿到压缩包先拆结构入口文件、模型定义和数据路径这类资源包没有统一的目录规范但常见做法是把入口训练脚本放在根目录模型定义放在单独的.m文件里运行结果放在result或output目录下。我拿到压缩包后不会直接双击运行而是先做三件事解压、改名、扫一遍入口脚本。mkdir pix2pix_demo cd pix2pix_demo unzip ../Pix2Pix对抗网络附matlab代码运行结果.zip find . -maxdepth 3 -type f -name *.m | sort解压后的第一件事是把整个项目目录名改成英文。不是我有洁癖而是 MATLAB 在 Windows 中文系统下处理中文路径经常出幺蛾子尤其是imageDatastore和fullfile拼接路径时。哪怕系统本身能识别中文训练过程中临时文件、checkpoint 路径也可能踩到编码问题。项目名里带中文的 zip 包解压后第一件事就是改名。然后打开主脚本重点看三行数据加载方式、生成器构造函数、训练循环入口。如果主脚本里有addpath也要确认路径写的是相对路径还是绝对路径。很多运行结果和预期不符的案例都是因为换一台电脑后绝对路径失效脚本读到了另一份数据。3.2 用 imageDatastore 组织配对数据省内存也好排错Pix2Pix 训练需要成对图片。最常见的目录结构是data/input和data/output两个目录下文件名一一对应。只要文件名一致combine就能按顺序配对。不要自己写 for 循环一张张imread那样不仅代码长内存还会被中间变量堆满。dsIn imageDatastore(data/input); dsOut imageDatastore(data/output); ds combine(dsIn, dsOut); ds transform(ds, preprocessPair); function xy preprocessPair(data) x imresize(data{1}, [256 256]); y imresize(data{2}, [256 256]); % 原始 Pix2Pix 用 tanh 输出这里把输入归一到 [-1, 1] x single(x) / 127.5 - 1; y single(y) / 127.5 - 1; xy {x, y}; endcombine返回的 datastore 每读取一次会分别从两个底层 datastore 里取一个数据项再打包进 cell 数组。transform接收这个 cell把预处理逻辑包装进去。代码里最值得注意的一句话是single(x) / 127.5 - 1它把 uint8 图像从 0 到 255 映射到 -1 到 1。如果省略singleMATLAB 会按 uint8 运算结果直接变成 0 或 255整个训练从一开始就是错的。如果你的输入和输出图片不在两个目录而是文件名里带_A和_B这样的后缀可以用imageDatastore的ReadFcn读取或者先写成两个文件清单再用fileDatastore自定义读取函数。重点保证输入输出是一一对应的顺序错一位整个网络学的东西就全错了。3.3 自定义训练循环dlgradient、loss 计算和 Adam 更新Pix2Pix 不太适合直接用trainNetwork训练因为对抗损失要同时更新两个网络而且每一轮生成器的梯度要穿过判别器。MATLAB 里标准做法是dlnetwork 自定义训练循环。这里给出训练循环里最核心的梯度函数function [gradGen, gradDis] ganGradients(genNet, disNet, x, y, lambda) % 生成器前向把条件 x 映射成 yFake yFake forward(genNet, x); % 判别器分别看“真实对”和“生成对” realScore forward(disNet, cat(3, x, y)); fakeScore forward(disNet, cat(3, x, yFake)); % 最小二乘对抗损失避免交叉熵梯度消失 lossD mean((realScore - 1).^2, all) mean(fakeScore.^2, all); lossAdv mean((fakeScore - 1).^2, all); lossL1 mean(abs(yFake - y), all); lossG lossAdv lambda * lossL1; % 分别自动求梯度 gradGen dlgradient(lossG, genNet.Learnables); gradDis dlgradient(lossD, disNet.Learnables); end这个函数有两个关键细节。第一生成器前向必须用forward不能用predict。predict会把网络切到推理模式丢弃可学习的内部状态对应的计算图也不完整后续dlgradient会报错。第二cat(3, x, y)要在进入判别器之前完成不能在判别器内部拼接否则梯度传不回去。外层训练循环里每轮迭代调用[gradGen, gradDis] dlfeval(ganGradients, genNet, disNet, x, y, lambda)然后用adamupdate更新参数。这里不展开完整 while 循环但有一点值得记住梯度函数每次调用都不要在内部修改网络参数把所有状态更新放在循环外面否则多 GPU 和断点恢复都会出问题。lambda默认取 100这是一个经过大量实验验证的经验值。它控制生成器对像素精度的重视程度。如果生成的图像结构正确但纹理模糊把lambda调大到 200 会看到明显改善如果生成器只顾着骗判别器完全不按原图结构来先把lambda调回 100 再说。4. 参数与边界Pix2Pix 调参表里最值得先动的几个旋钮4.1 loss 权重、学习率和优化器lambda 是第一刀Pix2Pix 的生成器损失里lambda是第一个要动的参数。原始论文给的是 100但那是针对街景、建筑这类结构明显的任务。如果你的任务边缘细节不多比如黑白轮廓图上色lambda可以降到 50如果任务特别强调轮廓一致性比如语义分割转街景lambda可以升到 200。学习率也值得专门看。标准配置是 Adam初始学习率 2e-4beta1设置为 0.5 而不是默认的 0.9。原因在于 GAN 训练对动量的历史信息很敏感beta1太大时更新方向容易被早期梯度带偏。MATLAB 的adamupdate函数里会自动维护这两个动量参数你只需要保证传入的learnRate是 2e-4 这个量级。learnRate 2e-4; beta1 0.5; beta2 0.999; [genNet.Learnables, stateGen] adamupdate(genNet.Learnables, ... gradGen, stateGen, learnRate, beta1, beta2);学习率不是越小越好。生成对抗网络两边是互相博弈的学习率太小判别器长期保持正确生成器梯度消失学习率太大生成器抖动幅度过大损失曲线像心电图。建议每 100 个 epoch 把学习率衰减一半观察生成图是否还在稳步变化。4.2 256 分辨率、随机裁剪与[-1,1]归一化边界条件先定死Pix2Pix 几乎所有公开预训练结果都基于 256×256 输入。分辨率太高PatchGAN 的感受野和判别器层数都要重新算分辨率太低语义信息丢失输出会一片模糊。如果你只有 512×512 的图建议先缩到 256×256 跑通而不是直接改输入层尺寸。随机裁剪是增强效果最明显的一步。标准做法是先缩放到 286×286再随机裁剪到 256×256让模型看到不同位置的局部结构。MATLAB 里做随机裁剪时要保证输入和输出使用同一个裁剪窗口不能用imcrop对两张独立做function [x, y] randomCropPair(x, y) cropSize 256; [h, w, ~] size(x); % 上限调整到 h-cropSize, w-cropSize startY randi([1, h - cropSize 1]); startX randi([1, w - cropSize 1]); x x(startY:startYcropSize-1, startX:startXcropSize-1, :); y y(startY:startYcropSize-1, startX:startXcropSize-1, :); end归一化必须在裁剪之后。如果先归一化再做灰度插值或裁剪大概率不会出问题但先裁剪再归一化可以少踩一个边界值溢出的坑。Pix2Pix 生成器输出用 tanh 时输入输出都要归一化到 -1 到 1如果生成器最后用 sigmoid就归一化到 0 到 1。两套方案混用是最隐蔽的错误损失照样降但生成图会整体偏移。4.3 batch size 与显存预算训练速度不够时先不换显卡256×256 的输入加上生成器和判别器同时反向传播显存开销比普通分类网络大得多。8GB 显存跑 batch size 4 已经很紧张很多人第一轮迭代就 Out of Memory解决办法不是换显卡而是先把 batch size 改到 1。batch size 太小会带来一个新问题判别器只能在 1 张图上更新梯度方差大损失震荡明显。如果只能 batch size 1我一般会给判别器的每个卷积层后接dropoutLayer(0.25)或者把标签平滑打开。所谓标签平滑就是真实样本的损失目标从 1 改成 0.9生成样本的损失目标从 0 改成 0.1给判别器留一点余地。% 标签平滑真实对分数目标是 0.9生成对是 0.1 lossD mean((realScore - 0.9).^2, all) mean(fakeScore.^2, all);训练时长也要有预期。一个 1000 张图片的数据集batch size 1200 个 epoch 就是 20 万次迭代单张消费级显卡至少跑三到五个小时。如果时间紧张先把 epoch 压到 50确认损失曲线正常再延长。不要一上来就训练 500 epoch浪费电也浪费调参节奏。5. 避坑指南MATLAB 跑 Pix2Pix 最常踩的坑5.1 中文注释乱码和中文路径先改文件编码再改存放路径现象打开.m文件中文注释变成乱码运行脚本时提示找不到data/input即使路径明明存在。原因MATLAB R2023 之前的版本默认以系统区域编码读取文件Windows 中文系统是 GBK而很多代码包保存时用的是 UTF-8。路径中包含中文字符时imageDatastore和addpath对编码的解析不一致就会报出莫名其妙的文件找不到错误。解决把整个项目放在纯英文路径下并且把.m文件统一另存为 UTF-8。如果你用的是 MATLAB R2023进入“预设项 → 常规 → 文件编码”把默认编码改成 UTF-8。代码文件第一行加一句feature(DefaultCharacterSet,UTF-8);也能缓解一部分问题但最稳妥的办法还是源头统一字符集。路径里的中文不是不能跑而是不值得为了一个目录名浪费一晚上的排错时间。5.2 第一轮迭代就 Out of Memory不一定是显存太小现象训练脚本刚开始进度条没走两步控制台报Out of memory on device或CUDA out of memory。原因最常见的是把整个数据集一次性读成了 cell 数组所有图片都进了内存。另一个常见原因是在循环里用gather把dlarray转回普通数组调imshow显存里的计算图没有释放多次迭代后累积爆掉。解决训练数据全部走minibatchqueue不要自己预先拼大矩阵。minibatchqueue会在每一批数据出队时才做预处理用完即释放。循环里需要可视化时每隔 20 个迭代用extractdata(gather(yFake))取一次不要每个迭代都做。如果确认是显存不够执行一次reset(gpuDevice(1))再接着跑但真正的解药是把 batch size 降下来。5.3 判别器 loss 掉到接近 0生成器还在原地翻车的经典组合现象训练到第 20 个 epoch判别器 loss 降到了 0.1 以下生成器 loss 却居高不下生成的图像全是噪声。原因判别器太强了。它很快学会了真样本和生成样本的区别生成器拿不到有效梯度。另一个原因可能是生成器太弱网络的层数不足以建模当前任务的翻译规律但大多数情况下是判别器把任务学得太快。解决把判别器的下采样层减少一层或者给判别器的卷积层加 dropout。学习率从 2e-4 降到 1e-4给生成器更多时间跟上。标签平滑也可以直接拉开差距。经验做法是当判别器 loss 从 0.5 快速掉到 0.1而生成器 loss 还在 10 以上时暂停训练检查数据配对是否错位。数据错位比网络结构问题常见得多。5.4 dlnetwork 与普通 network 混用inference 阶段报错的源头现象训练脚本一切正常保存模型后重新加载调用predict报错Invalid input data format或Network is in training mode。原因训练时生成的genNet是dlnetwork它期望输入是dlarray并且带有明确的维度标签。保存后重新加载如果直接用net(input)或forward跑普通数组MATLAB 不知道数据格式就会抛错。另一个常见原因是把trainNetwork训练出来的普通SeriesNetwork拿来和自定义训练循环里的dlnetwork混用。解决推理阶段统一走这个模板load(saved_genNet.mat, genNet); dlX dlarray(single(x) / 127.5 - 1, SSCB); dlY predict(genNet, dlX); y extractdata(gather(dlY)); y (y 1) / 2 * 255; % 如果是 tanh 输出转回 0~255这里predict会自动切到推理模式丢弃 dropout 和 BN 层的训练状态比forward更适合加载后直接使用。维度标签SSCB对应“空间、空间、通道、批”不能漏写。5.5 生成图像一片灰或颜色偏移先检查输出层和数据归一化现象训练了 100 个 epoch输出图像轮廓还算清楚但整体偏灰或者颜色明显不如原图鲜艳。原因如果数据端把图归一化到 0 到 1生成器输出层用的是tanhLayer模型只能输出 -1 到 1后处理没有把输出加 1 再除以 2颜色就对不上。还有一个隐蔽原因判别器输入层没有做归一化条件图是 -1 到 1目标图却是 0 到 1判别器学到的边界是错的。解决固定一套归一化方案最好从数据端就统一。我这里建议走原始 Pix2Pix 的路线数据归一化到 -1 到 1生成器输出层用tanhLayer判别器输入范围也在 -1 到 1。推理后处理时把网络输出从 -1 到 1 映射回 0 到 255。如果你偏爱 0 到 1就同时改生成器最后一层和数据预处理两个地方要同步不能只改一个。6. 最后一个值得掌握的技巧批量推理与 PSNR/SSIM 量化结果6.1 用 predict 和 extractdata 把训练切到部署训练收尾后最常用的操作是把验证集图片批量送入生成器保存所有输出。很多人会在循环里直接复制训练代码结果又走了一遍判别器和反向传播白白浪费时间和显存。正确做法是只保留生成器用predict做前向推理。load(saved_genNet.mat, genNet); dsTest imageDatastore(data/test_input); outPath result/output; if ~exist(outPath, dir), mkdir(outPath); end idx 1; reset(dsTest); while hasdata(dsTest) x read(dsTest); x imresize(x, [256 256]); dlX dlarray(single(x) / 127.5 - 1, SSCB); dlY predict(genNet, dlX); y extractdata(gather(dlY)); y (y 1) / 2 * 255; % 从 [-1,1] 回到 [0,255] imwrite(uint8(round(y)), fullfile(outPath, sprintf(test_%03d.png, idx))); idx idx 1; end这段代码里没有forward没有梯度也没有判别器。predict输出仍是dlarray所以要extractdata再gather才能写图片。如果你想保存的是网络输出的“中间层特征”那才需要forward否则一律predict。6.2 把运行结果量化成三个数再谈好不好主观看图很难判断模型是在真学还是在硬背。建议 20 个 epoch 存一次模型最后统一用 PSNR 和 SSIM 做量化对比。MATLAB 的psnr和ssim函数接受 0 到 255 范围的图像所以在推理代码里已经转回 uint8这里直接计算。score zeros(numel(dsTest), 2); % 假设 realPath 保存了对应的真实目标图 for i 1:numel(dsTest) gen imread(fullfile(outPath, sprintf(test_%03d.png, i))); real imread(fullfile(realPath, dsTest.Files{i})); score(i,1) psnr(gen, real); score(i,2) ssim(gen, real); end fprintf(average PSNR: %.2f dB\n, mean(score(:,1))); fprintf(average SSIM: %.4f\n, mean(score(:,2)));PSNR 在 25 以上说明结构大体正确SSIM 在 0.7 以上说明视觉感知比较接近。如果 PSNR 很高但生成结果还是模糊那是因为模糊图像计算 PSNR 可能不差这时一定再拼一张“条件图 生成图 真实图”的三联图肉眼对比。我自己跑这类 Pix2Pix 项目习惯是每 20 个 epoch 保存一次生成器故意留几版效果差的输出当作对照组。没有对照只看最后一张图很难判断是真正学到了映射还是正好挑中了测试集里意外好的一批。这个习惯帮我避开了很多次自我陶醉希望帮到你。本文还有配套的精品资源点击获取
