Skip to content

Latest commit

 

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alohacalib

Stereo calibration and camera QC for ZED X / ZED X Mini cameras on Jetson robots, shipped as a container image so the ZED SDK, OpenCV, NumPy and this code move as one pinned artefact.

Two audiences, one image:

  • Operators click a desktop icon, follow an on-screen guide while moving the official checkerboard, and finish. The UI is a local web app, so it works equally on the robot's own screen and on a tablet across the room.
  • Engineering runs the same image headless to re-derive calibrations, bootstrap dispersion and optical-quality metrics over stored sessions.

What the image pins — and what it does not

Containerising removes the software-drift failures (interrupted apt, stale package sources, half-upgraded system libraries). It does not virtualise the platform.

Pinned by the image Must be standardised on every robot
ZED SDK + pyzed JetPack / L4T release (r36.4r36.5, warned at runtime)
Python, NumPy, OpenCV stereolabs-zedx GMSL driver — pinned to L4T and capture-card SKU
CUDA / cuDNN toolkit zed_x_daemon + nvargus-daemon (host services)
This application udev rules (/etc/udev/rules.d/99-slabs.rules)
nvidia-container-toolkit + Docker's nvidia runtime
per-camera factory calibration in /usr/local/zed/settings

If the fleet is not on one JetPack version, this converts software drift into an image matrix. Standardise JetPack first.

alohacalib check verifies the whole contract and prints the fix for anything missing.

Install on a robot

Run as the operator's own account — not under sudo. The desktop-icon trust step writes per-user metadata and silently does nothing as root.

ALOHACALIB_IMAGE=<registry>/alohacalib:0.1.0 ./install/install.sh

It preflights the host, pulls the image, creates /var/lib/alohacalib, installs the launcher and icon, and runs the container's own self-check.

Docker group membership only takes effect in a new login session:

sudo usermod -aG docker "$USER"   # then log out and back in

Run

The desktop icon Calibración de cámaras is the operator path. By hand:

./install/alohacalib-launch.sh          # start if needed, then open the UI
./install/alohacalib-run.sh             # start only

The station listens on 8420. Port 80 is refused on purpose: robots already run a native service there. Because ZED X requires --network host, -p is ignored and the server binds the host port directly.

Headless use

docker run --rm --runtime nvidia --privileged --network host \
  -v /dev/:/dev/ -v /tmp/:/tmp/ \
  -v /usr/local/zed/settings:/usr/local/zed/settings \
  -v /var/lib/alohacalib:/data -e ALOHACALIB_DATA=/data \
  <image> calibrate /data/sessions/<unit>_<serial>/<session> --bootstrap 40

--detector classic re-detects corners with the detector the fielded pipeline uses, which is how you check a result is not an artefact of corner detection.

Commands, all of which work on a stored session and never touch a camera:

Command What it does Exit codes
check verifies the host contract and prints the fix for anything missing 1 on any failure
calibrate <session> the calibration alone 0 accepted, 2 not accepted, 1 unusable
optical <session> MTF50, contrast and residual structure by radial field zone 0, 1 unusable
qc <session> everything: calibration, dispersion, factory comparison, optical, verdicts, HTML report 0 pass, 2 warn, 3 fail, 1 error
report <result-id> re-renders the HTML from a stored result 0, 1 not found

qc splits warn from fail on purpose: a bench script needs to tell "measured, borderline" from "measured, out of spec", which calibrate's single non-zero code cannot express.

What the QC numbers do and do not support

