fix(annotation): 标注图重排,8 个量全标注且不重叠
- 修复七眼三个横向量(单眼宽度/两眼间距/脸宽)叠在同一条眼睛线上的重叠问题, 改为上下错开三个高度,各自虚线带箭头 + 紧贴标签 - 新增「全脸总高」标注:右侧竖向标尺(虚线双箭头,头顶→下巴)+ 标签 - 四庭(顶/上/中/下庭)仍在左侧各段中点 - 标签用全名:单眼宽度/两眼间距/脸宽/全脸总高 - 新增 tests/test_annotation.py 防回归 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+44
-21
@@ -69,8 +69,20 @@ def draw_dashed_line_with_arrows(draw, x1, y1, x2, y2, color=LINE_COLOR,
|
||||
draw.line([p2, (ex, ey)], fill=color, width=LINE_WIDTH)
|
||||
|
||||
|
||||
def _text_size(draw, text, font):
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
return bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
|
||||
|
||||
def create_annotated_image(image_bgr, measure_result):
|
||||
"""生成标注图层 PNG(透明底 RGBA,尺寸同原图)。返回 PIL.Image。"""
|
||||
"""生成标注图层 PNG(透明底 RGBA,尺寸同原图)。返回 PIL.Image。
|
||||
|
||||
布局(避免重叠):
|
||||
- 四庭水平分界线(渐变消失),四庭 cm 数值在**左侧**各段中点。
|
||||
- **全脸总高**:右侧竖向标尺(虚线双箭头,头顶→下巴),标签在标尺左侧竖排。
|
||||
- 七眼:单眼宽度 / 两眼间距 / 脸宽 三个量**上下错开三个高度**,
|
||||
各自虚线带箭头 + 紧贴各自线条的标签,互不重叠。
|
||||
"""
|
||||
h, w = image_bgr.shape[:2]
|
||||
v = measure_result.vertical
|
||||
|
||||
@@ -86,41 +98,52 @@ def create_annotated_image(image_bgr, measure_result):
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
font = _load_font()
|
||||
|
||||
# --- 2. 四庭 cm 数值(左侧) ---
|
||||
# --- 2. 四庭 cm 数值(左侧,各段中点) ---
|
||||
court_labels = [
|
||||
("顶庭", measure_result.top_cm),
|
||||
("上庭", measure_result.upper_cm),
|
||||
("中庭", measure_result.middle_cm),
|
||||
("下庭", measure_result.lower_cm),
|
||||
]
|
||||
left_margin = 16
|
||||
left_margin = 12
|
||||
for i, (label, cm_val) in enumerate(court_labels):
|
||||
y_mid = (ys[i] + ys[i + 1]) / 2 - FONT_SIZE / 2
|
||||
draw.text((left_margin, y_mid), f"{label} {cm_val:.2f}cm",
|
||||
fill=LINE_COLOR, font=font)
|
||||
|
||||
# --- 3. 七眼标注(虚线箭头 + 上下穿插标签) ---
|
||||
pts = measure_result.eyes["points"]
|
||||
pc = measure_result.px_per_cm
|
||||
eye_y = (pts["left_inner"][1] + pts["right_inner"][1]) / 2
|
||||
# --- 3. 全脸总高:右侧竖向标尺(头顶→下巴,虚线双箭头) ---
|
||||
top_y, chin_y = ys[0], ys[-1]
|
||||
ruler_x = w - 30 # 内移,使竖线与箭头完整可见
|
||||
draw_dashed_line_with_arrows(draw, ruler_x, top_y, ruler_x, chin_y)
|
||||
th_lines = ["全脸总高", f"{measure_result.face_total_cm:.2f}cm"]
|
||||
th_w = max(_text_size(draw, t, font)[0] for t in th_lines)
|
||||
th_x = ruler_x - 6 - th_w # 标签放标尺左侧,避免出界
|
||||
th_y = (top_y + chin_y) / 2 - FONT_SIZE # 竖向居中(两行)
|
||||
for j, t in enumerate(th_lines):
|
||||
draw.text((th_x, th_y + j * (FONT_SIZE + 3)), t, fill=LINE_COLOR, font=font)
|
||||
|
||||
def hline(p_left, p_right, label, cm_val, above):
|
||||
y = eye_y
|
||||
# --- 4. 七眼:上下错开三个高度,各自虚线箭头 + 标签 ---
|
||||
pts = measure_result.eyes["points"]
|
||||
eye_y = (pts["left_inner"][1] + pts["right_inner"][1]) / 2
|
||||
vgap = FONT_SIZE + 12 # 行间距,保证标签放得下
|
||||
|
||||
def span(p_left, p_right, y, label, cm_val, label_above):
|
||||
draw_dashed_line_with_arrows(draw, p_left[0], y, p_right[0], y)
|
||||
text = f"{label} {cm_val:.2f}cm"
|
||||
tx = (p_left[0] + p_right[0]) / 2
|
||||
ty = y - FONT_SIZE - 4 if above else y + 4
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
draw.text((tx - tw / 2, ty), text, fill=LINE_COLOR, font=font)
|
||||
tw, _ = _text_size(draw, text, font)
|
||||
tx = (p_left[0] + p_right[0]) / 2 - tw / 2
|
||||
ty = y - FONT_SIZE - 3 if label_above else y + 3
|
||||
draw.text((tx, ty), text, fill=LINE_COLOR, font=font)
|
||||
|
||||
# 眼宽(左眼,标签在上)、两眼间距(标签在下)、脸宽(标签在上)—— 上下穿插
|
||||
hline(pts["left_outer"], pts["left_inner"],
|
||||
"眼宽", measure_result.eye_width_cm, above=True)
|
||||
hline(pts["left_inner"], pts["right_inner"],
|
||||
"间距", measure_result.inter_eye_cm, above=False)
|
||||
hline(pts["left_cheek"], pts["right_cheek"],
|
||||
"脸宽", measure_result.face_width_cm, above=True)
|
||||
# 单眼宽度(眼睛线,标签在上)
|
||||
span(pts["left_outer"], pts["left_inner"], eye_y,
|
||||
"单眼宽度", measure_result.eye_width_cm, label_above=True)
|
||||
# 两眼间距(稍下一层,标签在线上方的空档)
|
||||
span(pts["left_inner"], pts["right_inner"], eye_y + vgap,
|
||||
"两眼间距", measure_result.inter_eye_cm, label_above=True)
|
||||
# 脸宽(再下一层,标签在下)
|
||||
span(pts["left_cheek"], pts["right_cheek"], eye_y + 2 * vgap,
|
||||
"脸宽", measure_result.face_width_cm, label_above=False)
|
||||
|
||||
return canvas
|
||||
|
||||
|
||||
Reference in New Issue
Block a user