feat: 拆分接口11/12,新增接口12 final / final v2 精简重绘端点
- 接口11 移除重绘,仅生成 final;接口12 (grow_v2) 负责发际线带 Flux-2 重绘 - 接口12 重绘带改为发际线外推 band_lo_mult~band_hi_mult 倍 push(默认 0.5~1.5),页面可调 - 接口12 同时产出 A 整帧重绘 与 B 局部加发+全脸美颜(beauty_alpha 可调) - 新增 grow_v2_final(整帧重绘)/ grow_v2_final_v2(B 局部+美颜)端点:仅需图片+发型 ID,其余用固化默认值(color_match 关闭) - 配套精简测试页 test_interface12_final.html / test_interface12_final_v2.html / test_interface12.html - 恢复接口11 调试页多频段与换发型可调参数、color_match 默认不勾选 - 删除旧脚本 batch_grow_v2.py / gen_report_hairline_v2.py / test_simple.html Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1414,13 +1414,11 @@ async def hairline_grow(
|
||||
color_match_strength: float = Form(default=1.0, description="颜色迁移强度(0~1,1=全迁移,<1 只迁移部分防过度改色),默认 1.0"),
|
||||
mb_feather_px: int = Form(default=1, description="多频段最细层掩码轻羽化像素(0=不羽化,消除发丝边缘锯齿),默认 1"),
|
||||
transition_band_px: int = Form(default=-1, description="keep-region 过渡带边距(-1=自动按层数 2**n,>=0 用绝对像素与层数解耦),默认 -1"),
|
||||
redraw: bool = Form(default=False, description="发际线带重绘开关:开启后额外跑一条分支——外推↔发际线带重绘,final输入,swapHair/Flux-2两路对比,结果单独展示(不替换final),默认 False"),
|
||||
inpainting_fill: int = Form(default=1, description="change_hair服务端重绘填充:0=保留原图(治染绿) | 1=填充噪声(默认/原始) | 2=纯色 | 3=潜变量噪声。默认 1"),
|
||||
mask_blur: int = Form(default=11, description="change_hair服务端遮罩边缘模糊像素(原始11,越大颜色越易从边缘渗透),默认 11"),
|
||||
mask_dilate_scale: float = Form(default=1.0, description="change_hair服务端遮罩膨胀缩放(1.0=原始核尺寸,<1收缩防越界),默认 1.0"),
|
||||
comfyui_prompt: Optional[str] = Form(default=None, description="redraw Flux-2路提示词,None用默认「补充遮罩区域的头发,加一点美颜」"),
|
||||
):
|
||||
"""接口11:发际线生发 + 分步可视化。遮罩固定 pushed,融合默认 multiband(可选 seamless/two_stage/feather)。"""
|
||||
"""接口11:发际线生发 + 分步可视化(**不含重绘**,重绘见接口12)。遮罩固定 pushed,融合默认 multiband(可选 seamless/two_stage/feather)。"""
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
@@ -1435,10 +1433,10 @@ async def hairline_grow(
|
||||
from uuid import uuid4 as _uuid4
|
||||
rid = _uuid4().hex[:8]
|
||||
logger.info("[%s] 接口11 收到请求: hairline_push_cm=%s hairline_edge=%s mb_levels=%s "
|
||||
"blend=%s color_match=%s cm_strength=%s mb_feather_px=%s transition_band_px=%s redraw=%s "
|
||||
"blend=%s color_match=%s cm_strength=%s mb_feather_px=%s transition_band_px=%s "
|
||||
"inpainting_fill=%s mask_blur=%s mask_dilate_scale=%s",
|
||||
rid, hairline_push_cm, hairline_edge, mb_levels, blend_method,
|
||||
color_match, color_match_strength, mb_feather_px, transition_band_px, redraw,
|
||||
color_match, color_match_strength, mb_feather_px, transition_band_px,
|
||||
inpainting_fill, mask_blur, mask_dilate_scale)
|
||||
try:
|
||||
data = await run_in_threadpool(
|
||||
@@ -1450,8 +1448,8 @@ async def hairline_grow(
|
||||
hairline_edge=hairline_edge, blend_method=blend_method,
|
||||
color_match=color_match, color_match_strength=color_match_strength,
|
||||
mb_feather_px=mb_feather_px, transition_band_px=transition_band_px,
|
||||
redraw=redraw, inpainting_fill=inpainting_fill, mask_blur=mask_blur,
|
||||
mask_dilate_scale=mask_dilate_scale, comfyui_prompt=comfyui_prompt, rid=rid)
|
||||
inpainting_fill=inpainting_fill, mask_blur=mask_blur,
|
||||
mask_dilate_scale=mask_dilate_scale, rid=rid)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
except SwapError as se:
|
||||
@@ -1464,20 +1462,33 @@ async def hairline_grow(
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 12:发际线生发(接口11 固定参数精简版)
|
||||
# 接口 12:发际线带重绘(调用接口11 的 ④final 作输入 + ⑤-①重绘带作遮罩,Flux-2 重绘)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.post(
|
||||
"/api/v1/hairline/grow_v2",
|
||||
summary="接口12 发际线生发(固定 pushed 遮罩 + 多频段融合,仅返回最终图)",
|
||||
summary="接口12 发际线带重绘(接口11 final + 发际线重绘带 → Flux-2 保色重绘)",
|
||||
tags=["生发"],
|
||||
description=f"""
|
||||
接口11 的固定参数精简版,适合生产直调。与接口11 共用同一管线,遮罩固定 pushed(发际线外推),
|
||||
融合固定 multiband(多频段金字塔)。本接口固定 `erode_cm=0.6`、`mb_levels=5` 不暴露,
|
||||
**只返回 `final_base64`**(最终合成图),不附带分步可视化。
|
||||
接口12 是接口11 的**下游重绘阶段**。内部先跑接口11 核心管线拿到 **④ 接缝融合最终图(final)**,
|
||||
再取 **⑤-① 发际线重绘带**(发际线沿外推方向 `band_lo_mult×push` ~ `band_hi_mult×push`
|
||||
之间、经 ①-a baseline 截断只留上部的带状区域,默认 0.5×~1.5×push)
|
||||
作为遮罩,用 **Flux-2(ComfyUI)** 做 reference-latent 保色重绘(不易染绿),重绘结果再与 final 融合。
|
||||
|
||||
其余参数(hairline_id、seg_model、gen_backend、is_hr、denoising_strength、edge_erode_px、
|
||||
hairline_push_cm、hairline_edge 等)保留为可选 Form,调用方可按需覆盖。
|
||||
与接口11 的关系:接口11 只负责生成 final(不再含重绘);接口12 负责在 final 上做发际线带重绘。
|
||||
接口11 的可调参数(seg_model/gen_backend/hairline_push_cm/blend_method/color_match 等)
|
||||
在本接口同样暴露,用于内部生成 final 与重绘带;另有 `comfyui_prompt` 控制 Flux-2 提示词。
|
||||
|
||||
⚠️ 依赖 ComfyUI(默认 :8188)在跑,否则重绘失败会在 `data.redraw.c_error` 报告。
|
||||
|
||||
**同时产出两版结果供对比**:
|
||||
- `redraw_full`:ComfyUI 整帧输出(全脸美颜 + 全脸重绘),与手动跑 ComfyUI 一致。
|
||||
- `redraw_band`:加发只在发际线带、美颜保留全脸(band 内用 ComfyUI 重绘,band 外 = final 结构
|
||||
+ 按 `beauty_alpha` 融入全脸美颜)。
|
||||
|
||||
返回 `data.steps`:`input`(原图)/ `final`(接口11 的 ④,重绘输入基底)/
|
||||
`redraw_band_overlay`(⑤-① 重绘带可视化)/ `redraw_full`(A 整帧)/ `redraw_band`(B 局部加发+全脸美颜)/
|
||||
`redraw_c`(兼容旧字段,=redraw_full)。
|
||||
|
||||
{_image_fields_desc}
|
||||
""",
|
||||
@@ -1491,13 +1502,27 @@ async def hairline_grow_v2(
|
||||
hairgrow_strength: float = Form(default=0.75, description="区域生发强度(仅 hairgrow 后端),默认 0.75"),
|
||||
is_hr: bool = Form(default=False, description="高清模式(换发型输出 1152×1536,否则 576×768)"),
|
||||
seg_model: str = Form(default="segformer", description="头发分割模型:bisenet | segformer(默认 segformer)"),
|
||||
erode_cm: float = Form(default=0.6, description="baseline 参考内缩距离(厘米),默认 0.6"),
|
||||
swap_mode: str = Form(default="ext_mask", description="换发型取图模式:ext_mask | as_is(默认 ext_mask)"),
|
||||
edge_erode_px: int = Form(default=3, description="贴图前遮罩内缩像素(防边缘露皮/光晕),默认 3"),
|
||||
denoising_strength: float = Form(default=0.6, description="换发型 webui 重绘强度(越大生发越激进),默认 0.6"),
|
||||
mb_levels: int = Form(default=5, description="多频段金字塔层数(2~6),默认 5"),
|
||||
hairline_push_cm: float = Form(default=1.0, description="发际线外推距离(厘米),默认 1.0"),
|
||||
hairline_edge: str = Form(default="column", description="发际线提取方式:column | contour,默认 column"),
|
||||
blend_method: str = Form(default="multiband", description="接缝融合方法:multiband | seamless | two_stage | feather | alpha_gradient"),
|
||||
color_match: bool = Form(default=True, description="融合前 Reinhard 颜色迁移消除整体色差,默认 True"),
|
||||
color_match_strength: float = Form(default=1.0, description="颜色迁移强度(0~1),默认 1.0"),
|
||||
mb_feather_px: int = Form(default=1, description="多频段最细层掩码轻羽化像素,默认 1"),
|
||||
transition_band_px: int = Form(default=-1, description="keep-region 过渡带边距(-1=自动),默认 -1"),
|
||||
inpainting_fill: int = Form(default=1, description="change_hair服务端重绘填充:0=保留原图 | 1=噪声 | 2=纯色 | 3=潜变量。默认 1"),
|
||||
mask_blur: int = Form(default=11, description="change_hair服务端遮罩边缘模糊像素,默认 11"),
|
||||
mask_dilate_scale: float = Form(default=1.0, description="change_hair服务端遮罩膨胀缩放,默认 1.0"),
|
||||
comfyui_prompt: Optional[str] = Form(default=None, description="Flux-2 重绘提示词,None 用默认「补充遮罩区域的头发,加一点美颜」"),
|
||||
beauty_alpha: float = Form(default=0.6, description="redraw_band 版 band 外的全脸美颜融入强度(0=band外无美颜纯用final,1≈整帧版),默认 0.6"),
|
||||
band_lo_mult: float = Form(default=0.5, description="重绘带外推倍率下限(相对 hairline_push_cm,内轮廓=0×、原外推线=1.0×),默认 0.5"),
|
||||
band_hi_mult: float = Form(default=1.5, description="重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5"),
|
||||
):
|
||||
"""接口12:发际线生发(固定 pushed 遮罩 + multiband 融合,仅返回最终图)。"""
|
||||
"""接口12:发际线带重绘。同时产出 redraw_full(整帧美颜) 与 redraw_band(局部加发+全脸美颜) 两版对比。"""
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
@@ -1508,30 +1533,137 @@ async def hairline_grow_v2(
|
||||
|
||||
try:
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from face_analysis.hairline_grow import generate_hairline_grow, NoFaceError, SwapError
|
||||
from face_analysis.hairline_grow import generate_hairline_redraw, NoFaceError, SwapError
|
||||
from uuid import uuid4 as _uuid4
|
||||
rid = _uuid4().hex[:8]
|
||||
logger.info("[%s] 接口12 收到请求: hairline_id=%s hairline_push_cm=%s blend=%s comfyui_prompt=%r",
|
||||
rid, hairline_id, hairline_push_cm, blend_method, comfyui_prompt)
|
||||
try:
|
||||
# 固定:mask_type=pushed、blend_method=multiband、erode_cm=0.6、mb_levels=5
|
||||
data = await run_in_threadpool(
|
||||
generate_hairline_grow, image, hairline_id,
|
||||
is_hr=is_hr, seg_model=seg_model, erode_cm=0.6, swap_mode=swap_mode,
|
||||
generate_hairline_redraw, image, hairline_id,
|
||||
is_hr=is_hr, seg_model=seg_model, erode_cm=erode_cm, swap_mode=swap_mode,
|
||||
edge_erode_px=edge_erode_px, denoising_strength=denoising_strength,
|
||||
gen_backend=gen_backend, hairgrow_strength=hairgrow_strength,
|
||||
mb_levels=5, hairline_push_cm=hairline_push_cm, hairline_edge=hairline_edge)
|
||||
mb_levels=mb_levels, hairline_push_cm=hairline_push_cm,
|
||||
hairline_edge=hairline_edge, blend_method=blend_method,
|
||||
color_match=color_match, color_match_strength=color_match_strength,
|
||||
mb_feather_px=mb_feather_px, transition_band_px=transition_band_px,
|
||||
inpainting_fill=inpainting_fill, mask_blur=mask_blur,
|
||||
mask_dilate_scale=mask_dilate_scale, comfyui_prompt=comfyui_prompt,
|
||||
beauty_alpha=beauty_alpha, band_lo_mult=band_lo_mult,
|
||||
band_hi_mult=band_hi_mult, rid=rid)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
except SwapError as se:
|
||||
return err(1007, f"换发型失败:{se}")
|
||||
# 精简返回:只取最终合成图,丢掉接口11 的全部分步可视化
|
||||
return ok({
|
||||
"hairline_id": data["hairline_id"],
|
||||
"image_size": data["image_size"],
|
||||
"final_base64": data["steps"]["final_base64"],
|
||||
})
|
||||
logger.info("[%s] 接口12 成功返回", rid)
|
||||
return ok(data)
|
||||
except Exception as ex: # noqa: BLE001
|
||||
logger.exception("接口12 处理异常")
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 12 final:精简版发际线带重绘(仅需图片 + 发型 ID,其余参数全用默认值)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 接口12 final 固化的默认参数(= test_interface12.html 当前默认值,color_match 关闭)
|
||||
_V2_FINAL_DEFAULTS = dict(
|
||||
gen_backend="swaphair", hairgrow_strength=0.75, is_hr=False, seg_model="segformer",
|
||||
erode_cm=0.6, swap_mode="ext_mask", edge_erode_px=3, denoising_strength=0.6,
|
||||
mb_levels=5, hairline_push_cm=0.8, hairline_edge="column", blend_method="two_stage",
|
||||
color_match=False, color_match_strength=0.4, mb_feather_px=1, transition_band_px=-1,
|
||||
inpainting_fill=1, mask_blur=11, mask_dilate_scale=1.0, comfyui_prompt=None,
|
||||
beauty_alpha=0.6, band_lo_mult=0.5, band_hi_mult=1.5,
|
||||
)
|
||||
|
||||
|
||||
async def _run_v2_final(image_file, image_url, image_base64, hairline_id, tag):
|
||||
"""接口12 final / final v2 共用:仅需图片 + hairline_id,其余用固化默认值。
|
||||
两者后端计算完全一致(同一次 ComfyUI 输出同时含 A 整帧与 B 局部+美颜),
|
||||
差异仅在配套测试页展示哪一版。"""
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
|
||||
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
|
||||
if image is None:
|
||||
return err(1008, "图片格式不支持(仅 JPG / PNG)")
|
||||
|
||||
try:
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from face_analysis.hairline_grow import generate_hairline_redraw, NoFaceError, SwapError
|
||||
from uuid import uuid4 as _uuid4
|
||||
rid = _uuid4().hex[:8]
|
||||
logger.info("[%s] %s 收到请求: hairline_id=%s(其余用默认值)", rid, tag, hairline_id)
|
||||
try:
|
||||
data = await run_in_threadpool(
|
||||
generate_hairline_redraw, image, hairline_id,
|
||||
rid=rid, **_V2_FINAL_DEFAULTS)
|
||||
except NoFaceError:
|
||||
return err(1001, "无法识别人像")
|
||||
except SwapError as se:
|
||||
return err(1007, f"换发型失败:{se}")
|
||||
logger.info("[%s] %s 成功返回", rid, tag)
|
||||
return ok(data)
|
||||
except Exception as ex: # noqa: BLE001
|
||||
logger.exception("%s 处理异常", tag)
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
@app.post(
|
||||
"/api/v1/hairline/grow_v2_final",
|
||||
summary="接口12 final 精简重绘(整帧重绘;仅图片 + 发型 ID)",
|
||||
tags=["生发"],
|
||||
description=f"""
|
||||
接口12 的**精简/生产版**:只需上传图片 + 选择 `hairline_id`,其余所有参数固化为当前调优默认值
|
||||
(hairline_push_cm=0.8 / blend_method=two_stage / **color_match=关闭** / color_match_strength=0.4 /
|
||||
beauty_alpha=0.6 / band_lo_mult=0.5 / band_hi_mult=1.5 等)。
|
||||
|
||||
**最终重绘取整帧重绘**(`data.steps.redraw_full`,全脸美颜 + 全脸重绘)。
|
||||
返回结构与接口12 一致(`data.steps` 同时含 `redraw_full`(A 整帧)/ `redraw_band`(B 局部+美颜))。
|
||||
|
||||
⚠️ 依赖 ComfyUI(默认 :8188)在跑。
|
||||
|
||||
{_image_fields_desc}
|
||||
""",
|
||||
)
|
||||
async def hairline_grow_v2_final(
|
||||
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG)"),
|
||||
image_url: Optional[str] = Form(default=None, description="图片 URL"),
|
||||
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
|
||||
hairline_id: str = Form(..., description="发际线类型 ID(= change_hair hair_id,如 chang_bolang)"),
|
||||
):
|
||||
"""接口12 final:整帧重绘版,仅需图片 + hairline_id。"""
|
||||
return await _run_v2_final(image_file, image_url, image_base64, hairline_id, "接口12final")
|
||||
|
||||
|
||||
@app.post(
|
||||
"/api/v1/hairline/grow_v2_final_v2",
|
||||
summary="接口12 final v2 精简重绘(B 局部加发+全脸美颜;仅图片 + 发型 ID)",
|
||||
tags=["生发"],
|
||||
description=f"""
|
||||
接口12 final 的**局部加发版**:参数与 `grow_v2_final` 完全相同(color_match 关闭等),
|
||||
唯一区别是**最终重绘取 B 局部加发+全脸美颜**(`data.steps.redraw_band`:
|
||||
加发只在发际线带内、band 外保留 final 结构并按 beauty_alpha 融入全脸美颜)。
|
||||
|
||||
返回结构与接口12 一致(`data.steps` 同时含 `redraw_full`(A 整帧)/ `redraw_band`(B 局部+美颜))。
|
||||
|
||||
⚠️ 依赖 ComfyUI(默认 :8188)在跑。
|
||||
|
||||
{_image_fields_desc}
|
||||
""",
|
||||
)
|
||||
async def hairline_grow_v2_final_v2(
|
||||
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG)"),
|
||||
image_url: Optional[str] = Form(default=None, description="图片 URL"),
|
||||
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
|
||||
hairline_id: str = Form(..., description="发际线类型 ID(= change_hair hair_id,如 chang_bolang)"),
|
||||
):
|
||||
"""接口12 final v2:B 局部加发+全脸美颜版,仅需图片 + hairline_id。"""
|
||||
return await _run_v2_final(image_file, image_url, image_base64, hairline_id, "接口12finalv2")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 调试:下载后端日志(接口11 遮罩计算全过程)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user