尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

图像修复技术解析:从传统扩散到深度学习Inpainting

图像修复技术解析:从传统扩散到深度学习Inpainting 简介一套面向图像修复Inpainting技术的 C 实现源码包适合计算机视觉学习者与开发者用于理解并动手实现选定区域缺失像素的填补算法。压缩包共64个文件、约4.21MB以 .cxx/.h 源码为主体辅以 OpenCV 工程配置.sln/.vcproj/Makefile、测试图像.jpg/.bmp/.pgm、PDF 论文Criminisi_cvpr2003及 README、CHECKLIST、WRITTEN 等说明文档可覆盖从阅读到复现的完整路径。内容预览显示项目分 partA/partB附有 bin 可执行文件与 VC 工程目录能帮助读者快速了解算法模块划分。已有115人学习。通过该包可掌握基于扩散或纹理合成的修复流程包括边缘检测、区域标记、像素传播与融合平滑等关键步骤并对照论文与代码进行优化实验。整体结构清晰适合作为课程设计或技术研究的参考实现。 老照片修复的需求找上门时对方提了一句把那个划痕区域删掉软件自动用旁边的东西填上就行。当时我脑子里冒出来的词就是 Inpainting。这个词听起来像是给图像“打补丁”真正做起来却是一个很有意思的研究方向给定图像和一块被抹掉的区域让算法根据周围幸存像素去推测、生成、补全那一片缺失的空间。这几年从传统方法一路做到深度学习方法踩过的坑不少但这个题目一直觉得很值得聊一聊。这篇文章适合对计算机视觉、图像处理感兴趣的开发者也适合准备入门图像修复方向的算法工程师。我会把问题定义、传统方法、深度学习方法、评价指标和训练技巧都串起来讲尽量讲清楚“为什么这样做”和“实际用的时候会碰到什么麻烦”。1. 先说清楚Inpainting到底在解决什么问题1.1 三句话版本的问题定义如果要把 Inpainting 的形式化定义压缩到一句话大概是给出原图 (I) 和一组掩码 (M)(M1) 的区域是待修复区域我们需要生成一张新图 (I)它在掩码外部尽量和原图一致在掩码内部则根据外部信息推断出“看起来真实、符合原图结构”的像素内容。这里面的关键词不是“填上去”而是“算出来”。因为掩码内部的像素信息本身就是缺失的算法能利用的信息只有掩码周围的颜色、边缘、纹理整张图的全局语义比如这是人像、风景还是物体图像结构上的连续性比如一堵墙的延长线、一条道路的走势。早期看到很多教程把 Inpainting 归类为“图像编辑工具”这个定位准确但不完整。它在实际项目里经常承担老照片划痕修复、物体移除、文字遮挡擦除、视频中的目标去除、深度图空洞填充等任务。在检测、分割等任务之前做数据清洗时也经常用 Inpainting 把无关障碍物抹掉。1.2 不要小看这个任务四个核心难点刚开始做这个方向时我犯过一个认知错误以为周围像素足够多做一个带权重的“模糊填充”就行。实际上手之后发现难点集中在四个地方第一结构延续性。比如一栋楼的墙角线延伸到掩码区域里面如果只是模糊填充墙角线就断掉了人眼一眼就能看出来。算法需要感知全局结构把线条、轮廓“接”上。第二高频纹理复现。草地的颗粒感、水面的波纹、墙面的砖缝这类高频信息在掩码内部无法被插值生成。简单的距离变换或者 Poisson 融合只能得到一团糊。第三语义理解。掩码压住了一个人物的半边身体时单纯靠周围像素撑不出“另一条手臂应该长什么样”。这需要模型知道“人”这类物体的先验分布也就是语义级别的推理能力。第四真实性度量。不像分类或者检测有明确的标签Inpainting 的“填充效果好不好”本身带有很强的主观性。后面我会专门说评价指标的坑。1.3 和图像转换、超分、生成的关系很多刚接触的同学会把 Inpainting 和图像超分辨率、风格迁移、GAN 图像生成混在一起。简单区分超分是低分辨率到高分辨率的映射输入信息是全的只是精度不够风格迁移是改变纹理风格而保持内容结构Inpainting 是信息缺失问题最难的地方在于“无中生有”。但解决方法又确实和图像生成的方法重合——尤其是深度学习阶段基本就是在用条件生成模型做填充。这也是为什么主流的 Inpainting 论文里面总能看到 GAN、扩散模型的身影。2. 从扩散到纹理合成传统方法为什么还没过时2.1 基于扩散的填补适合小缺口不适合大空洞传统 Inpainting 的第一个大类是偏微分方程PDE扩散类方法。最经典的是 Bertalmio 等人提出的 BSCB 模型思路很简单把掩码边缘已知像素的信息沿着等照度线方向往内部“推”像墨水在纸上洇开一样。这类方法没有训练概念也不需要数据集对很小的划痕、折痕效果不错。我当时用 OpenCV 里的cv2.inpaint接口做过实验它默认实现就是基于 Telea 的快速匹配方法和基于流体动力学的方法。代码量非常小import cv2 import numpy as np img cv2.imread(old_photo.jpg) mask cv2.imread(mask.png, cv2.IMREAD_GRAYSCALE) # mask 中待修复区域为白色255 result cv2.inpaint(img, mask, inpaintRadius3, flagscv2.INPAINT_TELEA) cv2.imwrite(result.jpg, result)但参数inpaintRadius一旦加大扩散方法立刻露馅空洞区域会出现明显的模糊边缘线断裂纹理变成一团浆糊。因为它本质是邻域插值不具备语义理解能力。我当时把它用在一张划痕横跨建筑物立面的老照片上半径调到 5 之后墙壁纹理直接糊成了灰色块完全没法交差。所以传统扩散方法的适用边界非常清晰小面积、背景平滑、纹理简单。大面积修复请直接考虑下一个方案。2.2 基于样本块的纹理合成Criminisi 方法的启示2004 年的 Criminisi 算法是传统方法里最有启发性的一篇。它的思路不是从零生成像素而是从图像的其他已知区域里“抠”相似的 patch 搬过来。核心步骤是计算掩码边界上每个点所在 patch 的置信度用梯度等信息计算结构强度置信度乘以结构强度得到修复优先级优先级最高的点先填充从已知区域里搜索最接近的 patch 贴过来更新置信度重复直到完成。这个算法解决了一个扩散方法做不到的事情能复现纹理细节因为它是直接从原图像素中搬移真实纹理而不是插值模拟。我常把 Criminisi 比作“用拼图补拼图”它有结构感知能力懂得优先补边缘线而不是像扩散那样从四周均匀渗透。局限也很明显当掩码区域太大、或者图像里没有可匹配的相似纹理时搬移的结果会出现重复畸变。而且这个算法里哪些 patch 算“相似”用的是简单的光度误差在复杂自然图像里这种匹配经常翻车偶尔会把完全不相关的物体纹理贴进来。2.3 传统方法的遗产虽然深度学习方法现在已经成为主流但传统方法在两类场景里依然有价值小空洞、实时性要求高的嵌入式场景传统方法不用加载模型延迟极低作为深度学习方法的后处理用于填补边缘处的微小瑕疵。而且 Criminisi 的“优先级 patch 匹配”这个框架某种意义上启发了后来深度方法里用的 patch-based 注意力机制比如 ICCV 2019 那篇有名的 Partial Convolution 里的注意力思想。所以说传统方法“过时”是不准确的更准确的说法是它们的思想已经被吸收进当代算法里了。3. 深度学习怎么把“填充”变成“语义生成”3.1 从卷积到部分卷积掩码如何参与计算深度学习做 Inpainting 表面上很简单把掩码区域内的像素置零丢进一个 U-Net 里回归重建。但第一代做法马上暴露了一个问题普通卷积会把掩码区域的零像素和周围真实像素混在一起做卷积导致输出颜色偏淡、边缘重影。2018 年 NVIDIA 提出的 Partial Convolution 给了一个很漂亮的解法卷积只在有效像素上操作然后根据有效像素数量做归一化。每次卷积之后再同步更新掩码。这个策略直接改善了掩码区域和正常区域的过渡成为了当时 Inpainting 方向一个非常有分量的 baseline。我自己的理解是Partial Convolution 本质上是在告诉网络“哪些像素的信息是可信的”。如果掩码区域一直保持在输入层面网络里的每个卷积层其实都在处理一堆假零值会严重干扰特征提取。有了掩码更新机制网络才能一层层从边缘向内部“侵蚀”空洞。3.2 两阶段范式先重建结构再生成纹理2019 年之后很多模型开始把 Inpainting 拆成两个阶段Edge Connect 是其中影响力比较大的一种思路。它先把问题切分第一阶段输入掩码和灰度图/边缘图输出修复后的边缘结构第二阶段把补全的边缘结构作为条件和彩色图像一起送入生成网络得到最终纹理。这种拆分的动机很简单人类的视觉感知本身就对结构特别敏感墙线、轮廓、地平线这些信息一旦对了纹理差一些还能接受但结构错了再好的纹理也是白搭。因此让模型先预测结构再在结构约束下生成像素等于人为给模型装了一个“空间约束器”。在实际项目里这种两阶段模型对于“建筑立面、道路延伸线”这类强几何结构的场景效果提升非常明显。但对于草地、皮肤、毛绒玩具这类几乎没什么强结构的区域两阶段优势就没有那么突出直接用端到端模型反而更省事。3.3 GAN 与损失函数的组合拳Inpainting 深度学习模型的训练损失通常不是单一 L2 损失而是多损失加权重建损失L1 或 L2保证掩码外区域和原图一致掩码区域有基本像素结构感知损失Perceptual Loss把预测图和原图都送入 VGG 等预训练网络比较中间层特征图的距离用来约束语义和结构的一致性风格损失Style Loss计算特征图的 Gram 矩阵差异提升纹理质感对抗损失用判别器判断修复区域真伪让生成的纹理更接近自然分布。踩坑经验L1/L2 损失过小会让图像发糊对抗损失过大会让图像产生奇怪的伪影。比较稳妥的做法是前几万步先放开重建损失和感知损失把结构学稳再逐渐提高对抗损失的权重。至于具体权重比例不同数据集和不同掩码比例差别很大我一般先按 Edge Connect 论文里的默认值跑通再用一小组验证集人工观察调整不能只盯着 PSNR 数值调权重。3.4 Transformer 和大模型的介入近几年 Transformer 结构也被引入 Inpainting。代表性思路是把掩码区域当作“要生成的序列 token”用注意力机制从已知区域中提取信息。相比 CNN 的局部感受野Transformer 能建模像素之间的长距离依赖这对跨越较大空洞的结构延续很有帮助。同时以扩散模型DDPM、Stable Diffusion为基础的大模型方案也在改变这个方向的工作方式。Stable Diffusion 的 Inpainting 版本可以在掩码区域内部根据文本提示重绘物体做“内容生成”而非“内容推测”。这里也提醒一下如果需要的是“严格保持原图语境下的信息恢复”传统深度修复模型和扩散模型都要设置好范围约束避免模型自由发挥过度把老照片修复成了“艺术创作”。从工程角度看小成本项目里我个人最常用的还是基于 U-Net/Partial Convolution 的中等规模模型训练和推理可控性高。扩散类大模型效果好但对显存、推理耗时和数据要求都比较高适合对效果要求高、且允许 seconds 级推理时延的场景。4. 评价指标怎么选才能不被PSNR骗到4.1 常见指标各有各的“盲区”Inpainting 方向的论文里最常见的指标组合是 PSNR、SSIM、LPIPS 和 FID。实际用下来每个指标都有明显短板PSNR 基于逐像素 MSE对全局亮度敏感但对纹理结构不敏感。一张模糊平滑的图可能 PSNR 不低观感却很差。SSIM 关注亮度、对比度和结构的局部相似性比 PSNR 合理一些但对高频纹理仍然不够敏感。LPIPS 是在深度特征空间里计算感知距离和人类视觉判断的相关性明显更高是当前比较推荐的单项指标。FID 衡量生成的分布和真实分布的差异适合评估整体真实感但对单张修复结果的精确度不够敏感。我曾经在实验里遇到一个很戏剧性的情况一个 Baseline 模型的 PSNR 比新模型高出 0.5dB但人工评测时大家一致认为新模型的修复结果更自然。差距主要就出现在高频纹理和边缘细节上PSNR 反而给“保守平滑”的 Baseline 加分了。4.2 我的建议指标组合如果做一个生产环境可用的 Inpainting 模型我个人建议同时报 LPIPS FID 人工倾向率。没有人工评测的 Inpainting 实验说服力非常有限。因为 Inpainting 的最终交付对象是人眼自动指标只能辅助筛选最终应该靠一套固定场景的人工盲评来做决策。盲评的设计也有一些细节不同掩码比例、不同图像类别最好分开评每张图停留时间要固定对比图最好无序展示评分选项要具体而不是简单“好/坏”。这样得到的结论才比较可靠。4.3 掩码比例是报告指标时必写的条件很多算法的效果会随掩码比例剧变。掩码占图像面积 5% 时大部分模型都表现不错一旦掩码比例升到 30%、50%模型差异就出来了。我习惯在验证集上按掩码比例分层统计指标比如 0-10%、10-20%、20-40%、40%以上几档分别报数。这样定位模型退化在哪一档非常有效也能更清楚地对比不同方案的适用边界。5. 实战经验数据、掩码与训练调参里最容易翻车的细节5.1 数据集准备不是简单“抠一块”Inpainting 的训练数据一般有两种来源一是已有的 Inpainting 公开数据集比如 Places2、CelebA-HQ 配随机不规则掩码二是自制数据的掩码模拟。无论哪种都需要一个足够“刁钻”的掩码生成器。不可预测的方形掩码是最初期做法效果不太好因为真实场景的划痕、杂物遮挡大多是任意形状。后来常用的是从不规则线段、膨胀噪点、甚至从 PASCAL VOC 的真实物体轮廓里提取掩码。推荐做法是混合多种掩码小划痕、中等圆形、大面积不规则块都要有。这样训练出来的模型泛化能力更强。下面是当时我做数据增强时用的一个快速生成随机掩码的小脚本脱胎于 Jocher 的 YOLOv5 里 mask 生成思路import numpy as np import cv2 def random_mask(height, width, max_vertex8, max_length80, max_brush_width24): mask np.zeros((height, width), dtypenp.uint8) num_brush np.random.randint(1, 4) for _ in range(num_brush): num_vertex np.random.randint(3, max_vertex 1) start_x np.random.randint(0, width) start_y np.random.randint(0, height) vertices [(start_x, start_y)] for _ in range(num_vertex): angle np.random.randint(0, 360) length np.random.randint(10, max_length) new_x max(0, min(width - 1, vertices[-1][0] int(length * np.cos(np.deg2rad(angle))))) new_y max(0, min(height - 1, vertices[-1][1] int(length * np.sin(np.deg2rad(angle))))) vertices.append((new_x, new_y)) brush_width np.random.randint(5, max_brush_width 1) for i in range(len(vertices) - 1): cv2.line(mask, vertices[i], vertices[i 1], 255, brush_width) return mask5.2 训练中的三个高频坑第一个坑是不知道冻结 BatchNorm。迁移学习时如果对预训练参数做 fine-tuneBatchNorm 层的统计量一开始会很混乱。测试时前向传播用的 running mean/var 如果仍沿用预训练统计量输出会有颜色偏移如果不冻结直接用 batch 统计量小 batch 时又会抖动。最稳妥的办法是如果输入分布差异不大固定住 BN 参数只微调其他层如果必须从头训练就把 batch size 拉大确保 BN 统计量稳定。颜色一致性在这个任务里非常关键因为 Inpainting 不允许修复区域和周边区域存在色差。第二个坑是掩码泄露。有些实现会在输入里把掩码本身作为额外通道喂给网络这是合理的但如果后续标注人员标注掩码时不小心在掩码区域上留了半透明边缘模型就会学到“周围半透明像素不可信”测试时凡是边缘都会被抹掉。所以我建议在预处理阶段对掩码做形态学腐蚀把边缘处所有像素都标记为待修复区宁可多修一圈。第三个坑是多尺度感受野不足导致结构断裂。输入分辨率太低比如 256x256 以下模型对大空洞里的长距离结构感知能力很弱容易出现结构接不上的问题。工程上可以两种办法并行训练时随机裁剪高分辨率图参与训练或者模型里加入空洞卷积/注意力模块来扩大感受野。如果显存不够也可以考虑分阶段训练先在低分辨率学全局语义再在局部高分辨率 patch 上精修纹理。5.3 推理阶段的后处理技巧就算模型训练得不错推理阶段也经常需要做一点收尾工作。我常用的思路是先用模型预测完整修复图然后只把掩码区域内的像素替换成预测值掩码外区域仍然用原图。这能强制保证非修复区域像素和原图完全一致。如果修复区域的边缘有轻微割裂感可以再对掩码边缘做 2~4 像素的 Gaussian 模糊过渡视觉上会自然很多。import cv2 import numpy as np # pred_img: 模型输出完整图, original_img: 原图, mask: 二值掩码 mask_blur cv2.GaussianBlur(mask.astype(np.float32), (5, 5), 0) mask_blur (mask_blur / 255.0)[..., None] result pred_img * mask_blur original_img * (1 - mask_blur) result result.astype(np.uint8)这个“软掩码融合”虽然简单但很实用特别是模型在掩码边缘出现过拟合伪影的时候能立刻把边缘观感拉回来不少。需要留意的是Gaussian 核大小不要太大否则修复区最外圈会混入原图信息等于没修干净。5.4 如何用零代码心态套用开源模型如果暂时没有训练预算没必要从零写模型。开源社区有几类方案可以直接拿来用OpenCV 自带传统算法适合小划痕几行代码搞定LaMaCVPR 2022 Outstanding Paperlarge mask inpainting模型效果非常好遇到大面积遮挡也不容易崩有现成的推理仓库和预训练权重Stable Diffusion 的 Inpainting 版本适合做生成式补全但要留意内容自由度问题不适合需要严格信息保持的修复任务。我建议刚接触 Inpainting 的人先用 OpenCV 方法找到“小划痕场景下的性能上限”再用 LaMa 跑一遍同一批验证图直观感受传统方法与深度方法的差距最后再自己训练模型比较哪些环节还有提升空间。这样对整个问题的认识会更立体。6. 走到这里Inpainting 还能往哪扩展如果只看标题“给定图像和选定区域填充该空间”它确实只是修复问题。但我在实际项目中越来越体会到这套能力可以被非常自然地迁移到其他任务里。比如视频 Inpainting 的把空间维度换成时空维度需要额外考虑时序一致性深度估计任务里的空洞填充需要结合点云的深度先验文字去除场景里则经常叠加 OCR 检测来引导掩码生成属于“感知 修复”的级联系统。一个让我印象很深的项目是把 Inpainting 用在了自动驾驶仿真场景中的目标移除上——把路上偶发的干扰车辆从图像里去掉再交给下游分割模型做训练。这种做法相当于一种数据增广策略效果比直接在原始数据上加噪声更鲁棒。这类“修复即增强”的思路我觉得比单纯做老照片修复更有想象力也更具工程价值。最后再分享一点个人体会Inpainting 这个方向上手不难但真正要把效果做到自然、稳定需要非常重视“合理的结构先验”和“可用的质量度量”。不要一开始就追 SOTA 模型先吃透传统方法为什么慢、为什么糊再理解深度模型为什么能补出纹理、为什么有时候会“脑补”出不该有的东西。这套分析能力在后续任何图像生成类任务里都通用。本文还有配套的精品资源点击获取
返回列表