review+docs: 接口4 回收到网关、对齐错误码、合并文档为一份实现说明
代码 review 后的清理: - 接口4 由网关本机实现,worker app.py 的 /face/features 回退 Mock(保持 worker 无外网依赖); worker requirements 标注 volcengine 改为网关侧;移除 worker 的接口4 测试(随实现挪到网关) - 网关接口4 业务错误 HTTP 状态统一改 200(与其余接口/worker 约定一致,原为400/503) - 接口文档:gender 非法码 1004(原误写1008);修正指向已删文档的链接 文档合并:把各接口技术方案/开发任务书/系统架构/网关任务书 合并成 docs/实现说明.md(简要总览), 删除原 7 份分散文档,README 收敛为索引(实现说明/接口文档/需求/OFFLINE_ASSETS)。 pytest 44 全绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# 旷视五接口 — 实现说明(总)
|
||||
|
||||
> 把原先分散的「各接口技术方案 + 开发任务书 + 系统架构 + 网关任务书」合并成这一份**简要总览**。
|
||||
> 对外 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
|
||||
```
|
||||
|
||||
- **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` |
|
||||
| 4 | (网关本机产出,无图片字段,`features` 为 JSON 字符串) | — |
|
||||
|
||||
> 实现建议:递归遍历 data,凡 key 以 `_base64` 结尾就落盘改 `_url`,自动覆盖嵌套/新增字段。
|
||||
|
||||
### 错误码
|
||||
|
||||
`1001` 无法识别人像 | `1002` 分辨率过低 | `1003` 非正面 | `1004` gender 必填/非法(接口2/5)|
|
||||
`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`(必填) → 该性别全部发际线(female5/male4) 各一组:**预览图**(发际线叠在照片上) + **生发后图**(植发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 序。
|
||||
同步、一次 N 张(~18s)。返回 `results[].image_base64` + `grown_image_base64`。
|
||||
|
||||
### 接口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. 部署 / 环境要点
|
||||
|
||||
**worker(GPU 机)**
|
||||
- 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(8182)**(Flux-2,它自带支持 5090 的 torch);worker 只调其 HTTP API,不跑 Flux。
|
||||
- `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 历史中已合并的旧技术方案文档。
|
||||
Reference in New Issue
Block a user