feat(测量): 接口1/6 返回与标注图一致的竖线坐标

前端需要按标注图重绘竖线,坐标与画线共用同一套几何。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xsl
2026-11-05 22:18:12 +08:00
co-authored by Cursor
parent 1c51f3eaab
commit b64313dc73
6 changed files with 273 additions and 36 deletions
+2 -2
View File
@@ -59,7 +59,7 @@
## 2. 各接口实现简述
### 接口1 四庭七眼测量 `/api/v1/face/measure`(worker)
- **做什么**:正面照 → 四庭(顶/上/中/下庭) + 七眼(眼宽/脸宽/间距) 的 cm 与占比、5 个关键点坐标、一张透明底标注 PNG。
- **做什么**:正面照 → 四庭(顶/上/中/下庭) + 七眼(眼宽/脸宽/间距) 的 cm 与占比、5 个关键点坐标、竖线坐标 `vertical_lines`、一张透明底标注 PNG。
- **怎么实现**(`face_analysis/`):MediaPipe Face Mesh 468+虹膜点 → solvePnP 姿态校验(非正面 1003) →
虹膜直径法定标(px→cm) → **眉心以下实测**;**眉心以上**用 BiSeNet 头发分割取真实发际线/头顶(方案B),
失败回退比例推算(方案A,`hairline_source` 透出)。标注图 numpy 向量化渐变线 + 思源黑体。返回 `annotated_image_base64`。
@@ -68,7 +68,7 @@
### 接口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 边界删顶庭字段并重算三庭比例。
- **怎么实现**:与接口 1 共用 `_face_measure_impl(variant="v6")`;v6 时标注走变体分支、数据由 app.py 边界删顶庭字段并重算三庭比例。`vertical_lines` 与标注图共用 `vertical_line_geometry`:接口 6 的 y 范围是发际线到下巴尖,`lines` 只有 6 条内线。
- **网关改动**:新增路由 `POST /api/v1/face/measure-v2`,转发到 worker 同路径;base64→URL 改写无需改动。
### 接口2 C端生发 `/api/v1/hair/grow`(worker)—— 预览 + 生发图
+50 -1
View File
@@ -113,6 +113,7 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
| 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 }` |
@@ -142,7 +143,28 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
| 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` 为从左到右共 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)规范
@@ -186,6 +208,19 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
"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 }
]
}
}
}
@@ -217,6 +252,7 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
| 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 }` |
@@ -246,6 +282,17 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
"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 }
]
}
}
}
@@ -453,6 +500,7 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
| 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 }` |
@@ -606,6 +654,7 @@ GPU worker的所有动态接口均启用IP限制:允许本机及`ip_access.jso
| 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 }` |