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
+41 -16
View File
@@ -381,8 +381,8 @@ def _run_face_measure_data(image, variant="v1", landmarks_override=None):
不含标注图(annotated_image_*),供接口5 容错复用;接口1 调用后再自行生成标注图。
任何分割/七眼计算异常均内部吞掉(七眼相关字段不出现),不影响主测量结果。
variant="v1"(接口1/接口5):完整四庭七眼 + eye1~eye7(含头部端线段)。
variant="v6"(接口6):去顶庭、不画端线、无 eye1~eye7。
variant="v1"(接口1/接口5):完整四庭七眼 + eye1~eye7(含头部端线段)+ vertical_lines。
variant="v6"(接口6):去顶庭、不画端线、无 eye1/eye7,vertical_lines 仅 6 条内线。
"""
h, w = image.shape[:2]
from face_analysis.detector import detector
@@ -428,30 +428,29 @@ def _run_face_measure_data(image, variant="v1", landmarks_override=None):
# 占比、标注图仍按三庭处理,显示效果不变。
# 七眼段宽度(cm)。eye1=左耳外段 eye2=左脸颊 eye3=左眼 eye4=两眼间距 eye5=右眼 eye6=右脸颊 eye7=右耳外段。
# eye2~eye6(5段)只用内部分点,接口1/6 共用;eye1/eye7 需耳朵分割端线,仅接口1 有。
# eye2~eye6(5段)只用内部分点,接口1/6 共用;eye1/eye7 用竖线坐标里的人头端线,仅接口1 有。
# vertical_lines 与标注图竖线同一套几何。
try:
from face_analysis.annotation import vertical_line_geometry
epts = result.eyes["points"]
lcx, rcx = epts["left_cheek"][0], epts["right_cheek"][0]
pc = result.px_per_cm
inner_xs = [lcx, epts["left_outer"][0], epts["left_inner"][0],
epts["right_inner"][0], epts["right_outer"][0], rcx]
geom = vertical_line_geometry(
result, w, h, ear_mask=ear_mask, hair_mask=hair_mask, variant=variant)
data["vertical_lines"] = geom
inner_xs = [epts["left_cheek"][0], epts["left_outer"][0], epts["left_inner"][0],
epts["right_inner"][0], epts["right_outer"][0], epts["right_cheek"][0]]
for i in range(5):
a, b = inner_xs[i], inner_xs[i + 1]
data["seven_eyes"][f"eye{i + 2}"] = (
None if (a is None or b is None) else round((b - a) / pc, 2))
if variant != "v6":
# 接口1 额外算 eye1/eye7(左/右耳外段),需耳朵分割端线。
# 竖向范围上界用头顶(hair_top 始终可用)。
from face_analysis.annotation import _ear_edges_from_mask
top_y = vd["hair_top"][1]
head_l, head_r = _ear_edges_from_mask(
ear_mask, hair_mask,
top_y, vd["chin_tip"][1],
lcx, rcx, (lcx + rcx) / 2)
by_name = {line["name"]: line["x"] for line in geom["lines"]}
head_l = by_name.get("left_head_edge")
head_r = by_name.get("right_head_edge")
data["seven_eyes"]["eye1"] = (
None if (head_l is None) else round((lcx - head_l) / pc, 2))
None if head_l is None else round((by_name["left_cheek"] - head_l) / pc, 2))
data["seven_eyes"]["eye7"] = (
None if (head_r is None) else round((head_r - rcx) / pc, 2))
None if head_r is None else round((head_r - by_name["right_cheek"]) / pc, 2))
except Exception as seg_e: # noqa: BLE001
logger.warning("七眼段宽度计算失败:%s", seg_e)
@@ -901,6 +900,7 @@ async def _face_measure_impl(image_file, image_url, image_base64, variant="v1"):
- 四庭(顶庭/上庭/中庭/下庭)各段**厘米数值及占比**
- 七眼(眼宽/脸宽/两眼间距)**厘米数值及占比**
- 五个关键分界点的**原图像素坐标**(头顶/发际线/眉心/鼻翼下缘/下巴尖)
- 竖线坐标 `vertical_lines`(与标注图同一套:共用 y 范围 + 从左到右的 x)
{_image_fields_desc}
@@ -956,6 +956,19 @@ async def _face_measure_impl(image_file, image_url, image_base64, variant="v1"):
"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},
],
},
},
}
}
@@ -1032,6 +1045,7 @@ async def face_measure_debug(
- 三庭(上庭/中庭/下庭)各段**厘米数值及占比**(**不含顶庭**)
- 五眼段宽(eye2~eye6:左脸颊/左眼/两眼间距/右眼/右脸颊)**厘米数值**,另含眼宽/脸宽/两眼间距
- 四个关键分界点的**原图像素坐标**(发际线/眉心/鼻翼下缘/下巴尖)
- 竖线坐标 `vertical_lines`(发际线→下巴尖,仅 6 条内线,无人头两端)
基于接口1 的变体,与接口1 的差异:
- **去顶庭**:不画头顶横线、不返回顶庭数据;`face_total_height_cm` 为三庭之和
@@ -1089,6 +1103,17 @@ async def face_measure_debug(
"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},
],
},
},
}
}