Files
hair/docs/接口3-B端生发-技术实现方案.md
T
xslandClaude Opus 4.8 bf76923591 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>
2026-06-15 10:28:13 +08:00

5.1 KiB
Raw Blame History

接口 3:B 端生发 — 技术实现方案(马克笔发际线检测 + 生发)

高性能 workerGPU 机) 实现,与接口 1/2 同机。对外经网关代理。 B 端:医生在患者额头用马克笔画出规划的发际线,拍照上传。系统检测这条手绘线 据此生成生发图。检测算法移植自 /home/xsl/headmark 的调研结论(黑帽 + Dijkstra)。


0. 契约(对齐 接口文档.md 接口3,不变)

POST /api/v1/hair/grow-b

输入 说明
marked_image_* 已用马克笔标注发际线的图,三选一,必填。只需这一张(划线图即用户照片+手绘线,不需要原图)
输出 data 决策
hair_growth_image_url 生发后图片ComfyUIworker 返回 hair_growth_image_base64
hairline_type 固定 "custom"(手绘定制)

落盘改 URL 由网关做(架构同接口1/2)。


1. 马克笔发际线检测(核心,源自 headmark 调研)

headmark docs/detection_research.md 结论:全局灰度阈值不可用(笔迹平均灰度反而高于阈值、 与皮肤阴影分布重叠);推荐 黑帽响应图 + 端点锚定 Dijkstra 最小路径,实测误差 ≤0.5px(GT锚点)。 本项目用 MediaPipe 锚点(非 GT)实测平均 3.2px、中位 0px —— 对生成遮罩足够(线会膨胀成带)。

detect_marker_hairline(marked_bgr, landmarks, parse_map):
  [1] ROI = forehead_upper_region(landmarks) ∩ head_silhouette(parse_map)   # 复用接口2 mask.py
  [2] 黑帽响应 bh = MORPH_BLACKHAT(gray, ksize=max(15,int(w*0.025)|1))ROI 外置 0
  [3] 锚点 = MediaPipe 21(左鬓角)/251(右鬓角),各自小窗口(≈w*3%)内吸附到 bh 最大处
  [4] 代价 cost = bh.max()-bh+1ROI 外设 1e6
      path = skimage.graph.route_through_array(cost, 左锚, 右锚, fully_connected, geometric)
  [5] 拒识:path 平均 bh 响应 < 阈值(可调) → None(上层返回 1001 "未检测到发际线划线"
  return path  # (N,2) row,col
  • 依赖:scikit-image==0.24.0⚠️ 0.25+ 强依赖 numpy≥2,会顶掉 mediapipe 的 numpy<2 → mediapipe/SegFormer 全崩。必须锁 0.24.x
  • 复用接口2forehead_upper_region / head_silhouettehairline/mask.py)、SegFormer / MediaPipe 单例。

2. 遮罩(检测路径只用来建遮罩;ComfyUI 输入图 = 划线图原样)

  • 遮罩path → 画成 curve_mask → 复用接口2 mask_from_curve(ROI ∩ 曲线以上 → 闭合) 得到"发际线以上闭合区域"。
  • ComfyUI 输入图:直接用 marked 划线图原样(已含医生手绘线;add_hair.json 节点26 本来就是"画了线的照片",提示词会清除黑线再生发)。不需要原图、不重画线
  • 合成 RGBARGB=marked 划线图,alpha=255mask(透明=重绘区)。复用 compose_comfy_rgba

3. 生发(复用接口2 ComfyUI 客户端)

hairline/comfyui.run(rgba_png) → 跑 add_hair.json(Flux-2)→ 生发图 PNG。同步。

4. worker handler/api/v1/hair/grow-b

1. marked 三选一取图(复用 resolve_image_bytes+ 校验(大小/解码/分辨率)。只需这一张。
2. landmarks(MediaPipe)+parse(SegFormer) → detect_marker_hairline
   - 无人脸 → 1001;未检测到画线 → 1001 "未检测到发际线划线"
3. 遮罩(mask_from_curve) + marked原样 → RGBA → comfyui.run → 生发图
4. return ok({ hair_growth_image_base64: 生发图, hairline_type: "custom" })
异常 → 1007;重活 run_in_threadpool。

5. 开发步骤

阶段 内容 验证
M1 检测 hairline/marker_detect.py(黑帽+锚点+Dijkstra+拒识) headmark test_image:检测线贴合真值;无线图被拒识
M2 遮罩 path→遮罩(复用 mask_from_curve) + marked原样 RGBA 合成 目视:遮罩贴合发际线
M3 接 app grow-b 真实实现(仅 marked) + 输出字段 + 1001 curlgrown 合法PNG/type=custom;无线→1001
M4 测试 检测/mask 单测 + mock-ComfyUI 集成 + 真机冒烟 pytest 绿;真机出生发图

6. 风险

  1. 锚点偏差/路径端点偏移MediaPipe 21/251 吸附后仍可能在鬓角端有偏移(实测 max~42px,少数点)。 膨胀成带 + 遮罩闭合可吸收;必要时改进吸附窗口或端点截断。
  2. 没画线/画线极浅:靠拒识阈值(路径平均黑帽响应)兜底,阈值需在更多真实图上标定。
  3. 医生手绘线毛刺/杂线ComfyUI 输入图用 marked 原样(含手绘线),提示词会清除黑线; 检测出的干净 path 只用于建遮罩。若手绘过乱影响生成,可改为在 marked 上重画干净检测线(备选)。
  4. 抬头纹/眉毛/发丝干扰:黑帽 + ROI + Dijkstra 平滑已大幅抑制(调研验证抬头纹零干扰),极端情况可在代价图抑制头发区域。

文档版本: v1.0 创建日期: 2026-06-15 检测来源: headmark(黑帽+Dijkstra)| 生发: 复用接口2 ComfyUI(add_hair.json) 运行位置: worker(GPU) + 本机 ComfyUI(8182)