Files
hair/docs/接口文档.md
T

1033 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 旷视五个接口 — 接口文档(输入 / 输出定义)
> 本文档依据《旷视具体需求.docx》整理,描述各接口的**输入**与**输出**。
> 文中标记为 **【待确认】** 的内容为需求文档中尚未明确、需与对方对齐的点。
> **当前为第一版 Mock 服务**:所有接口已上线,但**不做真实算法计算**,统一返回下文「响应示例」中的默认值;所有图片字段均返回示例图 `https://hair.xiangsilian.com/static/sample.jpg`。真实逻辑后续接入。
---
## 服务地址与接口路径
- **Base URL**:`https://hair.xiangsilian.com`
- **在线接口文档(Swagger)**:`https://hair.xiangsilian.com/docs`
| 接口 | 方法 | 路径 |
|------|------|------|
| 1 四庭七眼测量 | POST | `/api/v1/face/measure` |
| 6 四庭七眼测量 v2 | POST | `/api/v1/face/measure-v2` |
| 2 C 端生发 | POST | `/api/v1/hair/grow` |
| 22 生发密度与发际线高度 | POST | `/api/v1/hair/grow-22` |
| 3 B 端生发 | POST | `/api/v1/hair/grow-b` |
| 72 B 端五个关键点 | POST | `/api/v1/face/b-end-points` |
| 4 用户特征 | POST | `/api/v1/face/features` |
| 42号接口-加两个特征点 | POST | `/api/v1/face/features-42` |
| 5 发际线 PNG 生成 | POST | `/api/v1/hairline/generate` |
| 52 发际线新规则与裁切 | POST | `/api/v1/hairline/generate-52` |
---
## 通用约定
GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.json`白名单(当前为网关IP `49.232.51.171`),其他来源返回HTTP403、code=1010;静态页面与图片除外。按直接连接worker的IP判断,网关不启用此限制,已有鉴权继续有效。
以下约定适用于全部 5 个接口(最终以联调为准)。
### 请求
| 项目 | 约定 |
|------|------|
| 协议 | HTTPS |
| 请求方式 | `POST` |
| 图片传参方式 | 三选一,三种都支持:① `multipart/form-data` 上传文件、② 图片 URL、③ base64 字符串 |
| 字符编码 | UTF-8 |
| 坐标基准 | 默认以**原图像素**为基准;接口52开启预处理时使用响应中的处理后底图坐标(原点为左上角,x向右、y向下) |
图片传参字段(三者**严格互斥**,必须且只能传其一;传了 0 个或 ≥2 个均报错):
| 参数 | 类型 | 说明 |
|------|------|------|
| image_file | file | 上传文件(`multipart/form-data`) |
| image_url | string | 图片 URL |
| image_base64 | string | 图片 base64 字符串,**需带前缀**,如 `data:image/png;base64,xxxx` |
### 图片输入要求(接口 1/2/3/4/5 通用)
| 项目 | 要求 |
|------|------|
| 图片类型 | 用户正面照(人像) |
| 人脸数量 | 默认仅支持**单人**;若检测到 2 或 3 张人脸(即多人),报错并提示客户端 |
| 单文件大小 | ≤ 1 MB |
| 最低分辨率 | 720 × 1024 |
| 最大分辨率 | 2160 × 3840 |
| 格式 | 目前仅支持 JPG / PNG |
### 统一响应结构
```json
{
"code": 0,
"message": "success",
"request_id": "唯一请求标识",
"data": { }
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 0 表示成功,非 0 表示失败 |
| message | string | 结果描述 / 错误原因 |
| request_id | string | 请求追踪 ID |
| data | object | 业务数据,结构见各接口 |
### 通用错误码(识别失败时返回,见需求文档)
| code | message | 说明 |
|------|---------|------|
| 1001 | 无法识别人像 | 图片中未检测到人脸 |
| 1002 | 人像分辨率过低 | 低于最低分辨率要求 |
| 1003 | 角度问题,非正面照 | 非正面 / 角度过大 |
| 1004 | gender 必填/非法 | 接口2/5/52/7 的 `gender` 缺失或非 `male`/`female` |
| 1005 | 检测到多张人脸 | 默认仅支持单人,检测到 2 人或以上时返回 |
| 1006 | 文件超出大小限制 | 单文件超过 1 MB |
| 1007 | 图片参数错误 | file / url / base64 未传,或同时传了多个(三者严格互斥) |
| 1008 | 图片格式不支持 | 非 JPG / PNG |
| 1010 | 来源IP不允许 | 仅GPU worker:直连IP不在本机/白名单内,或来源/白名单配置无效;HTTP403 |
---
## 接口 1:四庭七眼测量标注接口
**说明**:输入用户照片,输出一张标注好四庭七眼数据的 PNG 图片(**不包含人物本体,仅标注图层**),同时返回四庭、七眼的比例与厘米数值。
**请求**:`POST /api/v1/face/measure`
### 输入
图片参数见「通用约定 → 图片传参字段」(`image_file` / `image_url` / `image_base64` 三选一)。本接口无其他专属参数。
### 输出(data)
| 字段 | 类型 | 说明 |
|------|------|------|
| annotated_image_url | string | 标注好数据的 PNG 图片(仅标注图层,不含人物) |
| face_total_height_cm | number | 全脸总高度(cm) |
| four_courts | object | 四庭数据,见下表 |
| seven_eyes | object | 七眼数据,见下表 |
| landmarks | object | 关键分界点坐标(头顶 / 发际线 / 眉心 / 鼻翼下缘 / 下巴尖),原图像素坐标 |
| vertical_lines | object | 标注图竖线坐标,见下表。与标注 PNG 上的竖线同一套几何 |
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),原图像素:`{ "x": int, "y": int }` |
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:`{ "x": int, "y": int }` |
`four_courts`(四庭,自上而下):
| 字段 | 类型 | 说明 |
|------|------|------|
| top_court_cm | number | 顶庭长度(cm),示例:约占全脸高度 22% |
| upper_court_cm | number | 上庭长度(cm),约占 25% |
| middle_court_cm | number | 中庭长度(cm),约占 28% |
| lower_court_cm | number | 下庭长度(cm),约占 25% |
| ratios | object | 各庭占全脸高度的百分比 |
`seven_eyes`(七眼,横向):
| 字段 | 类型 | 说明 |
|------|------|------|
| eye_width_cm | number | 单眼宽度(cm) |
| face_width_cm | number | 脸宽(cm) |
| inter_eye_distance_cm | number | 两眼间距(cm) |
| ratios | object | 七眼各段占脸宽的比例 |
| eye1 | number \| null | 从左到右第 1 段宽度(cm):人头最左 → 左脸颊(左耳外侧段)。该侧耳朵不可见时为 null |
| eye2 | number | 从左到右第 2 段宽度(cm):左脸颊 → 左眼外角 |
| eye3 | number | 从左到右第 3 段宽度(cm):左眼外角 → 左眼内角(左眼宽度) |
| eye4 | number | 从左到右第 4 段宽度(cm):左眼内角 → 右眼内角(两眼间距) |
| eye5 | number | 从左到右第 5 段宽度(cm):右眼内角 → 右眼外角(右眼宽度) |
| eye6 | number | 从左到右第 6 段宽度(cm):右眼外角 → 右脸颊 |
| eye7 | number \| null | 从左到右第 7 段宽度(cm):右脸颊 → 人头最右(右耳外侧段)。该侧耳朵不可见时为 null |
> `eye1`~`eye7` 为从左到右共 7 段宽度,与标注图竖线一一对应。最左/最右端线取自耳朵分割外缘;某侧耳朵被头发或侧脸遮挡(不可见)时该侧端线省略,对应 `eye1` 或 `eye7` 为 `null`(键始终保留),实际有效段为 5 或 6 段。`eye3`/`eye5` 为左右眼宽、`eye4` 为两眼间距,与 `eye_width_cm` / `inter_eye_distance_cm` 语义一致。`eye1`/`eye7` 的厘米数由 `vertical_lines` 里对应端点的 x 相减得到。
`vertical_lines`(竖线,原图像素,原点左上,x 向右,y 向下):
| 字段 | 类型 | 说明 |
|------|------|------|
| y_top | int | 竖线共用上端。接口 1 在头顶之上,伸出 `max(6, round(短边 × 0.03))` 像素,可能略小于 0 |
| y_bottom | int | 竖线共用下端。接口 1 在下巴尖之下,伸出同样像素,可能略大于图高 |
| lines | array | 从左到右实际画出的竖线。每项 `{ "name": string, "x": int }` |
`lines[].name` 从左到右:
| name | 竖线 |
|------|------|
| left_head_edge | 人头最左(耳朵外缘)。该侧耳朵不可见时不出现,此时 `eye1` 为 `null` |
| left_cheek | 左脸颊 |
| left_eye_outer | 左眼外角 |
| left_eye_inner | 左眼内角 |
| right_eye_inner | 右眼内角 |
| right_eye_outer | 右眼外角 |
| right_cheek | 右脸颊 |
| right_head_edge | 人头最右(耳朵外缘)。该侧耳朵不可见时不出现,此时 `eye7` 为 `null` |
### 标注图片(UI)规范
| 项目 | 要求 |
|------|------|
| 颜色 | 字体及所有线/箭头 `#FFFFFF` 100%,透明底 |
| 尺寸 | 字号/线宽/虚线/箭头按图片**短边自适应缩放**(非固定 pt) |
| 横线 | 5 条分界线(头顶/发际线/眉心/鼻翼下缘/下巴尖),两端渐变消失并略超出最外侧竖线;线名在线**右上方** |
| 竖线 | 人头最左 + 七眼 6 点 + 人头最右(最外两条取自头发分割轮廓),两端渐变消失并略超出头顶/下巴 |
| 数值排布 | 四庭数值(名 + 数值两行,**不带 cm**)统一在图片**左侧**呈现;七眼段宽**上下穿插**展示;底部统一标「单位cm」 |
| 线样式 | 段宽/庭高用**虚线 + 实心三角双箭头**标示(箭头尖端落在虚线两端) |
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"annotated_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"face_total_height_cm": 13.76,
"four_courts": {
"top_court_cm": 3.44,
"upper_court_cm": 3.44,
"middle_court_cm": 3.44,
"lower_court_cm": 3.44,
"ratios": { "top_court": 0.25, "upper_court": 0.25, "middle_court": 0.25, "lower_court": 0.25 }
},
"seven_eyes": {
"eye_width_cm": 3.44,
"face_width_cm": 24.08,
"inter_eye_distance_cm": 3.44,
"ratios": { "eye_width": 0.143, "inter_eye_distance": 0.143 },
"eye1": 3.44, "eye2": 3.44, "eye3": 3.44, "eye4": 3.44,
"eye5": 3.44, "eye6": 3.44, "eye7": 3.44
},
"landmarks": {
"hair_top": { "x": 540, "y": 120 },
"hairline": { "x": 540, "y": 430 },
"brow_center": { "x": 540, "y": 740 },
"nose_bottom": { "x": 540, "y": 1050 },
"chin_tip": { "x": 540, "y": 1360 }
},
"vertical_lines": {
"y_top": 98, "y_bottom": 1410,
"lines": [
{ "name": "left_head_edge", "x": 120 },
{ "name": "left_cheek", "x": 210 },
{ "name": "left_eye_outer", "x": 310 },
{ "name": "left_eye_inner", "x": 420 },
{ "name": "right_eye_inner", "x": 560 },
{ "name": "right_eye_outer", "x": 670 },
{ "name": "right_cheek", "x": 780 },
{ "name": "right_head_edge", "x": 890 }
]
}
}
}
```
---
## 接口 6:四庭七眼测量 v2 接口
**说明**:基于[接口 1](#接口-1四庭七眼测量标注接口)的变体。与接口 1 的差异:
- **去顶庭**:不画头顶横线、不返回顶庭数据。`four_courts` 仅含上/中/下庭,`landmarks` 无 `hair_top`,`face_total_height_cm` 为三庭之和(不含顶庭)。
- **竖线范围**:纵向竖线从**发际线**画到**下巴尖**(接口 1 为头顶→下巴尖)。
- **不画人头最左/最右端线**:仅画七眼 6 点(左脸颊/左眼外角/左眼内角/右眼内角/右眼外角/右脸颊)共 5 段标尺,不取头发轮廓的头部端线(接口 1 会多出最左/最右 2 条头部端线、共 7 段)。
- 其余(实心三角箭头、虚线样式、字体、单位cm、七眼数据)与接口 1 一致。
**请求**:`POST /api/v1/face/measure-v2`
### 输入
与接口 1 完全相同。图片参数见「通用约定 → 图片传参字段」(`image_file` / `image_url` / `image_base64` 三选一)。本接口无其他专属参数。
### 输出(data)
| 字段 | 类型 | 说明 |
|------|------|------|
| annotated_image_url | string | 标注图层 PNG URL(透明底,仅标注线/文字,不含人物) |
| face_total_height_cm | number | 面部总高度(cm)= 上庭 + 中庭 + 下庭(**不含顶庭**) |
| four_courts | object | 三庭数据(上/中/下庭,各含 cm 与 ratio;**无顶庭**) |
| seven_eyes | object | 七眼数据(眼宽/脸宽/两眼间距 cm + 占比 ratios + **eye2~eye6** 共 5 段宽度) |
| landmarks | object | 四个关键点像素坐标(发际线/眉心/鼻翼下缘/下巴尖) |
| vertical_lines | object | 标注图竖线坐标。结构同接口 1,但 `y_top`/`y_bottom` 为发际线到下巴尖(不伸出),`lines` 只有中间 6 条,无 `left_head_edge` / `right_head_edge` |
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),原图像素:`{ "x": int, "y": int }` |
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:`{ "x": int, "y": int }` |
> 接口6 是**三庭五眼**:`four_courts`/`landmarks` 不含顶庭与头顶点(无 `top_court_cm`/`hair_top`);`seven_eyes` 只含 **eye2~eye6**(左脸颊/左眼/两眼间距/右眼/右脸颊,5 段),**无 eye1/eye7**(耳外段需头发轮廓端线,仅接口1 有)。
### 响应示例
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"annotated_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
"face_total_height_cm": 10.32,
"four_courts": {
"upper_court_cm": 3.44, "middle_court_cm": 3.44, "lower_court_cm": 3.44,
"ratios": { "upper_court": 0.333, "middle_court": 0.333, "lower_court": 0.333 }
},
"seven_eyes": {
"eye_width_cm": 3.44, "face_width_cm": 24.08, "inter_eye_distance_cm": 3.44,
"ratios": { "eye_width": 0.143, "inter_eye_distance": 0.143 },
"eye2": 3.0, "eye3": 3.44, "eye4": 3.44, "eye5": 3.44, "eye6": 3.0
},
"landmarks": {
"hairline": { "x": 540, "y": 430 },
"brow_center": { "x": 540, "y": 740 },
"nose_bottom": { "x": 540, "y": 1050 },
"chin_tip": { "x": 540, "y": 1360 }
},
"vertical_lines": {
"y_top": 430, "y_bottom": 1360,
"lines": [
{ "name": "left_cheek", "x": 210 },
{ "name": "left_eye_outer", "x": 310 },
{ "name": "left_eye_inner", "x": 420 },
{ "name": "right_eye_inner", "x": 560 },
{ "name": "right_eye_outer", "x": 670 },
{ "name": "right_cheek", "x": 780 }
]
}
}
}
```
---
## 接口 2:C 端生发接口
**说明**:输入用户正面照 + 性别 + 发型序号(可多选),按指定发际线类型渲染发际线曲线透明 PNG + 生发图。
> **每个方案返回两张图**:`image_url`=「发际线曲线**透明 PNG**(仅白色曲线,透明底,需叠加原图显示)」;`grown_image_url`=
> 经 ComfyUI/Flux 的「植发 3 个月**生发后图片**」(完整人像照片)。两者均已实现,实现简述见 [`实现说明.md`](实现说明.md)。
**请求**:`POST /api/v1/hair/grow`
### 输入
图片参数见「通用约定 → 图片传参字段」。专属参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| gender | string | **是** | 性别:`male` / `female`。决定使用的发际线贴图集合 |
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数。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。越界/非法返回 `1007` |
| beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false(当前阶段不生效) |
| use_mask | bool | 否 | 是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比。仅对原生发(male / female 6/7)管线生效 |
| prompt | string | 否 | ComfyUI 提示词,默认「填充遮罩区域的头发,人物增加一点磨皮」,会替换工作流节点 60 的文本。所有分支生效(male / female 6/7 原生生发,female 1..5 换发型后的发际线带重绘;不进 change_hair)。注:非默认提示词会使 ComfyUI 的 CLIP 条件缓存失效,换词后首次请求明显变慢(实测 45s vs 命中缓存 8s),相同提示词的后续请求恢复正常 |
| flux_model | string | 否 | Flux 模型文件名(如 `flux-2-klein-9b-Q5_K_M.gguf`)。缺省用工作流默认(9B-Q4_K_M);`.gguf` 自动切 UnetLoaderGGUF 并同步配套 qwen CLIP |
| redraw_max_side | int | 否 | female 1..5 重绘管线送模型前的长边上限像素。缺省=服务端默认(`REDRAW_MAX_SIDE`,当前 1024);`0`=不缩图(原图直送,更慢) |
### 输出(data)
`results`:发际线方案数组,**数量 = 所选发型数**。每个元素:
| 字段 | 类型 | 说明 |
|------|------|------|
| image_url | string | 发际线曲线**透明 PNG** URL(仅白色发际线曲线,透明底,**不含人物**,需前端叠加原图显示) |
| grown_image_url | string | **生发后图片** URL(ComfyUI/Flux「植发 3 个月」效果图,完整人像照片) |
| hairline_type | string | 发际线类型 key:`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`(female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`/`wave`(male) |
| order | int | 排序序号(当前阶段固定 `1..N`,按贴图顺序,暂不计算合适度) |
> ⚠️ 生发图由本机 ComfyUI(Flux-2,端口 8188)生成,**一次请求生成指定发型的 1 张、同步返回**;
> female 1..5 的生发图另依赖 change_hair 换发型服务(:8801)。
> worker 侧返回 `image_base64` / `grown_image_base64`,
> 网关落盘后改写为上表的 `image_url` / `grown_image_url`。
>
> 💡 `image_url` 为透明底 PNG,前端需用绝对定位叠加到原图上显示(参考[测试页](https://hair.xiangsilian.com/static/test_interface2.html)的 `.img-stack` 叠加结构)。
### 响应示例
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"results": [
{ "image_url": "https://hair.xiangsilian.com/static/annotations/uuid1.png", "grown_image_url": "https://hair.xiangsilian.com/static/annotations/grown1.png", "hairline_type": "ellipse", "order": 1 },
{ "image_url": "https://hair.xiangsilian.com/static/annotations/uuid2.png", "grown_image_url": "https://hair.xiangsilian.com/static/annotations/grown2.png", "hairline_type": "flower", "order": 2 }
]
}
}
```
> 识别失败时返回通用错误码(1001 / 1002 / 1003 等)。`gender` 缺失或非法值返回 **1004**;本接口已改为必填入参,不再自动判别性别。
---
## 接口 22 生发密度与发际线高度
`POST /api/v1/hair/grow-22` 使用接口 2 的图片输入、`gender`、`hair_style` 和返回结构。
| 表单字段 | 默认值 | 取值和效果 |
| --- | --- | --- |
| `hair_density` | 必填 | `sparse` 稀疏、`normal` 正常、`dense` 浓密。至少提交一档,多选时重复提交同名字段。提示词为「填充遮罩区域的头发,发量{描述},人物增加一点磨皮」,三档描述依次为「稀疏」「正常」「适中偏多」。 |
| `hairline_height` | `0` | `[-1, 1]`,`-1` 最高,`0` 正常,`1` 最低。端点相对正常位置移动眉心到下巴垂直像素距离的 10%。非零时须启用 `use_mask`。 |
每个发型用一张正常高度贴图渲染预览,按请求高度移动预览线。女性 1–5 号在 WebUI 出图后移动贴回区域和重绘带。其他发型用移动后的预览曲线生成生发遮罩。
例如选择两档时,表单传 `hair_density=sparse` 和 `hair_density=dense`。每个「密度 × 发型」组合返回一项,结果的 `hair_density` 字段标明对应档位。
生发图与透明发际线 PNG 对齐到输入照片尺寸。测试页运行在本机 Worker 的 `/static/test_interface22.html`,扶正裁切预览依赖接口 52。
## 接口 3:B 端生发接口
**说明**:医生/操作端在用户照片上用马克笔标注目标发际线后,**只需上传这一张划线图**。系统检测划线 →
据此生成生发后图片。
**请求**:`POST /api/v1/hair/grow-b`
### 输入
划线图片支持「文件 / URL / base64」三选一(字段:`marked_image_file` / `marked_image_url` / `marked_image_base64`)。
**不需要原始照片**(划线图本身就是用户照片 + 手绘线)。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| marked_image_* | file / string | 是 | 已用马克笔标注发际线的图片,三选一 |
| use_mask | bool | 否 | 是否画发际线,默认 `true`。`false` 时跳过划线检测、直接送划线图,模型仅凭手绘黑线生发,供测试对比 |
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
### 输出(data)
| 字段 | 类型 | 说明 |
|------|------|------|
| hair_growth_image_url | string | **生发后图片**(检测划线 → ComfyUI/Flux「植发 3 个月」效果)。worker 返回 `hair_growth_image_base64` |
| hairline_type | string | 发际线形,手绘为定制,固定 `"custom"` |
> 未检测到划线(或无人脸)返回 **1001**。
### 响应示例
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"hair_growth_image_url": "https://hair.xiangsilian.com/static/annotations/grown.png",
"hairline_type": "custom"
}
}
```
---
## 接口 4:用户特征接口
**说明**:输入用户照片,由**火山方舟 豆包视觉模型**(`doubao-seed-1-6-vision`)分析,输出一大批面部特征。
**请求**:`POST /api/v1/face/features`
### 输入
图片参数见「通用约定 → 图片传参字段」。本接口无其他专属参数。
### 输出(data)
`data.features` 是一个 **JSON 字符串**(不是对象,客户端 `JSON.parse()` 后用),**仅含以下 6 个英文字段**:
| 字段 | 说明 |
|------|------|
| face_shape | 脸形(脸型)|
| eyebrow_shape | 眉形 |
| facial_age | 面部年龄(区间字符串,如"18-25岁")|
| dynamic_static_type | 动静类型(静态型/动态型)|
| gender | 性别(男/女)|
| gene_style | 基因风格(如"自然型")|
> 无人脸返回 `1001`(据 doubao「图片是否有人脸」判定)。⚠️ 本接口是唯一调**外网云模型**的接口,
> worker 需可访问 `ark.cn-beijing.volces.com`;API Key 走 worker 配置/环境变量。
### 响应示例
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"features": "{\"face_shape\":\"鹅蛋脸\",\"eyebrow_shape\":\"平眉\",\"facial_age\":\"18-25岁\",\"dynamic_static_type\":\"静态型\",\"gender\":\"女\",\"gene_style\":\"少年型\"}"
}
}
```
> `features` 为字符串形式的 JSON,固定上述 6 个字段。
---
## 接口 72:B 端五个关键点
`POST /api/v1/face/b-end-points` 接受三选一图片参数 `image_file`、`image_url`、`image_base64`。复用接口 52 的扶正裁切与虹膜/眼宽厘米估算,返回坐标以处理后底图为准。
| 点号 | 位置 | 标注 |
|---|---|---|
| 1 | MediaPipe 4 | 白点 |
| 2 | HL center | 白点 |
| 3 | HL center 向画面右侧水平偏移 2 cm | 白点 |
| 4 | HL left | 白点 |
| 5 | HL left left | 白点 |
| 6 | MediaPipe 446 | 白点,兼作射线锚点 |
六个白点的直径相同。标注层包含三条从起点向目标方向延伸并逐渐变淡的白色虚线(2→1、4→6、5→6),以及 2→3 白色虚线段。2→1 射线比默认可见长度短 15%,5→6 射线的绘制长度比默认长度多 30%,可见范围以图像边界为限。四个距离文字分别放在 2–1 中点左侧、2–3 中点上方、4–6 中点左侧和 5–6 中点右侧,距离保留一位小数,2–3 固定显示 `2.0 CM`。
| `data` 字段 | 内容 |
|---|---|
| `input_image_url` | 扶正裁切后的底图 |
| `annotated_image_url` | 与底图同尺寸的透明 PNG 标注层 |
| `points` | 按字符串点号 `"1"` 到 `"6"` 索引的像素坐标 `{x, y}` |
| `measurements_cm` | 四个距离,以 `2-1`、`2-3`、`4-6`、`5-6` 索引 |
| `image_size` | 处理后尺寸 `{width, height}` |
| `coordinate_space` | `processed_image` |
| `px_per_cm` | 估算的每厘米像素数 |
Worker 直连时图片字段使用 `_base64` 后缀。检测不到人脸返回 `1001`,非正面或标注所需点位无法取得时返回 `1003`,图片无法解码返回 `1008`。
对外 42、52、72 的客户端登录、令牌验证和刷新均由 `main` 分支的网关处理。网关将 52 和 72 转发至本分支的 Worker,Worker 通过直连来源 IP 白名单授权。对外测试请使用网关同源的测试页。
---
## 42号接口-加两个特征点
**请求** `POST /api/v1/face/features-42`,使用 `multipart/form-data`。
| 请求字段 | 类型 | 说明 |
|---|---|---|
| `image_file` | 文件 | 上传 JPG 或 PNG |
| `image_url` | 字符串 | 网关可访问的图片 URL |
| `image_base64` | 字符串 | 图片 Base64,支持带 `data:image/…;base64,` 前缀 |
以上三个字段必须且只能传一个。图片按 EXIF 方向解码后,先检查原图的人脸完整性和俯拍角度。俯拍使用头部姿态估算的正向 pitch,超过服务端环境变量 `FRONTAL_PITCH_THR`(默认 30°)时拦截。通过质检后复用接口 52 的扶正裁切规则,再用处理后的图片做面部特征分析。
### 质检失败与异常响应
以下业务响应的 HTTP 状态均为 `200`。
| `code` | 情况 | `message` |
|---|---|---|
| `1001` | 原图未检测到人脸 | 无法识别人像,请上传包含完整人脸的照片 |
| `1003` | 人脸关键点触及或超出图片边界 | 人脸不完整,请保留完整的脸部轮廓 |
| `1003` | 估算的俯拍角度超过阈值 | 俯拍角度过大,请将相机调整至与脸部平视 |
| `1003` | 扶正裁切失败,无法保留所需人脸关键点 | 人脸过于贴近边缘,无法扶正裁切 |
| `1008` | URL 图片下载失败 | 图片下载失败(URL 不可达) |
| `1008` | Base64 解码失败 | 图片格式不支持(base64 解码失败) |
| `1008` | 图片无法读取 | 图片格式不支持(仅 JPG / PNG) |
| `1007` | 图片字段未按三选一传入 | 图片参数错误:必须且只能传 image_file / image_url / image_base64 其中一个 |
| `1007` | 图片预处理内部异常 | 图片预处理失败 |
| `1001` | 质检通过,但视觉模型未识别到人脸 | 无法识别人像 |
| `1007` | 视觉模型调用异常 | 分析服务异常:… |
响应包含 `code`(整数业务码)、`message`(字符串提示)、`request_id`(字符串请求标识)和 `data`(失败时为 `null`)。三个 `1003` 情况可通过 `message` 区分,响应中没有独立的质检原因或角度字段。
```json
{
"code": 1003,
"message": "俯拍角度过大,请将相机调整至与脸部平视",
"request_id": "gw-xxxxxxxx",
"data": null
}
```
### 成功响应
`data.features` 仍为 JSON 字符串,保留接口 4 的六项特征,在其中新增:
| 字段 | 类型 | 说明 |
|------|------|------|
| `zygoma_expansion` | boolean 或 null | 颧弓外扩 |
| `temple_hollowing` | boolean 或 null | 太阳穴凹陷 |
Ft 两端保持横坐标并取平均高度,Zy 为 234 到 454 的距离。只有 Ft ÷ Zy 严格小于 0.8 时两项同时为 `true`,否则同时为 `false`。所需点位缺失或无法测量时两项均为 `null`。网关测量复用接口 99 的 MediaPipe 与 BiSeNet 依赖,裁切还需要 Pillow。
---
## 接口 5:发际线 PNG 生成接口
**说明**:入参同接口2(先选性别、再多选发型)。对每个选中发型返回 `middle` / `high` / `low` **三档**发际线叠图与**生发图**,并返回「最合适发际线」的面部中间点坐标。
**请求**:`POST /api/v1/hairline/generate`
### 输入
图片参数见「通用约定 → 图片传参字段」。专属参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| gender | string | **是** | 性别:`male` / `female`。决定发型集合(female 7 / male 7)。缺失/非法返回 `1004` |
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),决定返回哪些发际线类型。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。缺失/越界/非法返回 `1007` |
| use_mask | bool | 否 | 生发是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 |
| prompt | string | 否 | ComfyUI 提示词,默认「补充遮罩区域的头发,加一点美颜」,会替换工作流节点 60 的文本 |
| generate_grow_image | bool | 否 | 是否生成生发效果图(ComfyUI 生发,全流程最耗时),默认 `true`。传 `false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回三档发际线叠图与中心点,可大幅降低耗时 |
> ⚠️ 三档叠图分别用 `hairline_texture` / `hairline_texture_high` / `hairline_texture_low` 三套同名贴图;**生发黑模板固定取自 `hairline_texture_black/`(middle 档)**,即生发目标固定压到 middle 档,每个发型仅 1 张生发图。
### 输出(data)
| 字段 | 类型 | 说明 |
|------|------|------|
| hairline_images | object[] | **选中发型**列表,**数量 = 所选发型数**,元素见下表 |
| best_hairline_center_point | object \| null | **首个选中发型**的 **middle 档**发际线曲线「面部中间点」坐标,原图像素:`{ "x": number, "y": number }` |
| high_hairline_center_point | object \| null | 同上,**high 档**发际线中点(发际线偏高) |
| low_hairline_center_point | object \| null | 同上,**low 档**发际线中点(发际线偏低) |
| face_measure | object \| null | **复用接口1**的四庭七眼测量**数值**(不含标注图)。独立流程,测量失败(无人脸/非正面/分割失败)时为 `null`,不影响发际线主结果。字段结构见下表 |
`hairline_images` 元素:
| 字段 | 类型 | 说明 |
|------|------|------|
| hairline_type | string | 发际线类型 key:`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`(female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`/`wave`(male) |
| image_middle_url | string | middle 档发际线曲线**透明 PNG** URL(仅曲线,透明底,**不含人物**,需叠加原图显示) |
| image_high_url | string | high 档发际线曲线**透明 PNG** URL(同上,high 档曲线) |
| image_low_url | string | low 档发际线曲线**透明 PNG** URL(同上,low 档曲线) |
| grown_image_url | string \| null | **生发后图片** URL(ComfyUI「植发」效果图,完整人像照片,生发失败或 `generate_grow_image=false` 时为 `null`) |
| order | int | 发型序号(= 传入的 hair_style 值) |
> worker 侧返回 `image_middle_base64` / `image_high_base64` / `image_low_base64` / `grown_image_base64`,网关落盘后改写为上表对应的 `*_url`。
>
> 💡 三档 `image_*_url` 为透明底 PNG,前端需用绝对定位叠加到原图上显示(参考[测试页](https://hair.xiangsilian.com/static/test_interface5.html)的 `.img-stack` 叠加结构)。`grown_image_url` 是完整人像照片,直接显示即可。
`face_measure` 元素(与[接口1](#接口-1四庭七眼测量标注接口)的 `data` 同构,**不含** `annotated_image_*` 标注图字段):
| 字段 | 类型 | 说明 |
|------|------|------|
| face_total_height_cm | number | 全脸总高度(cm)= 四庭之和 |
| four_courts | object | 四庭数据(顶/上/中/下庭 cm + 占比 ratios),结构同接口1 |
| seven_eyes | object | 七眼数据(眼宽/脸宽/两眼间距 cm + 占比 ratios + eye1~eye7 从左到右 7 段宽度),结构同接口1 |
| landmarks | object | 5 个纵向关键点像素坐标(hair_top/hairline/brow_center/nose_bottom/chin_tip),结构同接口1 |
| vertical_lines | object | 竖线坐标,结构同接口1 |
| hairline_source | string | 发际线来源:`segmentation`(真实分割)/ `estimated`(比例估算)/ `corrected`(发际线贴近头顶、分割不可靠,已按 0.95×中庭反推修正) |
| head_pose | object | 头部姿态角度(yaw/pitch/roll,单位:度) |
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),原图像素:`{ "x": int, "y": int }` |
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),原图像素:`{ "x": int, "y": int }` |
> `eye1`~`eye7` 为从左到右共 7 段宽度,eye1=左耳外段、eye7=右耳外段,某侧耳朵不可见时对应段为 `null`。详见接口1说明。
### 响应示例(当前 Mock 返回值)
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"hairline_images": [
{
"hairline_type": "ellipse",
"image_middle_url": "https://hair.xiangsilian.com/static/annotations/mid1.png",
"image_high_url": "https://hair.xiangsilian.com/static/annotations/high1.png",
"image_low_url": "https://hair.xiangsilian.com/static/annotations/low1.png",
"grown_image_url": "https://hair.xiangsilian.com/static/annotations/grown1.png",
"order": 1
},
{
"hairline_type": "heart",
"image_middle_url": "https://hair.xiangsilian.com/static/annotations/mid3.png",
"image_high_url": "https://hair.xiangsilian.com/static/annotations/high3.png",
"image_low_url": "https://hair.xiangsilian.com/static/annotations/low3.png",
"grown_image_base64": null,
"order": 3
}
],
"best_hairline_center_point": { "x": 540, "y": 430 },
"high_hairline_center_point": { "x": 540, "y": 380 },
"low_hairline_center_point": { "x": 540, "y": 480 },
"face_measure": {
"face_total_height_cm": 26.76,
"four_courts": {
"top_court_cm": 5.77, "upper_court_cm": 5.93,
"middle_court_cm": 7.62, "lower_court_cm": 7.44,
"ratios": { "top_court": 0.216, "upper_court": 0.222,
"middle_court": 0.285, "lower_court": 0.278 }
},
"seven_eyes": {
"eye_width_cm": 2.76, "face_width_cm": 15.08,
"inter_eye_distance_cm": 3.9,
"ratios": { "eye_width": 0.183, "inter_eye_distance": 0.259 },
"eye1": null, "eye2": 3.0, "eye3": 2.76, "eye4": 3.9,
"eye5": 2.76, "eye6": 3.0, "eye7": null
},
"landmarks": {
"hair_top": { "x": 504, "y": 103 },
"hairline": { "x": 504, "y": 228 },
"brow_center": { "x": 504, "y": 357 },
"nose_bottom": { "x": 505, "y": 522 },
"chin_tip": { "x": 506, "y": 683 }
},
"hairline_source": "segmentation",
"head_pose": { "yaw": -1.39, "pitch": 2.49, "roll": -0.06 }
}
}
}
```
> 说明:生发失败的元素中,网关不改写 `null` 值,故字段名保持为 `grown_image_base64: null`(有值时才改写为 `grown_image_url`),与接口2生发失败项一致。
---
## 接口 52:发际线 PNG 生成接口
**说明**:入参同接口2(先选性别、再多选发型)。对每个选中发型返回一张 `middle` 设计叠图与生发图。**目标上庭 =(中庭 + 下庭)×0.45**,从眉心向上定位;所有形状的中点对齐同一目标。`high` / `low` 已停用,旧字段保留为 `null`。
**请求**:`POST /api/v1/hairline/generate-52`(仅GPU worker,端口8187;本次不在网关新增路由)
**IP限制**:GPU worker的所有动态接口统一仅允许本机回环地址、本机网卡地址及白名单;目前白名单含网关IP `49.232.51.171`,其他直连来源需加入`ip_access.json`的`allowed_ips`。该文件支持单个IP与CIDR,修改后立即生效;拒绝时返回HTTP403、code=1010。网关不启用此限制,已有鉴权仍保留。详见[接口IP白名单](接口IP白名单.md)。
### 图片扶正与标准构图
显式传`normalize_image=true`时,先扶正9→152,再按**高(3÷0.9)×L≈3.3333×L、宽2.5×L(3:4)**裁切,L为9→152的直线像素距离。相对原3L规则,裁切框宽高均扩大约11.11%,同样显示尺寸下人物缩小约10%。高度取最接近的4的倍数,宽度取高度的3/4。眉心定位沿用已扶正参考图:9号点相对位置约(49.0061%,34.4136%)。参考图仍为786×1048,两点X均为385.18784。
目标窗口完全在原图有效区域内才进行标准裁切。标准窗口不足时,偏长图片(宽/高<3/4)采用原图有效范围内最大的3:4窗口,尽量贴近眉心目标位置并保留完整人脸,`status=aspect_crop`;人脸放不进3:4窗口时回退仅扶正并返回原因。其余图片空间不足时仍仅扶正、去除旋转空角。不放大小图。**所有叠图与坐标使用响应中的处理后底图 `input_image_*`**,调用方应使用该底图叠加;`normalize_image=false` 可关闭此预处理。
`framing`包含处理前后尺寸、旋转角、是否标准裁切、跳过原因、`source_to_processed`/`processed_to_source`仿射矩阵。`status`区分`reference_crop`(标准窗口)、`aspect_crop`(长图3:4裁切)和`rotation_only`(仅扶正);`standard_crop=false`时仍可能有`aspect_crop=true`。`aspect_crop_skip_reason`说明长图无法保留人脸时的回退原因。`original_image_*`保留EXIF方向修正后的上传图,原图坐标可通过逆矩阵还原。
### 输入
图片参数见「通用约定 → 图片传参字段」。专属参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| gender | string | **是** | 性别:`male` / `female`。决定发型集合(female 7 / male 7)。缺失/非法返回 `1004` |
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),决定返回哪些发际线类型。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。缺失/越界/非法返回 `1007` |
| use_mask | bool | 否 | 生发是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比 |
| prompt | string | 否 | ComfyUI 提示词,默认「填充遮罩区域的头发,人物增加一点磨皮」,会替换工作流节点 60 的文本 |
| normalize_image | bool | 否 | 新增可选开关,默认false:保留原图尺寸与坐标。传true开启扶正和裁切;规则测试页显式传true,对比调试页默认开启 |
| generate_grow_image | bool | 否 | 是否生成生发效果图(ComfyUI 生发,全流程最耗时),默认 `true`。传 `false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回设计叠图、中心点与高度规则数据,可大幅降低耗时 |
> 生发划线与遮罩直接取新位置的 middle 曲线;旧规则的头发边界与模板用于保留形状和倾斜,不决定新中心高度。计算使用未取整的像素高度,与四庭测量采用同一 Y 方向;测量主流程失败时用发际线管线的同编号关键点计算,`measurement_source` 标明来源。目标越出图片或无法对齐时返回 1007,不静默回退旧规则。
### 高度规则与对比
- **参数与字段**:原有8个请求参数、响应外层结构和所有旧返回字段均保留。`use_mask=true`、默认prompt、`generate_grow_image=true`保持原值。新功能只新增可选参数和返回字段;旧请求未传`normalize_image`时保留原图坐标,high/low按已约定保留null,worker始终返回`*_base64`,不负责保存图片、生成公网URL或改名;网关保持原样。
- `height_rule`:`middle_height_px`、`lower_height_px`、`target_upper_height_px`、`target_upper_height_cm`(可空)、`target_center`、`brow_center`、`nose_bottom`、`chin_tip`、`measurement_source`。
- 每项 `height_placement`:`old_center`、`new_center`、`shift_x_px`、`shift_y_px`(正=下移)、`target_error_px`。浮点坐标保留精度,旧中心点字段继续返回取整像素。
- `face_measure` 为处理后底图的实际测量值,未被设计目标覆盖。
- `guide_landmarks`:9、150、152、162、251、389 号 MediaPipe 点的图片像素坐标,供对比页绘制参考框。参考框左/右边分别经过162/389,底边经过9,高度为9→152距离的70%;两侧均固定平行9→152。
- 规则页:`/static/test_interface52.html`;旧/新规则对比:`/static/test_interface52_compare.html`。
- 规则页每个发型依次展示未裁切原图、middle、裁切发际线叠加、生发图。“裁切发际线叠加”由页面按`guide_landmarks`生成:在相同处理后底图上叠加矩形内的middle PNG,矩形外保持透明;左/右边过162/389,底边过9,高度0.7×9→152,左右边平行9→152。
- 对比调试接口:`POST /api/v1/debug/hairline-height-compare`,图片三选一、gender、hair_style;默认只出旧/新曲线,可选 `generate_grow_image=true` 额外验证新规则生发。
- 测试清单:`tests/fixtures/hairline_height_cases.json`(用户指定30张);批量运行:`./venv/bin/python scripts/compare_hairline_height.py`,默认全部30张、男女各7型,共420组。每人一行,未裁切原图放在最前,后接一张可切换样式的旧规则图和全部新规则样式。可用 `--cases 1,2,3,4,5` 指定部分案例。
### 输出(data)
| 字段 | 类型 | 说明 |
|------|------|------|
| hairline_images | object[] | **选中发型**列表,**数量 = 所选发型数**,元素见下表 |
| best_hairline_center_point | object \| null | **首个选中发型**的 **middle 档**发际线曲线「面部中间点」坐标,当前底图像素:`{ "x": number, "y": number }` |
| high_hairline_center_point | object \| null | 已停用,固定 `null` |
| low_hairline_center_point | object \| null | 已停用,固定 `null` |
| input_image_base64 | string | 当前底图的JPEG base64,发际线叠图、参考点和测量值均使用该图的坐标系 |
| original_image_base64 | string | EXIF方向修正后的上传原图JPEG base64 |
| width / height | int | 当前底图的实际像素宽 / 高 |
| coordinate_space | string | 开启预处理时为 `processed_image`,关闭时为 `original_image` |
| framing | object \| null | 裁切状态、旋转角、前后尺寸、轴线误差及双向仿射矩阵;关闭预处理时为 `null` |
| height_rule | object | 中庭/下庭、目标上庭高度、目标中心及测量来源,详见“高度规则与对比” |
| guide_landmarks | object | 9、150、152、162、251、389 号点的当前底图像素坐标;参考框实际使用9、152、162、389 |
| face_measure | object \| null | **复用接口1**的四庭七眼测量**数值**(不含标注图)。独立流程,测量失败(无人脸/非正面/分割失败)时为 `null`,不影响发际线主结果。字段结构见下表 |
`hairline_images` 元素:
| 字段 | 类型 | 说明 |
|------|------|------|
| hairline_type | string | 发际线类型 key:`ellipse`/`flower`/`heart`/`straight`/`wave`/`bigflower`/`clasicalflower`(female),`ellipse`/`m`/`straight`/`inverse_arc`/`heart`/`Softpetal`/`wave`(male) |
| image_middle_base64 | string | middle 档发际线曲线**透明 PNG** base64(仅曲线,透明底,**不含人物**,需叠加 `input_image_base64` 显示) |
| image_high_base64 | null | 已停用,保留字段为 `null` |
| image_low_base64 | null | 已停用,保留字段为 `null` |
| grown_image_base64 | string \| null | **生发后图片** JPEG base64(ComfyUI「植发」效果图,完整人像照片,生发失败或 `generate_grow_image=false` 时为 `null`) |
| order | int | 发型序号(= 传入的 hair_style 值) |
| height_placement | object | 旧/新中心、XY移动距离、目标误差,均为未取整的像素值 |
> 上表为GPU worker的实际响应字段;所有图片使用base64,空图片保留原字段并返回null。网关的落盘与URL改写属于网关职责,本次不修改。
>
> 前端将base64转为data URI后显示。middle透明图叠加到`input_image_base64`,生发图直接显示。GPU worker本机测试页为`http://localhost:8187/static/test_interface52.html`。
`face_measure` 元素(与[接口1](#接口-1四庭七眼测量标注接口)的 `data` 同构,**不含** `annotated_image_*` 标注图字段):
| 字段 | 类型 | 说明 |
|------|------|------|
| face_total_height_cm | number | 全脸总高度(cm)= 四庭之和 |
| four_courts | object | 四庭数据(顶/上/中/下庭 cm + 占比 ratios),结构同接口1 |
| seven_eyes | object | 七眼数据(眼宽/脸宽/两眼间距 cm + 占比 ratios + eye1~eye7 从左到右 7 段宽度),结构同接口1 |
| landmarks | object | 5 个纵向关键点像素坐标(hair_top/hairline/brow_center/nose_bottom/chin_tip),结构同接口1 |
| vertical_lines | object | 竖线坐标,结构同接口1 |
| hairline_source | string | 发际线来源:`segmentation`(真实分割)/ `estimated`(比例估算)/ `corrected`(发际线贴近头顶、分割不可靠,已按 0.95×中庭反推修正) |
| head_pose | object | 头部姿态角度(yaw/pitch/roll,单位:度) |
| left_position | object | MediaPipe 21 号关键点坐标(左脸定位点),当前底图像素:`{ "x": int, "y": int }` |
| right_position | object | MediaPipe 251 号关键点坐标(右脸定位点,与 21 号镜像),当前底图像素:`{ "x": int, "y": int }` |
> `eye1`~`eye7` 为从左到右共 7 段宽度,eye1=左耳外段、eye7=右耳外段,某侧耳朵不可见时对应段为 `null`。详见接口1说明。
### GPU worker响应结构示例(normalize_image=false)
此例未开启裁切,坐标基于原图;`face_measure=null`时仍可按发际线关键点计算目标。图片内容以缩略base64字符串示意,空图片字段返回null,字段名不变。
```json
{
"code": 0,
"message": "success",
"request_id": "mock-request-id",
"data": {
"hairline_images": [
{
"hairline_type": "ellipse",
"image_middle_base64": "iVBORw0KGgo...",
"image_high_base64": null,
"image_low_base64": null,
"grown_image_base64": "/9j/4AAQ...",
"order": 1,
"height_placement": {
"old_center": {"x": 540.0, "y": 450.0},
"new_center": {"x": 540.0, "y": 461.0},
"shift_x_px": 0.0, "shift_y_px": 11.0, "target_error_px": 0.0
}
}
],
"best_hairline_center_point": {"x": 540, "y": 461},
"high_hairline_center_point": null,
"low_hairline_center_point": null,
"input_image_base64": "/9j/4AAQ...",
"original_image_base64": "/9j/4AAQ...",
"width": 1080, "height": 1440,
"coordinate_space": "original_image",
"framing": null,
"height_rule": {
"name": "court_average", "formula": "上庭 = (中庭 + 下庭) × 0.45",
"measurement_source": "hairline_landmarks",
"brow_center": {"x": 540.0, "y": 740.0},
"nose_bottom": {"x": 540.0, "y": 1050.0},
"chin_tip": {"x": 540.0, "y": 1360.0},
"middle_height_px": 310.0, "lower_height_px": 310.0,
"target_upper_height_px": 279.0, "target_upper_height_cm": null,
"target_center": {"x": 540.0, "y": 461.0}
},
"guide_landmarks": {
"9": {"x": 540.0, "y": 740.0}, "150": {"x": 470.0, "y": 1340.0},
"152": {"x": 540.0, "y": 1360.0}, "162": {"x": 300.0, "y": 680.0},
"251": {"x": 770.0, "y": 630.0}, "389": {"x": 780.0, "y": 680.0}
},
"face_measure": null
}
}
```
开启`normalize_image=true`后,`coordinate_space=processed_image`,`framing`包含前述裁切状态和坐标变换,所有发际线图片须叠加到`input_image_base64`。原始图片仅用于展示和坐标回溯。
---
## 接口 9:头发遮罩生成接口
**说明**:输入一张含人头的照片,生成头发遮罩,并返回**每一步的可视化图**(供对比调试)。
**请求**:`POST /api/v1/head/mask`
### 输入
图片参数见「通用约定 → 图片传参字段」。专属参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| erode_cm | float | 否 | 外缘朝中心(鼻梁 151 点)内缩的距离(厘米),默认 1.2 |
### 处理流程
1. MediaPipe 关键点 → 底部分割线(关键点 21,68,104,69,108,151,337,299,333,298,251 的连线,21 水平延伸到最左、251 延伸到最右)。
2. 分割线以上为「上半区」。
3. 头发分割(**BiSeNet** 与 **SegFormer** 两套);从每列最顶端头发向下填充到分割线,得到**含额头**的闭合区域(头发+额头,不割断)。
4. 外缘朝中心点 151 内缩 `erode_cm`(虹膜标定换算像素)、底线不动 → 最终遮罩。
### 输出(data)
| 字段 | 类型 | 说明 |
|------|------|------|
| px_per_cm | float | 虹膜标定出的像素/厘米 |
| erode_cm / erode_px | float / int | 内缩距离(厘米 / 像素) |
| image_size | object | `{width, height}` 原图尺寸 |
| steps_common | object | `landmarks_baseline_*`(关键点/分割线)、`upper_region_*`(上半区) |
| bisenet / segformer | object | 两套分割模型各自的结果:`hair_mask_*`(原始头发分割)、`closed_region_*`(含额头闭合区域)、`final_overlay_*`(最终遮罩叠加可视化)、`mask_*`(最终遮罩 PNG)及 `hair_pixels`/`closed_pixels`/`mask_pixels` 像素计数 |
> worker 返回 `*_base64`(data URI),经网关改写为 `*_url`。
---
## 接口 10:头部外缘膨胀带遮罩接口
**说明**:先和接口9 一样得到内缩后的基准遮罩,再沿头部外缘生成一条膨胀带遮罩。
**请求**:`POST /api/v1/head/band`
### 输入
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| erode_cm | float | 否 | 基准遮罩外缘朝 151 内缩距离(厘米,同接口9),默认 1.2 |
| dilate_cm | float | 否 | 外轮廓线膨胀后带子总宽(厘米),默认 2.0 |
### 处理流程
1. 取基准遮罩的**外轮廓线**,去掉贴着底部分割线的那一段(只留头发/头部外缘弧线)。
2. 把外轮廓线膨胀成带子(总宽 `dilate_cm`;半径=总宽/2,虹膜标定换算像素)。
3. 裁到分割线以上(不越过底线)。
### 输出(data)
结构同接口9,但每组结果为 `base_mask_*`(基准遮罩)/ `contour_*`(外轮廓线)/ `band_overlay_*`(带子叠加可视化)/ `mask_*`(最终带状遮罩)及 `base_pixels`/`band_pixels` 计数,另有 `dilate_cm`/`dilate_radius_px`。
---
## 接口 11:发际线生发接口(不含重绘)
**说明**:输入一张**发际线较高 / 头发稀少**的正脸图 + 发际线类型 ID,输出同一个人、同一发型、按该类型压低发际线后的图片,并返回**每一步可视化**。重绘版见接口12。
**请求**:`POST /api/v1/hairline/grow`
### 输入
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| hairline_id | string | **是** | 发际线类型 ID(= change_hair `hair_id`):`chang_tuoyuan` / `chang_huaban` / `chang_xinxing` / `chang_zhixian` / `chang_bolang` |
| gen_backend | string | 否 | 生成后端:`swaphair`(换发型 LoRA,默认)|`hairgrow`(区域生发 inpaint) |
| hairgrow_strength | float | 否 | 区域生发强度(仅 hairgrow 后端),默认 0.75 |
| is_hr | bool | 否 | 高清模式(换发型输出 1152×1536,否则 576×768),默认 false |
| seg_model | string | 否 | 头发分割模型:`bisenet` | `segformer`(默认) |
| erode_cm | float | 否 | baseline 参考内缩距离(厘米),默认 0.6 |
| hairline_push_cm | float | 否 | 发际线外推距离(厘米,往头发方向推进),默认 1.0 |
| hairline_edge | string | 否 | 发际线提取方式:`column`(默认)|`contour` |
| swap_mode | string | 否 | 换发型取图模式:`ext_mask`(默认,把接口9 遮罩传给 webui 精确重绘)|`as_is`(用内部遮罩,贴回时再裁) |
| edge_erode_px | int | 否 | 贴图前遮罩内缩像素(防边缘露皮/光晕),默认 3 |
| denoising_strength | float | 否 | 换发型 webui 重绘强度(越大生发越激进),默认 0.6 |
| mb_levels | int | 否 | 多频段金字塔层数(2~6),默认 5 |
| blend_method | string | 否 | 接缝融合:`multiband`(默认)|`seamless`(泊松)|`two_stage`(泊松→多频段)|`feather`(高斯羽化)|`alpha_gradient`(距离渐变) |
| color_match | bool | 否 | 融合前 Reinhard 颜色迁移消除整体色差,默认 true |
| color_match_strength | float | 否 | 颜色迁移强度(0~1),默认 1.0 |
| mb_feather_px | int | 否 | 多频段最细层掩码轻羽化像素,默认 1 |
| transition_band_px | int | 否 | keep-region 过渡带边距(-1=自动按层数 2^n),默认 -1 |
| inpainting_fill | int | 否 | change_hair 服务端重绘填充:0=保留原图 | 1=噪声(默认)| 2=纯色 | 3=潜变量噪声 |
| mask_blur | int | 否 | change_hair 服务端遮罩边缘模糊像素,默认 11 |
| mask_dilate_scale | float | 否 | change_hair 服务端遮罩膨胀缩放(<1 收缩防越界),默认 1.0 |
### 输出(data)
`steps`:`input`(原图)/ `baseline`(底部分割线)/ `hair_mask`(头发分割)/ `mask_overlay`(遮罩叠加)/ `mask`(遮罩)/ `swap_raw`(换发型原始输出)/ `alpha`(融合权重)/ `final`(最终结果)各步图(`*_base64`,经网关改写为 `*_url`),另有 `timings_ms` 分步耗时。
> ⚠️ `swaphair` 后端依赖 change_hair(:8801)在跑;`hairgrow` 后端依赖 ComfyUI(:8188)。
---
## 接口 12:发际线带重绘接口
**说明**:接口11 的**下游重绘阶段**——内部先跑接口11 核心管线拿 ④ 接缝融合最终图(final),再取发际线重绘带(外推 `band_lo_mult×push` ~ `band_hi_mult×push` 之间、经 baseline 截断只留上部的带状区域)作遮罩,用 **Flux-2(ComfyUI,reference-latent 保色重绘)** 重绘。
**同时产出两版结果**:
- `redraw_full`:ComfyUI 整帧输出(全脸美颜 + 全脸重绘)。
- `redraw_band`:加发只在发际线带内、band 外保留 final 结构并按 `beauty_alpha` 融入全脸美颜。
**请求**:`POST /api/v1/hairline/grow_v2`
### 输入
接口11 的全部参数(见上表)再叠加:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| comfyui_prompt | string | 否 | Flux-2 重绘提示词,缺省「填充遮罩区域的头发,人物增加一点磨皮」 |
| beauty_alpha | float | 否 | redraw_band 版 band 外的全脸美颜融入强度(0=band 外纯用 final,1≈整帧版),默认 0.6 |
| band_lo_mult | float | 否 | 重绘带外推倍率下限(相对 hairline_push_cm),默认 0.5 |
| band_hi_mult | float | 否 | 重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5 |
### 输出(data)
`steps`:`input` / `final`(重绘输入基底)/ `redraw_band_overlay`(重绘带可视化)/ `redraw_full`(A 整帧)/ `redraw_band`(B 局部+美颜)/ `redraw_c`(兼容旧字段,=redraw_full);重绘异常在 `redraw.c_error` 报告。
### 精简变体(生产推荐)
| 路径 | 说明 |
|------|------|
| `POST /api/v1/hairline/grow_v2_final` | 仅需图片 + `hairline_id`,其余参数固化为调优默认值(push=0.8cm / two_stage / color_match=关 等),**最终取整帧重绘 `redraw_full`** |
| `POST /api/v1/hairline/grow_v2_final_v2` | 参数同上,**最终取 B 局部加发+全脸美颜 `redraw_band`** |
> ⚠️ 依赖 ComfyUI(:8188)与 change_hair(:8801)均在跑。接口2 女性 1..5 的生发图即复用 `grow_v2_final` 管线。
---
## 接口 99:人脸特殊点检测接口
**说明**:输入一张人脸照片,返回一组**固定编号 MediaPipe Face Mesh 关键点**的像素坐标,外加 5 个**发际线特殊点**(射线与头发分割发际线的交点)。**生产路径:网关本机实现(纯 CPU,不转发 worker,同接口4 模式)**;worker 侧保留同路径接口供测试页直连。
**请求**:`POST /api/v1/face/special-points`
### 输入
图片参数见「通用约定 → 图片传参字段」,无专属参数。要求单人正面照(非正面返回 `1003`)。
### 输出(data)
`points`:字段名 = `<组英文名>_<点ID>`,值为原图像素坐标 `{x, y}`(int);发际线点不可得(秃顶/分割失败)时为 null。
| 组 | 字段前缀 | 点 ID |
|----|----------|-------|
| 眉毛 | `eyebrow` | 107 66 105 63 70 336 296 334 293 300 |
| 眼睛 | `eye` | 189 222 224 113 226 413 442 444 342 446 |
| 嘴 | `mouth` | 0 37 40 67 91 84 17 405 375 409 269 |
| 中线鼻 | `nose_midline` | 9 168 197 5 4 194 278 48 437 217 |
| 面颊 | `cheek` | 101 117 123 187 205 50 330 346 352 280 425 |
| 边线 | `face_edge` | 162 234 58 136 149 152 378 365 288 401 366 389 251 |
**发际线特殊点**(沿关键点 a→b 方向射线步进,首次进入 BiSeNet 头发 mask 处为交点;双射线取中点,仅一条命中返回该点):
| 字段 | 计算 |
|------|------|
| `hair_line_center` | 151→10 方向交点 |
| `hair_line_right` | mid(108→109 交点A, 69→67 交点B) |
| `hair_line_right_right` | mid(104→103 交点A, 68→54 交点B) |
| `hair_line_left` | mid(337→338 交点A, 299→297 交点B) |
| `hair_line_left_left` | mid(333→332 交点A, 298→284 交点B) |
另有 `image_size`(宽高)与 `hairline_source`(`segmentation`=发际线点已算出 / `unavailable`=分割不可用,发际线点为 null)。
---
## 汇总:输入输出一览
| 接口 | 路径 | 输入 | 主要输出 |
|------|------|------|----------|
| 1 四庭七眼测量 | `/api/v1/face/measure` | 用户照片 | 标注 PNG(无人物)+ 四庭/七眼厘米数值与坐标 |
| 6 四庭七眼测量 v2 | `/api/v1/face/measure-v2` | 用户照片 | 同接口1,去顶庭变体 |
| 2 C 端生发 | `/api/v1/hair/grow` | 用户照片 + gender + hair_style(多选) | 每发型预览 PNG + 生发图(female 1..5=换发型+重绘,其余=原生生发) |
| 7 C 端生发 v2 | `/api/v1/hair/grow-v2` | 同接口2 | 同接口2(add_hair2 工作流,已弃用隐藏) |
| 3 B 端生发 | `/api/v1/hair/grow-b` | 划线图片 | 检测到的发际线 + 生发后图片 |
| 4 用户特征 | `/api/v1/face/features` | 用户照片 | 6 个用户特征字段(脸形/眉形/年龄/动静/性别/基因风格),网关本机实现 |
| 5 发际线 PNG | `/api/v1/hairline/generate` | 用户照片 + gender + hair_style(多选) | 每个选中发型 middle/high/low 三档发际线叠图 + 生发图 + 最合适发际线面部中间点坐标 |
| 52 发际线新规则 | `/api/v1/hairline/generate-52` | 用户照片 + gender + hair_style(多选) | 每个选中发型的新规则 middle 设计叠图 + 生发图 + 目标中心坐标;high/low 旧字段为 null |
| 9 头发遮罩 | `/api/v1/head/mask` | 用户照片 | 头发遮罩 + 分步可视化(BiSeNet/SegFormer 双路) |
| 10 外缘膨胀带 | `/api/v1/head/band` | 用户照片 | 头部外缘带状遮罩 + 分步可视化 |
| 11 发际线生发 | `/api/v1/hairline/grow` | 用户照片 + hairline_id | 压低发际线图 + 全步骤可视化(不含重绘) |
| 12 发际线带重绘 | `/api/v1/hairline/grow_v2` | 同接口11 + 重绘带参数 | final 基底 + Flux-2 保色重绘(整帧/局部两版);精简版 `grow_v2_final[_v2]` 仅需图片+hairline_id |
| 99 人脸特殊点 | `/api/v1/face/special-points` | 用户照片 | 60 个固定编号关键点 + 5 个发际线特殊点像素坐标 JSON(**网关本机实现**,同接口4) |
> 另有 worker 本机工具路由(不在对外契约内):`/api/swapHair/v1`、`/hairColor/v2`(CORS 代理 change_hair)、`/api/v1/redraw`(直接 ComfyUI 重绘)、`/api/v1/debug/*`。
---
## 已确认结论
- 图片传参:**文件 / URL / base64 三种都支持,严格互斥**(必须且只能传一个,base64 需带 `data:image/...;base64,` 前缀)。
- 默认单人;检测到 2~3 人报错提示客户端;单文件 **≤ 1 MB**;格式仅 **JPG / PNG**。
- 坐标基准:默认**原图像素**;接口52开启预处理时使用响应中的**处理后底图像素**。
- 接口 2:提供「美颜」开关 `beauty_enabled`。
- 接口 3:仅传**划线图**(`marked_image_*` 三选一),需返回发际线类型(`custom`)。
- 接口 4:`data` 返回**一个 JSON 字符串**,字段不固定,可随时调整。
- 接口 5:发际线 PNG 数量 **N 不固定**,需按合适度**排序**。
- 英文字段命名由本文档约定(见各接口表格)。
## 待与需求方确认的问题清单
1. 接口 1 标注图片设计稿(见下方说明)。