feat: 接口1/5/6 发际线弃用逻辑(顶庭<0.7cm)
发际线离头顶<0.7cm时判定分割不可靠,弃用发际线: 顶/上庭字段置null、face_total只算中下庭、标注图保留头顶线去掉发际线、 只标中/下庭。eye1/7竖向范围改用眉心。
This commit is contained in:
+90
-39
@@ -144,9 +144,22 @@ def measure_seven_eyes(landmarks, image_width, image_height):
|
||||
}
|
||||
|
||||
|
||||
def pt_or_none(vertical, name):
|
||||
"""vertical dict 的点 → {"x","y"},值为 None 时返回 None。"""
|
||||
v = vertical.get(name)
|
||||
if v is None:
|
||||
return None
|
||||
return {"x": int(round(v[0])), "y": int(round(v[1]))}
|
||||
|
||||
|
||||
class MeasureResult:
|
||||
"""测量结果,提供 to_response() 输出与接口文档同构的 data 字段。"""
|
||||
|
||||
# 发际线弃用阈值:发际线离头顶(顶庭)< 此值时判定分割不可靠,弃用发际线。
|
||||
# hairline 与 hair_top 几乎重合(如稀疏头发中轴漏检只剩一小撮),说明发际线
|
||||
# 定位无意义 → 顶/上庭置 null、标注图不画头顶/发际线。
|
||||
HAIRLINE_DISCARD_TOP_CM = 0.7
|
||||
|
||||
def __init__(self, vertical, eyes, px_per_cm, hairline_source, head_pose,
|
||||
landmarks=None, image_width=None, image_height=None):
|
||||
self.vertical = vertical
|
||||
@@ -164,7 +177,14 @@ class MeasureResult:
|
||||
self.upper_cm = vertical["upper_court_px"] / px_per_cm
|
||||
self.middle_cm = vertical["middle_court_px"] / px_per_cm
|
||||
self.lower_cm = vertical["lower_court_px"] / px_per_cm
|
||||
self.face_total_cm = self.top_cm + self.upper_cm + self.middle_cm + self.lower_cm
|
||||
# 发际线弃用判定:顶庭(头顶→发际线)过小视为发际线贴近头顶、不可靠。
|
||||
# 弃用时 hairline_source 改为 "discarded",face_total 只算中庭+下庭。
|
||||
self.hairline_discarded = self.top_cm < self.HAIRLINE_DISCARD_TOP_CM
|
||||
if self.hairline_discarded:
|
||||
self.hairline_source = "discarded"
|
||||
self.face_total_cm = self.middle_cm + self.lower_cm
|
||||
else:
|
||||
self.face_total_cm = self.top_cm + self.upper_cm + self.middle_cm + self.lower_cm
|
||||
|
||||
# 七眼厘米
|
||||
self.eye_width_cm = eyes["eye_width_px"] / px_per_cm
|
||||
@@ -172,46 +192,77 @@ class MeasureResult:
|
||||
self.inter_eye_cm = eyes["inter_eye_distance_px"] / px_per_cm
|
||||
|
||||
def to_response(self):
|
||||
total_px = (self.vertical["top_court_px"] + self.vertical["upper_court_px"]
|
||||
+ self.vertical["middle_court_px"] + self.vertical["lower_court_px"])
|
||||
fw_px = self.eyes["face_width_px"]
|
||||
|
||||
def pt(name):
|
||||
x, y = self.vertical[name]
|
||||
return {"x": int(round(x)), "y": int(round(y))}
|
||||
|
||||
data = {
|
||||
"face_total_height_cm": round(self.face_total_cm, 2),
|
||||
"four_courts": {
|
||||
"top_court_cm": round(self.top_cm, 2),
|
||||
"upper_court_cm": round(self.upper_cm, 2),
|
||||
"middle_court_cm": round(self.middle_cm, 2),
|
||||
"lower_court_cm": round(self.lower_cm, 2),
|
||||
"ratios": {
|
||||
"top_court": round(self.vertical["top_court_px"] / total_px, 3),
|
||||
"upper_court": round(self.vertical["upper_court_px"] / total_px, 3),
|
||||
"middle_court": round(self.vertical["middle_court_px"] / total_px, 3),
|
||||
"lower_court": round(self.vertical["lower_court_px"] / total_px, 3),
|
||||
# 发际线弃用:顶/上庭相关字段置 null(保留键),ratio 分母只算中下庭;
|
||||
# landmarks.hair_top/hairline 置 null。否则按四庭正常输出。
|
||||
if self.hairline_discarded:
|
||||
base_px = (self.vertical["middle_court_px"] + self.vertical["lower_court_px"])
|
||||
data = {
|
||||
"face_total_height_cm": round(self.face_total_cm, 2),
|
||||
"four_courts": {
|
||||
"top_court_cm": None,
|
||||
"upper_court_cm": None,
|
||||
"middle_court_cm": round(self.middle_cm, 2),
|
||||
"lower_court_cm": round(self.lower_cm, 2),
|
||||
"ratios": {
|
||||
"top_court": None,
|
||||
"upper_court": None,
|
||||
"middle_court": round(self.vertical["middle_court_px"] / base_px, 3),
|
||||
"lower_court": round(self.vertical["lower_court_px"] / base_px, 3),
|
||||
},
|
||||
},
|
||||
},
|
||||
"seven_eyes": {
|
||||
"eye_width_cm": round(self.eye_width_cm, 2),
|
||||
"face_width_cm": round(self.face_width_cm, 2),
|
||||
"inter_eye_distance_cm": round(self.inter_eye_cm, 2),
|
||||
"ratios": {
|
||||
"eye_width": round(self.eyes["eye_width_px"] / fw_px, 3),
|
||||
"inter_eye_distance": round(self.eyes["inter_eye_distance_px"] / fw_px, 3),
|
||||
"seven_eyes": {
|
||||
"eye_width_cm": round(self.eye_width_cm, 2),
|
||||
"face_width_cm": round(self.face_width_cm, 2),
|
||||
"inter_eye_distance_cm": round(self.inter_eye_cm, 2),
|
||||
"ratios": {
|
||||
"eye_width": round(self.eyes["eye_width_px"] / self.eyes["face_width_px"], 3),
|
||||
"inter_eye_distance": round(self.eyes["inter_eye_distance_px"] / self.eyes["face_width_px"], 3),
|
||||
},
|
||||
},
|
||||
},
|
||||
"landmarks": {
|
||||
"hair_top": pt("hair_top"),
|
||||
"hairline": pt("hairline"),
|
||||
"brow_center": pt("brow_center"),
|
||||
"nose_bottom": pt("nose_bottom"),
|
||||
"chin_tip": pt("chin_tip"),
|
||||
},
|
||||
"hairline_source": self.hairline_source,
|
||||
}
|
||||
"landmarks": {
|
||||
"hair_top": None,
|
||||
"hairline": None,
|
||||
"brow_center": pt_or_none(self.vertical, "brow_center"),
|
||||
"nose_bottom": pt_or_none(self.vertical, "nose_bottom"),
|
||||
"chin_tip": pt_or_none(self.vertical, "chin_tip"),
|
||||
},
|
||||
"hairline_source": self.hairline_source,
|
||||
}
|
||||
else:
|
||||
total_px = (self.vertical["top_court_px"] + self.vertical["upper_court_px"]
|
||||
+ self.vertical["middle_court_px"] + self.vertical["lower_court_px"])
|
||||
data = {
|
||||
"face_total_height_cm": round(self.face_total_cm, 2),
|
||||
"four_courts": {
|
||||
"top_court_cm": round(self.top_cm, 2),
|
||||
"upper_court_cm": round(self.upper_cm, 2),
|
||||
"middle_court_cm": round(self.middle_cm, 2),
|
||||
"lower_court_cm": round(self.lower_cm, 2),
|
||||
"ratios": {
|
||||
"top_court": round(self.vertical["top_court_px"] / total_px, 3),
|
||||
"upper_court": round(self.vertical["upper_court_px"] / total_px, 3),
|
||||
"middle_court": round(self.vertical["middle_court_px"] / total_px, 3),
|
||||
"lower_court": round(self.vertical["lower_court_px"] / total_px, 3),
|
||||
},
|
||||
},
|
||||
"seven_eyes": {
|
||||
"eye_width_cm": round(self.eye_width_cm, 2),
|
||||
"face_width_cm": round(self.face_width_cm, 2),
|
||||
"inter_eye_distance_cm": round(self.inter_eye_cm, 2),
|
||||
"ratios": {
|
||||
"eye_width": round(self.eyes["eye_width_px"] / self.eyes["face_width_px"], 3),
|
||||
"inter_eye_distance": round(self.eyes["inter_eye_distance_px"] / self.eyes["face_width_px"], 3),
|
||||
},
|
||||
},
|
||||
"landmarks": {
|
||||
"hair_top": pt_or_none(self.vertical, "hair_top"),
|
||||
"hairline": pt_or_none(self.vertical, "hairline"),
|
||||
"brow_center": pt_or_none(self.vertical, "brow_center"),
|
||||
"nose_bottom": pt_or_none(self.vertical, "nose_bottom"),
|
||||
"chin_tip": pt_or_none(self.vertical, "chin_tip"),
|
||||
},
|
||||
"hairline_source": self.hairline_source,
|
||||
}
|
||||
# left/right_position:mediapipe 21/251 号定位点(原图像素,与 landmarks 同坐标系)。
|
||||
# landmarks 缺省(如测试直构 MeasureResult)时不输出,保持向后兼容。
|
||||
if self.landmarks is not None and self.w and self.h:
|
||||
|
||||
Reference in New Issue
Block a user