16 KiB
16 KiB
旷视五接口 — 实现说明(总)
把原先分散的「各接口技术方案 + 开发任务书 + 系统架构 + 网关任务书」合并成这一份简要总览。 对外 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(systemdhair-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/,逻辑参考 workerface_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_hairhair_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,可选 Reinhardcolor_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(useradmin+ 密码); 密码放password.txt(不入 git) /worker_config.json.comfyui_password/ 环境变量COMFYUI_PASSWORD, URL 用COMFYUI_URL。默认模型 flux-2-klein-9b Q4_K_M GGUF(接口2flux_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, 也有 systemdchange_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 历史中已合并的旧技术方案文档。