nix
This commit is contained in:
267
benchmark/stage0_corner_normals.py
Normal file
267
benchmark/stage0_corner_normals.py
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
stage0_corner_normals.py
|
||||
========================
|
||||
Go/No-Go test for corner-based marker orientation.
|
||||
|
||||
The current pipeline triangulates only each marker's CENTER (one point) and
|
||||
copies the normal from robot.json. This script instead triangulates the 4
|
||||
ArUco CORNERS of every marker (multi-view) from the existing detection + pose
|
||||
JSONs, derives the marker normal from the triangulated corner plane, and
|
||||
compares it against the ground-truth normal from render_*.json.
|
||||
|
||||
It answers one question, without touching the pipeline:
|
||||
|
||||
How accurate is a normal derived purely from triangulated corners?
|
||||
|
||||
Inputs (defaults target Scene8):
|
||||
--evalDir data/evaluations/Scene8 (render_*_aruco_detection.json + _camera_pose.json)
|
||||
--gt data/simulation/Scene8/render_a.json (ground-truth marker poses)
|
||||
|
||||
Output: per-link + overall statistics of the normal angle error (deg),
|
||||
a text histogram, and optional JSON.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Loading
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def load_cameras(eval_dir: str) -> Dict[str, dict]:
|
||||
"""Load intrinsics, world->cam pose and per-marker 4-corner pixels per camera."""
|
||||
cams: Dict[str, dict] = {}
|
||||
for det_path in glob.glob(os.path.join(eval_dir, "*_aruco_detection.json")):
|
||||
base = os.path.basename(det_path)
|
||||
m = re.match(r"render_([A-Za-z0-9]+)_aruco_detection\.json", base)
|
||||
if not m:
|
||||
continue
|
||||
cam_id = m.group(1)
|
||||
pose_path = os.path.join(eval_dir, f"render_{cam_id}_camera_pose.json")
|
||||
if not os.path.exists(pose_path):
|
||||
print(f"[WARN] no pose for camera {cam_id}, skipping")
|
||||
continue
|
||||
|
||||
det = json.load(open(det_path, "r", encoding="utf-8"))
|
||||
pose = json.load(open(pose_path, "r", encoding="utf-8"))
|
||||
|
||||
K = np.array(det["camera"]["camera_matrix"], dtype=float).reshape(3, 3)
|
||||
D = np.array(det["camera"]["distortion_coefficients"], dtype=float).reshape(-1, 1)
|
||||
|
||||
w2c = pose["camera_pose"]["world_to_camera"]
|
||||
R = np.array(w2c["rotation_matrix"], dtype=float).reshape(3, 3)
|
||||
t = np.array(w2c["translation_m"], dtype=float).reshape(3)
|
||||
|
||||
markers: Dict[int, np.ndarray] = {}
|
||||
for d in det.get("detections", []):
|
||||
pts = d.get("image_points_px")
|
||||
if pts is None:
|
||||
continue
|
||||
markers[int(d["marker_id"])] = np.array(pts, dtype=float).reshape(4, 2)
|
||||
|
||||
cams[cam_id] = dict(K=K, D=D, R=R, t=t, markers=markers)
|
||||
return cams
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Geometry
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def triangulate_multiview(observations: List[Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]]) -> np.ndarray:
|
||||
"""
|
||||
DLT triangulation of one 3D point from N cameras.
|
||||
observations: list of (K, D, R_wc, t_wc, uv_pixel).
|
||||
Uses undistorted normalized coordinates so P = [R | t].
|
||||
"""
|
||||
A = []
|
||||
for K, D, R, t, uv in observations:
|
||||
und = cv2.undistortPoints(np.array([[uv]], dtype=np.float32), K, D).reshape(2)
|
||||
x, y = float(und[0]), float(und[1])
|
||||
P = np.hstack([R, t.reshape(3, 1)]) # 3x4 normalized projection
|
||||
A.append(x * P[2] - P[0])
|
||||
A.append(y * P[2] - P[1])
|
||||
A = np.asarray(A, dtype=float)
|
||||
_, _, Vt = np.linalg.svd(A)
|
||||
X = Vt[-1]
|
||||
if abs(X[3]) < 1e-12:
|
||||
return np.array([np.nan, np.nan, np.nan])
|
||||
return X[:3] / X[3]
|
||||
|
||||
|
||||
def corner_plane_normal(corners3d: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Best-fit plane normal through the 4 triangulated corners (SVD), with the
|
||||
sign fixed by the ArUco corner ordering (right-hand rule on the first edges).
|
||||
Returns (unit_normal, center).
|
||||
"""
|
||||
center = corners3d.mean(axis=0)
|
||||
centered = corners3d - center
|
||||
_, _, Vt = np.linalg.svd(centered)
|
||||
n = Vt[-1]
|
||||
# ArUco corners are clockwise seen from the front, so the outward (camera-
|
||||
# facing) marker normal — matching the Blender ground-truth convention —
|
||||
# points opposite to cross(edge_01, edge_02). Align the sign to that.
|
||||
cross = np.cross(corners3d[1] - corners3d[0], corners3d[2] - corners3d[0])
|
||||
if np.dot(n, cross) > 0:
|
||||
n = -n
|
||||
nn = np.linalg.norm(n)
|
||||
return (n / nn if nn > 1e-12 else n), center
|
||||
|
||||
|
||||
def angle_between_deg(a: np.ndarray, b: np.ndarray) -> float:
|
||||
na, nb = np.linalg.norm(a), np.linalg.norm(b)
|
||||
if na < 1e-12 or nb < 1e-12:
|
||||
return float("nan")
|
||||
c = float(np.clip(np.dot(a, b) / (na * nb), -1.0, 1.0))
|
||||
return math.degrees(math.acos(c))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Reporting
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def stats(values: List[float]) -> dict:
|
||||
if not values:
|
||||
return dict(n=0)
|
||||
arr = np.array(values, dtype=float)
|
||||
return dict(
|
||||
n=len(arr),
|
||||
mean=float(arr.mean()),
|
||||
median=float(np.median(arr)),
|
||||
p90=float(np.percentile(arr, 90)),
|
||||
max=float(arr.max()),
|
||||
)
|
||||
|
||||
|
||||
def text_histogram(values: List[float], bins: List[float]) -> str:
|
||||
counts = [0] * (len(bins) + 1)
|
||||
for v in values:
|
||||
placed = False
|
||||
for i, b in enumerate(bins):
|
||||
if v < b:
|
||||
counts[i] += 1
|
||||
placed = True
|
||||
break
|
||||
if not placed:
|
||||
counts[-1] += 1
|
||||
total = max(1, len(values))
|
||||
lines = []
|
||||
edges = [f"<{bins[0]:g}"] + [f"{bins[i-1]:g}-{bins[i]:g}" for i in range(1, len(bins))] + [f">={bins[-1]:g}"]
|
||||
for label, c in zip(edges, counts):
|
||||
bar = "#" * int(round(40 * c / total))
|
||||
lines.append(f" {label:>10}deg | {c:3d} | {bar}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="Stage 0: corner-derived normal accuracy vs ground truth")
|
||||
ap.add_argument("--evalDir", default="data/evaluations/Scene8",
|
||||
help="folder with render_*_aruco_detection.json + _camera_pose.json")
|
||||
ap.add_argument("--gt", default="data/simulation/Scene8/render_a.json",
|
||||
help="ground-truth marker JSON (render_*.json)")
|
||||
ap.add_argument("--minCams", type=int, default=2, help="min cameras to triangulate a marker")
|
||||
ap.add_argument("--out", default=None, help="optional JSON output path")
|
||||
args = ap.parse_args()
|
||||
|
||||
cams = load_cameras(args.evalDir)
|
||||
print(f"[INFO] Cameras: {sorted(cams.keys())}")
|
||||
if len(cams) < 2:
|
||||
print("[ERROR] need >=2 cameras")
|
||||
return
|
||||
|
||||
gt = {int(m["id"]): m for m in json.load(open(args.gt, "r", encoding="utf-8"))}
|
||||
print(f"[INFO] Ground-truth markers: {len(gt)}")
|
||||
|
||||
marker_cams: Dict[int, List[str]] = defaultdict(list)
|
||||
for cid, cam in cams.items():
|
||||
for mid in cam["markers"]:
|
||||
marker_cams[mid].append(cid)
|
||||
|
||||
results = []
|
||||
for mid, cam_ids in sorted(marker_cams.items()):
|
||||
if len(cam_ids) < args.minCams or mid not in gt:
|
||||
continue
|
||||
corners3d = []
|
||||
ok = True
|
||||
for ci in range(4):
|
||||
obs = [(cams[c]["K"], cams[c]["D"], cams[c]["R"], cams[c]["t"], cams[c]["markers"][mid][ci])
|
||||
for c in cam_ids]
|
||||
X = triangulate_multiview(obs)
|
||||
if not np.all(np.isfinite(X)):
|
||||
ok = False
|
||||
break
|
||||
corners3d.append(X)
|
||||
if not ok:
|
||||
continue
|
||||
corners3d = np.array(corners3d)
|
||||
n_meas, center = corner_plane_normal(corners3d)
|
||||
n_gt = np.array(gt[mid]["normal"], dtype=float)
|
||||
|
||||
a_signed = angle_between_deg(n_meas, n_gt)
|
||||
a_flip = min(a_signed, 180.0 - a_signed)
|
||||
center_err_mm = float(np.linalg.norm(center - np.array(gt[mid]["position_m"], dtype=float)) * 1000.0)
|
||||
edge_mm = float(np.mean([np.linalg.norm(corners3d[(i + 1) % 4] - corners3d[i]) for i in range(4)]) * 1000.0)
|
||||
|
||||
results.append(dict(
|
||||
id=mid, link=gt[mid].get("link", "?"), n_cams=len(cam_ids),
|
||||
angle_signed_deg=a_signed, angle_flip_deg=a_flip,
|
||||
center_err_mm=center_err_mm, edge_mm=edge_mm,
|
||||
))
|
||||
|
||||
if not results:
|
||||
print("[ERROR] no markers triangulated")
|
||||
return
|
||||
|
||||
# ---- per-link breakdown ----
|
||||
by_link: Dict[str, List[dict]] = defaultdict(list)
|
||||
for r in results:
|
||||
by_link[r["link"]].append(r)
|
||||
|
||||
print(f"\n{'='*70}\nCORNER-DERIVED NORMAL ERROR vs GROUND TRUTH ({len(results)} markers)\n{'='*70}")
|
||||
print(f"{'link':>10} | {'n':>3} | {'normalErr(flip) mean/med/p90/max [deg]':>40} | {'ctrErr':>7} | {'edge':>6}")
|
||||
print("-" * 95)
|
||||
order = sorted(by_link.keys(), key=lambda k: -len(by_link[k]))
|
||||
for link in order:
|
||||
rs = by_link[link]
|
||||
af = stats([r["angle_flip_deg"] for r in rs])
|
||||
ce = np.mean([r["center_err_mm"] for r in rs])
|
||||
ed = np.mean([r["edge_mm"] for r in rs])
|
||||
print(f"{link:>10} | {af['n']:>3} | "
|
||||
f"{af['mean']:7.2f} /{af['median']:6.2f} /{af['p90']:6.2f} /{af['max']:6.2f} | "
|
||||
f"{ce:6.1f}mm | {ed:5.1f}mm")
|
||||
|
||||
all_flip = [r["angle_flip_deg"] for r in results]
|
||||
all_signed = [r["angle_signed_deg"] for r in results]
|
||||
a = stats(all_flip)
|
||||
print("-" * 95)
|
||||
print(f"{'ALL':>10} | {a['n']:>3} | "
|
||||
f"{a['mean']:7.2f} /{a['median']:6.2f} /{a['p90']:6.2f} /{a['max']:6.2f} |")
|
||||
|
||||
# sign consistency: how many are 'flipped' (>90 deg signed)
|
||||
flipped = sum(1 for s in all_signed if s > 90.0)
|
||||
print(f"\n[INFO] sign: {flipped}/{len(all_signed)} markers have signed angle >90deg "
|
||||
f"(consistent flip = trivially fixable; mixed = corner-order issue)")
|
||||
print(f"[INFO] GT marker edge length is 25.0mm — triangulated mean edge tells corner-triangulation quality.")
|
||||
|
||||
print(f"\nHistogram of normal error (flip-invariant), {len(all_flip)} markers:")
|
||||
print(text_histogram(all_flip, [1, 2, 5, 10, 20, 45]))
|
||||
|
||||
if args.out:
|
||||
json.dump({"results": results, "overall": a}, open(args.out, "w", encoding="utf-8"), indent=2)
|
||||
print(f"\n[INFO] wrote {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
515
benchmark/stage0_scene8.json
Normal file
515
benchmark/stage0_scene8.json
Normal file
@@ -0,0 +1,515 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": 41,
|
||||
"link": "FingerA",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 1.1434420142628594,
|
||||
"angle_flip_deg": 1.1434420142628594,
|
||||
"center_err_mm": 0.44066643086764096,
|
||||
"edge_mm": 24.016180354604842
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"link": "FingerA",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 0.7690528091207107,
|
||||
"angle_flip_deg": 0.7690528091207107,
|
||||
"center_err_mm": 0.47138540733240264,
|
||||
"edge_mm": 24.71301484445469
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"link": "FingerB",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.4318713194101393,
|
||||
"angle_flip_deg": 1.4318713194101393,
|
||||
"center_err_mm": 1.3060871755679715,
|
||||
"edge_mm": 24.559877670993526
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"link": "FingerB",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 1.4354091396273663,
|
||||
"angle_flip_deg": 1.4354091396273663,
|
||||
"center_err_mm": 0.49591116365549415,
|
||||
"edge_mm": 24.275020717296524
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 1.546856095969066,
|
||||
"angle_flip_deg": 1.546856095969066,
|
||||
"center_err_mm": 0.5153058457174745,
|
||||
"edge_mm": 23.732848555198473
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 0.4384043190770205,
|
||||
"angle_flip_deg": 0.4384043190770205,
|
||||
"center_err_mm": 0.3654847714391029,
|
||||
"edge_mm": 23.78437453104651
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 3.06869698457462,
|
||||
"angle_flip_deg": 3.06869698457462,
|
||||
"center_err_mm": 0.7361951285731234,
|
||||
"edge_mm": 24.111903805060948
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.8369743799079993,
|
||||
"angle_flip_deg": 0.8369743799079993,
|
||||
"center_err_mm": 0.4103871642969998,
|
||||
"edge_mm": 23.584999176949037
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 7.0704376693875295,
|
||||
"angle_flip_deg": 7.0704376693875295,
|
||||
"center_err_mm": 0.3377764024826177,
|
||||
"edge_mm": 23.502812403745992
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 0.5805708551806682,
|
||||
"angle_flip_deg": 0.5805708551806682,
|
||||
"center_err_mm": 0.4294083846132877,
|
||||
"edge_mm": 23.63991754007732
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.4444651006662814,
|
||||
"angle_flip_deg": 1.4444651006662814,
|
||||
"center_err_mm": 0.4211345799124642,
|
||||
"edge_mm": 23.797367435632474
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 1.0125201906777848,
|
||||
"angle_flip_deg": 1.0125201906777848,
|
||||
"center_err_mm": 0.42801314700148346,
|
||||
"edge_mm": 23.62386484154858
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 2.6210545533711227,
|
||||
"angle_flip_deg": 2.6210545533711227,
|
||||
"center_err_mm": 0.4749803803977136,
|
||||
"edge_mm": 23.77203259223079
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 3.072194121255888,
|
||||
"angle_flip_deg": 3.072194121255888,
|
||||
"center_err_mm": 0.6970136421239362,
|
||||
"edge_mm": 22.261411657808225
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 1.5093452673644796,
|
||||
"angle_flip_deg": 1.5093452673644796,
|
||||
"center_err_mm": 0.5892317628649099,
|
||||
"edge_mm": 23.687621228611007
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.7265363196160926,
|
||||
"angle_flip_deg": 1.7265363196160926,
|
||||
"center_err_mm": 0.7443757993233989,
|
||||
"edge_mm": 23.573066344516413
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"link": "Board",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 1.5794667324616416,
|
||||
"angle_flip_deg": 1.5794667324616416,
|
||||
"center_err_mm": 0.38746648221661434,
|
||||
"edge_mm": 23.91280234762309
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 1.067206162586506,
|
||||
"angle_flip_deg": 1.067206162586506,
|
||||
"center_err_mm": 0.47008892710515954,
|
||||
"edge_mm": 23.540483661229402
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.2374042106636467,
|
||||
"angle_flip_deg": 1.2374042106636467,
|
||||
"center_err_mm": 0.48097116988306843,
|
||||
"edge_mm": 23.579267072106983
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 2.331907164327804,
|
||||
"angle_flip_deg": 2.331907164327804,
|
||||
"center_err_mm": 0.7556520773496269,
|
||||
"edge_mm": 23.886317251793574
|
||||
},
|
||||
{
|
||||
"id": 72,
|
||||
"link": "Board",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 0.9756163609132039,
|
||||
"angle_flip_deg": 0.9756163609132039,
|
||||
"center_err_mm": 0.3559113756299301,
|
||||
"edge_mm": 23.912165684275976
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.6450376932823634,
|
||||
"angle_flip_deg": 1.6450376932823634,
|
||||
"center_err_mm": 0.5559668493434247,
|
||||
"edge_mm": 23.691809129413368
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.5908298530562284,
|
||||
"angle_flip_deg": 1.5908298530562284,
|
||||
"center_err_mm": 0.43818306200399165,
|
||||
"edge_mm": 23.83519637358736
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 1.9555389722730987,
|
||||
"angle_flip_deg": 1.9555389722730987,
|
||||
"center_err_mm": 0.6330383990904807,
|
||||
"edge_mm": 23.905818442604684
|
||||
},
|
||||
{
|
||||
"id": 82,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 5.64999716553466,
|
||||
"angle_flip_deg": 5.64999716553466,
|
||||
"center_err_mm": 0.45896967390213783,
|
||||
"edge_mm": 23.96349217834595
|
||||
},
|
||||
{
|
||||
"id": 83,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 1.3394651650608178,
|
||||
"angle_flip_deg": 1.3394651650608178,
|
||||
"center_err_mm": 0.5125771544940219,
|
||||
"edge_mm": 23.57790846394849
|
||||
},
|
||||
{
|
||||
"id": 84,
|
||||
"link": "Board",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 1.5903943120945165,
|
||||
"angle_flip_deg": 1.5903943120945165,
|
||||
"center_err_mm": 0.46852115904452035,
|
||||
"edge_mm": 23.782504641797235
|
||||
},
|
||||
{
|
||||
"id": 85,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 0.16007549674022167,
|
||||
"angle_flip_deg": 0.16007549674022167,
|
||||
"center_err_mm": 0.5959941236153405,
|
||||
"edge_mm": 23.456770837034693
|
||||
},
|
||||
{
|
||||
"id": 86,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 2.910409435547614,
|
||||
"angle_flip_deg": 2.910409435547614,
|
||||
"center_err_mm": 0.5941332401694139,
|
||||
"edge_mm": 23.616499433317873
|
||||
},
|
||||
{
|
||||
"id": 92,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.703424991461952,
|
||||
"angle_flip_deg": 0.703424991461952,
|
||||
"center_err_mm": 0.34516139006794366,
|
||||
"edge_mm": 23.589186098093407
|
||||
},
|
||||
{
|
||||
"id": 95,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 0.9077064412203442,
|
||||
"angle_flip_deg": 0.9077064412203442,
|
||||
"center_err_mm": 0.4679557488019616,
|
||||
"edge_mm": 23.640819756206223
|
||||
},
|
||||
{
|
||||
"id": 96,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.6787246328111383,
|
||||
"angle_flip_deg": 0.6787246328111383,
|
||||
"center_err_mm": 0.42096508219349743,
|
||||
"edge_mm": 23.615436793373675
|
||||
},
|
||||
{
|
||||
"id": 97,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 0.6173651478477283,
|
||||
"angle_flip_deg": 0.6173651478477283,
|
||||
"center_err_mm": 0.32409567465563904,
|
||||
"edge_mm": 23.529095190528608
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 1.2014930081148716,
|
||||
"angle_flip_deg": 1.2014930081148716,
|
||||
"center_err_mm": 0.8067803284068574,
|
||||
"edge_mm": 23.658019025511702
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"link": "Board",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 1.0645869855879095,
|
||||
"angle_flip_deg": 1.0645869855879095,
|
||||
"center_err_mm": 0.3981807809406007,
|
||||
"edge_mm": 23.681565447124076
|
||||
},
|
||||
{
|
||||
"id": 105,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 0.18416716706733607,
|
||||
"angle_flip_deg": 0.18416716706733607,
|
||||
"center_err_mm": 0.339889092552321,
|
||||
"edge_mm": 23.335067539509037
|
||||
},
|
||||
{
|
||||
"id": 114,
|
||||
"link": "Arm2",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 1.035150201301489,
|
||||
"angle_flip_deg": 1.035150201301489,
|
||||
"center_err_mm": 0.4746035317615624,
|
||||
"edge_mm": 24.913426591026695
|
||||
},
|
||||
{
|
||||
"id": 115,
|
||||
"link": "Arm2",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.9021760629087481,
|
||||
"angle_flip_deg": 0.9021760629087481,
|
||||
"center_err_mm": 0.6218495698238082,
|
||||
"edge_mm": 24.362103277922028
|
||||
},
|
||||
{
|
||||
"id": 120,
|
||||
"link": "Arm2",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.5569138868384742,
|
||||
"angle_flip_deg": 0.5569138868384742,
|
||||
"center_err_mm": 0.4409799514799929,
|
||||
"edge_mm": 24.53632605870948
|
||||
},
|
||||
{
|
||||
"id": 198,
|
||||
"link": "Arm1",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 0.937935423310891,
|
||||
"angle_flip_deg": 0.937935423310891,
|
||||
"center_err_mm": 0.3747023863955182,
|
||||
"edge_mm": 23.855002161679018
|
||||
},
|
||||
{
|
||||
"id": 205,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.7203600028293877,
|
||||
"angle_flip_deg": 1.7203600028293877,
|
||||
"center_err_mm": 0.3235782454486941,
|
||||
"edge_mm": 23.81796815521869
|
||||
},
|
||||
{
|
||||
"id": 206,
|
||||
"link": "Board",
|
||||
"n_cams": 3,
|
||||
"angle_signed_deg": 2.236776293627657,
|
||||
"angle_flip_deg": 2.236776293627657,
|
||||
"center_err_mm": 0.8150367716541782,
|
||||
"edge_mm": 23.7299964756003
|
||||
},
|
||||
{
|
||||
"id": 207,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 3.752570841912504,
|
||||
"angle_flip_deg": 3.752570841912504,
|
||||
"center_err_mm": 1.3596114357486246,
|
||||
"edge_mm": 22.924583680006528
|
||||
},
|
||||
{
|
||||
"id": 208,
|
||||
"link": "Board",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 0.15504134243057513,
|
||||
"angle_flip_deg": 0.15504134243057513,
|
||||
"center_err_mm": 0.34416967908488894,
|
||||
"edge_mm": 23.893344121965868
|
||||
},
|
||||
{
|
||||
"id": 210,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.8675183900515165,
|
||||
"angle_flip_deg": 0.8675183900515165,
|
||||
"center_err_mm": 0.39827550357589053,
|
||||
"edge_mm": 23.865763944763213
|
||||
},
|
||||
{
|
||||
"id": 211,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 6.778964810293535,
|
||||
"angle_flip_deg": 6.778964810293535,
|
||||
"center_err_mm": 0.6128161118740145,
|
||||
"edge_mm": 28.95194659717587
|
||||
},
|
||||
{
|
||||
"id": 214,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 2.263369686442294,
|
||||
"angle_flip_deg": 2.263369686442294,
|
||||
"center_err_mm": 0.41274063853843634,
|
||||
"edge_mm": 24.328457239006813
|
||||
},
|
||||
{
|
||||
"id": 215,
|
||||
"link": "Board",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 0.6839043688207129,
|
||||
"angle_flip_deg": 0.6839043688207129,
|
||||
"center_err_mm": 0.3731837721514992,
|
||||
"edge_mm": 23.315766350549882
|
||||
},
|
||||
{
|
||||
"id": 217,
|
||||
"link": "Board",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.5080287913248831,
|
||||
"angle_flip_deg": 0.5080287913248831,
|
||||
"center_err_mm": 0.6933389330998323,
|
||||
"edge_mm": 23.756701261435616
|
||||
},
|
||||
{
|
||||
"id": 219,
|
||||
"link": "Arm2",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 0.6129175586042959,
|
||||
"angle_flip_deg": 0.6129175586042959,
|
||||
"center_err_mm": 0.625908527719747,
|
||||
"edge_mm": 24.523390487648925
|
||||
},
|
||||
{
|
||||
"id": 229,
|
||||
"link": "Arm1",
|
||||
"n_cams": 4,
|
||||
"angle_signed_deg": 0.2605199187659484,
|
||||
"angle_flip_deg": 0.2605199187659484,
|
||||
"center_err_mm": 0.32836977551949925,
|
||||
"edge_mm": 23.868570044407132
|
||||
},
|
||||
{
|
||||
"id": 232,
|
||||
"link": "Ellbow",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.1784768319184977,
|
||||
"angle_flip_deg": 1.1784768319184977,
|
||||
"center_err_mm": 0.3146279496116689,
|
||||
"edge_mm": 24.565060454760506
|
||||
},
|
||||
{
|
||||
"id": 243,
|
||||
"link": "Arm1",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 0.7226952116246294,
|
||||
"angle_flip_deg": 0.7226952116246294,
|
||||
"center_err_mm": 0.32911426133868327,
|
||||
"edge_mm": 24.30342997031465
|
||||
},
|
||||
{
|
||||
"id": 244,
|
||||
"link": "Ellbow",
|
||||
"n_cams": 2,
|
||||
"angle_signed_deg": 1.9945025838741741,
|
||||
"angle_flip_deg": 1.9945025838741741,
|
||||
"center_err_mm": 0.8529221300355752,
|
||||
"edge_mm": 24.12077132350216
|
||||
},
|
||||
{
|
||||
"id": 245,
|
||||
"link": "Ellbow",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 0.40470626965848056,
|
||||
"angle_flip_deg": 0.40470626965848056,
|
||||
"center_err_mm": 0.49781974099512044,
|
||||
"edge_mm": 23.935770756719187
|
||||
},
|
||||
{
|
||||
"id": 248,
|
||||
"link": "Ellbow",
|
||||
"n_cams": 5,
|
||||
"angle_signed_deg": 0.25927270212557935,
|
||||
"angle_flip_deg": 0.25927270212557935,
|
||||
"center_err_mm": 0.3563043915502283,
|
||||
"edge_mm": 24.24622073478565
|
||||
}
|
||||
],
|
||||
"overall": {
|
||||
"n": 56,
|
||||
"mean": 1.5523294538712056,
|
||||
"median": 1.1609594230906786,
|
||||
"p90": 2.989553210061117,
|
||||
"max": 7.0704376693875295
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user