feat(接口2/7): 接口2加hair_style参数选单张发型;新增接口7用add_hair2工作流
接口2 变更: - 新增必填 hair_style(int) 参数,按序号只生成一张(不再全量) - female:1-5 male:1-4,越界返回1007 接口7 新增: - POST /api/v1/hair/grow-v2,功能与接口2一致 - 使用 add_hair2.json 工作流(Flux-2 Klein 9b) - SaveImage输出节点自动检测(75) comfyui.py 重构: - run() 支持 workflow_path 参数,多工作流按路径缓存 - SaveImage 输出节点自动检测,不再硬编码 - 输入/种子/提示词节点ID两个工作流相同(26/6/60) 文档: - 接口文档、实现说明、网关待改动 三份同步更新 - 网关只需加一行路由,base64→URL改写无需改动 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -446,8 +446,7 @@ async def face_measure(
|
||||
summary="接口2 C端生发",
|
||||
tags=["生发"],
|
||||
description=f"""
|
||||
输入用户正面照 + **性别**,返回该性别对应的多张「建议发际线预览图」(本期为
|
||||
**发际线曲线叠加在原照片上的预览图**,非最终文生图生发图)。每个方案包含:
|
||||
输入用户正面照 + **性别** + **发型序号**,返回指定发际线类型的预览图与生发图。每个方案包含:
|
||||
- 预览图(worker 返回 `image_base64`,网关落盘后改写为 `image_url`)
|
||||
- 发际线类型 `hairline_type`(英文 key)
|
||||
- 顺序 `order`(本期固定 `1..N`,不排序)
|
||||
@@ -460,6 +459,8 @@ async def face_measure(
|
||||
|
||||
- **gender**(必填):`male` / `female`。决定返回的贴图集合(female 5 张 / male 4 张)。
|
||||
非法或缺失返回 `1004`。
|
||||
- **hair_style**(必填):`int`,发型序号。`female`:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave;
|
||||
`male`:1=ellipse, 2=inverse_arc, 3=m, 4=straight。越界返回 `1007`。
|
||||
- **beauty_enabled**:本期保留但不生效。
|
||||
|
||||
`hairline_type` 取值:`ellipse` / `flower` / `heart` / `straight` / `wave`(female),
|
||||
@@ -477,7 +478,6 @@ async def face_measure(
|
||||
"data": {
|
||||
"results": [
|
||||
{"image_base64": "iVBORw0KGgo...", "hairline_type": "ellipse", "order": 1},
|
||||
{"image_base64": "iVBORw0KGgo...", "hairline_type": "flower", "order": 2},
|
||||
]
|
||||
},
|
||||
}
|
||||
@@ -502,6 +502,7 @@ async def hair_grow(
|
||||
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(必填)"),
|
||||
hair_style: Optional[int] = Form(default=None, description="发型序号(必填)。female:1-5 male:1-4"),
|
||||
beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"),
|
||||
use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"),
|
||||
prompt: str = Form(default="补充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"),
|
||||
@@ -510,7 +511,12 @@ async def hair_grow(
|
||||
if gender not in ("male", "female"):
|
||||
return err(1004, "gender 必填且只能为 male / female")
|
||||
|
||||
# 2. 三选一取图
|
||||
# 2. hair_style 必填校验(越界 → 1007)
|
||||
max_styles = {"female": 5, "male": 4}[gender]
|
||||
if hair_style is None or not isinstance(hair_style, int) or hair_style < 1 or hair_style > max_styles:
|
||||
return err(1007, f"hair_style 必填且为 1..{max_styles} 的整数,收到 {hair_style!r}")
|
||||
|
||||
# 3. 三选一取图
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
@@ -531,7 +537,7 @@ async def hair_grow(
|
||||
from hairline.service import generate_grow_results
|
||||
|
||||
# 预览 + 生发(ComfyUI) 都是阻塞且较慢,放线程池避免卡住事件循环
|
||||
items = await run_in_threadpool(generate_grow_results, image, gender, use_mask, prompt)
|
||||
items = await run_in_threadpool(generate_grow_results, image, gender, use_mask, prompt, hair_style)
|
||||
if items is None:
|
||||
return err(1001, "无法识别人像")
|
||||
|
||||
@@ -550,6 +556,126 @@ async def hair_grow(
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 7:C 端生发 v2(add_hair2.json 工作流)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_WORKFLOW2_PATH = os.path.join(os.path.dirname(__file__), "add_hair2.json")
|
||||
|
||||
|
||||
@app.post(
|
||||
"/api/v1/hair/grow-v2",
|
||||
summary="接口7 C端生发 v2(add_hair2 工作流)",
|
||||
tags=["生发"],
|
||||
description=f"""
|
||||
输入用户正面照 + **性别** + **发型序号**,使用 add_hair2.json 工作流生成指定发际线类型的预览图与生发图。
|
||||
功能与接口2 完全一致,仅 ComfyUI 工作流不同。
|
||||
|
||||
{_image_fields_desc}
|
||||
|
||||
图片同时支持 `multipart/form-data` 文件上传(字段名 `image_file`)。
|
||||
|
||||
---
|
||||
|
||||
- **gender**(必填):`male` / `female`。决定返回的贴图集合(female 5 张 / male 4 张)。
|
||||
非法或缺失返回 `1004`。
|
||||
- **hair_style**(必填):`int`,发型序号。`female`:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave;
|
||||
`male`:1=ellipse, 2=inverse_arc, 3=m, 4=straight。越界返回 `1007`。
|
||||
- **beauty_enabled**:本期保留但不生效。
|
||||
|
||||
`hairline_type` 取值:`ellipse` / `flower` / `heart` / `straight` / `wave`(female),
|
||||
`ellipse` / `m` / `straight` / `inverse_arc`(male)。
|
||||
""",
|
||||
responses={
|
||||
200: {
|
||||
"description": "成功",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"request_id": "mock-request-id",
|
||||
"data": {
|
||||
"results": [
|
||||
{"image_base64": "iVBORw0KGgo...", "hairline_type": "ellipse", "order": 1},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
400: {
|
||||
"description": "参数错误 / 图片识别失败",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"examples": {
|
||||
"图片参数错误": {"value": {"code": 1007, "message": "图片参数错误:必须且只能传 image_file / image_url / image_base64 其中一个", "request_id": "x", "data": None}},
|
||||
"非正面照": {"value": {"code": 1003, "message": "角度问题,请上传正面照", "request_id": "x", "data": None}},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
async def hair_grow_v2(
|
||||
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB)"),
|
||||
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(必填)"),
|
||||
hair_style: Optional[int] = Form(default=None, description="发型序号(必填)。female:1-5 male:1-4"),
|
||||
beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"),
|
||||
use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"),
|
||||
prompt: str = Form(default="补充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"),
|
||||
):
|
||||
# 1. gender 必填校验(非法/缺失 → 1004)
|
||||
if gender not in ("male", "female"):
|
||||
return err(1004, "gender 必填且只能为 male / female")
|
||||
|
||||
# 2. hair_style 必填校验(越界 → 1007)
|
||||
max_styles = {"female": 5, "male": 4}[gender]
|
||||
if hair_style is None or not isinstance(hair_style, int) or hair_style < 1 or hair_style > max_styles:
|
||||
return err(1007, f"hair_style 必填且为 1..{max_styles} 的整数,收到 {hair_style!r}")
|
||||
|
||||
# 3. 三选一取图
|
||||
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
|
||||
if e is not None:
|
||||
return e
|
||||
if len(raw) > MAX_FILE_BYTES:
|
||||
return err(1006, "文件超出 1 MB 限制")
|
||||
|
||||
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
|
||||
if image is None:
|
||||
return err(1008, "图片格式不支持(仅 JPG / PNG)")
|
||||
|
||||
h, w = image.shape[:2]
|
||||
short_side, long_side = min(w, h), max(w, h)
|
||||
if short_side < MIN_SHORT_SIDE or long_side < MIN_LONG_SIDE:
|
||||
return err(1002, "人像分辨率过低")
|
||||
|
||||
try:
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from hairline.service import generate_grow_results
|
||||
|
||||
# 预览 + 生发(ComfyUI) 都是阻塞且较慢,放线程池避免卡住事件循环
|
||||
items = await run_in_threadpool(generate_grow_results, image, gender, use_mask, prompt, hair_style, _WORKFLOW2_PATH)
|
||||
if items is None:
|
||||
return err(1001, "无法识别人像")
|
||||
|
||||
results = []
|
||||
for p in items:
|
||||
results.append({
|
||||
"image_base64": _jpg_b64(p["image_bgr"]), # 预览图 JPG
|
||||
"grown_image_base64": (_png_to_jpg_b64(p["grown_png"]) # 生发图 JPG
|
||||
if p["grown_png"] else None),
|
||||
"hairline_type": p["hairline_type"],
|
||||
"order": p["order"],
|
||||
})
|
||||
return ok({"results": results})
|
||||
except Exception as ex: # noqa: BLE001
|
||||
logger.exception("接口7 处理异常")
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 接口 3:B 端生发
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user