MKMinkyung Jun
INDUSTRY

Dual-Marker Relative Pose Estimation
System Overhaul

Period 2026.01 — 2026.06 Role Pose-estimation and detection pipeline improvement · Field validation Keywords C++ · Ceres Solver · Quaternion Filtering · Dynamic ROI

This is a project at my current employer. For confidentiality, product, customer, and site details are omitted; the write-up focuses on the problem-solving process and engineering decisions. The improved code runs on robots in production.

PART 1Pose stabilization — the filters that did nothing

The code had an LPF and a median filter, yet the output jittered almost exactly like the raw signal. Tracing the code revealed the LPF was re-initialized every frame, remembering no state — and its cutoff was set to 100 Hz, driving the interpolation coefficient to 1 so values passed through unchanged. Pitch variance also grew with marker distance (z), and error cases produced 48 frames jumping by more than 2°.

Per-stage pitch time series after fixing the initialization bug
Per-stage pitch time series after the init-bug fix — top to bottom: LPF, Median, Original. Before the fix all three were identical.

Rebuilding the filtering pipeline

Ceres-based pose estimation

Instead of stock OpenCV solvePnP, I re-implemented pose estimation to minimize reprojection error directly with Ceres Solver — Huber loss to bound outlier corners, optimization on the quaternion manifold to avoid rotation singularities, and soft constraints on roll/yaw, which the mechanism physically limits. Six version combinations were replayed on the same rosbag data and compared on pitch std/range, z std, and gap/jump counts to pick the final configuration.

Pose time series before: repeated roll spikes
Before — repeated ±180° roll-flip spikes and pitch noise.
Pose time series after: spikes eliminated
After — same segment, flip spikes gone, all axes stabilized.
59%↓pitch std (normal driving)
0.610° → 0.248°
42%↓noise on error data
1.982° → 1.158°
48 → 0pitch jumps over 2°
fully eliminated
54%↓pitch range
3.16° → 1.46°

PART 2Detection & compute — better detection costs more

On long-wheelbase vehicles the marker-to-camera distance grows, so far-range detection failed — only 5.8% of frames detected while driving. Turning on precise corner refinement (APRILTAG) recovered detection but multiplied processing time ~5×, while two always-on detector nodes loaded the onboard CPU. The task: satisfy detection performance and real-time budget at once.

Detection — quantitative sweep of preprocessing × refinement

Compute — dynamic ROI and execution gating

Rather than scanning full frames, a Kalman filter tracks the marker center and a dynamic ROI detects only where the marker should be. A 4-state design — INIT · TRACKING · SEARCHING · RECOVERY — gradually widens the search on tracking loss and falls back to full-frame search after sustained failure. Detector nodes are now activated only when the driving-mode flag requires them.

Dynamic ROI tracking dual markers in low light
Dynamic ROI in TRACKING — dual-marker detection with pose axes, under a vehicle in low light.
State transition diagram: INIT, TRACKING, SEARCHING, RECOVERY
Dynamic-ROI state machine — four states with fallback paths.

To mimic production conditions, latency was analyzed under stress-ng CPU load — not just the mean but P95/P99 tail latency, since intermittent spikes are what break real-time control.

17×detections while driving
31 → 534
50%↓mean processing time
(with refinement enabled)

DEMODemo video

Marker detection and ROI tracking with water reflections in the scene — TRACKING stays stable under low light and reflective glare

IMPACT