Files
hair/docs/实现说明.md
T
2026-10-15 23:04:07 +08:00

16 KiB
Raw Blame History

旷视五接口 — 实现说明(总)

把原先分散的「各接口技术方案 + 开发任务书 + 系统架构 + 网关任务书」合并成这一份简要总览。 对外 API 契约以 接口文档.md 为唯一权威;原始需求见 旷视具体需求.md; 离线模型清单见 ../OFFLINE_ASSETS.md。


1. 架构

两台机器、一个仓库:

客户端 ──HTTPS──> 外网网关(gateway/)  ──HTTP(X-Internal-Token)──> worker(GPU 机, app.py)
                     │ 薄代理 + 落盘改URL                          │ 跑算法(本地模型/ComfyUI/change_hair)
                     └ 接口4(豆包)/99(特殊点) 本机实现(不转发)      └ 接口1/2/3/5/6/7/9/10/11/12 + 工具路由
  • worker(app.py + face_analysis/ + hairline/):跑真正的算法,除 ComfyUI/change_hair 走本机 回环外无外网依赖。对 /api/* 校验 X-Internal-Token(密码在 worker_config.json 的 accept_passwords); /health 模型就绪才返回 200。监听 8187(systemd hair-worker.service,也可 ./run_worker.sh 手动起)。 另有 worker 本机的工具路由:/api/swapHair/v1、/hairColor/v2(CORS 代理转发 change_hair:8801)、 /api/v1/redraw(直接 ComfyUI 重绘)、/api/v1/debug/*(免鉴权调试)。
  • 网关(gateway/):薄反向代理,健康检查/派发/鉴权/把 worker 的 *_base64 落盘改成 *_url。 网关本机实现的接口(不转发 worker):接口4(调外网豆包)与接口99(纯 CPU 特殊点检测, 直接 import 同仓库 face_analysis/special_points,_if99_lock 单路排队)。
  • 图片三选一:所有接口图片入参 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
6 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_{middle,high,low}_base64 / grown_image_base64(可空) hairline_images[].image_{middle,high,low}_url / grown_image_url
9 steps_common.*_base64、`bisenet segformer.{hair,closed_region,final_overlay,mask}_mask_base64` 等
10 steps_common.*_base64、`bisenet segformer.{base_mask,contour,band_overlay,mask}_base64` 等
11 steps.{input,baseline,hair_mask,mask_overlay,mask,swap_raw,alpha,final}_base64 steps.*_url
12 steps.{input,final,redraw_band_overlay,redraw_full,redraw_band,redraw_c}_base64 steps.*_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°)。

接口6 四庭七眼测量 v2 /api/v1/face/measure-v2(worker)—— 接口1 的去顶庭变体

  • 做什么:基于接口 1,去顶庭:不画头顶横线、不返回顶庭数据(four_courts 仅上/中/下庭,landmarks 无 hair_top,face_total_height_cm 为三庭之和)。
  • 与接口1 的标注差异(create_annotated_image(variant="v6")):①竖线范围改为发际线→下巴尖;②不画人头最左/最右端线(仅七眼 6 点 5 段,接口1 为 8 线 7 段);③左侧只标上/中/下庭。箭头/虚线/字体等与接口1 一致。
  • 怎么实现:与接口 1 共用 _face_measure_impl(variant="v6");v6 时标注走变体分支、数据由 app.py 边界删顶庭字段并重算三庭比例。
  • 网关改动:新增路由 POST /api/v1/face/measure-v2,转发到 worker 同路径;base64→URL 改写无需改动。

接口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 8188 的 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=wave 6=bigflower 7=clasicalflower;male 1=ellipse 2=inverse_arc 3=m 4=straight 5=heart 6=Softpetal 7=wave。female 1..5 走「换发型(change_hair)」+Flux-2 重绘管线(generate_grow_results_swap,复用接口12 final 的 _V2_FINAL_DEFAULTS,长边默认压到 REDRAW_MAX_SIDE=1024 再放大回原尺寸);female 6/7 与 male 全部走原生发(ComfyUI add_hair)管线。可选参数 flux_model(切换 Flux unet,.gguf 自动配 UnetLoaderGGUF + 配套 qwen CLIP)、redraw_max_side(0=不缩图)。

接口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。

接口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)

  • 做什么:入参同接口2(gender + 多选 hair_style 必填)。对每个选中发型 → middle/high/low 三档发际线叠图 + 生发图 + 首个选中发型的面部中间点坐标。
  • 怎么实现:复用接口2 的 502 点渲染管线,三档分别用 hairline_texture[/_high|/_low] 同名贴图渲染叠图;生发同接口2(ComfyUI inpaint),黑模板固定取 hairline_texture_black/(middle),每发型 1 张生发图。best_hairline_center_point=眉心 x × 首个选中发型 middle 档发际线 y。

接口9 头发遮罩 /api/v1/head/mask(worker)

  • 做什么:人像照 → 头发遮罩 + 全流程分步可视化。
  • 怎么实现(face_analysis/head_mask.py):MediaPipe 底部分割线(21/68/104/69/108/151/337/299/333/298/251 连线,21/251 水平延伸)→ 线以上为上半区 → BiSeNet 与 SegFormer 双路头发分割、逐列向下填充到分割线得闭合区域(头发+额头)→ 外缘朝 151 内缩 erode_cm(默认1.2cm,虹膜标定) → 最终遮罩。返回 steps_common + bisenet/segformer 两组各四张图。

接口10 头部外缘膨胀带 /api/v1/head/band(worker)

  • 做什么:在接口9 基准遮罩基础上,沿头部外缘生成一条膨胀带遮罩。
  • 怎么实现(face_analysis/head_band.py):取基准遮罩外轮廓、去掉贴底分割线段 → 膨胀成总宽 dilate_cm(默认2cm) 的带子 → 裁到分割线以上。返回 base_mask/contour/band_overlay/mask 分步图。

接口11 发际线生发 /api/v1/hairline/grow(worker)—— 不含重绘

  • 做什么:发际线偏高/头发稀少正脸图 + hairline_id(= change_hair hair_id,如 chang_tuoyuan)→ 压低发际线图 + 全步骤可视化。
  • 怎么实现(face_analysis/hairline_grow.py,算法见 发际线增强算法.md):① 接口9 遮罩(seg_model 选 bisenet/segformer)→ ② 调 change_hair 生成该发际线类型图(swap_mode=ext_mask 把遮罩传给 webui 精确重绘 / as_is 用其内部遮罩;gen_backend 可换 hairgrow 区域生发 inpaint)→ ③ 严格按遮罩贴回 → ④ 接缝融合 blend_method(multiband 默认 / seamless / two_stage / feather / alpha_gradient,可选 Reinhard color_match)。返回 steps.{input,baseline,hair_mask,mask_overlay,mask,swap_raw,alpha,final}。

接口12 发际线带重绘 /api/v1/hairline/grow_v2[_final[_v2]](worker)

  • 做什么:接口11 的下游重绘阶段——先跑接口11 拿 ④final,再取发际线重绘带(外推 band_lo_mult×push~band_hi_mult×push,默认 0.5×~1.5×)作遮罩,用 Flux-2(ComfyUI,0716add-hair-api.json,reference-latent 保色) 重绘,产出两版:redraw_full(整帧美颜+整帧重绘)与 redraw_band(band 内加发、band 外 final 结构 + beauty_alpha 融入全脸美颜)。
  • 三个变体:grow_v2 全参数暴露(接口11 参数 + comfyui_prompt/beauty_alpha/band_lo_mult/band_hi_mult);grow_v2_final / grow_v2_final_v2 精简生产版——仅图片 + hairline_id,其余固化 _V2_FINAL_DEFAULTS(push=0.8cm / two_stage / color_match=关 / beauty_alpha=0.6 等),两者后端计算完全一致(同一次输出含 A/B 两版),差异只是测试页展示哪版。接口2 女性 1..5 的生发图即复用此管线。
  • ⚠️ 依赖 ComfyUI(:8188);重绘失败在 data.redraw.c_error 报告。

接口99 人脸特殊点 /api/v1/face/special-points(网关本机,worker 保留同路径备份)

  • 做什么:人脸照 → 60 个固定编号 MediaPipe 关键点(6 组:eyebrow/eye/mouth/nose_midline/cheek/face_edge)+ 5 个发际线特殊点,原图像素坐标 JSON(字段 <组名>_<点ID>,如 eyebrow_107)。
  • 怎么实现(face_analysis/special_points.py):MediaPipe 单例取关键点;发际线点=接口1 方案B 的 BiSeNet 头发 mask 上沿指定关键点方向(如 151→10)射线步进取首个头发像素,双射线的(right/right_right/left/left_left)取中点。纯 CPU 可跑(BiSeNet 单核 <1s),网关直接 import 本机实现(_if99_lock 单路排队);worker 侧保留同路径接口供测试页直连。部署依赖清单见 网关待改动.md §9。
  • 测试页:static/test_interface99.html(canvas 可视化 + JSON,worker 本机直连)。

3. 部署 / 环境要点

worker(GPU 机,本机 = gpu_worker 角色)

  • Python 3.10.12(本机 venv 在 ./venv);numpy<2(1.26.4),scikit-image==0.24.0 (别升 0.25+,会顶 numpy≥2 顶崩 mediapipe)。
  • 本机 RTX 4090:venv 实装 torch 2.7.1+cu128,CUDA 可用(BiSeNet/SegFormer GPU 推理)。 requirements.txt 里 pinned 的 torch 2.2.2(cu121) 及 5090 注释是旧机(sm_120 无 cu121 kernel、 自动回退 CPU)的记录,本机以 venv 实际版本为准。
  • 模型权重/字体见 ../OFFLINE_ASSETS.md;BiSeNet/SegFormer/face_landmarker.task 本地。
  • 生发接口依赖本机 ComfyUI(8188)(Flux-2,systemd comfyui.service,--cache-classic --fast); worker 只调其 HTTP API,不跑 Flux。ComfyUI 开了 HTTP Basic Auth(user admin + 密码); 密码放 password.txt(不入 git) / worker_config.json.comfyui_password / 环境变量 COMFYUI_PASSWORD, URL 用 COMFYUI_URL。默认模型 flux-2-klein-9b Q4_K_M GGUF(接口2 flux_model 参数可切)。
  • 换发型依赖本机 change_hair(/home/ubuntu/change_hair,三进程 webui:57860(sdwebui) → photo_service:32678(py310/kohya) → hair_service_sd:8801(my_hair),start_all.sh start|stop|status, 也有 systemd change_hair-{webui,photo,hair}.service)。不跑时接口2女1..5、接口11/12、 swapHair/hairColor 代理不可用。
  • worker_config.json(不入 git):accept_passwords(鉴权) + comfyui_password;鉴权头 X-Internal-Token。
  • systemd 单元:hair-worker.service(8187) / comfyui.service(8188),仓库根有副本;手动启停用 ./run_worker.sh / ./stop_worker.sh。

网关机

  • 很轻: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)。 可选 primary_workers(workers 的子集)启用主备分组:主 worker 全部下线时才派发备用 worker; 主组并行度由 dispatch.max_global_concurrency 控制(N 台主 worker 就设 N,依赖单进程 uvicorn)。 修改配置需重启 hair-gateway.service 生效(配置启动时加载一次)。
  • 外部监控探活是 GET /monitor(未演练时有在线 worker 才 200,否则 503)。POST /monitor/fault 只改这条探活的返回,用来做报警演练,不摘 worker。/health 只表示网关进程存活,不能用来报警。详见 监控探活接口.md。
  • 托管 /static/annotations/(落盘的图),定期清理。

维护:本文为简要总览;字段以 接口文档.md 为准。各接口更细的算法推导可查 git 历史中已合并的旧技术方案文档。