AI 图像 inpaint 漏出蒙版外:像素溢出排查

你只蒙了一小块做 inpaint,蒙版外的像素也被改了——通常是羽化、padding 或全图条件化的问题。修法是收紧蒙版处理和管线模式。

你 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_paddingpad_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_resmask_modewhole_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 必须缩回原分辨率以避免接缝处位移。

相关阅读

标签: #排查 #ai-image #inpaint #mask #diffusion