feat(接口99): 人脸特殊点检测 + 网关转发路由;文档同步至当前代码

- worker: POST /api/v1/face/special-points,60 个固定编号关键点(6组)
  + 5 个发际线特殊点(沿指定关键点方向射线步进,与 BiSeNet 头发 mask 首次相交;
  双射线取中点,left_left=mid(333→332, 298→284) 与右侧镜像)
- 网关: 同路径盲转发路由(调用频率低,维持转发不占网关依赖;
  响应无 *_base64 字段,改写逻辑天然跳过)
- 测试页 static/test_interface99.html:分组着色小点 + 点ID标注(可开关)
  + 发际线点大圆标签 + JSON 展示
- 文档: CLAUDE.md 重写至当前架构;实现说明/接口文档补接口 9~12/99 契约;
  网关待改动补 §7(9/10已加) §8(11/12待加) §9(接口99部署步骤)
This commit is contained in:
xsl
2026-09-06 23:37:01 +08:00
parent 989f4d0d18
commit 63bd265386
8 changed files with 906 additions and 93 deletions
+72 -52
View File
@@ -4,80 +4,100 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Overview
"旷视五接口" — a FastAPI facial analysis API service providing 5 endpoints for face measurement, hair growth simulation (C-end and B-end), user feature extraction, and hairline PNG generation. Currently a **Mock v1**: all endpoints return hardcoded sample data; real ML logic is pending.
旷视接口服务 — FastAPI 人像分析/生发服务,**一个仓库、两种部署角色**:
- **Base URL (prod)**: `https://hair.xiangsilian.com`
- **Swagger docs**: `https://hair.xiangsilian.com/docs`
- **Internal**: uvicorn on `127.0.0.1:8000` behind nginx reverse proxy
- **worker**(`app.py` + `face_analysis/` + `hairline/`):跑真实算法(本地模型 + ComfyUI + change_hair),监听 `0.0.0.0:8187`。**本机(GPU 机)即 worker 角色**,不跑网关。
- **网关**(`gateway/`):外网机器上的薄反向代理,负责鉴权/健康检查/派发,并把 worker 返回的 `*_base64` 落盘改写为 `*_url`。接口4(豆包)在网关本机实现,不转发 worker。
> 历史包袱:`app.py` 的 FastAPI `description` 和 `docs/接口文档.md` 部分示例仍写「Mock 第一版」字样,实际全部接口已是真实算法实现。**架构以 `docs/实现说明.md` 为准,契约以 `docs/接口文档.md` 为准。**
## Commands
```bash
# Development server
./venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000 --reload
# ---- worker(本机)----
./run_worker.sh # 后台启动(0.0.0.0:8187,日志 worker.log)
FOREGROUND=1 ./run_worker.sh # 前台调试
./stop_worker.sh # 停止
sudo systemctl restart hair-worker # systemd 方式(生产)
journalctl -u hair-worker -f
# Or via the start script
./start.sh
# ---- 依赖服务(worker 之前要先起)----
sudo systemctl start comfyui # ComfyUI 127.0.0.1:8188(Flux-2 生图)
cd /home/ubuntu/change_hair && bash start_all.sh start # 换发型三件套 8801/32678/57860
# Production (systemd)
sudo systemctl restart hair
sudo systemctl status hair
journalctl -u hair -f
# Install dependencies (uses Tencent Cloud PyPI mirror)
./venv/bin/pip install -r requirements.txt
# ---- 测试 ----
./venv/bin/pytest tests/ # 单元测试(conftest 会准备 fixtures)
./venv/bin/python -m hairline.comfyui tests/output/comfy_input.png # 手动跑一次生发工作流
```
依赖装在 `./venv`(Python 3.10,torch 2.7.1+cu128,RTX 4090 CUDA 可用)。`requirements.txt` 里的 torch 2.2.2/5090 注释是旧机器的记录,本机 venv 实际是 cu128 版。
## Architecture
**Single-file app** — `app.py` (~555 lines) contains the entire server: FastAPI app creation, Pydantic models, all 5 endpoint handlers, and health check routes. There are no separate routers, services, or middleware modules.
### 接口一览(worker 侧路由,`app.py`)
### Endpoints
| 接口 | 路径 | 实现位置 | 说明 |
|------|------|----------|------|
| 1 | `POST /api/v1/face/measure` | `face_analysis/` | 四庭七眼测量 + 标注 PNG |
| 6 | `POST /api/v1/face/measure-v2` | 同上(`variant="v6"`) | 接口1 去顶庭变体 |
| 2 | `POST /api/v1/hair/grow` | `hairline/service.py` | C端生发。**female 1..5** 走 change_hair swapHair + Flux-2 整帧重绘(接口12 final 管线);**female 6/7 与 male 全部**走 ComfyUI `add_hair.json` inpaint |
| 7 | `POST /api/v1/hair/grow-v2`(hidden/deprecated) | 同上 | 同接口2,用 `add_hair2.json` 工作流 |
| 3 | `POST /api/v1/hair/grow-b` | `hairline/marker_detect.py` | B端马克笔划线生发(黑帽响应 + Dijkstra) |
| 4 | `POST /api/v1/face/features` | worker 有实现;生产走**网关本机**(豆包) | 用户特征 |
| 5 | `POST /api/v1/hairline/generate` | `hairline/service.py` | 发际线 PNG 三档(middle/high/low)+ 生发 |
| 9 | `POST /api/v1/head/mask` | `face_analysis/head_mask.py` | 头发遮罩 + 分步可视化(BiSeNet/SegFormer 双路) |
| 10 | `POST /api/v1/head/band` | `face_analysis/head_band.py` | 头部外缘膨胀带遮罩 |
| 11 | `POST /api/v1/hairline/grow` | `face_analysis/hairline_grow.py` | 发际线生发(接口9 遮罩 + change_hair + 接缝融合),不含重绘 |
| 12 | `POST /api/v1/hairline/grow_v2` | 同上 | 接口11 final + 发际线重绘带 → Flux-2 保色重绘 |
| 12f | `POST /api/v1/hairline/grow_v2_final` | 同上(`_V2_FINAL_DEFAULTS`) | 精简版:仅图片 + hairline_id,取整帧重绘 |
| 12f2 | `POST /api/v1/hairline/grow_v2_final_v2` | 同上 | 同上,取 B 局部加发+全脸美颜 |
| 99 | `POST /api/v1/face/special-points` | `face_analysis/special_points.py` | 固定编号关键点 + 发际线特殊点(射线求交头发 mask)像素坐标 |
| 代理 | `POST /api/swapHair/v1`、`/hairColor/v2` | httpx 转发 127.0.0.1:8801 | 解决测试页 CORS |
| 工具 | `POST /api/v1/redraw` | `hairline/redraw.py` | 直接 ComfyUI 重绘(图+遮罩+prompt) |
| 调试 | `POST /api/v1/debug/grow-timing`、`GET /api/v1/debug/hairline_log` | — | 免鉴权,分步计时/下载日志 |
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/v1/face/measure` | 四庭七眼 measurement + annotated PNG |
| POST | `/api/v1/hair/grow` | C-end hair growth (user-facing) |
| POST | `/api/v1/hair/grow-b` | B-end hair growth (doctor/operator, with marked image) |
| POST | `/api/v1/face/features` | User facial feature analysis |
| POST | `/api/v1/hairline/generate` | Hairline PNG generation |
| GET | `/health` | Health check (excluded from OpenAPI schema) |
| GET | `/` | Service info (excluded from OpenAPI schema) |
配套测试页在 `static/test_interface{1..12}*.html`,经 `/static` 挂载。
### Image input convention (all endpoints)
### 关键模式
Every endpoint accepts images via **exactly one** of three mutually exclusive fields:
- `image_file` — multipart file upload (≤ 1 MB)
- `image_url` — HTTP/HTTPS URL string
- `image_base64` — base64 string with `data:image/...;base64,` prefix
- **统一信封**:`ok(data)` / `err(code, message)` 构造 `{code, message, request_id, data}`;业务错误 HTTP 一律 200。错误码:1001 无人像 / 1002 分辨率低 / 1003 非正面 / 1004 gender 非法 / 1005 多人 / 1006 >1MB / 1007 参数或处理失败 / 1008 格式不支持。
- **图片三选一**:每个接口 `image_file`(multipart) / `image_url` / `image_base64`(带 `data:` 前缀)严格互斥,由 `resolve_image_bytes()` 统一处理(0 个或多个 → 1007)。接口3 是 `marked_image_*`。
- **阻塞算法进线程池**:所有推理 handler 用 `run_in_threadpool(...)`,避免卡事件循环。
- **内网鉴权**:HTTP 中间件校验 `/api/*` 的 `X-Internal-Token` 头,密码来自 `worker_config.json` 的 `accept_passwords`(或环境变量 `WORKER_ACCEPT_PASSWORDS`)。豁免前缀见 `_AUTH_EXEMPT`(health/docs/static/debug/redraw/swapHair/hairColor)。
- **发型序号映射**:`hairline/service.py` 的 `_HAIRSTYLE_ORDER` 显式定义 1..7 顺序(不靠文件名字母序);female 1..5 → change_hair `chang_*` 映射在 `_FEMALE_KEY_TO_CHANG`。
- **接口12 固化参数**:`_V2_FINAL_DEFAULTS`(app.py)是生产默认值(push=0.8cm / two_stage / color_match=关 等),接口2 female 重绘也复用它。
Endpoint 3 (B-end) has **two** sets of image params: `marked_image_*` (the marked-up image) and `original_image_*` (the original photo). Each set follows the same three-way exclusive rule independently.
### 外部依赖(worker 视角)
### Unified response structure
| 依赖 | 地址 | 用途 | 配置 |
|------|------|------|------|
| ComfyUI | `http://127.0.0.1:8188`(`COMFYUI_URL`) | Flux-2 生图/重绘,走 HTTP API(`hairline/comfyui.py`) | Basic Auth `admin`,密码:`COMFYUI_PASSWORD` → `worker_config.json.comfyui_password` → `password.txt` |
| change_hair | `http://127.0.0.1:8801`(`SWAP_HAIR_URL`/`HAIR_GROW_URL`/`CHANGE_HAIR_BASE`) | 换发型(SD WebUI + LoRA)、hairGrow | 服务目录 `/home/ubuntu/change_hair`,`start_all.sh` 启停 |
```json
{"code": 0, "message": "success", "request_id": "mock-request-id", "data": {...}}
```
ComfyUI 工作流 JSON 在仓库根:`add_hair.json`(接口2/3/5 生发)、`add_hair2.json`(接口7)、`0716add-hair-api.json`(接口12/2女 重绘)、`hair_repaint.json`。节点约定:**26** 输入图 / **6** seed / **60** 提示词 / **16** unet(.gguf 自动切 UnetLoaderGGUF)/ **61** CLIP(4b↔9b 自动配套)。
Error codes: 1001–1008 (see `app.py` docstring or `docs/接口文档.md`).
### 性能/显存相关的坑
### Key code patterns
- 三个生发接口共用**同一提示词字符串**以命中 ComfyUI 的 CLIP 文本条件缓存——prompt 变了会触发 Flux(7.7G)+qwen CLIP(3.9G) 互相挤显存重载(每次 +4s)。
- 接口2 女性重绘管线默认把输入长边压到 `REDRAW_MAX_SIDE`(env,默认 1024)再放大回原尺寸;`redraw_max_side=0` 表示不缩图。
- `comfyui.run(front=True)` 可插队(接口2 对时延敏感);超时 `COMFY_TIMEOUT`(默认 600s)。
- `ok(data)` / `err(code, msg)` — helper functions at module level that build the standard response envelope
- `ImageJsonBody` — Pydantic model for JSON-based image params (URL/base64); form-upload endpoints use FastAPI `Form()` / `File()` parameters directly
- `SAMPLE_IMAGE_URL` — `https://hair.xiangsilian.com/static/sample.jpg`, returned as the image URL in all mock responses
- Static files (`/static/*`) are mounted via `app.mount("/static", StaticFiles(directory="static"), name="static")`
- All 5 POST endpoints use `multipart/form-data` with both `File` and `Form` fields — there are no JSON-body POST endpoints despite `ImageJsonBody` being defined (it exists for the OpenAPI schema but the actual handlers use `Form()` for all params)
## Deployment(本机 = gpu_worker)
### Deployment
systemd 单元(仓库根有副本,实装在 `/etc/systemd/system/`):
- **Process manager**: systemd (`hair.service` → `ExecStart` runs uvicorn directly from venv)
- **Reverse proxy**: nginx (`nginx/hair.conf`) proxies port 80 → `127.0.0.1:8000` for `hair.xiangsilian.com`
- **Python**: venv at `./venv`, dependencies in `requirements.txt`
- **PyPI mirror**: Tencent Cloud (`mirrors.cloud.tencent.com`), configured in `pip.conf`
| 服务 | 端口 | 说明 |
|------|------|------|
| `comfyui.service` | 127.0.0.1:8188 | `ComfyUI/venv` 的 `main.py --cache-classic --fast` |
| `hair-worker.service` | 0.0.0.0:8187 | 本仓库 `app:app`(Wants comfyui + change_hair-hair) |
| `change_hair-{webui,photo,hair}.service` | 57860 / 32678 / 8801 | conda envs `sdwebui`/`py310`/`my_hair`,依赖顺序 webui→photo→hair |
### Documentation
网关机另行部署 `gateway/`(config:`workers` 列表 + `shared_password` + `ark` key),见 `gateway/config.example.json` 与 `docs/网关待改动.md`。
- `docs/接口文档.md` — complete API documentation (inputs, outputs, examples, error codes)
- `docs/旷视具体需求.md` — original requirements spec (converted from `.docx`)
- The FastAPI app's `description` docstring in `app.py` is an additional source of API docs visible in Swagger UI
## Documentation
- `docs/接口文档.md` — 对外 API 契约(唯一权威)
- `docs/实现说明.md` — 架构总览 + 各接口实现简述 + 部署要点
- `docs/发际线增强算法.md`、`docs/发际线生发遮罩算法_pushed模式.md` — 接口11/12 算法推导
- `docs/gpu-worker改动说明.md`、`docs/网关待改动.md` — worker↔网关 协同变更记录
- `OFFLINE_ASSETS.md` — 离线模型权重/字体清单