feat: 接口2/5 发际线叠图改为透明 PNG(仅曲线),生发图不变
接口2(/api/v1/hair/grow)的 image_url 和接口5(/api/v1/hairline/generate) 的 image_middle/high/low_url 从「原图+白线合成 JPG」改为「透明底 PNG(仅含 发际线曲线)」,前端需叠加原图显示。grown_image_url 生发图保持不变(ComfyUI 完整人像照片)。 实现: - app.py 新增 _rgba_png_b64() 编码 RGBA 透明层为 PNG base64(保留 alpha) - hairline/service.py 接口2/5 改用 build_overlay_layer(返回 RGBA 透明层) 替代 render_hairline_overlay(合成到原图) - 网关零改动:rewrite_base64_to_url 已按 \x89PNG 魔数嗅探落盘为 .png 测试页:test_interface2/5.html 改为「原图打底 + 透明PNG 绝对定位叠加」显示 (复用 .img-stack 结构,固定叠加无开关)。 文档:接口文档.md / integration.html 更新接口2/5 图片字段说明。 测试:43 passed,接口2 image_base64 改断言为 PNG 魔数,接口5 三档叠图同。
This commit is contained in:
+12
-11
@@ -141,7 +141,7 @@ def generate_previews(image_bgr: np.ndarray, gender: str):
|
||||
def generate_grow_results(image_bgr: np.ndarray, gender: str, use_mask: bool = True,
|
||||
prompt: str = None, hair_styles: list[int] | None = None,
|
||||
workflow_path: str | None = None):
|
||||
"""指定发际线类型:预览图(白线) + 生发图(ComfyUI)。
|
||||
"""指定发际线类型:发际线透明叠图(白线 RGBA) + 生发图(ComfyUI)。
|
||||
|
||||
hair_styles(1-indexed 列表):指定生成哪几张发际线(按贴图排序)。female: 1..5,male: 1..4。
|
||||
为 None 时生成全部(兼容旧调用)。
|
||||
@@ -149,7 +149,8 @@ def generate_grow_results(image_bgr: np.ndarray, gender: str, use_mask: bool = T
|
||||
False 时用**干净原图 + 空遮罩**送 ComfyUI(不烧黑色模板线)。
|
||||
prompt(默认 None):ComfyUI 提示词,非 None 时替换工作流节点60文本。
|
||||
workflow_path(默认 None):ComfyUI 工作流 JSON 路径,None 用默认 add_hair.json。
|
||||
Returns: list[dict] {"hairline_type","order","image_bgr"(预览), "grown_png"(bytes 或 None)}。
|
||||
Returns: list[dict] {"hairline_type","order","overlay"((H,W,4) RGBA 透明层),
|
||||
"grown_png"(bytes 或 None)}。
|
||||
无人脸返回 None。某张 ComfyUI 失败时该项 grown_png=None,不抛异常。
|
||||
"""
|
||||
if gender not in ("male", "female"):
|
||||
@@ -177,9 +178,10 @@ def generate_grow_results(image_bgr: np.ndarray, gender: str, use_mask: bool = T
|
||||
logger.warning("接口2 生发图失败(无遮罩):%s", e)
|
||||
|
||||
results = []
|
||||
h, w = image_bgr.shape[:2]
|
||||
for order, (key, white_path) in items:
|
||||
white = load_texture_rgba(white_path)
|
||||
preview = render_hairline_overlay(image_bgr, ctx["points"], ext_faces, uv, white)
|
||||
overlay = build_overlay_layer(h, w, ctx["points"], ext_faces, uv, white)
|
||||
|
||||
if not use_mask:
|
||||
grown_png = shared_grown
|
||||
@@ -196,7 +198,7 @@ def generate_grow_results(image_bgr: np.ndarray, gender: str, use_mask: bool = T
|
||||
logger.warning("接口2 生发图失败 type=%s:%s", key, e)
|
||||
|
||||
results.append({"hairline_type": key, "order": order,
|
||||
"image_bgr": preview, "grown_png": grown_png})
|
||||
"overlay": overlay, "grown_png": grown_png})
|
||||
return results
|
||||
|
||||
|
||||
@@ -226,13 +228,13 @@ def _grow_from_texture(image_bgr: np.ndarray, ctx: dict, white_path: str | None,
|
||||
def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||
hair_styles: list[int], use_mask: bool = True,
|
||||
prompt: str | None = None):
|
||||
"""接口5:对选中发型返回 middle/high/low 三档发际线叠图 + 生发图(同接口2)。
|
||||
"""接口5:对选中发型返回 middle/high/low 三档发际线透明叠图 + 生发图(同接口2)。
|
||||
|
||||
入参同接口2:先选 gender,再多选 hair_styles(必填,1-indexed 按贴图排序)。
|
||||
每个选中发型返回三档叠图(middle/high/low)与一张生发图;三档贴图同名,
|
||||
生发黑模板固定取自 hairline_texture_black/(middle),故生发目标固定 middle 档。
|
||||
每个选中发型返回三档叠图(middle/high/low,RGBA 透明层只含发际线曲线)与一张生发图;
|
||||
三档贴图同名,生发黑模板固定取自 hairline_texture_black/(middle),故生发目标固定 middle 档。
|
||||
use_mask/prompt:同接口2 的生发参数。
|
||||
Returns: {"images":[{hairline_type,order,overlays:{middle,high,low}(BGR),grown_png}],
|
||||
Returns: {"images":[{hairline_type,order,overlays:{middle,high,low}((H,W,4) RGBA 透明层),grown_png}],
|
||||
"best_center":(x,y)};无人脸 None。best_center 取首个选中发型的 middle 档。
|
||||
"""
|
||||
if gender not in ("male", "female"):
|
||||
@@ -262,7 +264,7 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||
overlays = {}
|
||||
for lv in _TEXTURE_DIRS:
|
||||
white = load_texture_rgba(tex_by_level[lv][s - 1][1])
|
||||
overlays[lv] = render_hairline_overlay(image_bgr, ctx["points"], ext_faces, uv, white)
|
||||
overlays[lv] = build_overlay_layer(h, w, ctx["points"], ext_faces, uv, white)
|
||||
# 生发:固定 middle 黑模板
|
||||
grown_png = shared_grown if not use_mask else \
|
||||
_grow_from_texture(image_bgr, ctx, mid_path, use_mask=True, prompt=prompt)
|
||||
@@ -270,8 +272,7 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||
"overlays": overlays, "grown_png": grown_png})
|
||||
# best_center:首个选中发型的 middle 档发际线中点(面部中轴处的发际线 y)
|
||||
if best_center is None:
|
||||
white = load_texture_rgba(mid_path)
|
||||
overlay = build_overlay_layer(h, w, ctx["points"], ext_faces, uv, white)
|
||||
overlay = overlays["middle"] # 复用已渲染的 middle 档透明层
|
||||
ys, xs = np.where(overlay[:, :, 3] > 40)
|
||||
if xs.size:
|
||||
near = np.abs(xs - face_cx) <= max(2, int(w * 0.02))
|
||||
|
||||
Reference in New Issue
Block a user