feat(接口3): B端生发-马克笔发际线检测+生发(替换Mock)
医生在额头用马克笔画规划发际线 → 检测该线 → 生发。检测算法源自 /home/xsl/headmark。 - hairline/marker_detect.py: 黑帽响应图(MORPH_BLACKHAT)+鬓角锚点(MediaPipe 21/251吸附) +skimage route_through_array 最小路径检测画线;路径平均响应阈值拒识无画线 (headmark 调研:全局灰度阈值不可用,黑帽+Dijkstra 实测误差≤0.5px) - hairline/mask.py: 抽出 mask_from_curve(曲线+ROI闭合),接口2/3共用 - hairline/service.py: generate_grow_b——检测→遮罩→原图重画干净线→ComfyUI生发 - app.py: /hair/grow-b 真实实现,marked+original各三选一+校验;输出 best_hairline_image_base64(=原图)/hair_growth_image_base64/hairline_type="custom"; 无人脸或未检测到画线→1001;重活进线程池 - requirements: scikit-image==0.24.0 (⚠️锁0.24,0.25+强依赖numpy>=2会顶掉mediapipe的numpy<2) - 文档: docs/接口3-B端生发-技术实现方案.md - 测试: test_marker.py(检测/拒识/辅助) + test_api grow-b(mock ComfyUI),42全绿 实测(5090): grow-b ~6.4s,生发图把额头发际线补到医生画线、清除划线、人物保持。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -559,9 +559,9 @@ async def hair_grow(
|
||||
"message": "success",
|
||||
"request_id": "mock-request-id",
|
||||
"data": {
|
||||
"best_hairline_image_url": SAMPLE_IMAGE_URL,
|
||||
"hair_growth_image_url": SAMPLE_IMAGE_URL,
|
||||
"hairline_type": "花瓣形",
|
||||
"best_hairline_image_base64": "iVBORw0KGgo...(原图)",
|
||||
"hair_growth_image_base64": "iVBORw0KGgo...(生发图)",
|
||||
"hairline_type": "custom",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -585,12 +585,46 @@ async def hair_grow_b(
|
||||
original_image_url: Optional[str] = Form(default=None, description="原始用户照片 URL"),
|
||||
original_image_base64: Optional[str] = Form(default=None, description="原始用户照片 base64"),
|
||||
):
|
||||
data = {
|
||||
"best_hairline_image_url": SAMPLE_IMAGE_URL,
|
||||
"hair_growth_image_url": SAMPLE_IMAGE_URL,
|
||||
"hairline_type": "花瓣形",
|
||||
}
|
||||
return ok(data)
|
||||
# 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:
|
||||
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:
|
||||
return err(1008, "图片格式不支持(仅 JPG / PNG)")
|
||||
|
||||
h, w = marked.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_b
|
||||
|
||||
res = await run_in_threadpool(generate_grow_b, marked, original)
|
||||
if res["status"] == "no_face":
|
||||
return err(1001, "无法识别人像")
|
||||
if res["status"] == "no_line":
|
||||
return err(1001, "未检测到发际线划线,请确认划线图额头有清晰的手绘发际线")
|
||||
|
||||
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",
|
||||
}
|
||||
return ok(data)
|
||||
except Exception as ex: # noqa: BLE001
|
||||
logger.exception("接口3 处理异常")
|
||||
return err(1007, f"处理失败:{ex}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user