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:
@@ -0,0 +1,93 @@
|
||||
# 接口 3:B 端生发 — 技术实现方案(马克笔发际线检测 + 生发)
|
||||
|
||||
> 在 **高性能 worker(GPU 机)** 实现,与接口 1/2 同机。对外经网关代理。
|
||||
> B 端:医生在患者额头**用马克笔画出规划的发际线**,拍照上传。系统**检测这条手绘线**,
|
||||
> 据此生成生发图。检测算法移植自 `/home/xsl/headmark` 的调研结论(黑帽 + Dijkstra)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 契约(对齐 `接口文档.md` 接口3,不变)
|
||||
|
||||
`POST /api/v1/hair/grow-b`
|
||||
|
||||
| 输入 | 说明 |
|
||||
|------|------|
|
||||
| `marked_image_*` | 已划线(医生标注发际线)的图,三选一,必填 |
|
||||
| `original_image_*` | 原始用户照片,三选一,必填 |
|
||||
|
||||
| 输出 data | 决策 |
|
||||
|-----------|------|
|
||||
| `best_hairline_image_url` | **= 原图 original** 原样返回(worker 返回 `best_hairline_image_base64`) |
|
||||
| `hair_growth_image_url` | **生发后图片**(ComfyUI,worker 返回 `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+1;ROI 外设 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**。
|
||||
- 复用接口2:`forehead_upper_region` / `head_silhouette`(`hairline/mask.py`)、SegFormer / MediaPipe 单例。
|
||||
|
||||
## 2. 遮罩 + 原图重画干净线
|
||||
|
||||
- **遮罩**:path → 画成 curve_mask → 复用接口2 `_above_curve_region` + `head` + `_clean_mask`
|
||||
得到"发际线以上闭合区域"。
|
||||
- **ComfyUI 输入图**:用 **原图 original**(按需缩放到 marked 尺寸对齐坐标),**重画一条干净黑线**
|
||||
(检测 path 膨胀成线宽),避免医生手绘的毛刺/杂线干扰生成。
|
||||
- 合成 RGBA:RGB=重画线的原图,alpha=255−mask(透明=重绘区)。复用 `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 + original 各三选一取图(复用 resolve_image_bytes)+ 校验(大小/解码/分辨率)
|
||||
2. 在 marked 上:landmarks(MediaPipe)+parse(SegFormer) → detect_marker_hairline
|
||||
- 无人脸 → 1001;未检测到画线 → 1001 "未检测到发际线划线"
|
||||
3. 遮罩 + 原图重画线 → RGBA → comfyui.run → 生发图
|
||||
4. return ok({ best_hairline_image_base64: 原图, hair_growth_image_base64: 生发图,
|
||||
hairline_type: "custom" })
|
||||
异常 → 1007;重活 run_in_threadpool。
|
||||
```
|
||||
|
||||
## 5. 开发步骤
|
||||
|
||||
| 阶段 | 内容 | 验证 |
|
||||
|------|------|------|
|
||||
| **M1 检测** | `hairline/marker_detect.py`(黑帽+锚点+Dijkstra+拒识) | headmark test_image:检测线贴合真值;无线图被拒识 |
|
||||
| **M2 遮罩+重画** | path→遮罩(复用) + 原图重画干净线 + RGBA 合成 | 目视:干净线在原图、遮罩贴合 |
|
||||
| **M3 接 app** | grow-b 真实实现 + 输出字段 + 1001 | curl:best=原图/grown 合法PNG/type=custom;无线→1001 |
|
||||
| **M4 测试** | 检测/mask 单测 + mock-ComfyUI 集成 + 真机冒烟 | pytest 绿;真机出生发图 |
|
||||
|
||||
## 6. 风险
|
||||
|
||||
1. **锚点偏差/路径端点偏移**:MediaPipe 21/251 吸附后仍可能在鬓角端有偏移(实测 max~42px,少数点)。
|
||||
膨胀成带 + 遮罩闭合可吸收;必要时改进吸附窗口或端点截断。
|
||||
2. **没画线/画线极浅**:靠拒识阈值(路径平均黑帽响应)兜底,阈值需在更多真实图上标定。
|
||||
3. **marked 与 original 尺寸/对齐不一致**:按 marked 坐标系处理,original 缩放对齐;若两图非同源(不同姿态)会错位——约定二者为"同一张照片的划线版/原始版"。
|
||||
4. **抬头纹/眉毛/发丝干扰**:黑帽 + ROI + Dijkstra 平滑已大幅抑制(调研验证抬头纹零干扰),极端情况可在代价图抑制头发区域。
|
||||
|
||||
---
|
||||
|
||||
> **文档版本**: v1.0 | **创建日期**: 2026-06-15 | 检测来源: headmark(黑帽+Dijkstra)|
|
||||
> 生发: 复用接口2 ComfyUI(add_hair.json) | 运行位置: worker(GPU) + 本机 ComfyUI(8182)
|
||||
Reference in New Issue
Block a user