The station measures and never judges a camera on evidence it does not have. Most of what it reports carries no threshold, and says so on screen rather than leaving a blank that reads as a pass. The reasons are in config.QcGates, next to each value:

  • Fresh minus factory, per unit, is the primary statistic — not unit against unit. Across three units the largest deviation was 1.76 px, and that is an upper bound on the detection floor rather than a measured effect, so the gate sits above it at 2 px.
  • All twelve focal deltas in that corpus were negative (mean −1.14 px): a systematic fisheye-vs-factory model bias. A focal delta inside that band is reported without a verdict, because it is arithmetically indistinguishable from the bias.
  • Distortion has no factory reference. The .conf stores a plumb-bob model in [LEFT_CAM_*] and a rational one in [LEFT_DISTO]; we produce cv::fisheye k1..k4. They are never compared. [STEREO].Baseline is read, but ungated — no fresh-vs-factory baseline measurement exists yet.
  • Michelson contrast has an auto-exposure confound and is never evidence of no degradation: AE renormalises the histogram, so a lens transmitting less light can read identically. No exposure metadata is saved, so no threshold can fix this.
  • The outer field zone is edge-coverage limited, not optics limited, so it carries no threshold either. How many edges were rejected, and why, is reported per zone.
  • MTF50 is accurate where these cameras live and over-reports elsewhere. Against analytic ground truth the estimator tracks theory to within 4% for blur sigma 0.5–1.0 px — which covers the observed 0.37 cyc/px — then climbs to +34% at sigma 2.0, with a floor near 0.10 cyc/px. It is good for catching gross defocus and nothing finer.
  • The fielded RMS < 1.0 accept gate is a weak discriminator — all six study calibrations passed it, including the weakest unit — so the interface shows it in grey and labelled, never as a green pass.

Tests

python -m unittest discover -s tests

No camera required: the MTF core is checked against the closed-form Gaussian-blur MTF, the factory comparison reproduces the study's published 1.76 px figure from the real corpus in tests/data/, and optical.measure() runs end to end on a synthesised session. CI runs the suite against the OpenCV 4 the image ships and the version-sensitive files against OpenCV 5, because the fisheye flag values differ between them and a regression there yields wrong numbers rather than an error.

Configuration

Variable Default Purpose
ALOHACALIB_DATA ./alohacalib-data sessions, results, reports — must be a persistent volume
ALOHACALIB_ZED_SETTINGS /usr/local/zed/settings factory calibration cache (host bind mount)
ALOHACALIB_BOARD official calibration target profile
ALOHACALIB_PORT 8420 web station port
ALOHACALIB_CV_THREADS 4 OpenCV threads per worker

Never point ALOHACALIB_DATA at /tmp: these robots wipe it on reboot, which has already cost one study its raw captures. alohacalib check warns if you do.

Notes for maintainers

  • OpenCV version is not assumed. The fisheye calibration flags moved namespaces between OpenCV 4 and 5 and their values differ (MONO_FLAGS is 10 on 4.x and 41943040 on 5.x), so every version-sensitive call goes through cvcompat.py. It is verified against both.
  • The stock base image cannot import NumPy and OpenCV together. JetPack's OpenCV 4.8.0 is built against the NumPy 1.x C-API while the image ships NumPy 2.2.6; import cv2 then raises numpy.core.multiarray failed to import. The Dockerfile installs a NumPy-2-capable OpenCV and asserts the pair imports at build time.
  • CUDA will not speed this up. OpenCV has no CUDA implementation of findChessboardCorners, cornerSubPix, or anything in calib3d/fisheye — the bottleneck is CPU corner detection. What made the preview fluid was threading the camera loop apart from the detector, not the GPU.
  • The bootstrap is still serial, and it is the slowest thing here. Roughly 15 s a draw on an Orin, so about ten minutes at the default 40. ALOHACALIB_WORKERS and Settings.workers exist for a process pool that has not been written yet; until it is, they are unread. Whoever writes it: use a spawn context, not fork — analysis can run while a capture holds the camera and a CUDA context open, and forking that hangs — and have each worker call cvcompat.set_threads(1), because the LM solve barely threads and one thread per process beats four threads in one.
  • Two gate sets, and the difference matters. Gates mirrors the fielded recipe; changing anything in it breaks comparability with calibrations produced on a rig. QcGates is ours, derived from the mx-test three-unit corpus, and safe to retune as fleet data arrives. Its absences are load-bearing and documented in place: a quantity with no defensible threshold has no field there rather than a guessed one.

About

Calibration software for aloha robots

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages