weight apply = 0 running

This commit is contained in:
chk
2026-05-28 23:20:17 +02:00
parent 409dca204d
commit ade18bf25b
14 changed files with 7656 additions and 670 deletions

View File

@@ -149,9 +149,9 @@ class ObservationQualityConfig:
sharpness_ref: float = 2500.0 sharpness_ref: float = 2500.0
homography_ref: float = 0.18 homography_ref: float = 0.18
# factor f in min(1, q + f) # factor f scales the effect of each quality indicator:
# f = 1 -> factor disabled / neutral # f = 1 -> indicator is fully active
# f = 0 -> factor fully active # f = 0 -> indicator is ignored (neutral weight = 1)
size_factor: float = 1.0 size_factor: float = 1.0
aspect_factor: float = 1.0 aspect_factor: float = 1.0
border_factor: float = 1.0 border_factor: float = 1.0
@@ -341,9 +341,12 @@ def compute_marker_world_corners(marker: Dict[str, Any], link_transforms: Dict[s
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def quality_factor(q: float, f: float) -> float: def quality_factor(q: float, f: float) -> float:
# Saturating factor in [0, 1]. # Interpret f in [0, 1] as the per-indicator influence.
# f = 1.0 -> ignore this q-indicator completely. # f = 1.0 -> indicator is fully active; q is applied.
return clamp01(q + f) # f = 0.0 -> indicator is ignored and contributes a neutral weight of 1.0.
q = clamp01(q)
f = clamp01(f)
return 1.0 - f + f * q
def projective_homography_quality(image_points_px: np.ndarray, image_shape: Tuple[int, int], ref: float) -> float: def projective_homography_quality(image_points_px: np.ndarray, image_shape: Tuple[int, int], ref: float) -> float:

View File

@@ -0,0 +1,706 @@
#!/usr/bin/env python3
"""
============================================================
STEP 2b — Simultane Multiview-Optimierung für Roboterpose
============================================================
Ziel:
Aus mehreren ArUco-Detektionsdateien die gemeinsame
Roboterpose (x,y,z,a,b,c,e) schätzen und jede Kamera-Pose
sowie Marker-Weltpositionen ausgeben.
Eingabe:
--robot ../robot.json
--detections render_1a_aruco_detection.json render_1b_aruco_detection.json ...
--outDir .
Ausgabe:
multiview_pose.json
Hinweis:
Dieses Skript verwendet die Markerpositionen aus robot.json
als kinematische Constraints und optimiert gleichzeitig:
- Roboterzustand (x,y,z,a,b,c,e)
- Kameraextrinsische Parameter pro Bild
"""
import argparse
import datetime
import json
import math
import os
import time
from pathlib import Path
from typing import Any, Dict, List, Tuple
import cv2
import numpy as np
from scipy.optimize import least_squares
STATE_KEYS = ["x", "y", "z", "a", "b", "c", "e"]
# ------------------------------------------------------------------
# JSON helpers
# ------------------------------------------------------------------
def load_json(path: str) -> Dict[str, Any]:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def save_json(data: Dict[str, Any], path: Path) -> None:
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
# ------------------------------------------------------------------
# robot.json helpers
# ------------------------------------------------------------------
def resolve_scalar(value: Any, default: float = 0.0) -> float:
if value is None:
return default
if isinstance(value, (int, float)):
return float(value)
try:
return float(str(value).strip())
except ValueError:
return default
def resolve_vector(value: Any, default_len: int = 3) -> Tuple[float, ...]:
if value is None:
return tuple(0.0 for _ in range(default_len))
if isinstance(value, (int, float, str)):
return (resolve_scalar(value),) + tuple(0.0 for _ in range(default_len - 1))
if isinstance(value, (list, tuple)):
resolved = [resolve_scalar(v) for v in value]
if len(resolved) < default_len:
resolved.extend([0.0] * (default_len - len(resolved)))
return tuple(resolved[:default_len])
return tuple(0.0 for _ in range(default_len))
def parse_metric_scale(robot: Dict[str, Any]) -> float:
rendering_info = robot.get('renderingInfo', {}) or {}
metric = rendering_info.get('metric', 'mm')
return 0.001 if str(metric).strip().lower() == 'mm' else 1.0
def normalize_axis(axis: Any) -> np.ndarray:
vec = np.asarray(axis, dtype=np.float64)
if vec.shape != (3,):
vec = vec.reshape(-1)[:3]
norm = np.linalg.norm(vec)
return vec / max(norm, 1e-9)
def euler_deg_to_matrix(euler_deg: Any) -> np.ndarray:
x_deg, y_deg, z_deg = resolve_vector(euler_deg, 3)
x = math.radians(x_deg)
y = math.radians(y_deg)
z = math.radians(z_deg)
cx = math.cos(x)
sx = math.sin(x)
cy = math.cos(y)
sy = math.sin(y)
cz = math.cos(z)
sz = math.sin(z)
Rx = np.array([
[1.0, 0.0, 0.0],
[0.0, cx, -sx],
[0.0, sx, cx]
], dtype=np.float64)
Ry = np.array([
[cy, 0.0, sy],
[0.0, 1.0, 0.0],
[-sy, 0.0, cy]
], dtype=np.float64)
Rz = np.array([
[cz, -sz, 0.0],
[sz, cz, 0.0],
[0.0, 0.0, 1.0]
], dtype=np.float64)
return Rz @ Ry @ Rx
def transform_from_translation_rotation(translation: Any, rotation_deg: Any) -> np.ndarray:
T = np.eye(4, dtype=np.float64)
pos = np.asarray(resolve_vector(translation, 3), dtype=np.float64)
T[:3, 3] = pos
T[:3, :3] = euler_deg_to_matrix(rotation_deg)
return T
def axis_angle_matrix(axis: Any, angle_deg: float) -> np.ndarray:
axis_vec = normalize_axis(axis)
theta = math.radians(angle_deg)
kx, ky, kz = axis_vec
c = math.cos(theta)
s = math.sin(theta)
v = 1.0 - c
R = np.array([
[kx * kx * v + c, kx * ky * v - kz * s, kx * kz * v + ky * s],
[ky * kx * v + kz * s, ky * ky * v + c, ky * kz * v - kx * s],
[kz * kx * v - ky * s, kz * ky * v + kx * s, kz * kz * v + c]
], dtype=np.float64)
T = np.eye(4, dtype=np.float64)
T[:3, :3] = R
return T
# ------------------------------------------------------------------
# Kinematics and marker extraction
# ------------------------------------------------------------------
def extract_markers(robot: Dict[str, Any], scale: float) -> Dict[int, Dict[str, Any]]:
markers = {}
links = robot.get('links', {}) or {}
marker_defaults = (robot.get('renderingInfo', {}) or {}).get('markerDefaults', {}) or {}
default_size_mm = float(marker_defaults.get('size', 25.0))
for link_name, link_info in links.items():
for marker in link_info.get('markers', []) or []:
marker_id = int(marker.get('id', -1))
if marker_id < 0:
continue
pos = resolve_vector(marker.get('position', [0, 0, 0]), 3)
size_mm = float(marker.get('size', default_size_mm))
markers[marker_id] = {
'marker_id': marker_id,
'link_name': link_name,
'position_m': np.asarray([pos[0] * scale, pos[1] * scale, pos[2] * scale], dtype=np.float64),
'normal': normalize_axis(resolve_vector(marker.get('normal', [0, 0, 1]), 3)),
'spin_deg': float(marker.get('spin', 0.0)),
'size_m': size_mm * scale,
}
return markers
def marker_plane_axes(normal: np.ndarray, spin_deg: float) -> Tuple[np.ndarray, np.ndarray]:
n = normalize_axis(normal)
candidate = np.array((0.0, 0.0, 1.0), dtype=np.float64)
if abs(np.dot(n, candidate)) > 0.99:
candidate = np.array((1.0, 0.0, 0.0), dtype=np.float64)
x_dir = np.cross(candidate, n)
x_dir /= max(np.linalg.norm(x_dir), 1e-9)
y_dir = np.cross(n, x_dir)
if abs(spin_deg) > 1e-6:
theta = math.radians(spin_deg)
cos_t = math.cos(theta)
sin_t = math.sin(theta)
x_rot = x_dir * cos_t + np.cross(n, x_dir) * sin_t + n * np.dot(n, x_dir) * (1.0 - cos_t)
y_rot = y_dir * cos_t + np.cross(n, y_dir) * sin_t + n * np.dot(n, y_dir) * (1.0 - cos_t)
return x_rot, y_rot
return x_dir, y_dir
def marker_object_corners(marker: Dict[str, Any]) -> np.ndarray:
half = marker['size_m'] * 0.5
x_dir, y_dir = marker_plane_axes(marker['normal'], marker['spin_deg'])
corners = np.stack([
-x_dir * half + y_dir * half,
x_dir * half + y_dir * half,
x_dir * half - y_dir * half,
-x_dir * half - y_dir * half
], axis=0)
return marker['position_m'].reshape(1, 3) + corners
def build_link_chain(robot: Dict[str, Any]) -> List[str]:
links = robot.get('links', {}) or {}
ordered: List[str] = []
remaining = set(links.keys())
while remaining:
progress = False
for name in list(remaining):
parent = links[name].get('parent')
if not parent or parent in ordered:
ordered.append(name)
remaining.remove(name)
progress = True
if not progress:
raise RuntimeError('Cycle detected in robot link tree or missing parent link')
return ordered
def compute_link_transforms(robot: Dict[str, Any], state: Dict[str, float], scale: float) -> Dict[str, np.ndarray]:
links = robot.get('links', {}) or {}
ordered_links = build_link_chain(robot)
transforms: Dict[str, np.ndarray] = {}
for link_name in ordered_links:
link_info = links[link_name] or {}
parent_name = link_info.get('parent')
parent_transform = transforms[parent_name] if parent_name else np.eye(4, dtype=np.float64)
mount_translation = np.asarray(resolve_vector(link_info.get('mountPosition', [0, 0, 0]), 3), dtype=np.float64) * scale
mount = transform_from_translation_rotation(
mount_translation,
link_info.get('mountRotation', [0, 0, 0])
)
joint_info = link_info.get('jointToParent', {}) or {}
joint_origin = np.asarray(resolve_vector(joint_info.get('origin', [0, 0, 0]), 3), dtype=np.float64) * scale
joint = transform_from_translation_rotation(
joint_origin,
joint_info.get('rotation', [0, 0, 0])
)
motion = np.eye(4, dtype=np.float64)
joint_type = str(joint_info.get('type', 'fixed')).strip().lower()
control_var = str(joint_info.get('variable', joint_info.get('control', ''))).strip().lower()
axis = resolve_vector(joint_info.get('axis', [1, 0, 0]), 3)
if joint_type == 'linear':
motion[:3, 3] = normalize_axis(axis) * state.get(control_var, 0.0) * scale
elif joint_type == 'revolute':
motion = axis_angle_matrix(axis, state.get(control_var, 0.0))
transforms[link_name] = parent_transform @ mount @ joint @ motion
return transforms
def compute_marker_world_position(marker: Dict[str, Any], link_transforms: Dict[str, np.ndarray]) -> np.ndarray:
link_transform = link_transforms[marker['link_name']]
local = np.ones(4, dtype=np.float64)
local[:3] = marker['position_m']
world = link_transform @ local
return world[:3]
# ------------------------------------------------------------------
# Camera / observation helpers
# ------------------------------------------------------------------
def load_intrinsics(detection_json: Dict[str, Any]) -> Tuple[np.ndarray, np.ndarray]:
cam = detection_json['camera']
K = np.asarray(cam['camera_matrix'], dtype=np.float64)
D = np.asarray(cam.get('distortion_coefficients', [0, 0, 0, 0, 0]), dtype=np.float64).reshape(-1, 1)
return K, D
def collect_views_and_observations(
detection_files: List[str],
robot_markers: Dict[int, Dict[str, Any]]
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
views: List[Dict[str, Any]] = []
observations: List[Dict[str, Any]] = []
for idx, det_path in enumerate(detection_files):
detection_json = load_json(det_path)
K, D = load_intrinsics(detection_json)
views.append({
'index': idx,
'source_file': os.path.abspath(det_path),
'camera_id': detection_json.get('camera', {}).get('camera_id', f'cam{idx+1}'),
'image_file': detection_json.get('image', {}).get('image_file'),
'K': K,
'D': D
})
for det in detection_json.get('detections', []) or []:
marker_id = int(det.get('marker_id', -1))
if marker_id < 0 or marker_id not in robot_markers:
continue
image_points = det.get('image_points_px')
if isinstance(image_points, list) and len(image_points) == 4:
image_points = np.asarray(image_points, dtype=np.float64)
else:
center = resolve_vector(det.get('center_px', [0, 0]), 2)
image_points = np.asarray([center], dtype=np.float64)
confidence = float(det.get('confidence', 1.0))
marker = robot_markers[marker_id]
observations.append({
'view_index': idx,
'marker_id': marker_id,
'marker_link_corners': marker_object_corners(marker),
'image_points_px': image_points,
'confidence': max(0.01, min(1.0, confidence))
})
if len(views) == 0:
raise RuntimeError('No valid detection views found')
if len(observations) == 0:
raise RuntimeError('No marker observations matched robot.json markers')
return views, observations
def compute_marker_world_corners(marker: Dict[str, Any], link_transforms: Dict[str, np.ndarray]) -> np.ndarray:
link_transform = link_transforms[marker['link_name']]
local = marker_object_corners(marker)
homogeneous = np.concatenate([local, np.ones((local.shape[0], 1), dtype=np.float64)], axis=1)
world = (link_transform @ homogeneous.T).T
return world[:, :3]
def initial_camera_guess(
view: Dict[str, Any],
observations: List[Dict[str, Any]],
robot_markers: Dict[int, Dict[str, Any]],
default_state: Dict[str, float],
scale: float,
robot: Dict[str, Any]
) -> Tuple[np.ndarray, np.ndarray]:
object_points = []
image_points = []
link_transforms = compute_link_transforms(robot, default_state, scale)
for obs in observations:
if obs['view_index'] != view['index']:
continue
marker = robot_markers[obs['marker_id']]
object_points.append(compute_marker_world_corners(marker, link_transforms))
image_points.append(obs['image_points_px'])
if len(object_points) == 0:
return np.zeros((3, 1), dtype=np.float64), np.array([[0.0], [0.0], [1.0]], dtype=np.float64)
object_points = np.vstack(object_points)
image_points = np.vstack(image_points)
if object_points.shape[0] < 4:
return np.zeros((3, 1), dtype=np.float64), np.array([[0.0], [0.0], [1.0]], dtype=np.float64)
success, rvec, tvec = cv2.solvePnP(
object_points,
image_points,
view['K'],
view['D'],
flags=cv2.SOLVEPNP_ITERATIVE
)
if not success:
return np.zeros((3, 1), dtype=np.float64), np.array([[0.0], [0.0], [1.0]], dtype=np.float64)
return rvec, tvec
def project_points(
points_3d: np.ndarray,
rvec: np.ndarray,
tvec: np.ndarray,
K: np.ndarray,
D: np.ndarray
) -> np.ndarray:
projected, _ = cv2.projectPoints(points_3d, rvec, tvec, K, D)
return projected.reshape(-1, 2)
# ------------------------------------------------------------------
# Optimization
# ------------------------------------------------------------------
def pack_parameters(robot_state: Dict[str, float], camera_params: List[Tuple[np.ndarray, np.ndarray]]) -> np.ndarray:
state_vec = np.asarray([robot_state[k] for k in STATE_KEYS], dtype=np.float64)
cams = []
for rvec, tvec in camera_params:
cams.append(rvec.reshape(3))
cams.append(tvec.reshape(3))
return np.concatenate([state_vec] + cams)
def unpack_parameters(params: np.ndarray, n_views: int) -> Tuple[Dict[str, float], List[Tuple[np.ndarray, np.ndarray]]]:
robot_state = {STATE_KEYS[i]: float(params[i]) for i in range(len(STATE_KEYS))}
camera_params = []
offset = len(STATE_KEYS)
for _ in range(n_views):
rvec = params[offset:offset + 3].reshape(3, 1)
tvec = params[offset + 3:offset + 6].reshape(3, 1)
camera_params.append((rvec, tvec))
offset += 6
return robot_state, camera_params
def residuals_for_parameters(
params: np.ndarray,
views: List[Dict[str, Any]],
observations: List[Dict[str, Any]],
robot_markers: Dict[int, Dict[str, Any]],
robot: Dict[str, Any],
scale: float,
default_state: Dict[str, float]
) -> np.ndarray:
robot_state, camera_params = unpack_parameters(params, len(views))
link_transforms = compute_link_transforms(robot, robot_state, scale)
residuals = []
for obs in observations:
marker = robot_markers[obs['marker_id']]
world_corners = compute_marker_world_corners(marker, link_transforms)
rvec, tvec = camera_params[obs['view_index']]
proj = project_points(world_corners, rvec, tvec, views[obs['view_index']]['K'], views[obs['view_index']]['D'])
diffs = proj - obs['image_points_px']
weight = math.sqrt(obs['confidence'])
residuals.extend((diffs * weight).reshape(-1))
for key in STATE_KEYS:
diff = robot_state[key] - default_state.get(key, 0.0)
if key in ('x', 'y', 'z', 'e'):
w = 0.001
else:
w = 0.01
residuals.append(diff * w)
return np.asarray(residuals, dtype=np.float64)
def estimate_uncertainty(result: Any, n_params: int) -> np.ndarray:
if result.jac is None:
return np.full(n_params, float('nan'), dtype=np.float64)
J = result.jac
m, n = J.shape
JTJ = J.T @ J
try:
cov = np.linalg.pinv(JTJ)
except np.linalg.LinAlgError:
cov = np.linalg.pinv(JTJ + np.eye(n) * 1e-9)
residuals = result.fun
dof = max(1, m - n)
sigma2 = float(np.sum(residuals ** 2) / dof)
cov *= sigma2
return np.sqrt(np.diag(cov))
# ------------------------------------------------------------------
# Output assembly
# ------------------------------------------------------------------
def camera_position_world(rvec: np.ndarray, tvec: np.ndarray) -> np.ndarray:
R, _ = cv2.Rodrigues(rvec)
return (-R.T @ tvec).reshape(3)
def build_output(
robot_state: Dict[str, float],
state_uncertainty: np.ndarray,
views: List[Dict[str, Any]],
camera_params: List[Tuple[np.ndarray, np.ndarray]],
observations: List[Dict[str, Any]],
robot_markers: Dict[int, Dict[str, Any]],
scale: float,
robot: Dict[str, Any],
robot_json_path: str
) -> Dict[str, Any]:
link_transforms = compute_link_transforms(robot, robot_state, scale)
marker_summary: Dict[int, Dict[str, Any]] = {}
for marker_id, marker in robot_markers.items():
marker_summary[marker_id] = {
'marker_id': marker_id,
'link_name': marker['link_name'],
'position_world_m': compute_marker_world_position(marker, link_transforms).tolist(),
'size_m': marker['size_m'],
'observation_count': 0,
'mean_confidence': None,
'mean_reprojection_error_px': None,
'observations': []
}
per_marker_errors: Dict[int, List[float]] = {mid: [] for mid in marker_summary}
per_marker_confidences: Dict[int, List[float]] = {mid: [] for mid in marker_summary}
link_transforms = compute_link_transforms(robot, robot_state, scale)
for obs in observations:
marker_id = obs['marker_id']
marker = robot_markers[marker_id]
object_points_m = compute_marker_world_corners(marker, link_transforms)
rvec, tvec = camera_params[obs['view_index']]
proj = project_points(object_points_m, rvec, tvec, views[obs['view_index']]['K'], views[obs['view_index']]['D'])
diffs = proj - obs['image_points_px']
errors = np.linalg.norm(diffs, axis=1)
repro_error = float(np.mean(errors))
per_marker_errors[marker_id].extend(errors.tolist())
per_marker_confidences[marker_id].append(obs['confidence'])
marker_summary[marker_id]['observation_count'] += 1
marker_summary[marker_id]['observations'].append({
'view_index': obs['view_index'],
'source_file': views[obs['view_index']]['source_file'],
'image_file': views[obs['view_index']]['image_file'],
'confidence': obs['confidence'],
'mean_reprojection_error_px': repro_error,
'corner_reprojection_errors_px': errors.tolist()
})
for marker_id, summary in marker_summary.items():
if summary['observation_count'] > 0:
summary['mean_confidence'] = float(np.mean(per_marker_confidences[marker_id]))
summary['mean_reprojection_error_px'] = float(np.mean(per_marker_errors[marker_id]))
camera_outputs = []
for idx, view in enumerate(views):
rvec, tvec = camera_params[idx]
cam_pos = camera_position_world(rvec, tvec)
observed_count = sum(1 for obs in observations if obs['view_index'] == idx)
camera_outputs.append({
'view_index': idx,
'source_file': view['source_file'],
'camera_id': view['camera_id'],
'camera_position_world_m': cam_pos.tolist(),
'rvec': rvec.reshape(-1).tolist(),
'tvec': tvec.reshape(-1).tolist(),
'intrinsics': {
'camera_matrix': view['K'].tolist(),
'distortion_coefficients': view['D'].reshape(-1).tolist()
},
'observation_count': observed_count
})
robot_pose_output = {
'state': {k: float(robot_state[k]) for k in STATE_KEYS},
'uncertainty': {
'x_mm': float(state_uncertainty[0]),
'y_mm': float(state_uncertainty[1]),
'z_mm': float(state_uncertainty[2]),
'a_deg': float(state_uncertainty[3]),
'b_deg': float(state_uncertainty[4]),
'c_deg': float(state_uncertainty[5]),
'e_mm': float(state_uncertainty[6])
},
'confidence': {
'x': float(math.exp(-state_uncertainty[0] / 10.0)),
'y': float(math.exp(-state_uncertainty[1] / 10.0)),
'z': float(math.exp(-state_uncertainty[2] / 10.0)),
'a': float(math.exp(-state_uncertainty[3] / 10.0)),
'b': float(math.exp(-state_uncertainty[4] / 10.0)),
'c': float(math.exp(-state_uncertainty[5] / 10.0)),
'e': float(math.exp(-state_uncertainty[6] / max(1.0, state_uncertainty[6])))
}
}
return {
'schema_version': '1.0',
'created_utc': datetime.datetime.utcnow().isoformat() + 'Z',
'source_robot_json': os.path.abspath(robot_json_path),
'source_detections': [view['source_file'] for view in views],
'robot_pose': robot_pose_output,
'camera_poses': camera_outputs,
'marker_positions': list(marker_summary.values())
}
# ------------------------------------------------------------------
# Main
# ------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description='Multiview optimization of robot pose and camera extrinsics')
parser.add_argument('--robot', required=True, help='Path to robot.json')
parser.add_argument('--detections', required=True, nargs='+', help='List of detection JSON files')
parser.add_argument('--outDir', required=True, help='Output directory')
parser.add_argument('--write-summary', action='store_true', help='Write summary file')
parser.add_argument('--max-iter', type=int, default=500, help='Maximum optimizer iterations')
args = parser.parse_args()
os.makedirs(args.outDir, exist_ok=True)
robot_json_path = os.path.abspath(args.robot)
robot = load_json(robot_json_path)
scale = parse_metric_scale(robot)
default_state = {
k: float(robot.get('defaultPosition', {}).get(k, 0.0) or 0.0)
for k in STATE_KEYS
}
robot_markers = extract_markers(robot, scale)
views, observations = collect_views_and_observations(args.detections, robot_markers)
camera_guesses = []
for view in views:
rvec, tvec = initial_camera_guess(view, observations, robot_markers, default_state, scale, robot)
camera_guesses.append((rvec, tvec))
x0 = pack_parameters(default_state, camera_guesses)
progress = {
'iter': 0,
'last_cost': None,
'last_print': time.time(),
'prev_x': x0.copy()
}
def progress_callback(xk: np.ndarray) -> None:
progress['iter'] += 1
now = time.time()
if progress['iter'] == 1 or now - progress['last_print'] >= 1.0:
res = residuals_for_parameters(xk, views, observations, robot_markers, robot, scale, default_state)
cost = 0.5 * float(np.dot(res, res))
delta_cost = None
convergence = ''
if progress['last_cost'] is not None:
delta_cost = cost - progress['last_cost']
if abs(delta_cost) < 1e-3:
convergence = ' stable'
elif delta_cost < 0:
convergence = ' improving'
else:
convergence = ' worsening'
step_norm = float(np.linalg.norm(xk - progress['prev_x']))
print(
f'[Multiview] iter={progress["iter"]:4d} cost={cost:.4f}'
+ (f' delta={delta_cost:.4g}' if delta_cost is not None else '')
+ f' step={step_norm:.4g}'
+ convergence
)
progress['last_cost'] = cost
progress['last_print'] = now
progress['prev_x'] = xk.copy()
result = least_squares(
residuals_for_parameters,
x0,
args=(views, observations, robot_markers, robot, scale, default_state),
jac='2-point',
method='trf',
loss='soft_l1',
f_scale=1.0,
max_nfev=args.max_iter,
callback=progress_callback
)
robot_state, camera_params = unpack_parameters(result.x, len(views))
uncertainties = estimate_uncertainty(result, len(result.x))
output = build_output(robot_state, uncertainties[:len(STATE_KEYS)], views, camera_params, observations, robot_markers, scale, robot, robot_json_path)
out_path = Path(args.outDir) / 'multiview_pose.json'
save_json(output, out_path)
print(f'Saved: {out_path}')
if args.write_summary:
summary_path = Path(args.outDir) / 'multiview_pose_summary.json'
summary = {
'final_cost': float(result.cost),
'status': int(result.status),
'message': result.message,
'robot_state': output['robot_pose'],
'camera_count': len(views),
'marker_count': len(robot_markers)
}
save_json(summary, summary_path)
print(f'Saved: {summary_path}')
if __name__ == '__main__':
main()

1158
pipeline/2_Multiview_neu.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,971 @@
#!/usr/bin/env python3
"""
============================================================
STEP 2b — Simultane Multiview-Optimierung für Roboterpose
============================================================
Ziel:
Aus mehreren ArUco-Detektionsdateien die gemeinsame
Roboterpose (x,y,z,a,b,c,e) schätzen und jede Kamera-Pose
sowie Marker-Weltpositionen ausgeben.
Eingabe:
--robot ../robot.json
--detections render_1a_aruco_detection.json render_1b_aruco_detection.json ...
--outDir .
Ausgabe:
multiview_pose.json
Hinweis:
Dieses Skript verwendet die Markerpositionen aus robot.json
als kinematische Constraints und optimiert gleichzeitig:
- Roboterzustand (x,y,z,a,b,c,e)
- Kameraextrinsische Parameter pro Bild
"""
import argparse
import datetime
import json
import math
import os
import time
from pathlib import Path
from typing import Any, Dict, List, Tuple
import cv2
import numpy as np
from scipy.optimize import least_squares
STATE_KEYS = ["x", "y", "z", "a", "b", "c", "e"]
# ------------------------------------------------------------------
# Constraint definitions and validation
# ------------------------------------------------------------------
class ConstraintResult:
"""Result of validating/applying a single constraint"""
def __init__(self, name: str, enabled: bool, reason: str = ""):
self.name = name
self.enabled = enabled
self.reason = reason
self.residuals = []
def __str__(self) -> str:
status = "✓ ENABLED" if self.enabled else "✗ DISABLED"
return f"{self.name:40s} {status:12s} {self.reason}"
def validate_constraints(robot: Dict[str, Any], robot_markers: Dict[int, Dict[str, Any]]) -> Dict[str, ConstraintResult]:
"""
Validate which constraints can be applied based on robot geometry.
Returns a dict of constraint_name -> ConstraintResult
"""
results = {}
# --- Constraint 1: Rigid body distances within each link ---
rigid_body_result = ConstraintResult("RigidBodyDistances", False)
try:
rigid_body_count = 0
for link_name in ['Arm1', 'Ellbow', 'Arm2']:
link_markers = [m for m in robot_markers.values() if m['link_name'] == link_name]
if len(link_markers) >= 2:
rigid_body_count += 1
if rigid_body_count >= 2:
rigid_body_result.enabled = True
rigid_body_result.reason = f"Found {rigid_body_count} links with 2+ markers each"
else:
rigid_body_result.reason = "Not enough rigid links with multiple markers"
except Exception as e:
rigid_body_result.reason = f"Error: {str(e)}"
results['RigidBodyDistances'] = rigid_body_result
# --- Constraint 2: Fixed X-distances between links (rotation around X-axis) ---
inter_link_x_result = ConstraintResult("InterLinkXDistances", False)
try:
links_with_markers = set(m['link_name'] for m in robot_markers.values())
x_rotated_links = []
for link_name in ['Arm1', 'Ellbow']:
if link_name in links_with_markers:
link_markers = [m for m in robot_markers.values() if m['link_name'] == link_name]
if len(link_markers) >= 1:
x_rotated_links.append(link_name)
if len(x_rotated_links) >= 2:
inter_link_x_result.enabled = True
inter_link_x_result.reason = f"Found {len(x_rotated_links)} X-rotation links: {', '.join(x_rotated_links)}"
else:
inter_link_x_result.reason = "Not enough X-rotation links"
except Exception as e:
inter_link_x_result.reason = f"Error: {str(e)}"
results['InterLinkXDistances'] = inter_link_x_result
# --- Sanity check (not a hard constraint): Arm2 sin(a) dependency ---
arm2_sina_result = ConstraintResult("Arm2SinADependency", True, "Sanity check only (not enforced)")
try:
arm2_markers = [m for m in robot_markers.values() if m['link_name'] == 'Arm2']
if len(arm2_markers) >= 2:
z_values = set(m['position_m'][2] for m in arm2_markers)
if len(z_values) > 1:
arm2_sina_result.enabled = True
arm2_sina_result.reason = "Multiple Z-values detected; sin(a) dependency confirmed"
else:
arm2_sina_result.enabled = False
arm2_sina_result.reason = "No Z-variation in Arm2 markers (cannot use sin(a) constraint)"
else:
arm2_sina_result.enabled = False
arm2_sina_result.reason = "Not enough Arm2 markers"
except Exception as e:
arm2_sina_result.reason = f"Error: {str(e)}"
results['Arm2SinADependency'] = arm2_sina_result
return results
# ------------------------------------------------------------------
# JSON helpers
# ------------------------------------------------------------------
def load_json(path: str) -> Dict[str, Any]:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def save_json(data: Dict[str, Any], path: Path) -> None:
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
# ------------------------------------------------------------------
# robot.json helpers
# ------------------------------------------------------------------
def resolve_scalar(value: Any, default: float = 0.0) -> float:
if value is None:
return default
if isinstance(value, (int, float)):
return float(value)
try:
return float(str(value).strip())
except ValueError:
return default
def resolve_vector(value: Any, default_len: int = 3) -> Tuple[float, ...]:
if value is None:
return tuple(0.0 for _ in range(default_len))
if isinstance(value, (int, float, str)):
return (resolve_scalar(value),) + tuple(0.0 for _ in range(default_len - 1))
if isinstance(value, (list, tuple)):
resolved = [resolve_scalar(v) for v in value]
if len(resolved) < default_len:
resolved.extend([0.0] * (default_len - len(resolved)))
return tuple(resolved[:default_len])
return tuple(0.0 for _ in range(default_len))
def parse_metric_scale(robot: Dict[str, Any]) -> float:
rendering_info = robot.get('renderingInfo', {}) or {}
metric = rendering_info.get('metric', 'mm')
return 0.001 if str(metric).strip().lower() == 'mm' else 1.0
def normalize_axis(axis: Any) -> np.ndarray:
vec = np.asarray(axis, dtype=np.float64)
if vec.shape != (3,):
vec = vec.reshape(-1)[:3]
norm = np.linalg.norm(vec)
return vec / max(norm, 1e-9)
def euler_deg_to_matrix(euler_deg: Any) -> np.ndarray:
x_deg, y_deg, z_deg = resolve_vector(euler_deg, 3)
x = math.radians(x_deg)
y = math.radians(y_deg)
z = math.radians(z_deg)
cx = math.cos(x)
sx = math.sin(x)
cy = math.cos(y)
sy = math.sin(y)
cz = math.cos(z)
sz = math.sin(z)
Rx = np.array([
[1.0, 0.0, 0.0],
[0.0, cx, -sx],
[0.0, sx, cx]
], dtype=np.float64)
Ry = np.array([
[cy, 0.0, sy],
[0.0, 1.0, 0.0],
[-sy, 0.0, cy]
], dtype=np.float64)
Rz = np.array([
[cz, -sz, 0.0],
[sz, cz, 0.0],
[0.0, 0.0, 1.0]
], dtype=np.float64)
return Rz @ Ry @ Rx
def transform_from_translation_rotation(translation: Any, rotation_deg: Any) -> np.ndarray:
T = np.eye(4, dtype=np.float64)
pos = np.asarray(resolve_vector(translation, 3), dtype=np.float64)
T[:3, 3] = pos
T[:3, :3] = euler_deg_to_matrix(rotation_deg)
return T
def axis_angle_matrix(axis: Any, angle_deg: float) -> np.ndarray:
axis_vec = normalize_axis(axis)
theta = math.radians(angle_deg)
kx, ky, kz = axis_vec
c = math.cos(theta)
s = math.sin(theta)
v = 1.0 - c
R = np.array([
[kx * kx * v + c, kx * ky * v - kz * s, kx * kz * v + ky * s],
[ky * kx * v + kz * s, ky * ky * v + c, ky * kz * v - kx * s],
[kz * kx * v - ky * s, kz * ky * v + kx * s, kz * kz * v + c]
], dtype=np.float64)
T = np.eye(4, dtype=np.float64)
T[:3, :3] = R
return T
# ------------------------------------------------------------------
# Kinematics and marker extraction
# ------------------------------------------------------------------
def extract_markers(robot: Dict[str, Any], scale: float) -> Dict[int, Dict[str, Any]]:
markers = {}
links = robot.get('links', {}) or {}
marker_defaults = (robot.get('renderingInfo', {}) or {}).get('markerDefaults', {}) or {}
default_size_mm = float(marker_defaults.get('size', 25.0))
for link_name, link_info in links.items():
for marker in link_info.get('markers', []) or []:
marker_id = int(marker.get('id', -1))
if marker_id < 0:
continue
pos = resolve_vector(marker.get('position', [0, 0, 0]), 3)
size_mm = float(marker.get('size', default_size_mm))
markers[marker_id] = {
'marker_id': marker_id,
'link_name': link_name,
'position_m': np.asarray([pos[0] * scale, pos[1] * scale, pos[2] * scale], dtype=np.float64),
'normal': normalize_axis(resolve_vector(marker.get('normal', [0, 0, 1]), 3)),
'spin_deg': float(marker.get('spin', 0.0)),
'size_m': size_mm * scale,
}
return markers
def marker_plane_axes(normal: np.ndarray, spin_deg: float) -> Tuple[np.ndarray, np.ndarray]:
n = normalize_axis(normal)
candidate = np.array((0.0, 0.0, 1.0), dtype=np.float64)
if abs(np.dot(n, candidate)) > 0.99:
candidate = np.array((1.0, 0.0, 0.0), dtype=np.float64)
x_dir = np.cross(candidate, n)
x_dir /= max(np.linalg.norm(x_dir), 1e-9)
y_dir = np.cross(n, x_dir)
if abs(spin_deg) > 1e-6:
theta = math.radians(spin_deg)
cos_t = math.cos(theta)
sin_t = math.sin(theta)
x_rot = x_dir * cos_t + np.cross(n, x_dir) * sin_t + n * np.dot(n, x_dir) * (1.0 - cos_t)
y_rot = y_dir * cos_t + np.cross(n, y_dir) * sin_t + n * np.dot(n, y_dir) * (1.0 - cos_t)
return x_rot, y_rot
return x_dir, y_dir
def marker_object_corners(marker: Dict[str, Any]) -> np.ndarray:
half = marker['size_m'] * 0.5
x_dir, y_dir = marker_plane_axes(marker['normal'], marker['spin_deg'])
corners = np.stack([
-x_dir * half + y_dir * half,
x_dir * half + y_dir * half,
x_dir * half - y_dir * half,
-x_dir * half - y_dir * half
], axis=0)
return marker['position_m'].reshape(1, 3) + corners
def build_link_chain(robot: Dict[str, Any]) -> List[str]:
links = robot.get('links', {}) or {}
ordered: List[str] = []
remaining = set(links.keys())
while remaining:
progress = False
for name in list(remaining):
parent = links[name].get('parent')
if not parent or parent in ordered:
ordered.append(name)
remaining.remove(name)
progress = True
if not progress:
raise RuntimeError('Cycle detected in robot link tree or missing parent link')
return ordered
def compute_link_transforms(robot: Dict[str, Any], state: Dict[str, float], scale: float) -> Dict[str, np.ndarray]:
links = robot.get('links', {}) or {}
ordered_links = build_link_chain(robot)
transforms: Dict[str, np.ndarray] = {}
for link_name in ordered_links:
link_info = links[link_name] or {}
parent_name = link_info.get('parent')
parent_transform = transforms[parent_name] if parent_name else np.eye(4, dtype=np.float64)
mount_translation = np.asarray(resolve_vector(link_info.get('mountPosition', [0, 0, 0]), 3), dtype=np.float64) * scale
mount = transform_from_translation_rotation(
mount_translation,
link_info.get('mountRotation', [0, 0, 0])
)
joint_info = link_info.get('jointToParent', {}) or {}
joint_origin = np.asarray(resolve_vector(joint_info.get('origin', [0, 0, 0]), 3), dtype=np.float64) * scale
joint = transform_from_translation_rotation(
joint_origin,
joint_info.get('rotation', [0, 0, 0])
)
motion = np.eye(4, dtype=np.float64)
joint_type = str(joint_info.get('type', 'fixed')).strip().lower()
control_var = str(joint_info.get('variable', joint_info.get('control', ''))).strip().lower()
axis = resolve_vector(joint_info.get('axis', [1, 0, 0]), 3)
if joint_type == 'linear':
motion[:3, 3] = normalize_axis(axis) * state.get(control_var, 0.0) * scale
elif joint_type == 'revolute':
motion = axis_angle_matrix(axis, state.get(control_var, 0.0))
transforms[link_name] = parent_transform @ mount @ joint @ motion
return transforms
def compute_marker_world_position(marker: Dict[str, Any], link_transforms: Dict[str, np.ndarray]) -> np.ndarray:
link_transform = link_transforms[marker['link_name']]
local = np.ones(4, dtype=np.float64)
local[:3] = marker['position_m']
world = link_transform @ local
return world[:3]
# ------------------------------------------------------------------
# Camera / observation helpers
# ------------------------------------------------------------------
def load_intrinsics(detection_json: Dict[str, Any]) -> Tuple[np.ndarray, np.ndarray]:
cam = detection_json['camera']
K = np.asarray(cam['camera_matrix'], dtype=np.float64)
D = np.asarray(cam.get('distortion_coefficients', [0, 0, 0, 0, 0]), dtype=np.float64).reshape(-1, 1)
return K, D
def collect_views_and_observations(
detection_files: List[str],
robot_markers: Dict[int, Dict[str, Any]]
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
views: List[Dict[str, Any]] = []
observations: List[Dict[str, Any]] = []
for idx, det_path in enumerate(detection_files):
detection_json = load_json(det_path)
K, D = load_intrinsics(detection_json)
views.append({
'index': idx,
'source_file': os.path.abspath(det_path),
'camera_id': detection_json.get('camera', {}).get('camera_id', f'cam{idx+1}'),
'image_file': detection_json.get('image', {}).get('image_file'),
'K': K,
'D': D
})
for det in detection_json.get('detections', []) or []:
marker_id = int(det.get('marker_id', -1))
if marker_id < 0 or marker_id not in robot_markers:
continue
image_points = det.get('image_points_px')
if isinstance(image_points, list) and len(image_points) == 4:
image_points = np.asarray(image_points, dtype=np.float64)
else:
center = resolve_vector(det.get('center_px', [0, 0]), 2)
image_points = np.asarray([center], dtype=np.float64)
confidence = float(det.get('confidence', 1.0))
marker = robot_markers[marker_id]
observations.append({
'view_index': idx,
'marker_id': marker_id,
'marker_link_corners': marker_object_corners(marker),
'image_points_px': image_points,
'confidence': max(0.01, min(1.0, confidence))
})
if len(views) == 0:
raise RuntimeError('No valid detection views found')
if len(observations) == 0:
raise RuntimeError('No marker observations matched robot.json markers')
return views, observations
def compute_marker_world_corners(marker: Dict[str, Any], link_transforms: Dict[str, np.ndarray]) -> np.ndarray:
link_transform = link_transforms[marker['link_name']]
local = marker_object_corners(marker)
homogeneous = np.concatenate([local, np.ones((local.shape[0], 1), dtype=np.float64)], axis=1)
world = (link_transform @ homogeneous.T).T
return world[:, :3]
def initial_camera_guess(
view: Dict[str, Any],
observations: List[Dict[str, Any]],
robot_markers: Dict[int, Dict[str, Any]],
default_state: Dict[str, float],
scale: float,
robot: Dict[str, Any]
) -> Tuple[np.ndarray, np.ndarray]:
object_points = []
image_points = []
link_transforms = compute_link_transforms(robot, default_state, scale)
for obs in observations:
if obs['view_index'] != view['index']:
continue
marker = robot_markers[obs['marker_id']]
object_points.append(compute_marker_world_corners(marker, link_transforms))
image_points.append(obs['image_points_px'])
if len(object_points) == 0:
return np.zeros((3, 1), dtype=np.float64), np.array([[0.0], [0.0], [1.0]], dtype=np.float64)
object_points = np.vstack(object_points)
image_points = np.vstack(image_points)
if object_points.shape[0] < 4:
return np.zeros((3, 1), dtype=np.float64), np.array([[0.0], [0.0], [1.0]], dtype=np.float64)
success, rvec, tvec = cv2.solvePnP(
object_points,
image_points,
view['K'],
view['D'],
flags=cv2.SOLVEPNP_ITERATIVE
)
if not success:
return np.zeros((3, 1), dtype=np.float64), np.array([[0.0], [0.0], [1.0]], dtype=np.float64)
return rvec, tvec
def project_points(
points_3d: np.ndarray,
rvec: np.ndarray,
tvec: np.ndarray,
K: np.ndarray,
D: np.ndarray
) -> np.ndarray:
projected, _ = cv2.projectPoints(points_3d, rvec, tvec, K, D)
return projected.reshape(-1, 2)
def compute_soft_constraint_residuals(
robot_state: Dict[str, float],
robot_markers: Dict[int, Dict[str, Any]],
link_transforms: Dict[str, np.ndarray],
robot: Dict[str, Any],
enabled_constraints: Dict[str, ConstraintResult]
) -> List[float]:
"""
Compute residuals from soft constraints (kinematic consistency, rigid body distances).
Returns a list of constraint residuals to append to the total residual vector.
"""
residuals = []
weight_scale = 0.1 # Weight for soft constraints relative to reprojection errors
# Constraint 1: Rigid body distances within each link
if enabled_constraints['RigidBodyDistances'].enabled:
for link_name in ['Arm1', 'Ellbow', 'Arm2']:
link_markers = [m for m in robot_markers.values() if m['link_name'] == link_name]
if len(link_markers) < 2:
continue
# Compute all pairwise distances in world coords
for i in range(len(link_markers)):
for j in range(i + 1, len(link_markers)):
m_i = link_markers[i]
m_j = link_markers[j]
pos_i = compute_marker_world_position(m_i, link_transforms)
pos_j = compute_marker_world_position(m_j, link_transforms)
dist_world = np.linalg.norm(pos_i - pos_j)
# Reference distance in local coords
dist_local = np.linalg.norm(m_i['position_m'] - m_j['position_m'])
# Residual: difference should be zero (rigid body)
error = dist_world - dist_local
residuals.append(error * weight_scale * 0.1) # Very soft weight
# Constraint 2: Fixed X-distances between links (Arm1 <-> Ellbow)
if enabled_constraints['InterLinkXDistances'].enabled:
arm1_markers = [m for m in robot_markers.values() if m['link_name'] == 'Arm1']
ellbow_markers = [m for m in robot_markers.values() if m['link_name'] == 'Ellbow']
if len(arm1_markers) >= 1 and len(ellbow_markers) >= 1:
# Get first marker from each link
m_arm1 = arm1_markers[0]
m_ellbow = ellbow_markers[0]
pos_arm1 = compute_marker_world_position(m_arm1, link_transforms)
pos_ellbow = compute_marker_world_position(m_ellbow, link_transforms)
# X-distance in world should match reference (relative position)
# Since both rotate around X-axis at different points, we check consistency
x_diff_world = pos_ellbow[0] - pos_arm1[0]
x_diff_ref = m_ellbow['position_m'][0] - m_arm1['position_m'][0]
error = x_diff_world - x_diff_ref
residuals.append(error * weight_scale)
return residuals
def compute_marker_world_position(marker: Dict[str, Any], link_transforms: Dict[str, np.ndarray]) -> np.ndarray:
"""Compute the world position of a marker given current link transforms."""
link_transform = link_transforms[marker['link_name']]
local_pos = np.concatenate([marker['position_m'], [1.0]])
world_pos = (link_transform @ local_pos)[:3]
return world_pos
# ------------------------------------------------------------------
# Optimization
# ------------------------------------------------------------------
def pack_parameters(robot_state: Dict[str, float], camera_params: List[Tuple[np.ndarray, np.ndarray]]) -> np.ndarray:
state_vec = np.asarray([robot_state[k] for k in STATE_KEYS], dtype=np.float64)
cams = []
for rvec, tvec in camera_params:
cams.append(rvec.reshape(3))
cams.append(tvec.reshape(3))
return np.concatenate([state_vec] + cams)
def unpack_parameters(params: np.ndarray, n_views: int) -> Tuple[Dict[str, float], List[Tuple[np.ndarray, np.ndarray]]]:
robot_state = {STATE_KEYS[i]: float(params[i]) for i in range(len(STATE_KEYS))}
camera_params = []
offset = len(STATE_KEYS)
for _ in range(n_views):
rvec = params[offset:offset + 3].reshape(3, 1)
tvec = params[offset + 3:offset + 6].reshape(3, 1)
camera_params.append((rvec, tvec))
offset += 6
return robot_state, camera_params
def residuals_for_parameters(
params: np.ndarray,
views: List[Dict[str, Any]],
observations: List[Dict[str, Any]],
robot_markers: Dict[int, Dict[str, Any]],
robot: Dict[str, Any],
scale: float,
default_state: Dict[str, float],
enabled_constraints: Dict[str, ConstraintResult]
) -> np.ndarray:
robot_state, camera_params = unpack_parameters(params, len(views))
link_transforms = compute_link_transforms(robot, robot_state, scale)
residuals = []
# Reprojection residuals (primary observation)
for obs in observations:
marker = robot_markers[obs['marker_id']]
world_corners = compute_marker_world_corners(marker, link_transforms)
rvec, tvec = camera_params[obs['view_index']]
proj = project_points(world_corners, rvec, tvec, views[obs['view_index']]['K'], views[obs['view_index']]['D'])
diffs = proj - obs['image_points_px']
weight = math.sqrt(obs['confidence'])
residuals.extend((diffs * weight).reshape(-1))
# Weak priors on robot state
for key in STATE_KEYS:
diff = robot_state[key] - default_state.get(key, 0.0)
if key in ('x', 'y', 'z', 'e'):
w = 0.001
else:
w = 0.01
residuals.append(diff * w)
# Soft constraints (kinematic consistency, rigid body constraints)
soft_constraint_residuals = compute_soft_constraint_residuals(
robot_state, robot_markers, link_transforms, robot, enabled_constraints
)
residuals.extend(soft_constraint_residuals)
return np.asarray(residuals, dtype=np.float64)
def estimate_uncertainty(result: Any, n_params: int) -> np.ndarray:
if result.jac is None:
return np.full(n_params, float('nan'), dtype=np.float64)
J = result.jac
m, n = J.shape
JTJ = J.T @ J
try:
cov = np.linalg.pinv(JTJ)
except np.linalg.LinAlgError:
cov = np.linalg.pinv(JTJ + np.eye(n) * 1e-9)
residuals = result.fun
dof = max(1, m - n)
sigma2 = float(np.sum(residuals ** 2) / dof)
cov *= sigma2
return np.sqrt(np.diag(cov))
def print_constraint_sanity_check(
robot_state: Dict[str, float],
robot_markers: Dict[int, Dict[str, Any]],
link_transforms: Dict[str, np.ndarray],
robot: Dict[str, Any],
enabled_constraints: Dict[str, ConstraintResult]
) -> None:
"""
Print sanity checks for all constraints to verify the optimization result.
"""
print("\n" + "=" * 70)
print("CONSTRAINT SANITY CHECKS (after optimization)")
print("=" * 70)
# Check 1: Rigid body distances
if enabled_constraints['RigidBodyDistances'].enabled:
print("\n1. RIGID BODY DISTANCES")
for link_name in ['Arm1', 'Ellbow', 'Arm2']:
link_markers = [m for m in robot_markers.values() if m['link_name'] == link_name]
if len(link_markers) < 2:
continue
max_error = 0.0
for i in range(len(link_markers)):
for j in range(i + 1, len(link_markers)):
m_i = link_markers[i]
m_j = link_markers[j]
pos_i = compute_marker_world_position(m_i, link_transforms)
pos_j = compute_marker_world_position(m_j, link_transforms)
dist_world = np.linalg.norm(pos_i - pos_j)
dist_local = np.linalg.norm(m_i['position_m'] - m_j['position_m'])
error = abs(dist_world - dist_local)
max_error = max(max_error, error)
status = "" if max_error < 1.0 else "" if max_error < 5.0 else ""
print(f" {link_name:10s}: max_error = {max_error:.3f} mm {status}")
# Check 2: Inter-link X distances
if enabled_constraints['InterLinkXDistances'].enabled:
print("\n2. INTER-LINK X-DISTANCES")
arm1_markers = [m for m in robot_markers.values() if m['link_name'] == 'Arm1']
ellbow_markers = [m for m in robot_markers.values() if m['link_name'] == 'Ellbow']
if len(arm1_markers) >= 1 and len(ellbow_markers) >= 1:
m_arm1 = arm1_markers[0]
m_ellbow = ellbow_markers[0]
pos_arm1 = compute_marker_world_position(m_arm1, link_transforms)
pos_ellbow = compute_marker_world_position(m_ellbow, link_transforms)
x_diff_world = pos_ellbow[0] - pos_arm1[0]
x_diff_ref = m_ellbow['position_m'][0] - m_arm1['position_m'][0]
error = abs(x_diff_world - x_diff_ref)
status = "" if error < 1.0 else "" if error < 5.0 else ""
print(f" Arm1 <-> Ellbow: error = {error:.3f} mm {status}")
# Check 3: Arm2 sin(a) dependency
if enabled_constraints['Arm2SinADependency'].enabled:
print("\n3. ARM2 sin(a) DEPENDENCY (sanity check)")
arm2_markers = [m for m in robot_markers.values() if m['link_name'] == 'Arm2']
if len(arm2_markers) >= 2:
# Check that markers with different Z values have different X spreads
a_rad = math.radians(robot_state['a'])
sin_a = math.sin(a_rad)
cos_a = math.cos(a_rad)
z_variations = {}
for m in arm2_markers:
z_local = m['position_m'][2]
x_local = m['position_m'][0]
pos_world = compute_marker_world_position(m, link_transforms)
x_world = pos_world[0]
# Expected: x_world = 90 + x_local * cos(a) - z_local * sin(a)
x_expected = 90 * (robot.get('renderingInfo', {}).get('metric', 'mm') == 'mm' and 0.09 or 0.09) + x_local * cos_a - z_local * sin_a
x_error = abs(x_world - x_expected)
if z_local not in z_variations:
z_variations[z_local] = []
z_variations[z_local].append(x_error)
max_error = max(max(errors) for errors in z_variations.values()) if z_variations else 0.0
status = "" if max_error < 5.0 else "" if max_error < 10.0 else ""
print(f" X-consistency with sin(a): max_error = {max_error:.3f} mm {status}")
print(f" (Note: this is a consistency check, not a hard constraint)")
print("=" * 70)
def camera_position_world(rvec: np.ndarray, tvec: np.ndarray) -> np.ndarray:
R, _ = cv2.Rodrigues(rvec)
return (-R.T @ tvec).reshape(3)
def build_output(
robot_state: Dict[str, float],
state_uncertainty: np.ndarray,
views: List[Dict[str, Any]],
camera_params: List[Tuple[np.ndarray, np.ndarray]],
observations: List[Dict[str, Any]],
robot_markers: Dict[int, Dict[str, Any]],
scale: float,
robot: Dict[str, Any],
robot_json_path: str
) -> Dict[str, Any]:
link_transforms = compute_link_transforms(robot, robot_state, scale)
marker_summary: Dict[int, Dict[str, Any]] = {}
for marker_id, marker in robot_markers.items():
marker_summary[marker_id] = {
'marker_id': marker_id,
'link_name': marker['link_name'],
'position_world_m': compute_marker_world_position(marker, link_transforms).tolist(),
'size_m': marker['size_m'],
'observation_count': 0,
'mean_confidence': None,
'mean_reprojection_error_px': None,
'observations': []
}
per_marker_errors: Dict[int, List[float]] = {mid: [] for mid in marker_summary}
per_marker_confidences: Dict[int, List[float]] = {mid: [] for mid in marker_summary}
link_transforms = compute_link_transforms(robot, robot_state, scale)
for obs in observations:
marker_id = obs['marker_id']
marker = robot_markers[marker_id]
object_points_m = compute_marker_world_corners(marker, link_transforms)
rvec, tvec = camera_params[obs['view_index']]
proj = project_points(object_points_m, rvec, tvec, views[obs['view_index']]['K'], views[obs['view_index']]['D'])
diffs = proj - obs['image_points_px']
errors = np.linalg.norm(diffs, axis=1)
repro_error = float(np.mean(errors))
per_marker_errors[marker_id].extend(errors.tolist())
per_marker_confidences[marker_id].append(obs['confidence'])
marker_summary[marker_id]['observation_count'] += 1
marker_summary[marker_id]['observations'].append({
'view_index': obs['view_index'],
'source_file': views[obs['view_index']]['source_file'],
'image_file': views[obs['view_index']]['image_file'],
'confidence': obs['confidence'],
'mean_reprojection_error_px': repro_error,
'corner_reprojection_errors_px': errors.tolist()
})
for marker_id, summary in marker_summary.items():
if summary['observation_count'] > 0:
summary['mean_confidence'] = float(np.mean(per_marker_confidences[marker_id]))
summary['mean_reprojection_error_px'] = float(np.mean(per_marker_errors[marker_id]))
camera_outputs = []
for idx, view in enumerate(views):
rvec, tvec = camera_params[idx]
cam_pos = camera_position_world(rvec, tvec)
observed_count = sum(1 for obs in observations if obs['view_index'] == idx)
camera_outputs.append({
'view_index': idx,
'source_file': view['source_file'],
'camera_id': view['camera_id'],
'camera_position_world_m': cam_pos.tolist(),
'rvec': rvec.reshape(-1).tolist(),
'tvec': tvec.reshape(-1).tolist(),
'intrinsics': {
'camera_matrix': view['K'].tolist(),
'distortion_coefficients': view['D'].reshape(-1).tolist()
},
'observation_count': observed_count
})
robot_pose_output = {
'state': {k: float(robot_state[k]) for k in STATE_KEYS},
'uncertainty': {
'x_mm': float(state_uncertainty[0]),
'y_mm': float(state_uncertainty[1]),
'z_mm': float(state_uncertainty[2]),
'a_deg': float(state_uncertainty[3]),
'b_deg': float(state_uncertainty[4]),
'c_deg': float(state_uncertainty[5]),
'e_mm': float(state_uncertainty[6])
},
'confidence': {
'x': float(math.exp(-state_uncertainty[0] / 10.0)),
'y': float(math.exp(-state_uncertainty[1] / 10.0)),
'z': float(math.exp(-state_uncertainty[2] / 10.0)),
'a': float(math.exp(-state_uncertainty[3] / 10.0)),
'b': float(math.exp(-state_uncertainty[4] / 10.0)),
'c': float(math.exp(-state_uncertainty[5] / 10.0)),
'e': float(math.exp(-state_uncertainty[6] / max(1.0, state_uncertainty[6])))
}
}
return {
'schema_version': '1.0',
'created_utc': datetime.datetime.utcnow().isoformat() + 'Z',
'source_robot_json': os.path.abspath(robot_json_path),
'source_detections': [view['source_file'] for view in views],
'robot_pose': robot_pose_output,
'camera_poses': camera_outputs,
'marker_positions': list(marker_summary.values())
}
# ------------------------------------------------------------------
# Main
# ------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description='Multiview optimization of robot pose and camera extrinsics')
parser.add_argument('--robot', required=True, help='Path to robot.json')
parser.add_argument('--detections', required=True, nargs='+', help='List of detection JSON files')
parser.add_argument('--outDir', required=True, help='Output directory')
parser.add_argument('--write-summary', action='store_true', help='Write summary file')
parser.add_argument('--max-iter', type=int, default=500, help='Maximum optimizer iterations')
args = parser.parse_args()
os.makedirs(args.outDir, exist_ok=True)
robot_json_path = os.path.abspath(args.robot)
robot = load_json(robot_json_path)
scale = parse_metric_scale(robot)
default_state = {
k: float(robot.get('defaultPosition', {}).get(k, 0.0) or 0.0)
for k in STATE_KEYS
}
robot_markers = extract_markers(robot, scale)
# Validate constraints
print("\n" + "=" * 70)
print("CONSTRAINT VALIDATION")
print("=" * 70)
enabled_constraints = validate_constraints(robot, robot_markers)
for constraint_name, result in enabled_constraints.items():
print(result)
print("=" * 70)
views, observations = collect_views_and_observations(args.detections, robot_markers)
camera_guesses = []
for view in views:
rvec, tvec = initial_camera_guess(view, observations, robot_markers, default_state, scale, robot)
camera_guesses.append((rvec, tvec))
x0 = pack_parameters(default_state, camera_guesses)
progress = {
'iter': 0,
'last_cost': None,
'last_print': time.time(),
'prev_x': x0.copy()
}
def progress_callback(xk: np.ndarray) -> None:
progress['iter'] += 1
now = time.time()
if progress['iter'] == 1 or now - progress['last_print'] >= 1.0:
res = residuals_for_parameters(xk, views, observations, robot_markers, robot, scale, default_state, enabled_constraints)
cost = 0.5 * float(np.dot(res, res))
delta_cost = None
convergence = ''
if progress['last_cost'] is not None:
delta_cost = cost - progress['last_cost']
if abs(delta_cost) < 1e-3:
convergence = ' stable'
elif delta_cost < 0:
convergence = ' improving'
else:
convergence = ' worsening'
step_norm = float(np.linalg.norm(xk - progress['prev_x']))
print(
f'[Multiview] iter={progress["iter"]:4d} cost={cost:.4f}'
+ (f' delta={delta_cost:.4g}' if delta_cost is not None else '')
+ f' step={step_norm:.4g}'
+ convergence
)
progress['last_cost'] = cost
progress['last_print'] = now
progress['prev_x'] = xk.copy()
result = least_squares(
residuals_for_parameters,
x0,
args=(views, observations, robot_markers, robot, scale, default_state, enabled_constraints),
jac='2-point',
method='trf',
loss='soft_l1',
f_scale=1.0,
max_nfev=args.max_iter,
callback=progress_callback
)
robot_state, camera_params = unpack_parameters(result.x, len(views))
uncertainties = estimate_uncertainty(result, len(result.x))
# Print constraint sanity checks
link_transforms = compute_link_transforms(robot, robot_state, scale)
print_constraint_sanity_check(robot_state, robot_markers, link_transforms, robot, enabled_constraints)
output = build_output(robot_state, uncertainties[:len(STATE_KEYS)], views, camera_params, observations, robot_markers, scale, robot, robot_json_path)
out_path = Path(args.outDir) / 'multiview_pose.json'
save_json(output, out_path)
print(f'Saved: {out_path}')
if args.write_summary:
summary_path = Path(args.outDir) / 'multiview_pose_summary.json'
summary = {
'final_cost': float(result.cost),
'status': int(result.status),
'message': result.message,
'robot_state': output['robot_pose'],
'camera_count': len(views),
'marker_count': len(robot_markers)
}
save_json(summary, summary_path)
print(f'Saved: {summary_path}')
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,966 @@
{
"schema_version": "1.0",
"created_utc": "2026-05-28T21:07:44.288252Z",
"source_robot_json": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\robot.json",
"source_detections": [
"C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json"
],
"robot_pose": {
"state": {
"x": 193.66787382210828,
"y": 61.23821620356439,
"z": -119.96654664833596,
"a": -107.97036991399465,
"b": 21.99999999999934,
"c": 91.00000000000014,
"e": 9.999999999999135
},
"uncertainty": {
"x_mm": 444.1920850838452,
"y_mm": 2085.804738658986,
"z_mm": 216.50850058940748,
"a_deg": 273.19721127112774,
"b_deg": 9704.418760205353,
"c_deg": 9704.420149983844,
"e_mm": 97044.43203671245
},
"confidence": {
"x": 5.116616350285866e-20,
"y": 2.598071909247533e-91,
"z": 3.9550801677927533e-10,
"a": 1.3651987041054823e-12,
"b": 0.0,
"c": 0.0,
"e": 0.36787944117144233
}
},
"camera_poses": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"camera_id": "cam1",
"camera_position_world_m": [
-77.74926115576925,
-73.0492976258425,
-147.8089302398559
],
"rvec": [
-4.597800910443656,
-21.216692970790984,
5.280603517092184
],
"tvec": [
1.3186034894317018,
7.494557764024174,
-182.12830708406332
],
"intrinsics": {
"camera_matrix": [
[
1777.77783203125,
0.0,
640.0
],
[
0.0,
1500.0,
360.0
],
[
0.0,
0.0,
1.0
]
],
"distortion_coefficients": [
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"observation_count": 17
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"camera_id": "cam1",
"camera_position_world_m": [
0.0906907584418463,
0.2776617967661424,
-0.7247751841355033
],
"rvec": [
-0.5104408724728655,
-3.1865020935343082,
-0.7599897453664757
],
"tvec": [
0.18575285646205408,
0.031786357242087525,
-0.7583570784256441
],
"intrinsics": {
"camera_matrix": [
[
1777.77783203125,
0.0,
640.0
],
[
0.0,
1500.0,
360.0
],
[
0.0,
0.0,
1.0
]
],
"distortion_coefficients": [
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"observation_count": 10
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"camera_id": "cam1",
"camera_position_world_m": [
0.2258549281270687,
-0.3663871310029323,
0.6799707410888476
],
"rvec": [
-3.4730218301317435,
0.5992584877895241,
0.35858647149574585
],
"tvec": [
-0.1367303214358514,
0.0014859998076042164,
0.7930402247461223
],
"intrinsics": {
"camera_matrix": [
[
1777.77783203125,
0.0,
640.0
],
[
0.0,
1500.0,
360.0
],
[
0.0,
0.0,
1.0
]
],
"distortion_coefficients": [
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"observation_count": 7
}
],
"marker_positions": [
{
"marker_id": 210,
"link_name": "Board",
"position_world_m": [
0.02,
-0.02,
0.0003
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.6024643807746477,
"mean_reprojection_error_px": 219.44641687346314,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.5042074435920213,
"mean_reprojection_error_px": 584.8502187266689,
"corner_reprojection_errors_px": [
610.6060261840274,
566.4030412437936,
559.1036732421863,
603.2881342366682
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.6639531667043603,
"mean_reprojection_error_px": 43.90181819055642,
"corner_reprojection_errors_px": [
30.0527741674556,
58.738230110803315,
62.58264430539862,
24.23362417856814
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.6392325320275614,
"mean_reprojection_error_px": 29.587213703164036,
"corner_reprojection_errors_px": [
44.13807594611845,
1.1413153391747903,
31.635331629981838,
41.434131897381064
]
}
]
},
{
"marker_id": 211,
"link_name": "Board",
"position_world_m": [
0.25,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.5607331763105995,
"mean_reprojection_error_px": 215.88919812501328,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.5607331763105995,
"mean_reprojection_error_px": 215.88919812501328,
"corner_reprojection_errors_px": [
239.060567969831,
203.87810525007305,
193.21032729986996,
227.40779198027906
]
}
]
},
{
"marker_id": 215,
"link_name": "Board",
"position_world_m": [
0.25,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.6915990015172901,
"mean_reprojection_error_px": 185.82669857910088,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.6816798505186958,
"mean_reprojection_error_px": 206.76829215011884,
"corner_reprojection_errors_px": [
220.0957850912882,
186.544457069329,
194.82996305260656,
225.60296338725155
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.7015181525158845,
"mean_reprojection_error_px": 164.88510500808295,
"corner_reprojection_errors_px": [
197.75737719196465,
146.9529114839187,
132.41058001353588,
182.41955134291254
]
}
]
},
{
"marker_id": 214,
"link_name": "Board",
"position_world_m": [
0.35000000000000003,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.5223267506441452,
"mean_reprojection_error_px": 430.28505616193627,
"observations": [
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.5223267506441452,
"mean_reprojection_error_px": 430.28505616193627,
"corner_reprojection_errors_px": [
435.27929209106946,
407.3352893643994,
427.77239968551254,
450.7532435067638
]
}
]
},
{
"marker_id": 208,
"link_name": "Board",
"position_world_m": [
0.35000000000000003,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.5511135304821347,
"mean_reprojection_error_px": 104.93132940823983,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.5511135304821347,
"mean_reprojection_error_px": 104.93132940823983,
"corner_reprojection_errors_px": [
101.05437746855068,
84.48184159814882,
111.54460519378607,
122.64449337247373
]
}
]
},
{
"marker_id": 206,
"link_name": "Board",
"position_world_m": [
0.65,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.3243040576749168,
"mean_reprojection_error_px": 265.1269045088845,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.3243040576749168,
"mean_reprojection_error_px": 265.1269045088845,
"corner_reprojection_errors_px": [
246.4689237594987,
270.74271590412576,
283.7439899333724,
259.551988438541
]
}
]
},
{
"marker_id": 205,
"link_name": "Board",
"position_world_m": [
0.75,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.2813323639079811,
"mean_reprojection_error_px": 414.07105145074036,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.2813323639079811,
"mean_reprojection_error_px": 414.07105145074036,
"corner_reprojection_errors_px": [
393.31517220079184,
414.88055122924146,
434.6820277122509,
413.40645466067724
]
}
]
},
{
"marker_id": 207,
"link_name": "Board",
"position_world_m": [
0.75,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.270961511076242,
"mean_reprojection_error_px": 361.40219383857607,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.270961511076242,
"mean_reprojection_error_px": 361.40219383857607,
"corner_reprojection_errors_px": [
343.99480245991305,
365.3864615302313,
379.1131712173157,
357.11434014684426
]
}
]
},
{
"marker_id": 217,
"link_name": "Board",
"position_world_m": [
0.65,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.3364649702867649,
"mean_reprojection_error_px": 319.9894030743949,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.3364649702867649,
"mean_reprojection_error_px": 319.9894030743949,
"corner_reprojection_errors_px": [
297.7784176112965,
321.2632775986662,
342.12879416557814,
318.78712292203863
]
}
]
},
{
"marker_id": 198,
"link_name": "Arm1",
"position_world_m": [
0.1936678738221083,
-0.04630507797358846,
0.15710136776571965
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.1708737557403893,
"mean_reprojection_error_px": 173.75585674911616,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.20939472520051636,
"mean_reprojection_error_px": 250.68402341711896,
"corner_reprojection_errors_px": [
276.58098681044964,
248.3015862011881,
225.19824045129988,
252.6552802055382
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.13235278628026223,
"mean_reprojection_error_px": 96.82769008111335,
"corner_reprojection_errors_px": [
86.14028865296076,
96.54841237858139,
117.89661118485392,
86.72544810805726
]
}
]
},
{
"marker_id": 229,
"link_name": "Arm1",
"position_world_m": [
0.1936678738221083,
-0.08961029442397768,
0.2359978710354143
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.2199830218620032,
"mean_reprojection_error_px": 111.48418089668428,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.2634483409899881,
"mean_reprojection_error_px": 158.57789833970793,
"corner_reprojection_errors_px": [
183.23095634369403,
159.8980008563882,
134.10338119920908,
157.07925495954032
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.17651770273401826,
"mean_reprojection_error_px": 64.39046345366064,
"corner_reprojection_errors_px": [
53.76348865479547,
65.98012218463036,
89.14976602616233,
48.66847694905442
]
}
]
},
{
"marker_id": 242,
"link_name": "Arm1",
"position_world_m": [
0.1936678738221083,
-0.15097424141151797,
0.2023160360184449
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 243,
"link_name": "Arm1",
"position_world_m": [
0.1936678738221083,
-0.13713318542623254,
0.24983892702069976
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.783838885722734,
"mean_reprojection_error_px": 44.94196369988338,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.753480565397225,
"mean_reprojection_error_px": 82.27135266344914,
"corner_reprojection_errors_px": [
104.64641625637452,
75.05759016037157,
54.73600619528254,
94.64539804176789
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.7779633248368785,
"mean_reprojection_error_px": 17.133880995253847,
"corner_reprojection_errors_px": [
13.52613306369719,
20.79198380803457,
20.738458388035028,
13.478948721248596
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.8200727669340984,
"mean_reprojection_error_px": 35.42065744094714,
"corner_reprojection_errors_px": [
43.2051334803787,
2.291855750175984,
46.29738248992781,
49.88825804330606
]
}
]
},
{
"marker_id": 244,
"link_name": "Ellbow",
"position_world_m": [
0.31866787382210826,
0.0,
0.0
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 245,
"link_name": "Ellbow",
"position_world_m": [
0.2836678738221083,
0.029915046239746786,
-0.018168379357383857
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 246,
"link_name": "Ellbow",
"position_world_m": [
0.2836678738221083,
-0.029915046239746786,
0.018168379357383857
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.5848503519198133,
"mean_reprojection_error_px": 71.95677931061337,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.6846593913143055,
"mean_reprojection_error_px": 129.77061184774738,
"corner_reprojection_errors_px": [
131.10152630147377,
148.01757225834027,
132.02269451379757,
107.940654317378
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.6496660357669276,
"mean_reprojection_error_px": 39.429662635023675,
"corner_reprojection_errors_px": [
54.265219903618416,
25.613227047621212,
36.12153940947933,
41.71866417937572
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.4202256286782067,
"mean_reprojection_error_px": 46.67006344906903,
"corner_reprojection_errors_px": [
17.76180722171543,
53.41185602621912,
87.55552599091499,
27.95106455742659
]
}
]
},
{
"marker_id": 247,
"link_name": "Ellbow",
"position_world_m": [
0.24616787382210828,
-0.029915046239746786,
0.018168379357383857
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.6103408077771776,
"mean_reprojection_error_px": 72.45582056932597,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.7451994373791682,
"mean_reprojection_error_px": 109.52982904634278,
"corner_reprojection_errors_px": [
126.13937483803942,
126.72281250876043,
96.93499026995522,
88.3221385686161
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.6388783942274684,
"mean_reprojection_error_px": 42.48268761553851,
"corner_reprojection_errors_px": [
66.85934117736593,
52.808931850254176,
27.34232661092379,
22.920150823610143
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.44694459172489637,
"mean_reprojection_error_px": 65.35494504609662,
"corner_reprojection_errors_px": [
65.94881395992364,
79.79307781356299,
80.2686839721486,
35.40920443875125
]
}
]
},
{
"marker_id": 124,
"link_name": "Arm2",
"position_world_m": [
0.20446625306675464,
-0.14213782157794694,
-0.16990066121932695
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.38376633052345943,
"mean_reprojection_error_px": 119.57328172201322,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.31559423864949376,
"mean_reprojection_error_px": 134.82416625853605,
"corner_reprojection_errors_px": [
121.33281341523052,
103.50465151028168,
148.6485339972427,
165.81066611138934
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.4519384223974251,
"mean_reprojection_error_px": 104.32239718549039,
"corner_reprojection_errors_px": [
97.26022837754157,
65.51154041781498,
119.230195720015,
135.28762422659
]
}
]
},
{
"marker_id": 122,
"link_name": "Arm2",
"position_world_m": [
0.22220435967071417,
-0.08656453557061031,
-0.16036287025635595
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.5007571026495876,
"mean_reprojection_error_px": 100.5976968444762,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.19097609792206996,
"mean_reprojection_error_px": 191.04201527524393,
"corner_reprojection_errors_px": [
161.2461896774225,
187.67710037351839,
219.07478119264024,
196.16998985739463
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.8023854845720537,
"mean_reprojection_error_px": 37.97273645764808,
"corner_reprojection_errors_px": [
36.73735527126386,
66.77343554076022,
29.865847324866966,
18.51430769370129
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.5089097254546391,
"mean_reprojection_error_px": 72.77833880053663,
"corner_reprojection_errors_px": [
79.87926104625087,
114.41534929802285,
63.25062596802093,
33.56811888985185
]
}
]
},
{
"marker_id": 218,
"link_name": "Arm2",
"position_world_m": [
0.18286949457746193,
-0.029683137487597516,
-0.11301020464799262
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 101,
"link_name": "Arm2",
"position_world_m": [
0.1624892762454371,
-0.0808799499047689,
-0.16381530379227105
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.30496613331262606,
"mean_reprojection_error_px": 266.775950196611,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.30496613331262606,
"mean_reprojection_error_px": 266.775950196611,
"corner_reprojection_errors_px": [
251.08509253516107,
234.8164115324836,
283.5537072385155,
297.64858948028376
]
}
]
},
{
"marker_id": 102,
"link_name": "Arm2",
"position_world_m": [
0.17776126974857978,
-0.12112440660686963,
-0.13937353791513551
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.7291583793913716,
"mean_reprojection_error_px": 139.1860336202907,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.8030005342619584,
"mean_reprojection_error_px": 274.10231703594735,
"corner_reprojection_errors_px": [
237.94975046516782,
264.59434010512416,
308.4630690582601,
285.4021085152374
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.7644345761691943,
"mean_reprojection_error_px": 72.4273672978813,
"corner_reprojection_errors_px": [
46.62612295920637,
34.03763203823939,
100.37413904993618,
108.67157514414326
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.6200400277429619,
"mean_reprojection_error_px": 71.02841652704336,
"corner_reprojection_errors_px": [
102.37939191515659,
31.002903116690042,
42.58563098754617,
108.14574008878068
]
}
]
},
{
"marker_id": 219,
"link_name": "Arm2",
"position_world_m": [
0.18286949457746193,
-0.08522646866588529,
-0.20446477458093276
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,966 @@
{
"schema_version": "1.0",
"created_utc": "2026-05-28T21:06:06.508985Z",
"source_robot_json": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\robot.json",
"source_detections": [
"C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json"
],
"robot_pose": {
"state": {
"x": 178.4429017494112,
"y": 58.47337818356634,
"z": -115.44834336922801,
"a": -110.23287555471994,
"b": 21.99999999999879,
"c": 91.00000000000222,
"e": 10.000000000003752
},
"uncertainty": {
"x_mm": 452.93520846643963,
"y_mm": 2505.6853087014742,
"z_mm": 479.7547548672694,
"a_deg": 419.0184635434338,
"b_deg": 10357.838151169784,
"c_deg": 10357.838358456424,
"e_mm": 103578.27625405855
},
"confidence": {
"x": 2.1343902596830898e-20,
"y": 1.5117142415373693e-109,
"z": 1.460547647110293e-21,
"a": 6.342483509852416e-19,
"b": 0.0,
"c": 0.0,
"e": 0.36787944117144233
}
},
"camera_poses": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"camera_id": "cam1",
"camera_position_world_m": [
-152.34091569714903,
-19.013446299323782,
-99.72128351445228
],
"rvec": [
-5.499131960772023,
-21.91270066095422,
3.835322497331174
],
"tvec": [
1.5121559708775556,
7.396415796967461,
-182.91147186295072
],
"intrinsics": {
"camera_matrix": [
[
1777.77783203125,
0.0,
640.0
],
[
0.0,
1500.0,
360.0
],
[
0.0,
0.0,
1.0
]
],
"distortion_coefficients": [
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"observation_count": 17
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"camera_id": "cam1",
"camera_position_world_m": [
0.08962448933807897,
0.30899658581804224,
-0.6948843092613544
],
"rvec": [
-0.523540707058038,
-3.160660993109504,
-0.8426789395152159
],
"tvec": [
0.17310377758414208,
0.03144977103054412,
-0.7452661514399899
],
"intrinsics": {
"camera_matrix": [
[
1777.77783203125,
0.0,
640.0
],
[
0.0,
1500.0,
360.0
],
[
0.0,
0.0,
1.0
]
],
"distortion_coefficients": [
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"observation_count": 10
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"camera_id": "cam1",
"camera_position_world_m": [
0.19930315742175841,
-0.4034279127468583,
0.6690051405865579
],
"rvec": [
-3.515407873484386,
0.6592488811642211,
0.3554510183912642
],
"tvec": [
-0.12629764596438484,
0.0026714931553666757,
0.7962948418902289
],
"intrinsics": {
"camera_matrix": [
[
1777.77783203125,
0.0,
640.0
],
[
0.0,
1500.0,
360.0
],
[
0.0,
0.0,
1.0
]
],
"distortion_coefficients": [
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"observation_count": 7
}
],
"marker_positions": [
{
"marker_id": 210,
"link_name": "Board",
"position_world_m": [
0.02,
-0.02,
0.0003
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.6997700579082148,
"mean_reprojection_error_px": 219.98520262396713,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.5673043275049208,
"mean_reprojection_error_px": 582.6928891559149,
"corner_reprojection_errors_px": [
608.4008310831335,
564.1978138064919,
556.9944685718621,
601.1784431621722
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.7813266383036261,
"mean_reprojection_error_px": 39.955946109809304,
"corner_reprojection_errors_px": [
50.05440600297249,
62.06318313444874,
46.054117736318254,
1.6520775654977295
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.7506792079160973,
"mean_reprojection_error_px": 37.30677260617712,
"corner_reprojection_errors_px": [
63.966234566185975,
24.372791532782248,
11.26324714586789,
49.62481717987238
]
}
]
},
{
"marker_id": 211,
"link_name": "Board",
"position_world_m": [
0.25,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.6412317666518965,
"mean_reprojection_error_px": 213.01123894247837,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.6412317666518965,
"mean_reprojection_error_px": 213.01123894247837,
"corner_reprojection_errors_px": [
236.1031649719927,
200.91925610460427,
190.4144522672059,
224.6080824261106
]
}
]
},
{
"marker_id": 215,
"link_name": "Board",
"position_world_m": [
0.25,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.8108527003549935,
"mean_reprojection_error_px": 198.10515794746527,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.7952557920267838,
"mean_reprojection_error_px": 204.57188538155356,
"corner_reprojection_errors_px": [
217.65412604361978,
184.2181761177656,
192.91255827135913,
223.50268109346976
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.826449608683203,
"mean_reprojection_error_px": 191.63843051337702,
"corner_reprojection_errors_px": [
226.90990661141322,
176.45008691276055,
156.32935430702966,
206.86437422230463
]
}
]
},
{
"marker_id": 214,
"link_name": "Board",
"position_world_m": [
0.35000000000000003,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.6041252446922665,
"mean_reprojection_error_px": 443.42359136317395,
"observations": [
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.6041252446922665,
"mean_reprojection_error_px": 443.42359136317395,
"corner_reprojection_errors_px": [
448.5785815165172,
422.5680699902232,
440.44289571629525,
462.10481822966005
]
}
]
},
{
"marker_id": 208,
"link_name": "Board",
"position_world_m": [
0.35000000000000003,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.6280424706698967,
"mean_reprojection_error_px": 104.44146786678962,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.6280424706698967,
"mean_reprojection_error_px": 104.44146786678962,
"corner_reprojection_errors_px": [
99.73569678834396,
84.11026583409073,
111.81629138739532,
122.10361745732845
]
}
]
},
{
"marker_id": 206,
"link_name": "Board",
"position_world_m": [
0.65,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.33820423087105295,
"mean_reprojection_error_px": 269.1974700827843,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.33820423087105295,
"mean_reprojection_error_px": 269.1974700827843,
"corner_reprojection_errors_px": [
250.50345115135508,
274.79359034013294,
287.8396527117673,
263.6531861278821
]
}
]
},
{
"marker_id": 205,
"link_name": "Board",
"position_world_m": [
0.75,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.28724459014346065,
"mean_reprojection_error_px": 418.6572411272094,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.28724459014346065,
"mean_reprojection_error_px": 418.6572411272094,
"corner_reprojection_errors_px": [
397.9342521865105,
419.4901443591502,
439.2360756416858,
417.96849232149117
]
}
]
},
{
"marker_id": 207,
"link_name": "Board",
"position_world_m": [
0.75,
-0.01,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.27228561618555186,
"mean_reprojection_error_px": 365.7887375890425,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.27228561618555186,
"mean_reprojection_error_px": 365.7887375890425,
"corner_reprojection_errors_px": [
348.36626075065936,
369.7628063312705,
383.5097638627369,
361.51611941150327
]
}
]
},
{
"marker_id": 217,
"link_name": "Board",
"position_world_m": [
0.65,
-0.09,
0.0003
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.35596573288593486,
"mean_reprojection_error_px": 324.2590328208219,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.35596573288593486,
"mean_reprojection_error_px": 324.2590328208219,
"corner_reprojection_errors_px": [
302.09103568606685,
325.56745248682427,
346.3582043745394,
323.01943873585714
]
}
]
},
{
"marker_id": 198,
"link_name": "Arm1",
"position_world_m": [
0.1784429017494112,
-0.05382924293825046,
0.15468488162937843
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.16652042240877096,
"mean_reprojection_error_px": 173.03325228464936,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.20150745250271476,
"mean_reprojection_error_px": 247.8149724280193,
"corner_reprojection_errors_px": [
273.59743355359444,
245.42488018268014,
222.46143771784855,
249.77613825795413
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.13153339231482716,
"mean_reprojection_error_px": 98.25153214127941,
"corner_reprojection_errors_px": [
86.59176111374053,
97.5474468583387,
120.03541185467505,
88.83150873836338
]
}
]
},
{
"marker_id": 229,
"link_name": "Arm1",
"position_world_m": [
0.1784429017494112,
-0.100889763924023,
0.23140063858026605
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.2274747191640703,
"mean_reprojection_error_px": 110.21276026061363,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.26810902547334975,
"mean_reprojection_error_px": 155.73254773328267,
"corner_reprojection_errors_px": [
180.08971330368811,
157.10232370463987,
131.63039756460225,
154.10775636020045
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.18684041285479083,
"mean_reprojection_error_px": 64.69297278794461,
"corner_reprojection_errors_px": [
54.16045919019677,
65.65659031871526,
89.34107228321712,
49.613769359649325
]
}
]
},
{
"marker_id": 242,
"link_name": "Arm1",
"position_world_m": [
0.1784429017494112,
-0.1605575748858245,
0.19479801114688738
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 243,
"link_name": "Arm1",
"position_world_m": [
0.1784429017494112,
-0.14902498312161308,
0.24293323034447747
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.9174550709990235,
"mean_reprojection_error_px": 44.17934855555228,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.8899728634608616,
"mean_reprojection_error_px": 78.81046188174007,
"corner_reprojection_errors_px": [
101.13197340765323,
72.09114541671448,
50.99718387664307,
91.0215448259495
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.9132502955127223,
"mean_reprojection_error_px": 16.837449190455786,
"corner_reprojection_errors_px": [
14.377732738720587,
19.045826627822404,
19.362443297536196,
14.563794097743955
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.9491420540234864,
"mean_reprojection_error_px": 36.89013459446098,
"corner_reprojection_errors_px": [
44.535439947724775,
1.0820871934520055,
48.70513064720212,
53.23788058946502
]
}
]
},
{
"marker_id": 244,
"link_name": "Ellbow",
"position_world_m": [
0.3034429017494112,
0.0,
0.0
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 245,
"link_name": "Ellbow",
"position_world_m": [
0.2684429017494112,
0.02934513796721045,
-0.019075190108761277
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 246,
"link_name": "Ellbow",
"position_world_m": [
0.2684429017494112,
-0.02934513796721045,
0.019075190108761277
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.6792447690577089,
"mean_reprojection_error_px": 75.53581325353834,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.8015179238063419,
"mean_reprojection_error_px": 130.96780196095426,
"corner_reprojection_errors_px": [
131.52551717948674,
149.219632082114,
133.91939902794635,
109.2066595542699
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.7581700566520715,
"mean_reprojection_error_px": 40.470485652838775,
"corner_reprojection_errors_px": [
54.08557804238714,
23.05261314724495,
38.690970054967124,
46.052781366755866
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.4780463267147134,
"mean_reprojection_error_px": 55.16915214682197,
"corner_reprojection_errors_px": [
4.544326976345962,
67.214794241062,
103.3039176555024,
45.61356971437756
]
}
]
},
{
"marker_id": 247,
"link_name": "Ellbow",
"position_world_m": [
0.23094290174941118,
-0.02934513796721045,
0.019075190108761277
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.7117000257529918,
"mean_reprojection_error_px": 71.76825320561402,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.8796777163094818,
"mean_reprojection_error_px": 109.30571474994146,
"corner_reprojection_errors_px": [
125.10305772921419,
126.687286672494,
97.67977416118754,
87.7527404368701
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.7448005120854795,
"mean_reprojection_error_px": 41.53123348024193,
"corner_reprojection_errors_px": [
62.91946766510497,
50.39956997542899,
30.245119355881712,
22.56077692455205
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.5106218488640142,
"mean_reprojection_error_px": 64.46781138665867,
"corner_reprojection_errors_px": [
53.21697434340821,
81.71744187395875,
89.31098122106415,
33.62584810820357
]
}
]
},
{
"marker_id": 124,
"link_name": "Arm2",
"position_world_m": [
0.1905471840415046,
-0.14689057815962717,
-0.16571856986506908
],
"size_m": 0.025,
"observation_count": 2,
"mean_confidence": 0.4106676690676095,
"mean_reprojection_error_px": 110.73873242579455,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.34091855704160245,
"mean_reprojection_error_px": 135.68416308738642,
"corner_reprojection_errors_px": [
122.09592215917837,
104.54432626694273,
149.5516106827851,
166.54479324063954
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.4804167810936165,
"mean_reprojection_error_px": 85.79330176420268,
"corner_reprojection_errors_px": [
80.25443845468878,
44.769514846082515,
101.1257798229199,
117.02347393311956
]
}
]
},
{
"marker_id": 122,
"link_name": "Arm2",
"position_world_m": [
0.2065917439726603,
-0.09049216814460652,
-0.1582492027972934
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.5690911450460799,
"mean_reprojection_error_px": 112.17658015146829,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.18665602922528673,
"mean_reprojection_error_px": 193.21424613028685,
"corner_reprojection_errors_px": [
163.5154782260392,
189.8197178899714,
221.16013279196892,
198.36165561316793
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.9456847621099602,
"mean_reprojection_error_px": 59.619753741849934,
"corner_reprojection_errors_px": [
62.57446736991388,
94.35842345190635,
52.23720146118015,
29.308922684399352
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.5749326438029929,
"mean_reprojection_error_px": 83.69574058226807,
"corner_reprojection_errors_px": [
95.82678742449872,
125.24546180132528,
68.46920157539058,
45.24151152785769
]
}
]
},
{
"marker_id": 218,
"link_name": "Arm2",
"position_world_m": [
0.16633861945731776,
-0.03350621972608657,
-0.1118025920534069
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
},
{
"marker_id": 101,
"link_name": "Arm2",
"position_world_m": [
0.146660650151536,
-0.08689676917303694,
-0.16058631632445308
],
"size_m": 0.025,
"observation_count": 1,
"mean_confidence": 0.3325004465415643,
"mean_reprojection_error_px": 267.5418618263792,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.3325004465415643,
"mean_reprojection_error_px": 267.5418618263792,
"corner_reprojection_errors_px": [
251.71309778665415,
235.71974142034514,
284.44360570880553,
298.291002389712
]
}
]
},
{
"marker_id": 102,
"link_name": "Arm2",
"position_world_m": [
0.1637795636789253,
-0.12583826165265122,
-0.13527321767766717
],
"size_m": 0.025,
"observation_count": 3,
"mean_confidence": 0.8473641170835915,
"mean_reprojection_error_px": 135.5185983259116,
"observations": [
{
"view_index": 0,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a.png",
"confidence": 0.9494437061622057,
"mean_reprojection_error_px": 275.69603657656097,
"corner_reprojection_errors_px": [
239.57198324805327,
266.35716033620514,
310.0207932359541,
286.8342094860313
]
},
{
"view_index": 1,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b.png",
"confidence": 0.8913851020933449,
"mean_reprojection_error_px": 62.952236808026484,
"corner_reprojection_errors_px": [
32.059770721250835,
38.09936127795048,
90.48302392022927,
91.16679131267536
]
},
{
"view_index": 2,
"source_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json",
"image_file": "C:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c.png",
"confidence": 0.7012635429952238,
"mean_reprojection_error_px": 67.90752159314737,
"corner_reprojection_errors_px": [
102.72093538416912,
41.99761561398122,
26.866949386310086,
100.04458598812904
]
}
]
},
{
"marker_id": 219,
"link_name": "Arm2",
"position_world_m": [
0.16633861945731776,
-0.09182180091572817,
-0.201514870981736
],
"size_m": 0.025,
"observation_count": 0,
"mean_confidence": null,
"mean_reprojection_error_px": null,
"observations": []
}
]
}

View File

@@ -0,0 +1,72 @@
{
"schema_version": "1.0",
"created_utc": "2026-05-28T21:18:54.942233Z",
"source_robot_json": "c:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\robot.json",
"source_detections": [
"c:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1a_aruco_detection.json",
"c:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1b_aruco_detection.json",
"c:\\Users\\kech\\SynologyDrive\\2026-AppServer-AppRobot\\appRobotRendering\\pipeline\\render_1c_aruco_detection.json"
],
"solver": {
"final_cost": 26976.87543820547,
"status": 0,
"message": "The maximum number of function evaluations is exceeded."
},
"robot_pose": {
"state": {
"x": 100.21525656040845,
"y": 29.397626553503173,
"z": -36.04825291833897,
"a": -120.21337951359496,
"b": 22.00000000000008,
"c": 90.99999999999989,
"e": 10.000000000000258
},
"uncertainty": {
"x_mm": 17194.235819424397,
"y_mm": 6014.1408132596725,
"z_mm": 3621.298501957444,
"a_deg": 8796.009134059024,
"b_deg": 12562.017200522667,
"c_deg": 12562.017199380893,
"e_mm": 125620.17196327279
},
"confidence": {
"x": 0.0,
"y": 6.444409678452354e-262,
"z": 5.358019964976179e-158,
"a": 0.0,
"b": 0.0,
"c": 0.0,
"e": 0.36787944117144233
}
},
"statistics": {
"observation_count": 34,
"camera_count": 3,
"marker_count": 23,
"observed_marker_count": 18,
"mean_detector_confidence": 0.5871913728875101,
"mean_weighted_confidence": 0.5774319023193206,
"mean_reprojection_error_px": 236.06726174073253,
"quality_means": {
"size": 0.8143773112577551,
"aspect": 0.834768084426124,
"border": 0.9237745098039215,
"homography": 0.7934741744632792
},
"quality_config": {
"size_ref_px": 50.0,
"border_ref_px": 120.0,
"center_ref_norm": 0.01,
"sharpness_ref": 2500.0,
"homography_ref": 0.18,
"size_factor": 0.01,
"aspect_factor": 0.01,
"border_factor": 0.01,
"center_factor": 0.01,
"sharpness_factor": 0.01,
"homography_factor": 0.01
}
}
}

View File

@@ -1,6 +1,6 @@
{ {
"schema_version": "1.0", "schema_version": "1.0",
"created_utc": "2026-05-28T20:31:58Z", "created_utc": "2026-05-28T21:11:29Z",
"vision_config": { "vision_config": {
"MarkerType": "DICT_4X4_250", "MarkerType": "DICT_4X4_250",
"MarkerSize": 0.025 "MarkerSize": 0.025
@@ -46,7 +46,7 @@
}, },
"detections": [ "detections": [
{ {
"observation_id": "11b87d79-31e1-4293-abe2-4327e2021077", "observation_id": "e4f9bd9f-a867-4d2d-b0d3-c438f92fa01f",
"type": "aruco", "type": "aruco",
"marker_id": 102, "marker_id": 102,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -100,7 +100,7 @@
"confidence": 0.9494437061622057 "confidence": 0.9494437061622057
}, },
{ {
"observation_id": "3f6119be-7ed5-4ba2-b0b8-f2eb85c0f4ac", "observation_id": "3669c72d-6c64-46a9-9041-e0f332ed19a3",
"type": "aruco", "type": "aruco",
"marker_id": 243, "marker_id": 243,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -154,7 +154,7 @@
"confidence": 0.8899728634608616 "confidence": 0.8899728634608616
}, },
{ {
"observation_id": "a963cd6a-dea8-47d2-ba29-7410d1add84e", "observation_id": "920dccb3-6db8-43fb-b0b3-036af4ed6acb",
"type": "aruco", "type": "aruco",
"marker_id": 210, "marker_id": 210,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -208,7 +208,7 @@
"confidence": 0.5673043275049208 "confidence": 0.5673043275049208
}, },
{ {
"observation_id": "5da8c3b3-2b28-47b4-9132-c5225c5ba0bb", "observation_id": "858fbb79-58d9-436a-9147-96b0a15fd61e",
"type": "aruco", "type": "aruco",
"marker_id": 247, "marker_id": 247,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -262,7 +262,7 @@
"confidence": 0.8796777163094818 "confidence": 0.8796777163094818
}, },
{ {
"observation_id": "b765e797-5dd9-4123-ac41-85b5606132a0", "observation_id": "cafb1327-eefa-457c-9b1e-ed2d2ad78a8c",
"type": "aruco", "type": "aruco",
"marker_id": 246, "marker_id": 246,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -316,7 +316,7 @@
"confidence": 0.8015179238063419 "confidence": 0.8015179238063419
}, },
{ {
"observation_id": "aca8152b-525f-45e7-9849-c46d2123a21c", "observation_id": "cb014fcc-1cbe-4e59-afaa-6ea599b7f5ac",
"type": "aruco", "type": "aruco",
"marker_id": 101, "marker_id": 101,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -370,7 +370,7 @@
"confidence": 0.3325004465415643 "confidence": 0.3325004465415643
}, },
{ {
"observation_id": "f0a37d05-7187-4ae1-a53a-2b0260b751b4", "observation_id": "a59f4cec-6038-48f8-b124-11a1b6e89cf3",
"type": "aruco", "type": "aruco",
"marker_id": 215, "marker_id": 215,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -424,7 +424,7 @@
"confidence": 0.7952557920267838 "confidence": 0.7952557920267838
}, },
{ {
"observation_id": "38cfa10c-246e-439d-ba19-ab664d030ccc", "observation_id": "0d4d4912-7df5-4189-80d0-cc1066edd2e1",
"type": "aruco", "type": "aruco",
"marker_id": 124, "marker_id": 124,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -478,7 +478,7 @@
"confidence": 0.34091855704160245 "confidence": 0.34091855704160245
}, },
{ {
"observation_id": "95edc7e5-0883-4ab5-8b6a-1f35d392d98a", "observation_id": "47b5571d-68d0-4738-93c5-06c45c91168f",
"type": "aruco", "type": "aruco",
"marker_id": 229, "marker_id": 229,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -532,7 +532,7 @@
"confidence": 0.26810902547334975 "confidence": 0.26810902547334975
}, },
{ {
"observation_id": "602d2bac-2427-4211-86be-9a2b5905a4e4", "observation_id": "3a5f11f4-1233-41b7-9ce8-db0acaa2855e",
"type": "aruco", "type": "aruco",
"marker_id": 122, "marker_id": 122,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -586,7 +586,7 @@
"confidence": 0.18665602922528673 "confidence": 0.18665602922528673
}, },
{ {
"observation_id": "308e1529-24a2-49e0-a7ad-6ac4fdead845", "observation_id": "76947529-1679-4274-b547-56c7d61dde94",
"type": "aruco", "type": "aruco",
"marker_id": 198, "marker_id": 198,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -640,7 +640,7 @@
"confidence": 0.20150745250271476 "confidence": 0.20150745250271476
}, },
{ {
"observation_id": "0159ed42-fec0-4b1c-a383-9ca83f5a320f", "observation_id": "7a157cbe-69a0-4f8d-b89d-af82d8470779",
"type": "aruco", "type": "aruco",
"marker_id": 211, "marker_id": 211,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -694,7 +694,7 @@
"confidence": 0.6412317666518965 "confidence": 0.6412317666518965
}, },
{ {
"observation_id": "4fa2ceb4-7915-4dba-b0f8-9700378eec0b", "observation_id": "19c6229a-77c2-4846-86c2-2fe3cabd5ace",
"type": "aruco", "type": "aruco",
"marker_id": 208, "marker_id": 208,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -748,7 +748,7 @@
"confidence": 0.6280424706698967 "confidence": 0.6280424706698967
}, },
{ {
"observation_id": "d3bc0898-720e-4180-b70a-57a9adc4a30e", "observation_id": "cf1fe906-7f6e-459b-96cc-a983a15d58b7",
"type": "aruco", "type": "aruco",
"marker_id": 217, "marker_id": 217,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -802,7 +802,7 @@
"confidence": 0.35596573288593486 "confidence": 0.35596573288593486
}, },
{ {
"observation_id": "d0d5506c-abf1-495f-bce9-33d4b374e599", "observation_id": "249e0b3c-e2b1-40e4-baf8-073d85acc08c",
"type": "aruco", "type": "aruco",
"marker_id": 206, "marker_id": 206,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -856,7 +856,7 @@
"confidence": 0.33820423087105295 "confidence": 0.33820423087105295
}, },
{ {
"observation_id": "7b5443c9-fb76-4270-8c88-32c5a20953c1", "observation_id": "152dc984-1ca4-4ca0-aad7-1bbbd8aba04f",
"type": "aruco", "type": "aruco",
"marker_id": 205, "marker_id": 205,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -910,7 +910,7 @@
"confidence": 0.28724459014346065 "confidence": 0.28724459014346065
}, },
{ {
"observation_id": "ac8f8ef1-039a-4762-a4f6-8bdea9e3a4e4", "observation_id": "66ce6541-9898-4bfc-9201-0d05fa7e6e62",
"type": "aruco", "type": "aruco",
"marker_id": 207, "marker_id": 207,
"marker_size_m": 0.025, "marker_size_m": 0.025,

View File

@@ -1,6 +1,6 @@
{ {
"schema_version": "1.0", "schema_version": "1.0",
"created_utc": "2026-05-28T20:31:58Z", "created_utc": "2026-05-28T21:11:29Z",
"vision_config": { "vision_config": {
"MarkerType": "DICT_4X4_250", "MarkerType": "DICT_4X4_250",
"MarkerSize": 0.025 "MarkerSize": 0.025
@@ -46,7 +46,7 @@
}, },
"detections": [ "detections": [
{ {
"observation_id": "e2619c8d-750d-4334-9b42-cb95426ed979", "observation_id": "e57e88d8-4e83-459d-aacb-54dfb3d1a39f",
"type": "aruco", "type": "aruco",
"marker_id": 102, "marker_id": 102,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -100,7 +100,7 @@
"confidence": 0.8913851020933449 "confidence": 0.8913851020933449
}, },
{ {
"observation_id": "d1d011f0-a579-448e-ace9-ea148ecbc5e7", "observation_id": "3bb219ef-c7ff-4f0b-8cfa-284ead393c38",
"type": "aruco", "type": "aruco",
"marker_id": 124, "marker_id": 124,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -154,7 +154,7 @@
"confidence": 0.4804167810936165 "confidence": 0.4804167810936165
}, },
{ {
"observation_id": "1b5435e6-7883-434c-a9c7-63f4b8d750d1", "observation_id": "26604964-f6c1-492f-964c-850826bc69cf",
"type": "aruco", "type": "aruco",
"marker_id": 243, "marker_id": 243,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -208,7 +208,7 @@
"confidence": 0.9132502955127223 "confidence": 0.9132502955127223
}, },
{ {
"observation_id": "a673d83e-73ff-4d2d-a412-470b6da12d56", "observation_id": "5feff871-616f-43e8-b817-f5d67116b577",
"type": "aruco", "type": "aruco",
"marker_id": 122, "marker_id": 122,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -262,7 +262,7 @@
"confidence": 0.9456847621099602 "confidence": 0.9456847621099602
}, },
{ {
"observation_id": "a755c678-9ad1-4af2-a744-a3c1a8ae0596", "observation_id": "957db844-bb09-47b0-95a9-c2703a9f8a07",
"type": "aruco", "type": "aruco",
"marker_id": 247, "marker_id": 247,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -316,7 +316,7 @@
"confidence": 0.7448005120854795 "confidence": 0.7448005120854795
}, },
{ {
"observation_id": "38397299-767f-490d-a47f-a3f09635e995", "observation_id": "ce61eb47-425e-45a1-8079-a8da9414a07b",
"type": "aruco", "type": "aruco",
"marker_id": 246, "marker_id": 246,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -370,7 +370,7 @@
"confidence": 0.7581700566520715 "confidence": 0.7581700566520715
}, },
{ {
"observation_id": "273cc5e9-e6e4-4359-8fb1-02a33a935f0d", "observation_id": "408b46a0-047b-4718-bc75-25fe6edef9ed",
"type": "aruco", "type": "aruco",
"marker_id": 215, "marker_id": 215,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -424,7 +424,7 @@
"confidence": 0.826449608683203 "confidence": 0.826449608683203
}, },
{ {
"observation_id": "8da984fe-3152-43d6-8a39-b4e9266c2184", "observation_id": "e971c366-cf09-4c98-8aba-6778415b54e2",
"type": "aruco", "type": "aruco",
"marker_id": 210, "marker_id": 210,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -478,7 +478,7 @@
"confidence": 0.7813266383036261 "confidence": 0.7813266383036261
}, },
{ {
"observation_id": "92d1be16-65a4-4855-a63c-4d21357d0a9d", "observation_id": "898e0c56-a4d2-4cbf-945e-1fa1cbec6ad5",
"type": "aruco", "type": "aruco",
"marker_id": 229, "marker_id": 229,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -532,7 +532,7 @@
"confidence": 0.18684041285479083 "confidence": 0.18684041285479083
}, },
{ {
"observation_id": "5cbcbaf2-7581-4f25-81e3-85b4ce5a03b2", "observation_id": "87adf180-14ed-459f-8d33-64e48ff2b517",
"type": "aruco", "type": "aruco",
"marker_id": 198, "marker_id": 198,
"marker_size_m": 0.025, "marker_size_m": 0.025,

View File

@@ -1,6 +1,6 @@
{ {
"schema_version": "1.0", "schema_version": "1.0",
"created_utc": "2026-05-28T20:31:59Z", "created_utc": "2026-05-28T21:11:30Z",
"vision_config": { "vision_config": {
"MarkerType": "DICT_4X4_250", "MarkerType": "DICT_4X4_250",
"MarkerSize": 0.025 "MarkerSize": 0.025
@@ -46,7 +46,7 @@
}, },
"detections": [ "detections": [
{ {
"observation_id": "6df4525f-58c7-4757-8a5e-130486fc123b", "observation_id": "55dad250-7787-4d70-b26b-51bf9c63187f",
"type": "aruco", "type": "aruco",
"marker_id": 102, "marker_id": 102,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -100,7 +100,7 @@
"confidence": 0.7012635429952238 "confidence": 0.7012635429952238
}, },
{ {
"observation_id": "c4dd28db-6041-4464-867a-96b25d671918", "observation_id": "5af894e7-68a0-4872-ada4-689be1d630b6",
"type": "aruco", "type": "aruco",
"marker_id": 122, "marker_id": 122,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -154,7 +154,7 @@
"confidence": 0.5749326438029929 "confidence": 0.5749326438029929
}, },
{ {
"observation_id": "5bdf65ad-9a27-4a28-be1e-6e925df61c72", "observation_id": "47253afe-906c-444d-af3f-db6e99351519",
"type": "aruco", "type": "aruco",
"marker_id": 243, "marker_id": 243,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -208,7 +208,7 @@
"confidence": 0.9491420540234864 "confidence": 0.9491420540234864
}, },
{ {
"observation_id": "37b9dec9-2578-4765-a40a-516fbe0424f5", "observation_id": "4b090e16-a2e9-4cb6-81e4-307a04f04820",
"type": "aruco", "type": "aruco",
"marker_id": 246, "marker_id": 246,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -262,7 +262,7 @@
"confidence": 0.4780463267147134 "confidence": 0.4780463267147134
}, },
{ {
"observation_id": "a49c96d9-7884-4ddb-8c07-8f86130b5220", "observation_id": "8d0cb44f-1cd1-41a3-a6bc-c39527148ec7",
"type": "aruco", "type": "aruco",
"marker_id": 247, "marker_id": 247,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -316,7 +316,7 @@
"confidence": 0.5106218488640142 "confidence": 0.5106218488640142
}, },
{ {
"observation_id": "4da1fc9d-a7a9-4dd8-a39b-a667b8d5d165", "observation_id": "cde0d217-048d-42a3-88bd-52c1d620984f",
"type": "aruco", "type": "aruco",
"marker_id": 214, "marker_id": 214,
"marker_size_m": 0.025, "marker_size_m": 0.025,
@@ -370,7 +370,7 @@
"confidence": 0.6041252446922665 "confidence": 0.6041252446922665
}, },
{ {
"observation_id": "a88abb43-d181-4584-b59b-28c65a2fff45", "observation_id": "1e6148fd-0380-48d2-93f9-b4a546a35a7c",
"type": "aruco", "type": "aruco",
"marker_id": 210, "marker_id": 210,
"marker_size_m": 0.025, "marker_size_m": 0.025,

View File

@@ -3,4 +3,4 @@ python3 1_detect_aruco_observations.py --image render_1a.png -npz render.npz -ro
python3 1_detect_aruco_observations.py --image render_1b.png -npz render.npz -robot ../robot.json -cameraId cam1 -outDir . python3 1_detect_aruco_observations.py --image render_1b.png -npz render.npz -robot ../robot.json -cameraId cam1 -outDir .
python3 1_detect_aruco_observations.py --image render_1c.png -npz render.npz -robot ../robot.json -cameraId cam1 -outDir . python3 1_detect_aruco_observations.py --image render_1c.png -npz render.npz -robot ../robot.json -cameraId cam1 -outDir .
python3 2_Multiview.py --robot ../robot.json --detections render_1a_aruco_detection.json render_1b_aruco_detection.json render_1c_aruco_detection.json --outDir . python3 2_Multiview_ersteVersion.py --robot ../robot.json --detections render_1a_aruco_detection.json render_1b_aruco_detection.json render_1c_aruco_detection.json --outDir .