11 补全后的一致性检查
📐 设计
补全的输出要在进入叠层之前过一遍检查。检查的东西和第 9 章的验收一一对应,这里写怎么量。
检查清单
# 伪代码, 每一项返回 (pass: bool, value: float)
def check_completion(orig_rgb, mask_p, rgba_p, mask_p2, tiers_p, occluders):
filled = mask_p2 & ~mask_p # 补全区
# 1 重叠区不变
d = np.abs(rgba_p[mask_p, :3] - orig_rgb[mask_p]).max()
yield "重叠区不变", d == 0, d
# 2 边界连续: 交界带梯度 vs 部件内部梯度
seam = dilate(mask_p, 2) & dilate(filled, 2)
inner = erode(mask_p, 6)
g = gradient_mag(rgba_p[..., :3])
ratio = p90(g[seam]) / p90(g[inner])
yield "边界连续", ratio <= 1.5, ratio
# 3 色阶一致: 补全区像素到最近档的距离
cols = np.array([rgb for _, rgb, _ in tiers_p])
dist = nearest_color_distance(rgba_p[filled, :3], cols)
off = (dist > 24).mean()
yield "色阶一致", off <= 0.05, off
# 4 不发明结构: 高频能量
e_fill = bandpass_energy(rgba_p, filled, sigma=1)
e_seen = bandpass_energy(rgba_p, mask_p, sigma=1)
yield "不发明结构", e_fill <= 1.2 * e_seen, e_fill / e_seen
第 5 条(下游判据不变差)在叠层之后由 gauge.py 量,不在这里。
失败了怎么办
| 失败项 | 处理 |
|---|---|
| 重叠区不变 | 直接用原图覆盖回去(这一条永远能修) |
| 边界连续 | 在交界带上做归一化卷积羽化(第 18 章的 _blur(mask=) 就是这个),宽度 2–3px |
| 色阶一致 | 把超出的像素拉到最近档的颜色 |
| 不发明结构 | 换工具(LaMa → ComfyUI 或反过来),或缩小 budget |
重新解边界
补全区的外边界是新的,α 要重解:对 mask_p' 跑 contour.alpha_from_image()。注意补全区的外侧是遮挡它的部件,clean() 取的「另一侧干净像素」会是遮挡物的颜色——这是对的,叠层时它确实压在上面。
记录
每个部件的检查结果进 plan.json 的 parts[i].completion 字段:
"completion": {"filled_px": 4120, "skipped_px": 880, "tool": "lama",
"checks": {"重叠区不变": [true, 0], "边界连续": [true, 1.12], ...}}
没补的部件这个字段为 null。这样第 15 章的判据变差时能追到是哪个部件、哪一条。