接口1 四庭七眼标注:字体更小、数值带cm、增加百分比、线名右移居中对齐
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -509,7 +509,7 @@ async def _face_measure_impl(image_file, image_url, image_base64, variant="v1"):
|
||||
**标注图片 UI 规范**(真实版本生效):
|
||||
- 字体/线条/箭头颜色:`#FFFFFF 100%`,透明底
|
||||
- 字号/线宽/虚线/箭头按图片短边自适应缩放
|
||||
- 四庭数值(名+数值两行,不带 cm)在图片**左侧**呈现,七眼段宽**上下穿插**展示,底部标「单位cm」
|
||||
- 四庭(名 + 数值带cm + 百分比 三行)在图片**左侧**呈现,七眼段宽**上下穿插**展示(数值带cm,下方另起一行标占头宽百分比)
|
||||
- 横线/竖线渐变消失并略超出端点;段宽/庭高用虚线 + 实心三角双箭头标示
|
||||
- 竖线含人头最左/最右端线(取自头发分割轮廓),共 8 线 7 段
|
||||
""",
|
||||
@@ -614,7 +614,7 @@ async def face_measure(
|
||||
**标注图片 UI 规范**(真实版本生效):
|
||||
- 字体/线条/箭头颜色:`#FFFFFF 100%`,透明底
|
||||
- 字号/线宽/虚线/箭头按图片短边自适应缩放
|
||||
- 三庭数值(名+数值两行,不带 cm)在图片**左侧**呈现,七眼段宽**上下穿插**展示,底部标「单位cm」
|
||||
- 三庭(名 + 数值带cm + 百分比 三行)在图片**左侧**呈现,七眼段宽**上下穿插**展示(数值带cm,下方另起一行标占头宽百分比)
|
||||
- 段宽/庭高用虚线 + 实心三角双箭头标示
|
||||
""",
|
||||
responses={
|
||||
|
||||
+32
-27
@@ -6,9 +6,9 @@
|
||||
- 纵向竖线 8 条:人头最左 + 左脸颊/左眼外/内角/右眼内/外角/右脸颊 + 人头最右,
|
||||
把头宽切 7 段(七眼),段宽数值上下交替(上 3 / 下 4),带虚线双箭头。
|
||||
人头最左/最右取自耳朵分割外缘,看不到耳朵则省略该侧(最少 6 点 5 段)。
|
||||
- 四庭:图片左侧,「名」上「数值」下两行换行(不带 cm),带竖向虚线双箭头。
|
||||
- 四庭:图片左侧,「名」「数值(带 cm)」「百分比」三行换行,带竖向虚线双箭头。
|
||||
- 五条横线右侧标名:头顶/发际线/眉心/鼻翼下缘/下巴尖。
|
||||
- 单位 cm 统一标在底部「单位cm」。
|
||||
- 每段数值直接带 cm 后缀,下方另起一行标百分比(不再单独标底部「单位cm」)。
|
||||
中文字体用打包的思源黑体绝对路径加载,缺字体直接抛错(不静默降级成方块)。
|
||||
"""
|
||||
import os
|
||||
@@ -189,9 +189,8 @@ def create_annotated_image(image_bgr, measure_result, ear_mask=None, hair_mask=N
|
||||
取自耳朵分割掩膜的外缘(方案 B,BiSeNet 类 7/8);耳朵不可见(被头发/侧脸
|
||||
遮挡 → 掩膜空)或无掩膜时省略该侧端线,只画对应脸颊线。
|
||||
- 横向 5 条分界线:头顶/发际线/眉心/鼻翼下缘/下巴尖,右侧标名。
|
||||
- 四庭(顶/上/中/下庭)在左侧:名 + 数值两行换行(无 cm),竖向虚线双箭头。
|
||||
- 七眼段宽数值上下交替(上 3 / 下 4,无 cm),横向虚线双箭头。
|
||||
- 底部统一标「单位cm」。
|
||||
- 四庭(顶/上/中/下庭)在左侧:名 + 数值(带 cm) + 百分比三行换行,竖向虚线双箭头。
|
||||
- 七眼段宽上下交替(上 3 / 下 4):数值(带 cm) 上、百分比(占头宽比)下,横向虚线双箭头。
|
||||
|
||||
variant="v6"(接口6):去掉头顶横线与顶庭(只画发际线/眉心/鼻翼下缘/下巴尖 4 条
|
||||
横线 + 上/中/下庭),竖线纵向范围改为发际线→下巴尖,且不画人头最左/最右端线
|
||||
@@ -203,7 +202,7 @@ def create_annotated_image(image_bgr, measure_result, ear_mask=None, hair_mask=N
|
||||
|
||||
# --- 自适应尺寸:字号/线宽/虚线/箭头按短边缩放 ---
|
||||
s = min(w, h)
|
||||
font_size = max(11, round(s * 0.026)) # 字体更小
|
||||
font_size = max(8, round(s * 0.017)) # 字体更小
|
||||
line_w = max(1, round(s * 0.0022))
|
||||
dash_len = max(4, round(s * 0.008))
|
||||
gap_len = max(2, round(dash_len * 0.7)) # 虚线更稠密(间隙<划线)
|
||||
@@ -256,14 +255,14 @@ def create_annotated_image(image_bgr, measure_result, ear_mask=None, hair_mask=N
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
font = _load_font(font_size)
|
||||
|
||||
# --- 3a. 横线右侧:线名(头顶/发际线/眉心/鼻翼下缘/下巴尖),文字在线上方 ---
|
||||
name_x = fx1 + pad
|
||||
name_gap = max(2, round(pad * 1.6)) # 文字底部到线的间距(再上移)
|
||||
# --- 3a. 横线右侧:线名(头顶/发际线/眉心/鼻翼下缘/下巴尖),文字纵向居中对齐到线 ---
|
||||
name_x = fx1 + over + pad # 移到横线右端外侧一点(往右)
|
||||
for i, name in enumerate(order):
|
||||
text = _LINE_NAMES[name]
|
||||
tw, th = _text_size(draw, text, font)
|
||||
tw, _ = _text_size(draw, text, font)
|
||||
x = min(name_x, w - 2 - tw) # 右侧越界时回收
|
||||
draw.text((x, max(2, ys[i] - th - name_gap)), text, fill=LINE_COLOR, font=font)
|
||||
# anchor="lm":x 为左、y 为竖直中点 → 文字中线正好压在横线上(与线对齐)
|
||||
draw.text((x, ys[i]), text, fill=LINE_COLOR, font=font, anchor="lm")
|
||||
|
||||
# --- 3b. 左侧四庭:名 + 数值两行(无 cm)+ 竖向虚线双箭头 ---
|
||||
if variant == "v6":
|
||||
@@ -276,6 +275,7 @@ def create_annotated_image(image_bgr, measure_result, ear_mask=None, hair_mask=N
|
||||
court_name = ["顶庭", "上庭", "中庭", "下庭"]
|
||||
n_court = 4
|
||||
arrow_x = max(arrow_size + 1, fx0 - pad) # 竖箭头所在 x(脸左侧,贴近最左竖线)
|
||||
court_total = sum(court_cm) or 1.0 # 各庭占比分母 = 四庭(v6 三庭)之和
|
||||
for i in range(n_court):
|
||||
y_a, y_b = ys[i], ys[i + 1]
|
||||
# 竖向虚线双箭头,覆盖该庭高度(略收一点避免压到横线)
|
||||
@@ -283,43 +283,48 @@ def create_annotated_image(image_bgr, measure_result, ear_mask=None, hair_mask=N
|
||||
draw_dashed_line_with_arrows(
|
||||
draw, arrow_x, y_a + inset, arrow_x, y_b - inset,
|
||||
dash_len=dash_len, gap_len=gap_len, arrow_size=arrow_size, width=line_w)
|
||||
# 名 + 数值两行,右对齐到箭头左侧
|
||||
# 名 + 数值(带 cm) + 百分比三行,右对齐到箭头左侧
|
||||
name = court_name[i]
|
||||
val = f"{court_cm[i]:.2f}"
|
||||
val = f"{court_cm[i]:.2f}cm"
|
||||
pct = f"{court_cm[i] / court_total * 100:.1f}%"
|
||||
nw, _ = _text_size(draw, name, font)
|
||||
vw, _ = _text_size(draw, val, font)
|
||||
pw, _ = _text_size(draw, pct, font)
|
||||
label_right = arrow_x - pad
|
||||
y_mid = (y_a + y_b) / 2
|
||||
y_top = y_mid - line_h
|
||||
y_top = y_mid - 1.5 * line_h
|
||||
draw.text((max(2, label_right - nw), y_top), name, fill=LINE_COLOR, font=font)
|
||||
draw.text((max(2, label_right - vw), y_top + line_h), val, fill=LINE_COLOR, font=font)
|
||||
draw.text((max(2, label_right - pw), y_top + 2 * line_h), pct, fill=LINE_COLOR, font=font)
|
||||
|
||||
# --- 4. 七眼每段宽度:上下交替(上 3 / 下 4),横向虚线双箭头 + 数值(无 cm) ---
|
||||
# 文字与箭头间留更大间距,避免文字压住箭头
|
||||
# --- 4. 七眼每段宽度:上下交替(上 3 / 下 4),横向虚线双箭头 + 数值(带 cm) + 百分比 ---
|
||||
# 每段两行:数值(带 cm) 上、百分比 下;百分比分母 = 整个头宽(七段之和)
|
||||
txt_off = arrow_size + pad * 2
|
||||
y_arrow_top = max(txt_off + font_size + 2, fy0 - pad - arrow_size)
|
||||
y_arrow_bot = min(h - txt_off - font_size - 2, fy1 + pad + arrow_size)
|
||||
txt_block = 2 * line_h # 两行文字总高(数值 + 百分比)
|
||||
y_arrow_top = max(txt_off + txt_block + 2, fy0 - pad - arrow_size)
|
||||
y_arrow_bot = min(h - txt_off - txt_block - 2, fy1 + pad + arrow_size)
|
||||
head_w = (xs[-1] - xs[0]) or 1.0 # 头宽(像素)= 百分比分母
|
||||
for i in range(len(xs) - 1):
|
||||
x_a, x_b = xs[i], xs[i + 1]
|
||||
if x_b - x_a < 1:
|
||||
continue
|
||||
seg_cm = (x_b - x_a) / pc
|
||||
seg_pct = (x_b - x_a) / head_w * 100
|
||||
cx_seg = (x_a + x_b) / 2
|
||||
text = f"{seg_cm:.2f}"
|
||||
tw, th = _text_size(draw, text, font)
|
||||
val = f"{seg_cm:.2f}cm"
|
||||
pct = f"{seg_pct:.1f}%"
|
||||
vw, _ = _text_size(draw, val, font)
|
||||
pw, _ = _text_size(draw, pct, font)
|
||||
inset = min(arrow_size, (x_b - x_a) * 0.12)
|
||||
on_top = (i % 2 == 1) # 奇数段在上 → 上 3 / 下 4
|
||||
y_arrow = y_arrow_top if on_top else y_arrow_bot
|
||||
draw_dashed_line_with_arrows(
|
||||
draw, x_a + inset, y_arrow, x_b - inset, y_arrow,
|
||||
dash_len=dash_len, gap_len=gap_len, arrow_size=arrow_size, width=line_w)
|
||||
ty = (y_arrow - th - txt_off) if on_top else (y_arrow + txt_off)
|
||||
draw.text((cx_seg - tw / 2, ty), text, fill=LINE_COLOR, font=font)
|
||||
|
||||
# --- 5. 底部统一单位 ---
|
||||
unit = "单位cm"
|
||||
uw, uh = _text_size(draw, unit, font)
|
||||
draw.text(((w - uw) / 2, h - uh - max(2, pad)), unit, fill=LINE_COLOR, font=font)
|
||||
# 数值行在上、百分比行在下;on_top 时整块置于箭头上方,否则下方
|
||||
text_top = (y_arrow - txt_off - txt_block) if on_top else (y_arrow + txt_off)
|
||||
draw.text((cx_seg - vw / 2, text_top), val, fill=LINE_COLOR, font=font)
|
||||
draw.text((cx_seg - pw / 2, text_top + line_h), pct, fill=LINE_COLOR, font=font)
|
||||
|
||||
return canvas
|
||||
|
||||
|
||||
Reference in New Issue
Block a user