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
+14 -16
View File
@@ -12,12 +12,10 @@
| 输入 | 说明 |
|------|------|
| `marked_image_*` | 已划线(医生标注发际线的图,三选一,必填 |
| `original_image_*` | 原始用户照片,三选一,必填 |
| `marked_image_*` | 已用马克笔标注发际线的图,三选一,必填。**只需这一张**(划线图即用户照片+手绘线,不需要原图) |
| 输出 data | 决策 |
|-----------|------|
| `best_hairline_image_url` | **= 原图 original** 原样返回(worker 返回 `best_hairline_image_base64` |
| `hair_growth_image_url` | **生发后图片**ComfyUIworker 返回 `hair_growth_image_base64` |
| `hairline_type` | 固定 **`"custom"`**(手绘定制) |
@@ -46,13 +44,13 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
mediapipe/SegFormer 全崩。**必须锁 0.24.x**。
- 复用接口2`forehead_upper_region` / `head_silhouette``hairline/mask.py`)、SegFormer / MediaPipe 单例。
## 2. 遮罩 + 原图重画干净线
## 2. 遮罩(检测路径只用来建遮罩;ComfyUI 输入图 = 划线图原样)
- **遮罩**path → 画成 curve_mask → 复用接口2 `_above_curve_region` + `head` + `_clean_mask`
- **遮罩**path → 画成 curve_mask → 复用接口2 `mask_from_curve`ROI ∩ 曲线以上 → 闭合)
得到"发际线以上闭合区域"。
- **ComfyUI 输入图**:用 **原图 original**(按需缩放到 marked 尺寸对齐坐标),**重画一条干净黑线**
(检测 path 膨胀成线宽),避免医生手绘的毛刺/杂线干扰生成
- 合成 RGBARGB=重画线的原图,alpha=255mask(透明=重绘区)。复用 `compose_comfy_rgba`
- **ComfyUI 输入图**直接**marked 划线图原样**(已含医生手绘线;`add_hair.json` 节点26
本来就是"画了线的照片",提示词会清除黑线再生发)。**不需要原图、不重画线**
- 合成 RGBARGB=marked 划线图,alpha=255mask(透明=重绘区)。复用 `compose_comfy_rgba`
## 3. 生发(复用接口2 ComfyUI 客户端)
@@ -61,12 +59,11 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
## 4. worker handler`/api/v1/hair/grow-b`
```
1. marked + original 各三选一取图(复用 resolve_image_bytes+ 校验(大小/解码/分辨率)
2. 在 marked 上:landmarks(MediaPipe)+parse(SegFormer) → detect_marker_hairline
1. marked 三选一取图(复用 resolve_image_bytes+ 校验(大小/解码/分辨率)。只需这一张。
2. 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" })
3. 遮罩(mask_from_curve) + marked原样 → RGBA → comfyui.run → 生发图
4. return ok({ hair_growth_image_base64: 生发图, hairline_type: "custom" })
异常 → 1007;重活 run_in_threadpool。
```
@@ -75,8 +72,8 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
| 阶段 | 内容 | 验证 |
|------|------|------|
| **M1 检测** | `hairline/marker_detect.py`(黑帽+锚点+Dijkstra+拒识) | headmark test_image:检测线贴合真值;无线图被拒识 |
| **M2 遮罩+重画** | path→遮罩(复用) + 原图重画干净线 + RGBA 合成 | 目视:干净线在原图、遮罩贴合 |
| **M3 接 app** | grow-b 真实实现 + 输出字段 + 1001 | curlbest=原图/grown 合法PNG/type=custom;无线→1001 |
| **M2 遮罩** | path→遮罩(复用 mask_from_curve) + marked原样 RGBA 合成 | 目视:遮罩贴合发际线 |
| **M3 接 app** | grow-b 真实实现(仅 marked) + 输出字段 + 1001 | curlgrown 合法PNG/type=custom;无线→1001 |
| **M4 测试** | 检测/mask 单测 + mock-ComfyUI 集成 + 真机冒烟 | pytest 绿;真机出生发图 |
## 6. 风险
@@ -84,7 +81,8 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
1. **锚点偏差/路径端点偏移**MediaPipe 21/251 吸附后仍可能在鬓角端有偏移(实测 max~42px,少数点)。
膨胀成带 + 遮罩闭合可吸收;必要时改进吸附窗口或端点截断。
2. **没画线/画线极浅**:靠拒识阈值(路径平均黑帽响应)兜底,阈值需在更多真实图上标定。
3. **marked 与 original 尺寸/对齐不一致**:按 marked 坐标系处理,original 缩放对齐;若两图非同源(不同姿态)会错位——约定二者为"同一张照片的划线版/原始版"。
3. **医生手绘线毛刺/杂线**ComfyUI 输入图用 marked 原样(含手绘线),提示词会清除黑线;
检测出的干净 path 只用于建遮罩。若手绘过乱影响生成,可改为在 marked 上重画干净检测线(备选)。
4. **抬头纹/眉毛/发丝干扰**:黑帽 + ROI + Dijkstra 平滑已大幅抑制(调研验证抬头纹零干扰),极端情况可在代价图抑制头发区域。
---
+12 -11
View File
@@ -230,28 +230,30 @@
## 接口 3B 端生发接口
**说明**输入医生/操作端的「划线图片」(在原图上标注目标发际线),输出最合适的发际线图片 + 生发后图片。
**说明**:医生/操作端在用户照片上用马克笔标注目标发际线后,**只需上传这一张划线图**。系统检测划线 →
据此生成生发后图片。
**请求**`POST /api/v1/hair/grow-b`
### 输入
划线图片同样支持「文件 / URL / base64」三种方式(字段:`marked_image_file` / `marked_image_url` / `marked_image_base64`)。
划线图片支持「文件 / URL / base64」三选一(字段:`marked_image_file` / `marked_image_url` / `marked_image_base64`)。
**不需要原始照片**(划线图本身就是用户照片 + 手绘线)。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| marked_image_* | file / string | 是 | 已划线(标注发际线的图片,三选一 |
| original_image_* | file / string | 是 | 原始用户照片,需同时上传 |
| marked_image_* | file / string | 是 | 已用马克笔标注发际线的图片,三选一 |
### 输出(data
| 字段 | 类型 | 说明 |
|------|------|------|
| best_hairline_image_url | string | 最合适的发际线图片 |
| hair_growth_image_url | string | 生发后图片 |
| hairline_type | string | 发际线形,需返回 |
| hair_growth_image_url | string | **生发后图片**(检测划线 → ComfyUI/Flux「植发 3 个月」效果)。worker 返回 `hair_growth_image_base64` |
| hairline_type | string | 发际线形,手绘为定制,固定 `"custom"` |
### 响应示例(当前 Mock 返回值)
> 未检测到划线(或无人脸)返回 **1001**。
### 响应示例
```json
{
@@ -259,9 +261,8 @@
"message": "success",
"request_id": "mock-request-id",
"data": {
"best_hairline_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"hair_growth_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"hairline_type": "花瓣形"
"hair_growth_image_url": "https://hair.xiangsilian.com/static/annotations/grown.png",
"hairline_type": "custom"
}
}
```
+1 -2
View File
@@ -133,8 +133,7 @@ curl -s http://127.0.0.1:8080/gateway-health # 200
| 1 | `/api/v1/face/measure` | `annotated_image_base64` | `annotated_image_url` | data 顶层 |
| 2 | `/api/v1/hair/grow` | `results[].image_base64` | `results[].image_url` | **数组元素** |
| 2 | 〃 | `results[].grown_image_base64` | `results[].grown_image_url` | **数组元素**(可空) |
| 3 | `/api/v1/hair/grow-b` | `best_hairline_image_base64` | `best_hairline_image_url` | data 顶层 |
| 3 | 〃 | `hair_growth_image_base64` | `hair_growth_image_url` | data 顶层(可空) |
| 3 | `/api/v1/hair/grow-b` | `hair_growth_image_base64` | `hair_growth_image_url` | data 顶层(可空) |
| 4 | `/api/v1/face/features` | —(无图片字段,原样透传)| — | — |
| 5 | `/api/v1/hairline/generate` | `hairline_images[].image_base64` | `hairline_images[].image_url` | **数组元素** |