Files
hair/docs/网关待改动.md
T
xsl 0b02ad1693 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部署步骤)
2026-09-06 23:32:24 +08:00

10 KiB
Raw Blame History

网关侧改动清单(worker 近期变更引发)

给网关开发:以下是 worker/契约近期变化里与网关有关的点。标 ✅ 的我已在本仓库 gateway/ 改好(你 review/拉取即可);标 🔲 的是建议你确认或改。功能必需只有第 1 条。


1. ✅【功能必需】base64→URL 落盘扩展名按内容嗅探(支持 JPG)

背景:接口 2/3/5 的返回图改成了 JPG(体积约小 9×),接口 1 标注图仍是 PNG(含透明)。 网关把 *_base64 落盘时若硬编码 .png,JPG 会被存成 .png(内容是 JPG、扩展名错)。

改动(gateway/forward.py 的 rewrite_base64_to_url,已改):

# 原:filename = f"{uuid.uuid4().hex}.png"
ext = "png" if img_bytes[:8] == b"\x89PNG\r\n\x1a\n" else "jpg"   # 按内容嗅探
filename = f"{uuid.uuid4().hex}.{ext}"

这样接口1 存 .png、接口2/3/5 存 .jpg,对外 URL 后缀也就正确。若你的网关是独立部署/独立代码,按上面这两行改一下即可。


2. 🔲【建议】生发接口超时调大

接口 2(一次 N 张 Flux,~18s)/ 3(~6s) 经 ComfyUI 同步出图较慢。 gateway/config.json 的 dispatch.request_timeout_seconds 建议 ≥ 120,否则网关会先超时换 worker 重试。


3. 🔲【确认】base64→URL 通用改写仍覆盖这些场景

  • 数组里的图片字段:接口2 results[].image_base64 / results[].grown_image_base64、 接口5 hairline_images[].image_base64 在数组元素内——改写要递归进数组(你现有的递归实现已覆盖)。
  • 可空字段:接口2/3 的生发图(ComfyUI 没起/失败时)*_base64 为 null → 保留 null、不落盘。

4. 🔲【可选·仅影响 /docs】OpenAPI 表单声明

纯文档展示,不影响转发功能(网关是盲转发)。若想让网关 /docs 准确:

  • 接口2 /hair/grow、接口5 /hairline/generate 入参新增必填 gender(male/female)。
  • 接口3 /hair/grow-b 入参只剩 marked_image_*(已去掉 original_image_*)。
  • (gateway/app.py 里的 _*_FORMS 字典当前未被路由引用,所以不改也不影响实际行为。)

5. 🔲【新增】接口7 C端生发 v2(/api/v1/hair/grow-v2)

背景:worker 侧已新增接口 7,功能与接口 2 完全一致,区别仅在于 ComfyUI 工作流使用 add_hair2.json(而非 add_hair.json)。

网关需新增一个路由,代理转发到 worker 的同路径:

# gateway/app.py

@app.post("/api/v1/hair/grow-v2", tags=["生发"])
async def hair_grow_v2(request: Request):
    """接口7:C端生发 v2(add_hair2 工作流)"""
    return await _proxy(request, "/api/v1/hair/grow-v2")

无需额外改动:

  • 请求:multipart/form-data,参数与接口 2 完全相同(image_file/url/base64 三选一 + gender + hair_style + beauty_enabled + use_mask + prompt),网关盲转发即可
  • 响应:结构与接口 2 完全一致,results[].image_base64 / results[].grown_image_base64 经现有 rewrite_base64_to_url 自动改写为 URL
  • base64→URL:数组内图片字段递归改写已覆盖,无需修改

入参(与接口 2 一致)

参数 类型 必填 说明
image_file / image_url / image_base64 — 三选一 用户正面照
gender string 是 male / female
hair_style string 是 发型序号,逗号分隔多选(如 1,2,3)。female / male 均为 1–7
beauty_enabled bool 否 美颜开关(本期不生效)
use_mask bool 否 默认 true,false 跳过遮罩
prompt string 否 ComfyUI 提示词

出参(与接口 2 一致)

{
  "code": 0,
  "message": "success",
  "request_id": "gw-xxxxxxxx",
  "data": {
    "results": [
      {
        "image_url": "https://hair.xiangsilian.com/static/annotations/xxx.jpg",
        "grown_image_url": "https://hair.xiangsilian.com/static/annotations/xxx.jpg",
        "hairline_type": "ellipse",
        "order": 1
      }
    ]
  }
}

worker 侧信息

项目 值
worker 路径 /api/v1/hair/grow-v2
工作流文件 add_hair2.json
输入/遮罩节点 26(LoadImage)
输出节点 75(SaveImage,自动检测)
提示词节点 60(JjkText)

🔲【可选·仅影响 /docs】OpenAPI 表单声明

若想让网关 /docs 展示准确,在 gateway/app.py 新增 _GROW_V2_FORMS(或复用 _GROW_FORMS 并补充 gender/hair_style 字段),然后将路由函数签名改为显式声明 Form 参数(参考接口 4 的写法)。不改也不影响实际转发。


6. ✅【已完成】接口6 四庭七眼测量 v2(/api/v1/face/measure-v2)

