feat: 接口12/接口2 发际线重绘改走 local_test 外部 ComfyUI 服务

后端 generate_hairline_redraw 跳过内置 Flux-2 重绘,改为产出 final(接缝融合基底)
+ 纯红遮罩 PNG(redraw_band_mask_base64,遮罩区=(255,0,0,255)、其余全透明)。

接口2 female(generate_grow_results_swap) 取 final+遮罩后在后端调 local_test
(0716add-hair.json 工作流) 完成重绘,结果作为生发图返回;male 分支不变。

测试页 test_interface12.html / test_interface12_final.html 改为两阶段:
先生成 final+纯红遮罩,再前端调 local_test 重绘并展示;color_match 默认不勾选。

local_test/app.py 加 CORS 头(OPTIONS 预检),支持浏览器跨域直连。
This commit is contained in:
xsl
2026-07-17 00:00:15 +08:00
parent 0bbb15d668
commit 5fbc03a6df
9 changed files with 291 additions and 101 deletions
+35 -39
View File
@@ -102,11 +102,26 @@ def _png_b64(bgr_or_gray):
def _gray_b64(gray_float):
"""0~1 的浮图 → 灰度 PNG data URI。"""
"""0~1 的浮图 → 灰度 PNG data URI。"""
g = np.clip(gray_float * 255.0, 0, 255).astype(np.uint8)
return _png_b64(g)
def _red_mask_b64(mask_bool, h, w):
"""布尔遮罩 → 纯红 alpha PNG data URI。
遮罩区域 RGBA=(255,0,0,255),其余区域 RGBA=(0,0,0,0)。
供外部 ComfyUI 重绘服务(如 local_test)按 alpha 通道识别重绘区。
注意:cv2.imencode 写 PNG 用的是 **BGRA** 顺序(B,G,R,A),所以要得到
浏览器显示的红色 R=255,需赋值 (B=0,G=0,R=255,A=255)。
"""
m = (mask_bool.astype(np.uint8)) * 255 if mask_bool is not None else np.zeros((h, w), np.uint8)
rgba = np.zeros((h, w, 4), np.uint8)
rgba[m > 0] = (0, 0, 255, 255) # BGRA: B=0,G=0,R=255 → PNG 读出为红色 + 不透明
ok, buf = cv2.imencode(".png", rgba)
return "data:image/png;base64," + base64.b64encode(buf.tobytes()).decode() if ok else ""
# ---------------------------------------------------------------------------
# 步骤1:接口9 头发遮罩(复用 head_mask 构件)
# ---------------------------------------------------------------------------
@@ -1009,19 +1024,17 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
comfyui_prompt=None, beauty_alpha=0.6,
band_lo_mult=0.5, band_hi_mult=1.5, rid=None):
"""接口12 发际线带重绘。内部先跑接口11 核心拿到 ④ final,再取 ⑤-① 发际线重绘带
(外推↔内推之间、经 baseline 截断只留上部)作遮罩,用 Flux-2ComfyUIhair_repaint.json
重绘(band 经 alpha 送进 ComfyUI 决定加发位置,ComfyUI 输出为整帧重绘+美颜图)。
(外推↔内推之间、经 baseline 截断只留上部)作遮罩
**同时产出两版结果供对比**
- `redraw_full`ComfyUI 整帧输出(全脸美颜 + 全脸重绘),与手动跑 ComfyUI 一致。
- `redraw_band`:加发只在发际线带、美颜保留全脸。band 内完全用 ComfyUI 重绘(加发),
band 外用 `final` 结构 + 按 `beauty_alpha` 融入 ComfyUI 的全脸美颜
**本接口不再做 Flux-2 重绘**:只产出 `final`(接缝融合基底)+ 纯红遮罩
`redraw_band_mask`RGBA,遮罩区=(255,0,0,255)、其余全透明),重绘交给前端调
外部 ComfyUI 重绘服务(见 local_test)完成。旧的 `redraw_full` / `redraw_band`
字段保留为空,仅作结构兼容
返回可直接进 ok() 的 data dict。未检出人脸抛 NoFaceError。
comfyui_promptFlux-2 提示词,None 用默认「补充遮罩区域的头发,加一点美颜」
beauty_alpharedraw_band 版 band 外的全脸美颜融入强度(0=完全保留 final 无美颜,
1=band 外也完全用 ComfyUI 输出≈redraw_full),默认 0.6。
comfyui_prompt保留入参,但本接口不再使用(重绘提示词由外部服务自行决定)
beauty_alpha保留入参,但本接口不再使用(美颜由外部服务控制)。
band_lo_mult / band_hi_mult:重绘带外推倍率(相对 hairline_push_cm),带位于
lo×push ~ hi×push 之间(内轮廓=0×、原外推线=1.0×),默认 0.5 / 1.5。
其余参数含义与接口11 相同(用于内部生成 final 与重绘带)。
@@ -1050,8 +1063,7 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
upper_mask = mask_viz.get("_upper_mask")
push_px = int(round(max(0.0, hairline_push_cm) * px_per_cm))
redraw_band_overlay_b64 = ""
redraw_full_b64 = "" # A:ComfyUI 整帧(全脸美颜+全脸重绘
redraw_band_b64 = "" # B:加发只在发际线带、美颜保留全脸
redraw_band_mask_b64 = "" # 纯红 alpha PNG(遮罩区=(255,0,0,255),其余全透明
redraw_info = {"enabled": False}
band_mask = None
try:
@@ -1062,37 +1074,19 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
logger.info("[%s] 重绘带 push_px=%d lo_mult=%s hi_mult=%s band_pixels=%d",
rid, push_px, band_lo_mult, band_hi_mult, int(band_mask.sum()))
redraw_band_overlay_b64 = _jpg_b64(_overlay(final, band_mask, (255, 0, 255)))
# 纯红遮罩 PNG(供外部重绘服务按 alpha 识别重绘区)
redraw_band_mask_b64 = _red_mask_b64(band_mask, h, w)
redraw_info = {"enabled": True, "band_pixels": int(band_mask.sum()), "push_px": push_px,
"band_lo_mult": float(band_lo_mult), "band_hi_mult": float(band_hi_mult)}
except Exception as ex: # noqa: BLE001
logger.exception("[%s] 重绘带计算失败,整个重绘跳过", rid)
redraw_info = {"enabled": False, "error": f"band: {ex}"}
# ② Flux-2 路:final + band 调 ComfyUIhair_repaint.json 整图重绘 + reference latent
# 保色 + ColorMatch + 美颜)。band 经 alpha 送进 ComfyUI 决定加发位置
beauty_alpha = float(min(max(beauty_alpha, 0.0), 1.0))
if redraw_info.get("enabled"):
try:
prompt = comfyui_prompt if comfyui_prompt else "补充遮罩区域的头发,加一点美颜"
redraw_c_raw = _call_comfyui(final, band_mask, prompt=prompt)
# A:整帧输出(与手动跑 ComfyUI 一致)
redraw_full_b64 = _jpg_b64(redraw_c_raw)
# B:加发只在 band、美颜保留全脸。
# alpha = band 内 1(羽化边缘);band 外 = beauty_alpha。
# band 内完全用 ComfyUI(加发);band 外用 final 结构 + beauty_alpha 融入全脸美颜。
feather_px = max(8, int(round(push_px * 0.6)))
band_a = _feather_alpha(band_mask, "feather", feather_px, 0) # 0..1band 外=0
a = band_a + (1.0 - band_a) * beauty_alpha
a3 = a[:, :, None]
band_mix = (final.astype(np.float32) * (1.0 - a3)
+ redraw_c_raw.astype(np.float32) * a3)
redraw_band_b64 = _jpg_b64(np.clip(band_mix, 0, 255).astype(np.uint8))
redraw_info["beauty_alpha"] = beauty_alpha
logger.info("[%s] Flux-2 重绘完成:redraw_full(整帧) + redraw_band(局部加发+全脸美颜 beauty_alpha=%.2f)",
rid, beauty_alpha)
except Exception as ex: # noqa: BLE001
logger.warning("[%s] Flux-2 路重绘失败,跳过: %s", rid, ex)
redraw_info["c_error"] = str(ex)
# ② Flux-2 重绘已下线:本接口现在只产出 final(接缝融合基底)+ 纯红重绘带遮罩,
# 重绘交给前端调外部 ComfyUI 重绘服务(见 local_test)完成
# 下面保留 redraw_full_b64 / redraw_band_b64 为空,保持返回结构兼容(旧字段)。
redraw_full_b64 = ""
redraw_band_b64 = ""
t_redraw = time.time() - t0
data = {
@@ -1116,9 +1110,11 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
"final_base64": _jpg_b64(final),
# ⑤-① 发际线重绘带(紫,已按 baseline 截断只留上部)
"redraw_band_overlay_base64": redraw_band_overlay_b64,
# A:ComfyUI 整帧重绘+美颜(与手动跑 ComfyUI 一致
# ⑤-② 发际线重绘带遮罩(纯红 alpha PNG,遮罩区=(255,0,0,255)
"redraw_band_mask_base64": redraw_band_mask_b64,
# A:ComfyUI 整帧重绘+美颜(已下线,保留空字段兼容旧前端)
"redraw_full_base64": redraw_full_b64,
# B:加发只在发际线带、美颜保留全脸
# B:加发只在发际线带、美颜保留全脸(已下线,保留空字段兼容旧前端)
"redraw_band_base64": redraw_band_b64,
# 兼容旧字段:指向 A(整帧版)
"redraw_c_base64": redraw_full_b64,