Files
hair/docs/实现说明.md
T
xslandClaude 9678b54267 feat(接口2/7): hair_style 支持逗号分隔多选,如 1,2,3
- 参数类型从 int 改为 string(逗号分隔),自动解析去重排序
- 越界/非法值返回 1007
- _parse_hair_styles() 辅助函数:解析 + 去重 + 范围校验
- service 层 hair_style 参数改为 hair_styles: list[int]
- 接口2 和 接口7 同步更新
- 服务已重启,smoke test 通过

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 23:36:13 +08:00

112 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 旷视五接口 — 实现说明(总)
> 把原先分散的「各接口技术方案 + 开发任务书 + 系统架构 + 网关任务书」合并成这一份**简要总览**。
> 对外 API 契约以 [`接口文档.md`](接口文档.md) 为唯一权威;原始需求见 [`旷视具体需求.md`](旷视具体需求.md);
> 离线模型清单见 [`../OFFLINE_ASSETS.md`](../OFFLINE_ASSETS.md)。
---
## 1. 架构
两台机器、一个仓库:
```
客户端 ──HTTPS──> 外网网关(gateway/) ──HTTP(X-Internal-Token)──> worker(GPU 机, app.py)
│ 薄代理 + 落盘改URL │ 跑算法(本地模型/ComfyUI)
└ 接口4 本机直接调豆包(不转发) └ 接口1/2/3/5/7
```
- **worker**`app.py` + `face_analysis/` + `hairline/`):跑真正的算法,**纯本地、无外网依赖**。
`/api/*` 校验 `X-Internal-Token`(密码在 `worker_config.json``accept_passwords`);
`/health` 模型就绪才返回 200。监听 **8187**`./start.sh` 控制开关,`./run_worker.sh` 热重载)。
- **网关**`gateway/`):薄反向代理,健康检查/派发/鉴权/把 worker 的 `*_base64` 落盘改成 `*_url`
唯一例外是**接口4 在网关本机直接实现**(调外网豆包,不转发 worker)。
- **图片三选一**:所有接口图片入参 `image_file`/`image_url`/`image_base64` 严格三选一(接口3 是 `marked_image_*`)。
- **响应信封**`{code, message, request_id, data}`;业务错误用 `code`HTTP 一律 200)。
### base64 → URL 映射(网关落盘改写,递归进数组、可空保留 null)
| 接口 | worker 字段(内部) | 对外字段 |
|------|--------------------|----------|
| 1 | `annotated_image_base64` | `annotated_image_url` |
| 2 | `results[].image_base64` / `results[].grown_image_base64`(可空) | `results[].image_url` / `results[].grown_image_url` |
| 3 | `hair_growth_image_base64`(可空) | `hair_growth_image_url` |
| 5 | `hairline_images[].image_base64` | `hairline_images[].image_url` |
| 7 | `results[].image_base64` / `results[].grown_image_base64`(可空) | `results[].image_url` / `results[].grown_image_url` |
| 4 | (网关本机产出,无图片字段,`features` 为 JSON 字符串) | — |
> 实现建议:递归遍历 data,凡 key 以 `_base64` 结尾就落盘改 `_url`,自动覆盖嵌套/新增字段。
> **图片格式**:接口1 标注图含透明用 **PNG**;接口2/3/5 是不透明照片用 **JPG**(小很多,~9×)。
> 网关落盘按内容嗅探扩展名(PNG 头→`.png`,否则 `.jpg`)。
### 错误码
`1001` 无法识别人像 `1002` 分辨率过低 `1003` 非正面 `1004` gender 必填/非法(接口2/5/7)|
`1006` >1MB `1007` 图片参数错误(0或多个)/未预期异常 | `1008` 格式不支持。
---
## 2. 五个接口实现简述
### 接口1 四庭七眼测量 `/api/v1/face/measure`worker
- **做什么**:正面照 → 四庭(顶/上/中/下庭) + 七眼(眼宽/脸宽/间距) 的 cm 与占比、5 个关键点坐标、一张透明底标注 PNG。
- **怎么实现**`face_analysis/`):MediaPipe Face Mesh 468+虹膜点 → solvePnP 姿态校验(非正面 1003) →
虹膜直径法定标(px→cm) → **眉心以下实测****眉心以上**用 BiSeNet 头发分割取真实发际线/头顶(方案B),
失败回退比例推算(方案A`hairline_source` 透出)。标注图 numpy 向量化渐变线 + 思源黑体。返回 `annotated_image_base64`
- 门槛可配:`MIN_SHORT_SIDE`/`MIN_LONG_SIDE`(默认600/800)、姿态阈值 `FRONTAL_*_THR`(默认30°)。
### 接口2 C端生发 `/api/v1/hair/grow`worker)—— 预览 + 生发图
- **做什么**:正面照 + `gender`(必填) + `hair_style`(必填,逗号分隔多选,如 `1,2,3`) → 指定发际线类型 **N 组**:**预览图**(发际线叠在照片上) + **生发后图**(植发3个月效果)。
- **怎么实现**`hairline/`):移植 head3d——MediaPipe(Tasks) + SegFormer 分割 + 17 锚点射线检测 → 502 点 mesh,
`face_ext.obj` 的 UV 把发际线贴图渲染到额头(预览)。生发:黑贴图渲染遮罩 → 调本机 **ComfyUI 8182**
`add_hair.json`(Flux-2) 出图。**关键坑**obj 是重排序,需 `INDEX_MAP_468` 把 MP 序→OBJ 序。
返回 `results[].image_base64` + `grown_image_base64`
- `hair_style` 映射:female 1=ellipse 2=flower 3=heart 4=straight 5=wavemale 1=ellipse 2=inverse_arc 3=m 4=straight。
### 接口7 C端生发 v2 `/api/v1/hair/grow-v2`worker)—— 接口2同款,add_hair2 工作流
- **做什么**:与接口 2 完全一致(正面照 + `gender` + `hair_style` 逗号分隔多选 → N 组预览+生发图)。
- **与接口 2 的唯一区别**:ComfyUI 工作流使用 `add_hair2.json`Flux-2 Klein 9b),输入/遮罩节点同为 26,
SaveImage 输出节点为 75(自动检测)。其他参数、响应结构、错误码**完全相同**。
- **网关改动**:在 `gateway/app.py` 新增路由 `POST /api/v1/hair/grow-v2`,转发到 worker 同路径即可(盲转发,
base64→URL 改写逻辑无需改动,数组内图片字段已覆盖)。详见 [`网关待改动.md`](网关待改动.md)。
### 接口3 B端生发 `/api/v1/hair/grow-b`worker)—— 马克笔发际线
- **做什么**:医生用马克笔在额头画好发际线,**只传这一张划线图** → 检测线 → 生发图。输出 `hair_growth_image_url` + `hairline_type="custom"`
- **怎么实现**:检测算法源自 headmark——**黑帽响应图 + 鬓角锚点(MediaPipe 21/251) + Dijkstra 最小路径**(scikit-image)
比全局阈值鲁棒;路径平均响应过低→拒识(1001)。检测路径建遮罩,划线图原样送 ComfyUI(提示词清除黑线)。
### 接口4 用户特征 `/api/v1/face/features`**网关本机**
- **做什么**:照片 → 几十项面部特征(脸型/眉形/肤色/三庭五眼/四季色彩季型/量感/基因风格/性别…)。`data.features` 是 JSON 字符串。
- **怎么实现**`gateway/`,逻辑参考 worker `face_features.py` / `/home/xsl/fuyan`):调**火山方舟 豆包视觉模型**
`doubao-seed-1-6-vision`(OpenAI 兼容,base64 data URI 喂图),解析 JSON + 映射 6 个英文优先字段并保留全部中文。
无人脸→1001。**唯一调外网的接口**:网关需可达 `ark.cn-beijing.volces.com`API Key 走网关配置(不入 git)。
### 接口5 发际线PNG生成 `/api/v1/hairline/generate`worker
- **做什么**:照片 + `gender`(必填) → N 张发际线叠加图(同接口2预览) + 最佳(order1)发际线曲线的面部中间点坐标。
- **怎么实现**:复用接口2 的 502 点渲染管线,输出 N 张叠图 + `best_hairline_center_point`(眉心 x × 该处发际线 y)。无生发。
---
## 3. 部署 / 环境要点
**workerGPU 机)**
- Python **3.12**(系统 3.13 无 mediapipe/torch wheel);venv 在 `./venv`,依赖 `requirements.txt`
- `numpy<2`(1.26.4)`scikit-image==0.24.0`**别升 0.25+,会顶 numpy≥2 顶崩 mediapipe**)。
- ⚠️ 本机 **RTX 5090(sm_120)**pinned `torch 2.2.2(cu121)` 只到 sm_90 → GPU 算子报 "no kernel image"
代码已自动**回退 CPU**BiSeNet/SegFormer CPU 推理可用)。要用 5090 GPU 需换 torch cu128(≥2.7)。
- 模型权重/字体见 [`../OFFLINE_ASSETS.md`](../OFFLINE_ASSETS.md)BiSeNet/SegFormer/face_landmarker.task 本地。
- 生发接口依赖本机 **ComfyUI(8188)**Flux-2,它自带支持 5090 的 torch);worker 只调其 HTTP API,不跑 Flux。
ComfyUI 开了 **HTTP Basic Auth**user `admin` + 密码);密码放 `password.txt`(不入 git) /
`worker_config.json.comfyui_password` / 环境变量 `COMFYUI_PASSWORD`URL 用 `COMFYUI_URL`
- `worker_config.json`(不入 git)`accept_passwords`(鉴权) + 鉴权头 `X-Internal-Token`
**网关机**
- 很轻:FastAPI/uvicorn/httpx + **接口4 的 `volcengine-python-sdk[ark]`**(或直接 httpx 调,OpenAI 兼容)。
- 不装 torch/mediapipe/opencv。配置 `gateway/config.json`(不入 git)`workers` 列表、`shared_password`
`ark` 的 api_key/base_url/model、`public_base_url`、超时(**生发接口慢,`request_timeout_seconds` 调大 ≥120s**)。
- 托管 `/static/annotations/`(落盘的图),定期清理。
---
> 维护:本文为简要总览;字段以 `接口文档.md` 为准。各接口更细的算法推导可查 git 历史中已合并的旧技术方案文档。