From 9c674bac42ef7469deab069419c94491f6e1e234 Mon Sep 17 00:00:00 2001 From: rui40000 Date: Thu, 30 Jul 2026 09:35:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20bg=5Fmode=3D=E4=B8=8D=E5=A4=84=E7=90=86?= =?UTF-8?q?=20=E6=97=B6=E8=A7=92=E8=89=B2=E4=BB=8D=E8=A2=AB=E6=A0=BC?= =?UTF-8?q?=E7=BA=BF=E5=88=87=E6=96=AD=EF=BC=88=E4=B8=8A=E6=AC=A1=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=BC=8F=E6=8E=89=E4=BA=86=E8=BF=99=E6=9D=A1=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上次加的 expand_beyond_cell 有个致命条件写错: use_expand = expand_beyond_cell and mode != "none" and _HAS_SCIPY 选「不处理(输出不透明)」时整个修复被跳过,退回严格格线切分, 输出尺寸恒为格子大小(834/3 x 1112/3 = 278x371),角色照样被切。 当初这么写的理由是「不透明模式没有 alpha 可做连通分析」—— 但定位和输出是两件事,内部照样可以算一份白底检测只用于圈定范围。 改为:locate_alpha() 专用于定位(三种 bg_mode 都算), frame_alpha() 才是真正写进输出的。修复后三种模式尺寸完全一致。 连带解决「不处理」模式的一个矛盾:裁剪框为了不切断角色必然框进 邻居的像素,而该模式不做透明处理,邻居就会直接显示出来。 现在把非本角色区域按软 alpha 合成到白底 —— 保住完整角色, 也不会混进旁边那位。 crop_padding 默认 8 → 16:给描边/发光/阴影等后续特效留余量, 也免得内容贴着边看起来像被切。 验证:三种 bg_mode 全部「贴边被切的方向 = 无」。 判据按模式区分——透明模式看输出 alpha 的四边,不处理模式看非白像素的四边。 Co-Authored-By: Claude Opus 4.8 --- eightdir_node.py | 56 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/eightdir_node.py b/eightdir_node.py index acdeb5b..79bd865 100644 --- a/eightdir_node.py +++ b/eightdir_node.py @@ -214,8 +214,9 @@ class RuiEightDirSplit: "关闭时按格子严格切分,角色只要探出格线就会被切断\n" "——最常见的是脚、飘起的斗篷和手杖被削掉一截。\n" "开启后格子只用来判定「这是哪个方向」,实际范围由角色\n" - "自身的连通区域决定,因此不会被切;按质心归属,\n" - "相邻角色也不会被卷进来。" + "自身的连通区域决定;按质心归属,相邻角色不会被卷进来。\n" + "对三种 bg_mode 都有效:选「不处理」时也会在内部算一份\n" + "白底检测来圈定范围,输出仍保持不透明。" }), "auto_crop": ("BOOLEAN", { "default": True, @@ -224,8 +225,10 @@ class RuiEightDirSplit: "既能合成视频,角色也不会在帧间跳动。" }), "crop_padding": ("INT", { - "default": 8, "min": 0, "max": 200, "step": 1, - "tooltip": "裁剪时在内容外保留的边距(像素)。" + "default": 16, "min": 0, "max": 200, "step": 1, + "tooltip": "裁剪时在内容外保留的边距(像素)。\n" + "给足边距不仅好看,也给后续的描边、发光、\n" + "阴影等特效留出余量,免得贴着边显得像被切了。" }), }, "optional": { @@ -280,14 +283,25 @@ class RuiEightDirSplit: xbnd = [int(round(c * W / cols)) for c in range(cols + 1)] pad = int(crop_padding) - use_expand = bool(expand_beyond_cell) and mode != "none" and _HAS_SCIPY + # 注意不要因为 bg_mode=不处理 就跳过这条路径:那样会退回严格格线切分, + # 角色照样被切。定位与输出是两件事,分开处理即可。 + use_expand = bool(expand_beyond_cell) and _HAS_SCIPY - def frame_alpha(b): - if mode == "white": - return _white_to_alpha(rgb_all[b], bg_threshold, edge_softness) + def locate_alpha(b): + """ + 仅用于圈定角色范围的 alpha。 + 即使用户选了「不处理(输出不透明)」,这里也要照算一份 —— + 否则无从判断角色到哪儿为止,只能按格线硬切。 + """ if mode == "keep" and a_in is not None: return a_in[b] - return np.ones((H, W), dtype=np.float32) + return _white_to_alpha(rgb_all[b], bg_threshold, edge_softness) + + def frame_alpha(b): + """真正写进输出的 alpha。""" + if mode == "none": + return np.ones((H, W), dtype=np.float32) + return locate_alpha(b) outs, notes = [], [] @@ -296,7 +310,7 @@ class RuiEightDirSplit: # 第一遍只求包围盒(全图坐标),不留像素,避免整段序列驻留内存 boxes = [None] * n_dir for b in range(B): - lab, groups = _assign_blocks(frame_alpha(b), ybnd, xbnd, + lab, groups = _assign_blocks(locate_alpha(b), ybnd, xbnd, cols, fragment_threshold) if lab is None: continue @@ -331,16 +345,28 @@ class RuiEightDirSplit: # 第二遍按并集框提取;只保留归属本格的连通块,邻居不会混进来 for b in range(B): + la = locate_alpha(b) af = frame_alpha(b) - lab, groups = _assign_blocks(af, ybnd, xbnd, cols, fragment_threshold) + lab, groups = _assign_blocks(la, ybnd, xbnd, cols, fragment_threshold) for di, cid in enumerate(cell_ids): y0, y1, x0, x1 = final[di] - outs[di][b, ..., :3] = rgb_all[b, y0:y1, x0:x1] + rgb = rgb_all[b, y0:y1, x0:x1] blk = groups.get(cid) if groups else None - if not blk: + m = (np.isin(lab[y0:y1, x0:x1], blk) if blk + else np.zeros(rgb.shape[:2], dtype=bool)) + + if mode == "none": + # 不透明输出。裁剪框为了不切断角色必然会框进邻居的像素, + # 「原样保留」与「不切断」不可兼得 —— 这里把非本角色的部分 + # 合成到白底,既保住完整的角色,也不会混进旁边那位。 + a = (locate_alpha(b)[y0:y1, x0:x1] * m)[..., None] + outs[di][b, ..., :3] = rgb * a + (1.0 - a) + outs[di][b, ..., 3] = 1.0 continue - m = np.isin(lab[y0:y1, x0:x1], blk) - outs[di][b, ..., 3] = af[y0:y1, x0:x1] * m + + outs[di][b, ..., :3] = rgb + if blk: + outs[di][b, ..., 3] = af[y0:y1, x0:x1] * m for di in range(n_dir): y0, y1, x0, x1 = final[di]