背景:worker 侧接口 6 是接口 1 的去顶庭变体(不再完全一致):不画头顶横线/不返回顶庭数据、竖线范围发际线→下巴尖、不画人头最左/最右端线。worker 侧与接口 1 共用 _face_measure_impl(variant="v6")。

网关已新增路由(gateway/app.py 已实现,网关机器 pull 后生效):

# gateway/app.py

@app.post("/api/v1/face/measure-v2", tags=["人脸分析"])
async def face_measure_v2(request: Request):
    """接口6:四庭七眼测量 v2(去顶庭 + 去头部端线)"""
    return await _proxy(request, "/api/v1/face/measure-v2")

无需额外改动:

  • 入参:与接口 1 完全相同(image_file/url/base64 三选一)
  • 出参:annotated_image_base64 → 经现有 rewrite_base64_to_url 自动改写为 annotated_image_url
  • worker 侧 v6 差异(去顶庭字段、变体标注)由 _face_measure_impl 内部处理,网关透明转发

7. ✅【已完成】接口9/10 头部遮罩(/api/v1/head/mask、/api/v1/head/band)

gateway/app.py 已新增两条盲转发路由(同接口6 的写法):

@app.post("/api/v1/head/mask", tags=["人脸分析"])
@app.post("/api/v1/head/band", tags=["人脸分析"])
  • 入参:图片三选一 + erode_cm(接口10 另有 dilate_cm)。
  • 出参:steps_common.*_base64 与 bisenet/segformer 组内的 *_base64 字段——现有递归 rewrite_base64_to_url 已覆盖(嵌套 dict/数组),无需额外改动。
  • 两个接口响应较慢(双分割模型跑两遍),注意 request_timeout_seconds。

8. 🔲【新增】接口11/12 发际线生发/带重绘(/api/v1/hairline/grow*)

worker 侧已上线 4 条路由,网关尚无对应转发路由(当前仅测试页直连 worker)。如需对外提供,参考接口9 加法:

@app.post("/api/v1/hairline/grow", tags=["生发"])            # 接口11
@app.post("/api/v1/hairline/grow_v2", tags=["生发"])         # 接口12 全参数
@app.post("/api/v1/hairline/grow_v2_final", tags=["生发"])   # 接口12 final 精简版
@app.post("/api/v1/hairline/grow_v2_final_v2", tags=["生发"])
  • 入参:图片三选一 + hairline_id(接口12 final 两变体仅需这两个)。接口11/12 全参数版还有 20+ 个调优 Form 参数,网关盲转发即可,无需逐个声明。
  • 出参:data.steps.*_base64(input/final/redraw_full/redraw_band 等各步图)→ 现有递归改写覆盖。
  • ⚠️ 依赖 worker 机的 change_hair(:8801) 与 ComfyUI(:8188) 都在跑;单请求 10s+,超时建议 ≥120s。
  • 契约细节见 接口文档.md 接口11/12 章节。

9. ✅【新增】接口99 人脸特殊点(/api/v1/face/special-points)

背景:worker 侧新增接口99——固定编号 MediaPipe 关键点(6 组 60 点)+ 5 个发际线特殊点, 返回原图像素坐标 JSON(无图片字段)。

网关代码已在本仓库 gateway/app.py 改好(同接口1 的盲转发写法,pull 后重启即生效):

@app.post("/api/v1/face/special-points", tags=["人脸分析"])
async def face_special_points(request: Request):
    """接口99:人脸特殊点检测(固定关键点 + 发际线特殊点像素坐标)"""
    return await _proxy(request, "/api/v1/face/special-points")

网关机部署/调试步骤:

cd /path/to/hair && git pull          # 拉取本分支
sudo systemctl restart hair-gateway   # 或项目实际的网关服务名(uvicorn gateway.app:app)
# 验证路由已注册(应能在输出里看到 /api/v1/face/special-points)
curl -s http://127.0.0.1:8080/openapi.json | python3 -m json.tool | grep special-points
# 冒烟:经网关调一次(multipart 传图,无需 X-Internal-Token,网关会注入)
curl -s -X POST http://127.0.0.1:8080/api/v1/face/special-points \
  -F "image_file=@/tmp/face.jpg" | python3 -m json.tool | head -30

无需额外改动:

  • 请求:multipart/form-data,仅图片三选一参数,盲转发即可。
  • 响应:data.points 全是坐标({"x":..,"y":..}),没有任何 *_base64 字段, 现有 rewrite_base64_to_url 递归改写天然跳过,原样返回。
  • 性能:纯本地推理(MediaPipe + BiSeNet,秒级),不依赖 ComfyUI/change_hair, request_timeout_seconds 无需调大。

注意:

  • worker 侧测试页 static/test_interface99.html 是 worker 本机直连的(带 token 头), 经网关调试请用 curl 或网关 /docs(网关只盲转发 /api/*,不代理 worker 的 /static)。
  • 契约详见 接口文档.md 接口99 章节;worker 错误码 1001(无人脸)/1003(非正面) /1007/1008 原样透传。

已经做好、无需再动的

  • 接口4 在网关本机实现(调豆包,不转发 worker)——已完成;config 里配 ark。
  • 接口4 业务错误 HTTP 状态已统一为 200(与其余接口一致)。
  • 接口3 去 original / best_hairline——网关盲转发,无需改(映射表里也没有 best_hairline)。

对外字段映射总表见 实现说明.md §1;契约以 接口文档.md 为准。