接口5:新增 hairline_level 档位(middle/high/low)贴图 + 集成接口2生发能力

- hairline_level: 可选 middle(默认)/high/low,选用不同高度档位发际线贴图;
  新增 hairline_texture_high/low 两套贴图,get_texture_map 改为按档位缓存
- hair_style: 可选逗号分隔序号,对选中发际线类型同步生发(ComfyUI),
  结果合并进 hairline_images[].grown_image_base64(未选中为 null);
  生发黑模板固定取 middle(hairline_texture_black/),与档位无关
- 新增 use_mask/prompt 生发控制参数(同接口2)
- 测试页/接口文档同步更新

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
xsl
2026-07-09 23:30:21 +08:00
co-authored by Claude Opus 4.8
parent 0ddfa83743
commit 9675e147a0
22 changed files with 127 additions and 27 deletions
+30 -5
View File
@@ -979,13 +979,21 @@ async def face_features(
---
**入参**新增必填 `gender``male`/`female`),决定返回的发际线集合(female 5 / male 4)。
**入参**
- 必填 `gender``male`/`female`),决定返回的发际线集合(female 5 / male 4)。
- 可选 `hairline_level``middle`(默认) / `high` / `low`),选用不同档位的发际线贴图。
- 可选 `hair_style`(发型序号,逗号分隔如 `1,2,3`):对选中的发际线类型额外**生发**(同接口2)。
留空则只返回发际线叠图、不生发。越界/非法返回 `1007`。
`female`1=ellipse,2=flower,3=heart,4=straight,5=wave`male`1=ellipse,2=inverse_arc,3=m,4=straight。
- 可选 `use_mask` / `prompt`:同接口2 的生发控制参数。
注:生发的黑模板固定取 `hairline_texture_black/`middle),与 `hairline_level` 无关。
**返回说明**
- `hairline_images`:发际线叠加图列表(发际线曲线叠加在用户照片上,同接口2预览),
数量 = 该性别的发际线类型数,本期按贴图顺序 `order=1..N`(暂不计算合适度)。
worker 返回 `image_base64`,网关落盘后改写为 `image_url`。
每项还含 `grown_image_base64`:当 `order` 在 `hair_style` 中时为该类型的**生发图**,否则为 `null`。
- `best_hairline_center_point`:最佳(`order=1`)发际线曲线的**面部中间点**坐标,
以**原图像素**为基准(左上角为原点,x 向右,y 向下)。
""",
@@ -1000,8 +1008,8 @@ async def face_features(
"request_id": "mock-request-id",
"data": {
"hairline_images": [
{"image_base64": "iVBORw0KGgo...", "order": 1},
{"image_base64": "iVBORw0KGgo...", "order": 2},
{"image_base64": "iVBORw0KGgo...", "grown_image_base64": "iVBORw0KGgo...", "order": 1},
{"image_base64": "iVBORw0KGgo...", "grown_image_base64": None, "order": 2},
],
"best_hairline_center_point": {"x": 540, "y": 430},
},
@@ -1024,9 +1032,23 @@ async def hairline_generate(
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
hairline_level: str = Form(default="middle", description="发际线贴图档位 middle(默认)/high/low"),
hair_style: Optional[str] = Form(default=None, description="生发发型序号逗号分隔(可选,如 1,2,3)。留空则只返回发际线叠图不生发。female:1-5 male:1-4"),
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
prompt: str = Form(default="补充遮罩区域的头发", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
):
if gender not in ("male", "female"):
return err(1004, "gender 必填且只能为 male / female")
if hairline_level not in ("middle", "high", "low"):
return err(1004, "hairline_level 只能为 middle / high / low")
# hair_style 可选:留空 → 不生发;非空但非法 → 1007
hair_styles = None
if hair_style and hair_style.strip():
max_styles = {"female": 5, "male": 4}[gender]
hair_styles = _parse_hair_styles(hair_style, max_styles)
if hair_styles is None:
return err(1007, f"hair_style 需为 1..{max_styles} 的整数(逗号分隔),收到 {hair_style!r}")
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
if e is not None:
@@ -1040,14 +1062,17 @@ async def hairline_generate(
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_hairline_pngs
res = await run_in_threadpool(generate_hairline_pngs, image, gender)
res = await run_in_threadpool(
generate_hairline_pngs, image, gender, hairline_level, hair_styles, use_mask, prompt)
if res is None:
return err(1001, "无法识别人像")
hairline_images = []
for it in res["images"]:
hairline_images.append({
"image_base64": _jpg_b64(it["image_bgr"]), # 发际线叠图 JPG
"image_base64": _jpg_b64(it["image_bgr"]), # 发际线叠图 JPG
"grown_image_base64": (_png_to_jpg_b64(it["grown_png"]) # 生发图 JPG(未生发为 null)
if it.get("grown_png") else None),
"order": it["order"],
})
c = res["best_center"]