
老照片修复这件事我从三年前第一次动手做就栽过跟头。当时拿着一堆泛黄、带折痕、还有霉斑的家庭相片心想不就是去噪加超分再加个上色吗几个开源模型串起来就完事了。结果第一版跑出来的图人脸被修成了塑料娃娃衣服纹理全糊成一片划痕倒是没了因为整体都被抹平了。后来慢慢摸索才把基于深度学习的老照片修复系统拆成一条可控的流水线退化建模、分任务处理、人脸先验约束、工程侧分块推理每一环都得单独调。这篇文章就把这套系统的设计与实现过程完整摊开讲包括数据怎么造、模型怎么选、损失怎么配、大图怎么跑、出问题怎么查。适合已经懂一点PyTorch、想把这套东西真正落地成可用工具的朋友也适合刚入门、想搞清楚为什么我的修复结果总是糊的新手对照着看。1. 从需求倒推老照片修复系统到底要解决什么问题动手写第一行代码之前我把手上那批样本摊在桌上一张张看这个动作比看任何论文都管用。很多人一上来就冲着修复两个字去直接找个图像复原网络开训最后发现模型在合成测试集上PSNR很漂亮一上真实老照片就翻车。根本原因在于老照片退化不是一个单一退化它是好几种损伤叠在一起而且每种损伤的物理成因、空间分布、可恢复程度完全不同。1.1 用户手里的老照片坏在哪里我习惯把退化分成四类这个分类直接决定了后面系统的模块划分。第一类是结构性损伤包括折痕、撕裂、划痕、霉斑、水渍、装订孔。这类损伤的特点是局部像素信息被彻底破坏不是变模糊而是没了。你不可能从周围像素推断出一条划痕底下原本是什么只能靠语义先验去猜。这也是为什么纯回归模型处理划痕总是糊——它在做插值而人眼期待的是生成。第二类是光学与传感器退化包括失焦模糊、镜头畸变、胶片颗粒、扫描噪声。这类是全局性的可以用反卷积或者去噪网络处理相对好搞。第三类是分辨率与压缩退化老照片扫描件常见的问题尤其是早期数码化时为了省空间压成了低质量JPEG块效应明显。这块本质上是超分去压缩伪影的联合任务。第四类是色彩退化泛黄、偏色、褪色、对比度丢失。黑白照片还涉及上色这是另一个维度的任务因为上色本身是病态问题——同一件衣服可以是任何颜色模型只能给出统计上最合理的答案。我在系统里给这四类分别留了处理位而不是指望一个网络全包。原因很实在一个网络同时学四件事容量会被互相挤占而且训练时的梯度方向会打架。结构损伤需要生成能力强的分支色彩退化需要色彩空间约束强的分支硬塞在一起最后往往是两边都不讨好。1.2 三种技术路线的取舍端到端黑盒、分治流水线、先验增强市面上能见到的实现思路基本就三条我把当时的对比整理成了下面这张表路线做法优势硬伤适用场景端到端黑盒一个网络输入退化图、输出干净图结构简单、推理一次搞定需要海量成对数据多任务冲突严重不可调试退化类型单一、有真实成对数据的场景分治流水线划痕修复、去噪、超分、上色分模块串行每模块可单独优化和替换便于定位问题误差累积模块间色彩/亮度不一致退化类型复杂、需要可控输出的工程场景先验增强在主干上挂人脸/语义/结构先验分支关键区域质量高小样本下效果好依赖检测精度先验错位会引入伪影人像占比高的老照片我最终选的是分治流水线打底 关键环节挂先验的混合方案。理由有三个一是老照片的退化组合太随机单一网络很难覆盖二是工程上我需要能单独替换某个模块比如上色模型换一版不影响超分三是用户反馈的badcase往往集中在人脸挂一个人脸先验分支的收益远大于整体加容量。1.3 系统边界与效果预期管理这一点特别想强调因为它决定了你会不会白干三个月。深度学习修复的上限是合理重建不是还原真相。一条贯穿人脸的划痕模型补出来的鼻子可能形状对但绝对不可能和历史上一模一样。所以我在系统里做了两件事一是输出置信度提示对损伤面积超过阈值的区域标注重建结果仅供参考二是保留原图对照让用户自己判断。注意不要向使用者承诺完全还原。我见过有团队把修复结果直接当档案原件用这在实际业务里是会出问题的。预期管理也影响技术选型如果只是社交场景分享色彩讨喜、皮肤干净比PSNR重要如果是档案数字化那必须优先保真宁可保留一点噪点也不能生成不存在的细节。这两条路的技术方案差别很大动手前必须问清楚。2. 数据是命门退化合成流水线的设计做过图像复原的都知道这类任务七分靠数据、三分靠模型。老照片修复最难受的地方在于你几乎不可能拿到同一张照片的损坏版和完好版这样的成对数据。真实的档案里完好版早就没了剩下的只有坏的那张。2.1 为什么必须自己造数据我试过三条路。第一条是找公开的成对老照片数据集能找到的规模都很小几百到几千对而且退化类型集中在某一两种训出来的模型泛化很差换个扫描仪、换个年代就崩。第二条是拿无配对数据做循环一致性训练这条路能跑通但训练不稳定而且循环一致性约束只能保证风格像保证不了内容对。实测下来人脸五官容易漂移眼睛位置会挪。第三条是自己写退化合成器拿高清干净图当真值按物理模型往上面施加退化造出成对样本。这条路我最后走了因为它可控、可扩展、可以针对badcase定向补充。代价是你得把退化模型建得足够真否则就是自欺欺人——模型学会的只是你写的那个退化函数不是真实世界的退化。2.2 退化因子的建模与参数区间合成器的核心是把每种退化写成带随机参数的算子参数区间必须贴近真实分布。我这里是按大量真实样本统计出来的经验区间可以直接参考但强烈建议你用自己的样本重标一遍。退化类型建模方式参数区间经验值说明划痕随机贝塞尔曲线 形态学膨胀曲线1~6条宽度1~5px随机亮度偏移±40曲线控制点要跨越多行多列避免过短霉斑随机椭圆 高斯模糊边缘半径2~15px数量0~8个颜色偏青绿或灰白透明度0.3~0.8折痕直线 轻微弯曲 两侧亮度梯度长度占比30%~90%宽度2~8px关键是要有阴影纯白线不真实高斯噪声加性高斯sigma 3~25按图像尺寸缩放大图适当降低胶片颗粒泊松噪声 低频调制强度0.02~0.1颗粒要有空间相关性纯白噪声不像失焦模糊高斯核卷积kernel 3~15随机各向异性各向异性很重要老镜头像差不是圆的运动模糊线性核长度5~25角度0~180扫描时手抖造成的方向性模糊下采样双三次/Bilinear缩放0.3~0.8注意要用抗锯齿否则产生假细节JPEG压缩标准量化表quality 20~75二次压缩更真实泛黄褪色通道增益 伽马 饱和度衰减R增益1.0~1.15B增益0.75~1.0gamma 0.8~1.2泛黄本质是蓝色通道衰减这里有个细节值得单拎出来说退化必须按顺序叠加而且顺序有讲究。真实物理过程是先有光学模糊再有胶片颗粒然后化学老化褪色最后扫描时产生噪声和压缩。如果你把顺序打乱比如先加划痕再模糊划痕边缘会被糊掉和真实样本的锐利边缘对不上模型学到的特征就是错的。我第一版就是随机打乱顺序训出来的模型对划痕边缘特别不敏感实测才发现问题。2.3 合成脚本实现与踩坑核心的合成函数我简化成了下面这样可以直接拿去改import random import cv2 import numpy as np def random_scratch(h, w, n_curves3): mask np.zeros((h, w), dtypenp.uint8) for _ in range(n_curves): # 曲线起点终点随机分布在边缘附近 p0 (random.randint(0, w - 1), random.choice([0, h - 1]) if random.random() 0.5 else random.randint(0, h - 1)) p3 (random.randint(0, w - 1), random.randint(0, h - 1)) p1 (random.randint(0, w - 1), random.randint(0, h - 1)) p2 (random.randint(0, w - 1), random.randint(0, h - 1)) pts np.array([p0, p1, p2, p3], dtypenp.int32) width random.randint(1, 5) cv2.polylines(mask, [pts], False, 255, width, cv2.LINE_AA) # 膨胀让边缘不那么整齐 k random.choice([1, 3]) mask cv2.dilate(mask, np.ones((k, k), np.uint8), iterations1) return mask def apply_scratch(img, mask): out img.astype(np.float32).copy() # 亮度偏移而不是纯白纯白太假 offset random.uniform(-40, 40) out[mask 0] np.clip(out[mask 0] offset, 0, 255) return out.astype(np.uint8) def color_fade(img): # 通道增益模拟泛黄 gains np.array([random.uniform(1.0, 1.15), random.uniform(0.95, 1.05), random.uniform(0.75, 1.0)], dtypenp.float32) out img.astype(np.float32) * gains # 伽马 gamma random.uniform(0.8, 1.2) out 255.0 * np.power(np.clip(out, 0, 255) / 255.0, gamma) # 饱和度衰减 hsv cv2.cvtColor(out.astype(np.uint8), cv2.COLOR_RGB2HSV).astype(np.float32) hsv[..., 1] * random.uniform(0.5, 1.0) out cv2.cvtColor(np.clip(hsv, 0, 255).astype(np.uint8), cv2.COLOR_HSV2RGB) return out def degrade(img): h, w img.shape[:2] img apply_scratch(img, random_scratch(h, w, random.randint(1, 6))) if random.random() 0.6: img color_fade(img) if random.random() 0.7: k random.choice([3, 5, 7]) img cv2.GaussianBlur(img, (k, k), random.uniform(0.3, 1.5)) if random.random() 0.5: scale random.uniform(0.4, 0.8) img cv2.resize(img, None, fxscale, fyscale, interpolationcv2.INTER_AREA) img cv2.resize(img, (w, h), interpolationcv2.INTER_CUBIC) if random.random() 0.8: sigma random.uniform(3, 15) img np.clip(img.astype(np.float32) np.random.randn(h, w, 3) * sigma, 0, 255).astype(np.uint8) if random.random() 0.6: img cv2.imencode(.jpg, img, [cv2.IMWRITE_JPEG_QUALITY, random.randint(25, 75)])[1] img cv2.imdecode(img, cv2.IMREAD_COLOR) return img踩过的坑里最值得一提的两个。第一个是用cv2.polylines画直线划痕太整齐模型很快就能学到直线划痕这个捷径结果真实照片里弯曲的折痕完全去不掉。后来加了贝塞尔曲线控制点随机并且把线宽做成渐变情况才好转。第二个是下采样时的插值方式一开始用INTER_LINEAR产生大量摩尔纹模型把这些伪影也当成要修复的目标最后输出的图全是振铃。换成INTER_AREA做缩小、INTER_CUBIC做放大之后伪影明显减少。2.4 真实样本的少量标注与微调合成数据训练出来的模型直接上真实样本一定有域差。我的做法是收集200~500张真实老照片请人做粗标注——不需要像素级只需要这里要修好用低质量的真值图比如人工粗修的版本做微调。微调阶段有个技巧冻结主干的前几层只调后几层和先验分支。原因是合成数据学到的基础特征边缘、纹理是通用的真实数据量太小全量微调容易过拟合到那几百张图上。我实测冻结前1/3层微调学习率设成预训练的1/10效果最稳。实操心得微调集一定要包含极差样本——比如整张脸被划痕覆盖的、严重霉斑的。这类样本在训练里不能太多否则模型会退化成什么都往人脸方向生成但完全没有模型在真实badcase上就会暴露。3. 模型选型与结构拆解结构这块我没有追新选的是成熟可用的组合重点在于怎么把它们粘起来不打架。3.1 主干网络U-Net还是Transformer先说结论图像尺寸大、纹理复杂的老照片用带注意力机制的Transformer结构更稳追求推理速度和中等质量U-Net变体够用。我做过对比实验同一批合成数据U-Net在256×256 patch上训练PSNR能到28dB左右但一到512以上分辨率细节就明显塌陷主要原因是卷积的感受野有限全局色彩一致性差会出现局部色块。换成基于窗口注意力的Transformer主干后同等数据下PSNR提升约1.2dB纹理连贯性肉眼可见地好代价是显存占用翻了三倍多。对老照片这个场景我的建议是分阶段用不同主干划痕修复和去噪用U-Net因为这两件事更依赖局部感受野且需要快速推理超分和上色用Transformer或者带全局注意力的混合结构因为它们需要理解整体语义。这里有个必须注意的点Transformer类结构对训练数据量要求高如果你的合成数据只有几万张很容易欠拟合。我当时是用20万张合成patch起步的少于这个量级注意力分支基本学不出有意义的东西。3.2 划痕去除分支的设计划痕去除是整条流水线里最需要生成能力的一环。我的做法是把它当成条件生成 mask辅助来做而不是纯回归。具体实现输入除了退化图还并联一个预测出来的损伤mask。这个mask可以由一个轻量分割网络给出也可以由训练时的真值mask监督出来。有了mask之后主干网络只需要关注mask区域非损伤区域的像素直接shortcut保留。这样做的好处非常直接——非损伤区域不会被无谓地重绘细节得以完整保留。损失函数上损伤区域用L1 感知损失非损伤区域用强约束直接保留再加上一个对抗损失提升真实感。对抗损失的权重我压在0.02~0.05之间太高会引入伪纹理太低边缘发糊。有个反直觉的经验划痕分支不要追求把所有划痕都去掉。真实老照片里有些折痕是照片的历史痕迹全部抹掉反而显得假。我在系统里加了一个强度参数用户可以选择轻度修补和彻底修复前者只处理高置信度的大面积损伤保留浅表折痕。3.3 人脸先验模块的接入方式人像占了老照片的绝大部分而人眼对人脸的瑕疵极其敏感。合成数据训出来的模型修人脸最常见的问题是五官漂移、皮肤过度平滑、眼睛失去高光。我试过三种接入方式。第一种是串联整图修完之后检测人脸裁剪出来送进人脸复原模型再贴回去。实现简单但贴回时的色彩和边缘融合是噩梦接缝处会出现色差。第二种是并联分支主干网络在编码器某一层引出人脸区域的特征单独过一个重建分支解码时融合。这种方式融合自然但训练复杂得保证两个分支的梯度尺度匹配。第三种是两阶段第一阶段用主模型出粗修结果第二阶段用人脸先验做精修并且用泊松融合或者注意力融合贴回。我最后用的是这个因为工程上最可控出错时容易定位是哪一阶段的问题。贴回时的融合是个关键细节。直接覆盖会在边缘产生硬边我用的做法是以人脸框为中心生成一个高斯权重图在边缘区域做加权混合权重图的sigma设成人脸框短边的0.1~0.15倍。这个参数调小了接缝明显调大了人脸区域被原图污染实测0.12左右比较平衡。注意人脸检测失败的图一定要走fallback路径不能让流程中断。老照片里侧脸、遮挡、低分辨率的情况太多了检测器召回率远低于常规场景。3.4 上色分支的色彩约束黑白照片上色是另一个独立模块它的难点不在模型结构而在色彩不可判定。一件衣服是红的还是蓝的模型没有任何依据。我在上色上做了三条约束。第一条是语义引导先跑一个语义分割把天空、植被、皮肤、衣物分开然后对不同类别施加不同的色彩先验。比如天空偏蓝、植被偏绿肤色限制在一个较窄的色域内。这个做法能显著减少绿脸紫天空这类离谱结果。第二条是色彩空间选择不在RGB上做回归转成Lab只预测ab两个通道L通道保留原图亮度。这样亮度信息完全不会丢失避免了上色之后发灰的问题。第三条是多点提示给用户一个可选操作在图上点几个颜色点作为条件输入。技术上就是把点坐标和颜色编码成稀疏的condition map和图像一起送进网络。这个功能在真实使用中反馈极好因为用户自己知道照片里的人穿的是什么颜色。4. 损失函数与训练策略损失配比这块我调了大概两周试了十几个组合。这部分没法完全照搬别人的参数因为你的数据分布、任务侧重都不一样但下面这套思路和量级可以作为起点。4.1 多任务损失的配比我最终用的组合是五项损失项作用权重经验值说明L1 重建损失保证像素级接近真值1.0作为基准尺度其他项都相对它调感知损失保持高层语义和纹理0.1用预训练VGG的relu3_3和relu4_3特征对抗损失提升真实感和细节锐度0.03用PatchGAN判别器感受野70×70身份损失约束人脸身份一致0.5只在人脸区域内计算色彩损失约束上色结果的色相分布0.05在ab通道上算缓解偏色先说权重的确定逻辑。L1作为基准设为1.0是因为它数值量级最容易控制其他项都按比L1小一到两个数量级来配。感知损失定0.1是我实测下能明显改善纹理又不至于引入伪影的位置再往上加会出现纹理堆砌平坦区域被填上莫名其妙的细节。对抗损失压到0.03是因为老照片修复的判别器太容易训强权重一高生成器就开始往判别器的偏好上偏输出塑料感极重的图。身份损失值得多说一句。这个损失的作用是让修复前后的人脸在特征空间里保持接近防止模型把张三修成李四。实现上就是把人脸裁剪出来过一个人脸特征提取网络算余弦距离。权重0.5听起来很大但因为它的数值范围在0~2之间实际影响没想象中那么强。注意这个损失只在检测到人脸时计算没有人脸的patch要跳过不然会引入噪声梯度。4.2 两阶段训练与梯度平衡直接带对抗损失从零训十次有八次崩。我的做法是分两阶段。第一阶段纯重建预训练。只开L1 感知损失训到收敛。这个阶段的目标是让模型学会退化图到干净图的基本映射输出虽然糊但结构正确。学习率1e-4Adam优化器余弦退火batch size 8patch 256。这一步大概需要80~120个epoch具体看数据量。第二阶段加入对抗损失和身份损失微调。生成器和判别器交替训练生成器学习率降到2e-5判别器用5e-5。这里有个关键技巧判别器不能训太强。我的做法是给判别器加一个梯度惩罚项同时监控判别器的准确率一旦超过85%就暂停判别器更新几轮。判别器太强生成器的梯度就全是噪声输出会变得花里胡哨。梯度平衡上我一开始用的是固定权重结果发现训练到中后期对抗损失会突然主导L1的贡献被淹没。后来改成自适应权重——按各项损失相对于其历史均值的比值动态缩放让各项的有效梯度量级接近。这套机制实现起来不复杂但对训练稳定性的提升非常明显。4.3 训练中的稳定性技巧几个血泪教训。第一个是数据加载的随机性。退化合成器里的随机数种子必须每个epoch都变但如果用多进程DataLoader要注意每个worker的种子不能相同否则八个worker生成的退化样本一模一样等于数据量缩水八倍。我在这上面浪费了一整天才发现loss曲线异常地平滑。第二个是混合精度训练的陷阱。用AMP能省一半显存但感知损失和对抗损失在fp16下容易溢出尤其是对抗损失的log项。我的处理是把这两项的计算强制转成fp32只让主干前向走fp16速度损失不大稳定性提升明显。第三个是验证集的选择。验证集一定要包含两类合成退化的看基础能力和真实老照片的看泛化。只有合成验证集的话你会被虚高的指标骗过去。我一般按8:1:1划分训练/合成验证/真实验证真实验证集虽然小但每次看它的结果最有参考价值。5. 工程化落地从单张推理到批量服务模型训出来只是开始真正难的是让它在普通设备上跑得动、跑得快、跑得稳。5.1 分块推理与大图显存控制老照片扫描件动辄4000×6000直接送进网络必然爆显存。分块推理是标准解法但细节很多。我的分块策略是块大小512重叠64像素用余弦窗做加权融合。重叠区不能省因为卷积和注意力的边界效应会导致块边缘质量下降重叠64能覆盖大部分感受野。融合时不能用简单平均否则重叠区的纹理会有明显的重影用余弦窗中间权重高、边缘权重低能平滑过渡。def tile_inference(model, img, tile512, overlap64, scale1): h, w img.shape[:2] out np.zeros((h * scale, w * scale, 3), dtypenp.float32) weight np.zeros((h * scale, w * scale, 1), dtypenp.float32) # 余弦窗 wy np.hanning(tile * scale).reshape(-1, 1) wx np.hanning(tile * scale).reshape(1, -1) win (wy * wx)[..., None].astype(np.float32) 1e-6 step tile - overlap for y in range(0, h, step): for x in range(0, w, step): y2 min(y tile, h) x2 min(x tile, w) patch img[y:y2, x:x2] # 边缘补齐到固定尺寸否则模型输入尺寸不一致 ph, pw patch.shape[:2] pad np.zeros((tile, tile, 3), dtypepatch.dtype) pad[:ph, :pw] patch pred model(pad[None])[0] pred pred[:ph * scale, :pw * scale] out[y*scale:y2*scale, x*scale:x2*scale] pred * win[:ph*scale, :pw*scale] weight[y*scale:y2*scale, x*scale:x2*scale] win[:ph*scale, :pw*scale] return (out / np.maximum(weight, 1e-6)).clip(0, 255).astype(np.uint8)这里有个坑tile尺寸必须是模型下采样倍数的整数倍。如果你的主干有四次下采样tile必须能被16整除否则特征图尺寸对不齐要么报错要么静默出错。我一开始用500调试了半天才发现问题。还有个优化点分块推理时如果相邻块的内容高度相似比如大片天空可以跳过后面的计算。但这个优化要谨慎容易在纹理交界处留下痕迹我最后没上。5.2 模型压缩与推理加速部署到普通设备上原始模型太大了。我做了三步压缩。第一步是剪枝。对卷积通道做结构化剪枝按BN层的缩放因子排序剪掉最小的20%。这一步模型小了约25%PSNR掉不到0.3dB性价比很高。剪完要微调10个epoch恢复性能。第二步是量化。导出成ONNX之后做INT8量化配合校准集。这里要注意校准集必须覆盖你的真实数据分布用纯合成数据校准量化后的模型在真实老照片上掉点会很明显可能掉2dB以上。INT8之后模型体积大概是fp32的四分之一速度提升两倍左右。第三步是推理引擎适配。把量化后的模型转到TensorRT用FP16模式跑。实测在中等配置的显卡上512×512的patch推理时间从180ms降到45ms左右。如果目标设备没有独立显卡那就退回ONNX Runtime的CPU推理配合多线程一张2000×3000的图大概需要6~10秒也能接受。提示量化之后一定要做一次全量回归测试。我遇到过一个很隐蔽的问题——INT8量化后模型对浅色划痕的敏感度下降导致浅色背景上的划痕修不掉。这种问题在单个样本上看不出来必须批量测。5.3 前后端接口与批处理队列系统接口我设计成异步任务制而不是同步请求。原因很简单一张大图处理可能要十几秒同步请求必然超时。流程是这样前端上传图片后端返回一个task_id把任务丢进队列工作进程从队列取任务依次跑划痕修复、去噪、超分、上色、人脸精修每完成一步更新进度前端轮询或者用长连接拿进度完成后拿结果URL。队列我用了两种优先级单张交互式的走高速队列批量任务走普通队列。批量任务的并发度要控制因为每个任务都吃显存开太多会OOM。我的经验值是单卡并发不超过2个任务再高就排队等着不然全是超时重试整体吞吐反而下降。结果存储上原图和修复图都要留并且存一份处理参数用了哪些模块、强度多少。这个日志在后期排查问题时价值极大——用户投诉这张修坏了你能立刻复现出当时的处理链路。6. 效果评估与常见问题排查6.1 指标之外的主观评估PSNR和SSIM在合成测试集上很有用但它们和人的主观感受相关性很差。一张PSNR 30dB的图可能看起来很糊一张PSNR 26dB的图可能看起来很自然因为GAN类的输出本来就不追求像素级对齐。我建了一套自己的评估流程分三层。第一层是客观指标在合成测试集上跑PSNR、SSIM、LPIPS。LPIPS比前两个更贴近人眼判断我一般以LPIPS为主。真实数据没有真值就跑无参考指标比如NIQE和BRISQUE但这俩指标对GAN输出不太友好参考价值有限。第二层是人工打分。我组织了5个人每人看100组图从清晰度自然度保真度三个维度打1~5分。样本要盲测不能让人知道哪个是模型输出。这一步最费时间但最准。第三层是A/B对比。拿新版本和上一版本各修同一批图让人二选一。这个方法特别适合判断改动到底有没有用比绝对打分更敏感。有个数据可以分享在我的测试集上单纯优化PSNR从27.5提到28.2人工打分几乎没有变化但把对抗损失权重从0.01调到0.03PSNR反而降了0.4人工打分的自然度提升了0.6分。这说明什么不言而喻。6.2 典型问题速查表下面这张表是我这两年攒下来的遇到问题基本能对号入座现象可能原因排查方法解决方向输出整体发灰、对比度低上色模块L通道被改动对抗损失权重过低单独跑上色模块看L通道差异锁定L通道提高对抗权重到0.03人脸变成塑料娃娃身份损失或对抗损失过强感知损失权重过高关掉对抗损失看是否恢复降低对抗权重加肤色纹理约束划痕去不干净尤其是浅色划痕合成数据里划痕对比度太高量化掉点检查合成器亮度偏移范围扩大偏移范围到±40重新校准量化大图出现明显方块接缝tile重叠不足融合权重不合理可视化weight图重叠加到64以上用余弦窗天空、墙面出现莫名纹理对抗损失导致纹理幻觉对比有无对抗损失的输出降低对抗权重对平坦区域加平滑约束上色后颜色离谱绿脸、紫天缺少语义引导色彩损失权重不足检查分割模块输出加语义先验提高色彩损失推理速度突然变慢输入尺寸触发非最优tile显存不足触发换页打点记录每个模块耗时调整tile尺寸限制并发部分图片处理到一半失败人脸检测异常图像通道数不为3加异常捕获和日志加fallback统一预处理为RGB6.3 实操心得最后分享几条不太好写在文档里、但实际很管用的经验。关于迭代顺序。不要试图一次把所有模块都做到最好。我的做法是先把去噪超分这条最基础的链路跑通做到能出图再加划痕修复最后加上色和人脸精修。每加一个模块都做一次完整的回归测试确保没有破坏已有能力。我见过有团队并行开发四个模块最后集成时互相冲突返工了两个月。关于badcase的收集。系统上线后一定要有便捷的badcase反馈通道。用户点一下这张修得不好图和参数就自动进到一个待分析池里。我每周会花半天看这些样本按退化类型归类然后定向往合成器里补充对应的退化。这个闭环跑起来之后模型效果的提升比盲目加数据快得多。关于参数的暴露程度。一开始我把所有参数都藏在后台用一个智能模式应付所有图。结果用户抱怨很多因为不同类型的照片需求完全不同——证件照希望保守艺术照希望鲜艳。后来我把修复强度上色饱和度人脸精修程度三个滑块放出来投诉率降了一大半。让用户参与控制比让模型猜要靠谱。关于色彩的一致性。分模块处理最大的副作用是色彩不统一比如划痕修复模块输出的偏冷上色模块又偏暖拼在一起很怪。我的解法是在流水线末端加一个轻量的色彩对齐模块以原图的整体色调为参考对结果做一次全局的色彩迁移。这个模块很小就是在Lab空间做均值方差匹配但效果立竿见影。关于长尾退化。真实老照片里总有一些你没想到的退化比如照片背面透过来的字迹、相纸的纹理、装订孔的阴影。这些在合成数据里没有模型处理起来会当成正常内容保留下来。我的处理是加一个异常检测的旁路用无监督方法找出与整体分布差异大的区域标记出来提示用户。不一定要自动修能提示就已经很有价值了。这套系统我从最初的一版跑通到现在前后迭代了十几个版本最大的感受是老照片修复的难点从来不在模型结构上而在数据、在退化建模、在对什么算修好了的判断标准上。模型可以换流水线的设计思路和对真实场景的理解才是真正决定成败的东西。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。