你 inpaint 了一小块——比如把咖啡马克杯换成酒杯——蒙版内效果不错,但蒙版外的一切都微妙地变了。肤色稍微偏暖了,桌面木纹横移 2 像素,墙面漆色变了。这就是”inpaint 溢出”,也是 diffusion 编辑里最让人崩溃的坑之一,因为表面上”成功了”,直到你做像素 diff 才发现整张图都动了。有几种机制会引起,且只有一种能靠提示词修;其他都要改管线。
常见原因
按频率排。
1. 管线在做全图 inpaint,不是仅蒙版 inpaint
很多 diffusion 管线有两种 inpaint 模式:
- 仅蒙版:只有蒙版内的像素会变;蒙版外按字节复制原图。
- 全图带蒙版条件:模型重新生成整张图,把蒙版当作引导提示。
第二种会溢出。在很多 SDXL inpainting workflow 里它是默认。
如何识别:把输出和原图在蒙版外做 diff。任何像素不同 = 你在全图模式。
2. 蒙版有羽化 / 软边
带羽化(高斯模糊)的蒙版告诉模型”从内到外渐变混合”。混合区被部分重生成——意味着你以为”在外面”的像素也被微妙地改了。
如何识别:打开蒙版文件。边缘是灰色渐变 = 有羽化。锐利黑白边 = 无羽化。
3. VAE 解码有损
哪怕模型只动蒙版内,编码-解码通过 VAE 不是逐字节一致。蒙版外的区域经过 VAE 往返一次回来,带量化噪声。
如何识别:在蒙版外做 diff。差异很小(0-255 范围内 ±2 以内)且看起来像随机噪声 = VAE 往返损失。
4. Inpaint padding 扩展了蒙版区域
大多 inpaint 管线会把蒙版按 N 像素 padding,对 padding 区域做处理来融合。这片 padding 从你视角是”蒙版外”,从管线视角是”蒙版内”。
如何识别:查 inpaint padding 参数(常叫 inpaint_full_res_padding 或 pad_pixels)。值为 32 或 64 = 预期 32-64 像素的”外部”会被动。
5. CFG-rescale 或 img2img strength 太高
基于 img2img 的 inpaint 里,denoising_strength 接近 1.0 实际上等于做带弱条件的文生图。模型重新生成一切。
如何识别:输出整体看起来”不一样”了,不只是蒙版内。把强度降到 0.7 重测。
6. SDXL refiner pass 处理整张图
底模 inpaint 之后,SDXL refiner 第二阶段可能在整张图上跑。某些实现里 refiner 不感知蒙版。
如何识别:只跑底模(关闭 refiner)。蒙版外像素稳定 = refiner 是元凶。
7. PNG inpaint 之后再被存成 JPEG
如果导出成 JPEG,JPEG 量化会改变每个像素,不只是蒙版内。这不是 inpaint 溢出,是 JPEG。
如何识别:把输出存成 PNG 重做 diff。蒙版外一致了 = JPEG 是原因。
开始前准备
- 全分辨率保存原图。
- 同分辨率保存蒙版文件。
- 记下你管线在用哪种 inpaint 模式(仅蒙版 vs 全图)。
- 决定你的容忍度:蒙版外像素完美保留,还是视觉不可区分(几个单位 VAE 噪声没关系)。
需要收集的信息
- Inpaint 模式标志(
inpaint_full_res、mask_mode、whole_image等)。 - 蒙版文件(打开它;验证边缘是否锐利)。
- 蒙版羽化 / 模糊值。
- Inpaint padding 值。
- denoising strength。
- 是否启用 refiner。
- 输出文件格式和质量设置。
一步步修复
步骤 1:切到仅蒙版的 paste-back 模式
AUTOMATIC1111:启用 “Only masked” + “Inpaint at full resolution”。
ComfyUI:用 “Set Latent Noise Mask” 节点 + 末端 “Paste By Mask” 节点把蒙版外像素从原图还原。
Diffusers:
from PIL import Image
import numpy as np
generated = pipe(prompt, image=img, mask_image=mask).images[0]
# 合成:蒙版为黑的地方用原图,蒙版为白的地方用生成
mask_arr = np.array(mask) / 255.0
orig_arr = np.array(img).astype(np.float32)
gen_arr = np.array(generated).astype(np.float32)
final = (1 - mask_arr[:, :, None]) * orig_arr + mask_arr[:, :, None] * gen_arr
Image.fromarray(final.astype(np.uint8)).save("out.png")
这是字面 paste-back:蒙版外逐字节一致。
步骤 2:用硬边蒙版
需要柔过渡时,先画硬蒙版,最后用一个独立的 alpha 渐变做合成。不要让 diffusion 管线看到软蒙版:
# 把灰阶蒙版转成硬蒙版(阈值化)
convert mask_soft.png -threshold 50% mask_hard.png
步骤 3:减少 inpaint padding
padding 溢出太多时:
pipe(
prompt=prompt,
image=img,
mask_image=mask,
padding_mask_crop=8, # 更小
)
或设为 0 关掉混合。4-8 像素 padding 能给干净接缝又不太溢出。
步骤 4:降低 denoising strength
小改动的 inpaint(换色、换物):
strength = 0.7 # 不是 0.95
0.5 以下蒙版区几乎不变。0.85 以上溢出风险上升。0.7 是大多数编辑的甜点。
步骤 5:Inpaint 时关闭 refiner pass
SDXL workflow 里:
inpaint_pipe(
prompt=prompt,
image=img,
mask_image=mask,
use_refiner=False,
)
如需精修,只在 inpainted 区域跑 refiner,保留蒙版。
步骤 6:用 PNG 输出并验证像素稳定性
# 蒙版外像素 diff
python -c "
from PIL import Image
import numpy as np
a = np.array(Image.open('orig.png'))
b = np.array(Image.open('out.png'))
m = np.array(Image.open('mask.png'))[:,:,0] > 128
outside_diff = np.abs(a[~m].astype(int) - b[~m].astype(int)).max()
print('Max outside-mask pixel diff:', outside_diff)
"
paste-back 工作正常 = 期望 0。VAE 往返 = 期望 ≤2(可接受)。再高 = 还在溢出。
步骤 7:极小编辑用裁剪-编辑-粘贴
非常小的变动(256x256 以内),裁出区域,在裁块里 inpaint,再贴回。绕过所有全图溢出:
crop = img.crop((x, y, x + 256, y + 256))
edited_crop = pipe(prompt, image=crop, mask_image=mask_crop).images[0]
img.paste(edited_crop, (x, y))
相关质量控制参见 AI 图像与提示词不符。
验证
- 蒙版外像素 diff = 0(paste-back)或 ≤2(VAE 往返)。
- 蒙版边界处肉眼检查:没有可见色阶。
- 在三张不同测试图上跑。无溢出应该可重现,不是巧合。
长期预防
- inpaint 总是用 paste-back 合成。全图重生成只在风格变换时显式使用。
- 把 inpaint workflow 存成命名预设:“tight-edit”(paste-back、硬蒙版、低 padding)vs “blend-edit”(软蒙版、大 padding)vs “full-redo”(全图重做)。
- 生产管线 CI 里校验蒙版外像素 diff。
- 记录你用的蒙版格式(8 位灰度、alpha、RGBA 蒙版通道)并坚持用。
- inpaint 过的图避免 JPEG 输出;总是 PNG。
- 当编辑里包含合理影响全图的光照变化时,显式选择全图模式而不是意外发现。
常见坑
- 用高斯模糊蒙版因为它”在 UI 里看起来更柔”,没意识到软边会溢出。
- 相信模型”尊重蒙版”,从未做像素 diff。
- 把 denoising strength 拉到 1.0 做 inpaint 还指望蒙版外像素保留。
- inpaint 之后让 SDXL refiner 跑而没关掉它。
- 导出成 JPEG 然后把 JPEG 量化产生的色偏怪到模型头上。
- 用了默认
inpaint_full_res_padding=64的管线,结果被 64 像素溢出搞蒙圈。
FAQ
Q:我的 UI 写着”仅蒙版”为什么还有溢出?
检查 UI 是否在生成后用原图做合成,还是只把蒙版区可视化。有些 UI 把”仅蒙版”当视觉提示但仍重生成整张图。用硬像素 diff 测试。
Q:有没有蒙版感知的 refining 方式?
有。底模 inpaint,然后只在蒙版裁切区上跑 refiner,最后贴回。顺序:base → crop → refine → paste。
Q:Inpaint 后的人脸修复会溢出吗?
会。人脸修复器(CodeFormer、GFPGAN)无视蒙版。在 inpaint 前跑人脸修复,或在 inpaint workflow 里跳过它。
Q:能在比源图更高分辨率上做 inpaint 吗?
可以——把裁切块放大、编辑、缩小、贴回。Paste-back 必须缩回原分辨率以避免接缝处位移。