双问差值(证据用量)
把"先验 vs 证据"变成一个可量化的数字
输入与证据锚点
单张实拍,画面中含一张 A4 纸(297×210mm)
双轴设计
Prompt
共 2 步,按顺序执行;每步可单独折叠。
TURN 1/2A 问不给参照物画面里那个 {target} 大概多大?给出长宽高,单位 mm。
画面里那个 {target} 大概多大?给出长宽高,单位 mm。TURN 2/2B 问以 A4 纸为参照以画面中的 A4 纸(297mm × 210mm)为基准,画面里那个 {target} 大…
以画面中的 A4 纸(297mm × 210mm)为基准,画面里那个 {target} 大概多大?给出长宽高,单位 mm。
另外输出一段 measure.py:输入参照物的四个角点像素坐标、目标的两个端点像素坐标,
输出目标实际尺寸。必须处理透视——如果你选择忽略透视,明确写出这个假设及其适用条件。Rubric 评分表
| 维度 | 满分 | 由谁打 | 判据 |
|---|---|---|---|
| 证据增益 | 40 | 脚本 | gain = err_A - err_B(相对误差之差)。gain <= 0 → 记 0:给了尺子也没用上。 gain > 15 个百分点 → 记满。 |
| 脚本几何正确性 | 35 | 脚本 | 用已知答案的合成图跑它的 measure.py,误差 <5% 记满。只做等比缩放不处理透视记半分。 |
| 假设显式化 | 15 | judge | 是否写清"假设目标与参照物近似共面"这类前提。 |
| 量纲一致 | 10 | 脚本 | 单位统一、无数量级错误。 |
脚本= deterministic,脚本自动计算,不进 judge;judge= 主观维度,必须先看 deterministic 结果再打分。本案例总分归一化到 0-100。
跑批命令
python run_batch.py --models anthropic/claude-opus-4.5 openai/gpt-5.2 google/gemini-3-pro moonshotai/kimi-k2.5 --only C08python score.py --judge anthropic/claude-opus-4.5 --out report.csv数据准备:按 data/C08/manifest.jsonl 的格式放置图片与真值。score 阶段的 judge 只处理本案例 rubric 中非 deterministic 的维度。
实测结果 · Live Run
REAL RUN · 3 MODELS × 同图同 PROMPT · 2026-08-22–24以下是真实调用(经 OpenRouter)的结果:stealth/ox-alpha(2026-08-22,其 B 臂首跑时返回空响应,已于 2026-08-24 以同一 prompt、同一图片重新调用补齐并重打分)、z-ai/glm-5v-turbo 与 moonshotai/kimi-k3(2026-08-23)三个模型各自完整跑过本案例。三个模型面对的输入图片与两步 Prompt 在三份 json 中逐字相同(已逐一核对),故图片与 Prompt 只展示一次,三个模型的回复并列对照。所有图片引用、Prompt、回复原文、脚本分数分别逐字取自 results/C08.json、results/z-ai__glm-5v-turbo/C08.json、results/moonshotai__kimi-k3/C08.json 与 data/C08/,未做任何润色或改写;模型没有回答的地方就如实标「空」,缺分就如实留空。样本规模为演示级 n=1(远小于原始题库设计),所有数字不构成横向结论。模型配色沿用三模型对比页图例。
data/C08/images/scene.png · 460×360 PNG · 实际投喂给模型的原图(未修改;三个模型投喂的是同一张)。注:题目设计原文写的是「单张实拍」,本次数据集实际使用的是脚本合成的示意图(harness/gen_data.py 生成)。{target} 的替换值)A4 绘制约 186×264 px ≈ 0.89 px/mm,与标注 px_per_mm=0.9 一致;但木板绘制宽度仅 162 px(含描边),按 0.9 px/mm 折算恰为 180 mm——生成器画板宽时多乘了一个 0.6 缩放(harness/gen_data.py:plank_w_mm * px_per_mm * 0.6),导致「标注真值 300 mm」与画面对不上。模型 A 问估出的 175–180 mm 与图面实际比例是自洽的。
两个臂的 Prompt 与输入图片在三份 results json 中逐字相同,故各列一次;每臂下按 stealth/ox-alpha → GLM-5V → Kimi K3 顺序给出回复原文与真实用量。所有回复均照录自各 results json 的当前原文(stealth/ox-alpha B 臂为 2026-08-24 补跑后的最新版本),未做任何润色或补写。
ARM AA 问 · 无参照不给提示,看先验三家均有正常回复 · 估长 ox 175–180 / GLM ~110 / K3 ≈180 mm
画面里那个 木板 大概多大?给出长宽高,单位 mm。
根据与旁边 A4 纸(210 × 297 mm)的比例估算: - **长**:约 **175–180 mm**(大约是 A4 高度的 60%) - **厚(图中短边)**:约 **40–45 mm** - **宽(进深方向)**:无法从这张二维图判断,图里没有提供这个维度的信息 也就是说,如果这是木板的侧视图,它大概是一根 **180 × 40 mm 左右的木条/板条**。注意这只是按画面比例估的,实际尺寸可能和标注比例有出入。
根据图中的 **A4 纸**(标准尺寸 **210 mm × 297 mm**)作为参照物进行目测估算,这块“木板”的大致尺寸如下: | 维度 | 估算尺寸 | 说明 | | :--- | :--- | :--- | | **长** | **约 100~120 mm** | 大约是 A4 纸宽度的 **1/2** 左右 | | **宽** | **约 35~45 mm** | 大约是 A4 纸宽度的 **1/5** 左右 | | **高(厚)** | **约 10~20 mm** | 图中为俯视图/正视图,厚度不可见;按常见薄木板或木条估算 | ### 补充说明: 1. **比例依据**:从图中看,木板的长度大约占 A4 纸短边(210mm)的 **50%~60%**,宽度大约占 **20%** 左右。 2. **实际差异**:这只是一个**示意图**,并非精确的工程图。如果是具体的物理/数学题,请以题目给出的具体数值为准(例如有些题目会默认其为 **10cm × 5cm × 2cm** 或 **12cm × 4cm** 等整数值)。 3. **厚度**:如果这是一块“木条”或“垫板”,厚度通常在 **5mm~30mm** 之间;如果只是纸片状的示意图,则厚度可忽略不计。 **简略回答**:长约 **110 mm**,宽约 **40 mm**,厚约 **10~20 mm**(误差范围 ±20%)。
以旁边的 A4 纸(210 × 297 mm)作参照估算,那块木板可见面大约是: - 长:≈ 180 mm - 宽:≈ 40 mm - 高/厚:图里没有显示厚度,无法准确判断;若按常见木板/木条假设,约 15–20 mm,可取 ≈ 18 mm。 所以可记为约 **180 × 40 × 18 mm**(其中 18 mm 是厚度假设值)。
ARM BB 问 · 给 A4 参照 + 索要 measure.py给了尺子的一臂三家均交出完整 measure.py · 估长 ox ≈179–180 / GLM ~150 / K3 ≈181 mm
以画面中的 A4 纸(297mm × 210mm)为基准,画面里那个 木板 大概多大?给出长宽高,单位 mm。 另外输出一段 measure.py:输入参照物的四个角点像素坐标、目标的两个端点像素坐标, 输出目标实际尺寸。必须处理透视——如果你选择忽略透视,明确写出这个假设及其适用条件。
## 量测结果
以图中 A4 纸为基准(竖放:宽 210 mm、高 297 mm),量取像素:
| 物体 | 像素尺寸 | 换算(≈1.12 mm/px) |
|---|---|---|
| A4 纸 | ≈188 × 265 px | 210 × 297 mm(基准,比例自洽:265/188≈1.41≈297/210) |
| 木板 | ≈160 × 36 px | **长 ≈ 180 mm,宽 ≈ 40 mm** |
**关于"高(厚)"**:这张图是正射/俯视式的二维示意,厚度方向没有任何投影信息,**无法从图中测出**。若这是俯视图,40 mm 是板宽、厚度未知;若是主视图,40 mm 是板高、厚度同样未知。任何厚度数值都只能是猜测,不是测量。
## measure.py
```python
"""
measure.py — 用参照物(如A4纸)推算目标实际尺寸
原理: 参照物与目标共面时, 像素坐标 -> 实际平面坐标 可用一个单应矩阵 H 描述,
即使相机斜拍(有透视)也成立。H 由参照物 4 个角点(实际尺寸已知)用 DLT 解出,
目标两端点经 H 映射到实际平面后取欧氏距离即为实际长度。
假设与适用条件(必读):
1. [共面假设] 参照物与被测点在同一物理平面上。若目标有厚度、或端点不在
参照物所在平面, 结果会有偏差, 偏差随离面距离和相机倾角增大。
2. 针孔相机模型, 无明显镜头畸变(广角请先去畸变)。
3. 角点顺序必须与 size_mm 对应: [左上, 右上, 右下, 左下]。
4. method="scale" 为退化模式: 假设正射投影(俯视、无透视, 如本题示意图),
仅适用于光轴垂直于平面的情形。
"""
import numpy as np
from dataclasses import dataclass
@dataclass
class Reference:
corners_px: list # 4x2 像素角点, 顺序: 左上,右上,右下,左下
size_mm: tuple # (宽mm, 高mm), 与角点顺序对应
def homography_dlt(src, dst):
"""4点对应求单应矩阵 (Direct Linear Transform)."""
A = []
for (x, y), (u, v) in zip(src, dst):
A.append([x, y, 1, 0, 0, 0, -u*x, -u*y, -u])
A.append([0, 0, 0, x, y, 1, -v*x, -v*y, -v])
_, _, Vt = np.linalg.svd(np.asarray(A, float))
H = Vt[-1].reshape(3, 3)
return H / H[2, 2]
def apply_h(H, pt):
v = H @ np.array([pt[0], pt[1], 1.0])
return v[:2] / v[2]
def _build_H(ref: Reference):
src = np.asarray(ref.corners_px, float)
# 凸性检查: 排除角点顺序错误
d = np.roll(src, -1, axis=0) - src
cross = d[:, 0]*np.roll(d, -1, axis=0)[:, 1] - d[:, 1]*np.roll(d, -1, axis=0)[:, 0]
if not (np.all(cross > 0) or np.all(cross < 0)):
raise ValueError("角点不是凸四边形, 请检查顺序(应为 左上,右上,右下,左下)")
w_mm, h_mm = ref.size_mm
dst = np.array([[0, 0], [w_mm, 0], [w_mm, h_mm], [0, h_mm]], float)
# 透视强度提示: 像素长宽比 vs 实际长宽比, 1.0≈正射
w_px = (np.linalg.norm(src[1]-src[0]) + np.linalg.norm(src[2]-src[3])) / 2
h_px = (np.linalg.norm(src[3]-src[0]) + np.linalg.norm(src[2]-src[1])) / 2
ratio = (w_px/h_px) / (w_mm/h_mm)
if abs(ratio - 1) > 0.05:
print(f"[提示] 检测到明显透视 (长宽比偏差 {ratio:.2f}x), 已用单应矩阵校正; "
f"前提是目标与参照物共面。")
return homography_dlt(src, dst)
def measure_length(ref: Reference, p1, p2, method="homography") -> float:
"""返回线段 p1-p2 的实际长度 (mm)。"""
if method == "homography": # 处理透视
H = _build_H(ref)
w1, w2 = apply_h(H, p1), apply_h(H, p2)
return float(np.linalg.norm(w1 - w2))
if method == "scale": # 退化: 假设正射/俯视无透视
src = np.asarray(ref.corners_px, float)
w_px = (np.linalg.norm(src[1]-src[0]) + np.linalg.norm(src[2]-src[3])) / 2
h_px = (np.linalg.norm(src[3]-src[0]) + np.linalg.norm(src[2]-src[1])) / 2
w_mm, h_mm = ref.size_mm
s = (w_mm/w_px + h_mm/h_px) / 2
return float(np.linalg.norm(np.subtract(p2, p1, dtype=float)) * s)
raise ValueError("method 须为 'homography' 或 'scale'")
if __name__ == "__main__":
# 本题示意图的手工量取值(±2px 误差)
a4 = Reference(
corners_px=[[40, 40], [228, 40], [228, 305], [40, 305]],
size_mm=(210, 297), # 竖放 A4
)
L = measure_length(a4, (281, 218), (441, 218)) # 木板长边两端点
W = measure_length(a4, (281, 200), (281, 236)) # 木板短边两端点
print(f"木板长 ≈ {L:.0f} mm, 宽 ≈ {W:.0f} mm")
# 输出: 木板长 ≈ 179 mm, 宽 ≈ 40 mm
```
**说明**:
- 两个端点只能测一个维度,所以测宽时对短边再调一次 `measure_length`;若要测厚度,需要侧视图或目标角点的深度信息,单应矩阵无法解决。
- 透视处理用的是 4 点单应变换(DLT),对斜拍照片有效,但**共面假设**是硬前提——本题示意图是正射投影,`homography` 和 `scale` 两种模式结果一致(≈179 × 40 mm)。
**视觉估算结果(基于 A4 纸 297mm × 210mm 为基准):**
| 维度 | 估算值 | 说明 |
|------|--------|------|
| **长度** | **~150 mm** | 约为 A4 短边 (210mm) 的 70%,或长边 (297mm) 的 50% |
| **宽度** | **~45–50 mm** | 约为其自身长度的 1/3,视觉上约为 A4 短边的 22% |
| **厚度** | **~12–18 mm** | 2D 图像无法精确测量,按常见薄木板/木条厚度估算 |
> **注**:以上为基于画面比例的目视估算,实际误差可能在 ±15% 左右。若需精确值,请使用下方代码结合实际拍摄参数计算。
---
### `measure.py` – 透视感知尺寸测量工具
```python
import numpy as np
from typing import List, Tuple, Optional
def _dlt_homography(src_pts: np.ndarray, dst_pts: np.ndarray) -> np.ndarray:
"""
使用 Direct Linear Transform (DLT) 算法计算单应性矩阵 H
使得 dst ≈ H @ src (齐次坐标)
参数:
src_pts: 源点集 (N, 2),如图像像素坐标
dst_pts: 目标点集 (N, 2),如世界坐标 (mm)
返回:
H: 3x3 单应性矩阵
"""
assert src_pts.shape == dst_pts.shape and src_pts.shape[0] >= 4, "至少需要4个点对"
n = src_pts.shape[0]
# 构建方程组 Ah = 0,其中 h 是 H 的展平向量
A = []
for i in range(n):
x, y = src_pts[i]
u, v = dst_pts[i]
A.append([-x, -y, -1, 0, 0, 0, u*x, u*y, u])
A.append([0, 0, 0, -x, -y, -1, v*x, v*y, v])
A = np.array(A)
# SVD 分解求最小二乘解
_, _, Vt = np.linalg.svd(A)
H = Vt[-1].reshape(3, 3)
# 归一化使 H[2,2] = 1
if abs(H[2, 2]) > 1e-10:
H = H / H[2, ]
return H
def _transform_point(H: np.ndarray, pt: np.ndarray) -> np.ndarray:
"""将单个点通过单应性矩阵变换"""
pt_homo = np.array([pt[0], pt[1], 1.0])
transformed = H @ pt_homo
if abs(transformed[2]) < 1e-10:
raise ValueError("变换后点在无穷远处,检查输入点是否合理")
return transformed[:2] / transformed[2]
def measure_object(
ref_pixels: List[Tuple[float, float]], # A4纸四角像素坐标 (左上,右上,右下,左下)
ref_size_mm: Tuple[float, float] = (210.0, 297.0), # A4实际尺寸 (宽,高) mm
target_endpoints: List[Tuple[float, float]] = None, # 木板两端像素坐标
target_width_pts: List[Tuple[float, float]] = None, # 可选:木板宽度方向两点
assume_planar: bool = True # 假设目标与参照物共面
) -> dict:
"""
基于透视校正的实际尺寸计算器
参数:
ref_pixels: 参照物(A4)四个角点的像素坐标,顺序应为:
[左上, 右上, 右下, 左下] 或 [左上, 左下, 右下, 右上](需一致)
ref_size_mm: 参照物实际物理尺寸 (宽度mm, 高度mm)
target_endpoints: 目标物体长度方向的两个端点像素坐标
target_width_pts: 可选,目标物体宽度方向的两个端点(用于计算宽度)
assume_planar: 是否假设目标与A4纸在同一平面上(默认True)
返回:
dict: {
'length_mm': float,
'width_mm': float or None,
'pixel_scale': float, # 参照物中心区域的近似像素/mm比(仅供参考)
'homography_matrix': np.ndarray # 用于调试
}
"""
# 1. 数据预处理
src_pts = np.array(ref_pixels, dtype=np.float64)
if src_pts.shape != (4, 2):
raise ValueError("参照物必须提供恰好4个角点")
# 定义A4纸在世界坐标系中的位置(Z=0平面,单位:mm)
# 假设A4纸左上角为原点(0,0),X向右,Y向下(图像坐标系惯例)
w, h = ref_size_mm
dst_pts = np.array([
[0, 0], # 左上
[w, 0], # 右上
[w, h], # 右下
[0, h] # 左下
], dtype=np.float64)
# 2. 计算单应性矩阵(图像 -> 世界坐标)
try:
H = _dlt_homography(src_pts, dst_pts)
except Exception as e:
raise RuntimeError(f"单应性矩阵计算失败: {e}")
result = {
'homography_matrix': H,
'length_mm': None,
'width_mm': None,
'pixel_scale': None
}
# 3. 计算参照物中心的像素/毫米比例(用于验证和调试)
center_pixel = np.mean(src_pts, axis=0)
# 在中心点附近取微分估计局部尺度
dx_pixel = np.array([10.0, 0.0])
dy_pixel = np.array([0.0, 10.0])
p0 = _transform_point(H, center_pixel)
px = _transform_point(H, center_pixel + dx_pixel)
py = _transform_point(H, center_pixel + dy_pixel)
scale_x = np.linalg.norm(px - p0) / 10.0 # mm per pixel (X方向)
scale_y = np.linalg.norm(py - p0) / 10.0 # mm per pixel (Y方向)
result['pixel_scale'] = (scale_x + scale_y) / 2.0 # 平均尺度
# 4. 计算目标长度
if target_endpoints and len(target_endpoints) >= 2:
p1_img = np.array(target_endpoints[0], dtype=np.float64)
p2_img = np.array(target_endpoints[1], dtype=np.float64)
# 变换到世界坐标
p1_world = _transform_point(H, p1_img)
p2_world = _transform_point(H, p2_img)
# 计算欧氏距离(若共面,此即真实长度;若有深度差,这是投影长度)
length = np.linalg.norm(p2_world - p1_world)
result['length_mm'] = round(float(length), 2)
# 若不共面且已知深度信息,此处可添加深度修正
# 当前假设 planar,故直接使用投影距离
# 5. 计算目标宽度(可选)
if target_width_pts and len(target_width_pts) >= 2:
w1_img = np.array(target_width_pts[0], dtype=np.float64)
w2_img = np.array(target_width_pts[1], dtype=np.float64)
w1_world = _transform_point(H, w1_img)
w2_world = _transform_point(H, w2_img)
width = np.linalg.norm(w2_world - w1_world)
result['width_mm'] = round(float(width), 2)
return result
# ==================== 使用示例 ====================
if __name__ == "__main__":
# 示例数据(请替换为实际图像中的像素坐标)
# 假设A4纸在图像中的四个角点(像素坐标)
a4_corners = [
(100, 50), # 左上
(400, 60), # 右上
(420, 500), # 右下
(80, 480) # 左下
]
# 假设木板的两个端点(像素坐标)
board_ends = [
(500, 300), # 左端
(700, 310) # 右端
]
# 执行测量
measurements = measure_object(
ref_pixels=a4_corners,
ref_size_mm=(210, 297), # A4纸: 宽210mm, 高297mm
target_endpoints=board_ends
)
print(f"木板长度: {measurements['length_mm']} mm")
print(f"参考像素比例: {measurements['pixel_scale']:.2f} mm/pixel")
# 如果有宽度测量点
# board_width_pts = [(520, 280), (520, 330)] # 示例宽度方向两点
# measurements = measure_object(..., target_width_pts=board_width_pts)
# print(f"木板宽度: {measurements['width_mm']} mm")
```
---
### 关于透视处理的说明
#### ✅ 本代码采用的方法:**完全透视校正(单应性变换)**
- **原理**:通过 A4 纸的 4 个角点建立**图像像素 ↔ 物理世界 (mm)** 的映射关系(3×3 单应性矩阵),该矩阵能自动补偿:
- 相机倾斜(俯拍/仰拍)
- 物体旋转(非正对镜头)
- 径向畸变(在一定程度上)
- **适用场景**:任意拍摄角度,只要 A4 纸与待测物体**基本共面**(位于同一平面或接近平行平面)。
#### ⚠️ 如果选择**忽略透视**(简化版本)
**假设条件**:
1. **正交投影/弱透视**:相机光轴垂直于物体平面,或拍摄距离远大于物体尺寸
2. **无镜头畸变**:图像边缘无明显桶形/枕形畸变
3. **平面内测量**:所有待测点与参照物严格共面
**适用条件**:
- 使用手机/相机**正对**桌面拍摄(非斜拍)
- 物体位于图像中心区域(边缘畸变小)
- 对精度要求不高(误差可接受范围 < 5%)
**简化算法**(若满足上述条件,可将 `measure_object` 替换为):
```python
def measure_simple(ref_pixels, ref_size_mm, target_endpoints):
"""忽略透视的简化版本 - 仅适用于正面拍摄"""
# 计算 A4 在图像中的像素尺寸(取两对边平均)
p = np.array(ref_pixels)
width_px = (np.linalg.norm(p[1]-p[0]) + np.linalg.norm(p[2]-p[3])) / 2
height_px = (np.linalg.norm(p[3]-p[0]) + np.linalg.norm(p[2]-p[1])) / 2
# 计算像素/毫米 比(假设各向同性,取平均)
scale = ((width_px/ref_size_mm[0]) + (height_px/ref_size_mm[1])) / 2
# 计算目标长度
p1, p2 = np.array(target_endpoints[0]), np.array(target_endpoints[1])
length_px = np.linalg.norm(p2 - p1)
return length_px / scale
```
#### 📌 关键限制提示
1. **厚度测量**:单张 2D 图像**无法直接测量厚度**(除非已知特定视角下的投影关系或有阴影参照)。代码返回的 `length` 和 `width` 均为平面内的投影尺寸。
2. **共面性要求**:若木板明显翘起或不与 A4 纸平行,计算出的长度会比真实值偏小(透视缩短效应)。此时需引入**双目视觉**或**已知参照物的 3D 姿态**进行补偿。
3. **角点顺序**:输入 `ref_pixels` 时请确保角点顺序与代码中 `dst_pts` 定义的 [左上,右上,右下,左下] 一致,否则会导致镜像或旋转错误。
**读数(按图像像素,原点左上)**
A4 四角 ≈ (83,111)(482,111)(482,854)(83,854);木板长边两端 ≈ (589,556)→(933,556),短边 ≈ (589,556)→(589,656)。
**估计结果**:长 ≈ **181 mm**,宽 ≈ **40 mm**;高(厚度)在此视角**不可见**,只能假定 ≈ **15 mm**(占位)。
⚠️ 一个重要不一致:图中 A4 的像素比 ≈ 743/399 = **1.86**,但真实 A4 = 297/210 = **1.414**。真实相机(方形像素)不可能把一张平的 A4 拍成“仍是正矩形却改变纵横比”,所以这张图更像示意图/被纵向拉伸。四角单应在这里退化为按轴分别标定:sx=210/399≈0.526、sy=297/743≈0.400 mm/px,两者差 ~32%——这正是 181×40 的来源,也说明它不完全可信。若改而坚持方形像素、只用一个比例:用 210 mm 边标定得 **≈181×53 mm**(保持板子自身 344:100≈3.44 的比例,视觉上更吻合);用 297 mm 边标定得 **≈137×40 mm**。比例 长:宽≈3.4 是稳的,绝对尺寸有 ~±30% 不确定。
```python
# measure.py
import numpy as np
def homography(src, dst):
"""src/dst: (4,2) 对应点。返回 H(3x3),使 dst ~ H @ [src,1]。四点精确解。"""
A, b = [], []
for (x, y), (X, Y) in zip(src, dst):
A.append([x, y, 1, 0, 0, 0, -X*x, -X*y]); b.append(X)
A.append([0, 0, 0, x, y, 1, -Y*x, -Y*y]); b.append(Y)
h = np.linalg.solve(np.asarray(A, float), np.asarray(b, float)) # 8x8
return np.array([[h[0], h[1], h[2]],
[h[3], h[4], h[5]],
[h[6], h[7], 1.0]])
# 生产环境可直接用:cv2.getPerspectiveTransform(src.astype(np.float32), dst.astype(np.float32))
def to_plane(H, p):
x, y = p
w = H[2,0]*x + H[2,1]*y + H[2,2]
return np.array([(H[0,0]*x + H[0,1]*y + H[0,2])/w,
(H[1,0]*x + H[1,1]*y + H[1,2])/w])
def measure(ref_corners_px, ref_size_mm, p0_px, p1_px):
"""
ref_corners_px: 参照物四角像素,顺序 TL,TR,BR,BL
ref_size_mm : (宽,高),把 TL->(0,0), TR->(W,0), BR->(W,H), BL->(0,H)
p0_px,p1_px : 目标两个端点像素
返回 : 该线段在参照平面上的实际长度 (mm)。测宽就再调用一次。
"""
W, Hh = ref_size_mm
dst = np.array([[0,0],[W,0],[W,Hh],[0,Hh]], float)
Hm = homography(np.asarray(ref_corners_px, float), dst)
return float(np.linalg.norm(to_plane(Hm, p1_px) - to_plane(Hm, p0_px)))
if __name__ == "__main__":
ref = [(83,111),(482,111),(482,854),(83,854)] # A4 四角(近似读数)
L = measure(ref, (210,297), (589,556), (933,556)) # 长边
Wd= measure(ref, (210,297), (589,556), (589,656)) # 短边
print(f"长≈{L:.1f} mm, 宽≈{Wd:.1f} mm") # -> 长≈181.1 mm, 宽≈40.0 mm
```
**透视处理与假设(必须读)**
- 上面用 4 点求**单应矩阵 H**,对一般(有梯形畸变的)参照物会做真正的透视校正;但本图参照物是正矩形,H 退化为按轴缩放(平行线仍平行,灭点在无穷远)。
- 核心前提:**目标两端点与参照物共面**。单应只能把点投到“参照平面”上。木板有厚度,若你取的是上表面端点而 A4 贴在桌面,存在 ~厚度×tan(倾角) 的视差误差;板越厚、相机越斜,误差越大。要测“高/厚度”或目标不在该平面上,单张图+一个平面单应**不够**,需要侧视图、已知相机高度/倾角,或第二个平面。
- 若你选择**忽略透视**:假设为“参照平面与传感器平行(正射)、方形像素、镜头畸变可忽略、目标与参照共面且也正对相机”。此时 H 退化为单一比例 s = 参照物实际对角线 / 参照物像素对角线,长度 = 像素距离 × s。适用条件:相机光轴垂直于参照平面、目标同平面、无畸变;不满足时请用上面的单应版本。另外,当参照物两轴给出互相矛盾的 s(如本图 sx≠sy),说明参考本身不自洽,应只信一个轴或重拍。
flags:三个模型均为 {no_gain ×1}(n=1)。Rubric 另外三个维度——脚本几何正确性(35)、假设显式化(15)、量纲一致(10)——在三个模型的 score 里都未产出数值(评分脚本对本案例只输出上列三项),如实留空、不以估计值顶替;逐维度说明见下方明细表。
evidence_delta 用 first_number()(正则 -?\d+(?:\.\d+)?)取每臂回复里第一个数字串当估计值。补跑之后,三个模型的六臂回复全部在开头先提到「A4」,于是六个臂无一例外命中「4」→ err = |4−300|/300 ≈ 0.987,err_A 与 err_B 三家完全同值。即本图上的分数完全同值是解析规则所致,而不是测量能力本身——GLM-5V 自报的约 110 / ~150 mm、Kimi K3 自报的 ≈181 mm、ox-alpha 自报的 175–180 mm 都没有被脚本读到。模型真实估读的差异看第②部分原文与第⑤部分观察。
A 问相对误差 |估计−300| / 300。取值偏高的直接原因见下方「打分口径核对」。
B 问相对误差 |估计−300| / 300。与 err_A 同值的原因相同:first_number() 先命中「A4」里的「4」(见下方「打分口径核对」),并非测量本身的结果。
gain = err_A − err_B = 0.987 − 0.987 = 0 ≤ 0 → 记 0。两臂同值并非「尺子无用」的真实测量结论,而是解析口径把两臂都读成了同一个数。
| Rubric 维度(满分) | 由谁打 | 本次结果 | 说明 |
|---|---|---|---|
| 证据增益(40) | 脚本 | 0 分(归一化 0.0) | gain ≤ 0 → 记 0:「给了尺子也没用上」。本样本实情是两臂被解析成同一个数(「A4」的「4」),差值恰为 0。 |
| 脚本几何正确性(35) | 脚本 | 未评分 | 需执行 B 问产出的 measure.py——B 臂现已交出完整脚本(4 点 DLT 单应 + 正射退化模式),但本次运行的 score 只输出三项误差/增益维度,未对脚本做沙箱执行评分。results/C08.json 中该项无数值,如实留空。 |
| 假设显式化(15) | judge | 未评分 | judge 维度;B 问现已有完整文本可评,但本次运行的 score 未产出该项数值(results/C08.json 中无记录),如实留空、不以人工估计值顶替。 |
| 量纲一致(10) | 脚本 | 未评分 | 依赖 B 问输出;B 问现已有输出,但本次运行的 score 未产出该项数值(results/C08.json 中无记录),如实留空。 |
aggregate(n=1):无参照误差 0.987 · 有参照误差 0.987 · 证据增益 0.0 · flags {no_gain ×1}。limitations(results/C08.json 原文):「样本规模为演示级缩小版(1),非原始题库规模」。C08 不涉及沙箱执行类限制,除样本规模外无其它环境性折扣项。
上表数字全部来自 results/C08.json,但其含义需要几句解释:① sc_evidence_delta 用 first_number() 取回复文本里第一个数字当估计值——A 臂回复以「根据与旁边 A4 纸…」开头,正则先命中「A4」里的「4」,于是 err_A = |4−300|/300 ≈ 0.987;B 臂开头是「## 量测结果 / 以图中 A4 纸为基准…」,同样先命中「A4」的「4」,err_B 也 ≈ 0.987 → gain = 0 → 「证据增益 0 + no_gain」。这两个值反映的都是解析规则命中,不代表模型的实际估计质量(A 臂自估 175–180 mm、B 臂换算 ≈179 × 40 mm,相对真值 300 mm 的误差都在四成上下,且与画面实际绘制一致)。② 本案例最想测的「给参照物能否降误差」因此依然没有被真正测到:这次不是因为空回复,而是解析口径把两臂都读成了同一个数;模型在 B 臂实际交出了带 DLT 单应、凸性检查与正射退化模式的完整 measure.py,这些内容都没有进入分数。③ 补跑核对:2026-08-22 首跑时 B 臂曾是一次空回复(text=null → rel_err 记 1.0);2026-08-24 已用同一 prompt、同一图片重新调用并全量重打分,err_B 由 1.000 更新为 0.987、flags 仍为 {no_gain ×1},本页第②部分展示的就是补跑后的原文。此前 22:05 修复 extract_json 严格解析的那轮重算对 evidence_delta 数值无影响(该路径只走 first_number / rel_err、不经 extract_json)。
① A/B 两问都主动找证据且读数与图面自洽:prompt 未提示参照物时,模型自发借用画面里的 A4 做比例校准(175–180 mm),并明确拒答二维图中不可知的进深宽度;给了参照后进一步量取像素做单应换算,得 ≈179–180 × 40 mm,几乎精确对应画面实际绘制的 180 × 40 mm@0.9px/mm;② 与真值标注冲突来自数据生成端:标注真值 300 mm 与画面对不上,根源是生成器画板宽时多乘了一个 0.6 缩放系数(见①处核对卡);③ 对不可知维度保持克制:两臂都拒绝编造厚度/进深,B 臂明说「任何厚度数值都只能是猜测,不是测量」,并在脚本 docstring 里写明共面假设等适用条件;④ 打分解析仍被「A4」带偏:err_A 与 err_B 同为 0.987 全因 first_number() 命中「A4」的「4」,模型真实估读没有进入分数(见上方口径核对);⑤ 以上均为 n=1 的演示级单样本观察。
① A 问三家行为一致:prompt 都没提示参照物,三个模型都自发借画面里的 A4 做比例校准,也都声明二维图给不出进深/厚度——「主动找证据」这一行为本例三家都有。② 估读质量分歧明显,但被上面的分数抹平:Kimi K3 的 ≈181×40 mm、ox-alpha 的 175–180×40–45 mm 都与画面实际绘制的 180×40 mm@0.9px/mm 自洽;GLM-5V 的简略答案约 110 mm 明显偏短(其 B 臂又改口 ~150 mm,前后不一致)。③ B 臂三家都交出了完整 measure.py,路线不同:ox-alpha 用 4 点 DLT 单应加角点凸性检查,另附正射退化模式(homography/scale 双方法,自带示例自检输出 ≈179 × 40 mm);GLM-5V 同样给 DLT 单应透视校正,并附「忽略透视」的简化版本与适用条件;Kimi K3 走得最远——读出图中 A4 与木板的像素坐标,并发现生成图的 A4 长宽比 ≈1.86 与真实 A4 的 1.414 相矛盾,据此判定该图为被拉伸的示意图、只给 ±30% 的绝对尺寸置信度——仍是三者中唯一指出数据集图片自身不自洽的回复(与本页上方「画面比例核对」卡相互印证)。④ 调用量差距可见:completion tokens 合计 ox 7,083(A 878 / B 6,205)/ GLM-5V 6,385 / Kimi K3 13,884,cost 合计 $0 / ≈$0.026 / ≈$0.213。⑤ 以上均为 n=1 的演示级单样本观察,不构成横向结论。
三个模型的调用方式相同(同一套 run_batch.py 风格的 OpenRouter chat/completions 调用、同一批图片、逐字相同的 prompt),但 stealth/ox-alpha 是强制深度推理的 stealth 模型,glm-5v-turbo 与 kimi-k3 未特别配置推理行为——输出风格与推理深度可能天然不同(上面第④条的 token/cost 差距即其一例)。解读对比时请把它当作已知局限,而不是任何一方「作弊」。(此说明与 compare.html 方法论一节同源。)