refactor(接口3): 简化为只需划线图一张,去掉 original + best_hairline

按需求方意见——B端只需上传一张已画好发际线的图,用不着原图:
- 入参去掉 original_image_*,只保留 marked_image_*(三选一)
- 输出去掉 best_hairline_image_url,只返回 hair_growth_image_url + hairline_type
- ComfyUI 输入图改用 marked 划线图原样(add_hair.json 本就是"画了线的照片",
  提示词清除黑线);检测路径只用于建遮罩,不再重画干净线/不需对齐原图
- service.generate_grow_b 签名改 (marked_bgr) 单参
- 同步文档:接口文档/接口3技术方案/网关映射表(去掉接口3 best_hairline 行)
- 测试更新:grow-b 只传 marked,断言无 best_hairline 字段,44全绿

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
xsl
2026-06-15 10:28:13 +08:00
co-authored by Claude Opus 4.8
parent cd4ca278d4
commit bf76923591
6 changed files with 47 additions and 78 deletions
+8 -22
View File
@@ -538,16 +538,11 @@ async def hair_grow(
summary="接口3 B端生发(医生/操作端)",
tags=["生发"],
description="""
医生/操作端在用户原图上**手动划线标注**目标发际线后,上传划线图与原图,返回:
- 最合适的发际线图片 URL
- 生发后效果图 URL
- 发际线形
医生/操作端在用户照片上**手动用马克笔划线标注**目标发际线后,**只需上传这一张划线图**,返回:
- 生发后效果图(系统检测划线 → 据此生成「植发 3 个月」效果)
- 发际线形(手绘为定制,固定 `custom`)
**划线图片**:字段名前缀为 `marked_image_`同样支持文件/URL/base64 三选一。
**原始用户照片**:字段名前缀为 `original_image_`,同样支持文件/URL/base64 三选一,**必填**。
> 两组图片的传参方式可以不同,例如划线图用 URL,原图用 base64,但各自组内严格互斥。
**划线图片**:字段名前缀为 `marked_image_`,支持文件/URL/base64 三选一。**不需要原始照片**。
""",
responses={
200: {
@@ -559,7 +554,6 @@ async def hair_grow(
"message": "success",
"request_id": "mock-request-id",
"data": {
"best_hairline_image_base64": "iVBORw0KGgo...(原图)",
"hair_growth_image_base64": "iVBORw0KGgo...(生发图)",
"hairline_type": "custom",
},
@@ -581,23 +575,16 @@ async def hair_grow_b(
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG,≤ 1 MB"),
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
original_image_file: Optional[UploadFile] = File(default=None, description="原始用户照片文件(JPG/PNG,≤ 1 MB"),
original_image_url: Optional[str] = Form(default=None, description="原始用户照片 URL"),
original_image_base64: Optional[str] = Form(default=None, description="原始用户照片 base64"),
):
# 1. 两组图各三选一取图
# 划线图三选一取图(只需这一张)
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
if e is not None:
return e
orig_raw, e = await resolve_image_bytes(original_image_file, original_image_url, original_image_base64)
if e is not None:
return e
if len(marked_raw) > MAX_FILE_BYTES or len(orig_raw) > MAX_FILE_BYTES:
if len(marked_raw) > MAX_FILE_BYTES:
return err(1006, "文件超出 1 MB 限制")
marked = cv2.imdecode(np.frombuffer(marked_raw, np.uint8), cv2.IMREAD_COLOR)
original = cv2.imdecode(np.frombuffer(orig_raw, np.uint8), cv2.IMREAD_COLOR)
if marked is None or original is None:
if marked is None:
return err(1008, "图片格式不支持(仅 JPG / PNG)")
h, w = marked.shape[:2]
@@ -609,7 +596,7 @@ async def hair_grow_b(
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_grow_b
res = await run_in_threadpool(generate_grow_b, marked, original)
res = await run_in_threadpool(generate_grow_b, marked)
if res["status"] == "no_face":
return err(1001, "无法识别人像")
if res["status"] == "no_line":
@@ -617,7 +604,6 @@ async def hair_grow_b(
grown_b64 = base64.b64encode(res["grown_png"]).decode() if res["grown_png"] else None
data = {
"best_hairline_image_base64": base64.b64encode(orig_raw).decode(), # 原图原样
"hair_growth_image_base64": grown_b64,
"hairline_type": "custom",
}