TurboWarp-Camera-Calibration

Calibrate a camera shared through TurboWarp-Camera-Source against a ChArUco board, and publish its intrinsic calibration profile.

Every block is available as soon as the extension is loaded; there is nothing to switch on. The solver runs on a Web Worker and is created on first use, so loading the extension costs nothing until a calibration starts.

Requirements

Calibrating a camera

The simplest way is to let the extension take the samples:

start shared camera [default]
start camera [default] calibration [calibration-1] board [9] by [6] square [0.025] m marker [0.018] m max error [1.5] px
start automatic calibration capture for camera [default]
repeat until <not <automatic capture running for camera [default]?>>
  say (camera calibration guidance [default])
if <(camera calibration state [default]) = [solved]> then
  publish calibration profile for camera [default]

Automatic capture looks at the frame every 250 ms and keeps a view only when it would be accepted. Once it has enough views it solves in the background, and it finishes the session when the error on views held back from the fit is within the limit. At 40 views it does not stop; the view most like the others makes room for the new one.

To take the samples by hand instead:

start camera [default] calibration [calibration-1] board [9] by [6] square [0.025] m marker [0.018] m max error [1.5] px
repeat until <(camera [default] calibration sample count) = 12>
  add calibration sample for camera [default]
solve calibration for camera [default]
publish calibration profile for camera [default]

Each shared camera has its own session, profile, and diagnostics. Cancelling one camera never stops another consumer of the same shared camera.

Guiding the operator

During automatic capture a frame that cannot be used is not an error. camera calibration guidance says what to do instead:

GuidanceMeaning
show-the-boardNothing is visible
wrong-boardMarkers are visible but fewer than six corners of the selected board: another board, or the right one at an unreadable angle
hold-steadierThe view is blurred
move-or-tiltThe view is too much like one already kept
tilt-moreThe kept views are not tilted enough to solve from
keep-goingThe background solve cannot reproduce even its own samples yet
vary-moreIt reproduces its own samples but not the held-back ones: the views are too alike
solving, completeWorking, or done

The person holding the board is often not looking at the screen. Three reporters are meant for continuous cues such as sounds:

Getting a usable result

Do thisWhy
Solve by hand from at least 8 accepted samples; automatic capture collects 12 or more and holds about a fifth backFewer views leave the distortion terms under-determined, and a solve with nothing held back cannot be validated
Tilt the board roughly 20°–45° in different directions across samplesViews that are all fronto-parallel cannot separate focal length from distance. Sliding the board sideways does not help
Vary the distance and fill different parts of the frame, including the cornersDistortion is strongest away from the image center. The markers name the corners, so a board that runs off the frame still counts when at least six corners are visible
Keep the board in focus and hold it still for a momentA blurred view is refused
Do not change resolution, camera device, mirroring, resize mode, focus, or zoom during a sessionThe session is fixed to the conditions it started with, and a change ends it with an error rather than a profile that does not fit the camera
Prefer a printed board or a display set to 1:1; avoid projectorsKeystone correction, oblique projection, and the projector's own lens distort the board in ways the reprojection error does not show

Compare the reprojection error with the hold-out error. A low error on the calibration samples alone does not prove the result generalizes; the two agreeing is the evidence that it does.

Blocks

BlockTypeDoes
start camera [CAMERA_ID] calibration [CALIBRATION_ID] board [COLUMNS] by [ROWS] square [SQUARE_METERS] m marker [MARKER_METERS] m max error [MAX_ERROR_PX] pxCommandLeases one shared camera and fixes its real capture resolution and conditions for a session
add calibration sample for camera [CAMERA_ID]CommandDetects the board in the current frame and keeps it when quality and novelty pass
start automatic calibration capture for camera [CAMERA_ID]CommandHands the shutter to the extension, which also solves and finishes the session
stop automatic calibration capture for camera [CAMERA_ID]CommandHands the shutter back; the session and its samples stay
automatic capture running for camera [CAMERA_ID]?BooleanWhether the shutter is still watching that camera
camera calibration guidance [CAMERA_ID]ReporterWhat the operator should do next
camera calibration novelty [CAMERA_ID]ReporterHow much the current view would add, 0 to 1
camera calibration tilt direction [CAMERA_ID]ReporterWhich way to turn the board next
camera calibration progress [CAMERA_ID]ReporterHow far the session has come, 0 to 16
solve calibration for camera [CAMERA_ID]CommandSolves the intrinsics from at least eight samples and releases the lease
cancel calibration for camera [CAMERA_ID]CommandReleases the lease and the samples, keeping the last validated profile
cleanup calibration for camera [CAMERA_ID]CommandAlso clears the in-memory profile for that camera
publish calibration profile for camera [CAMERA_ID]CommandRegisters the profile with Camera Source, which owns the contract
measure camera [CAMERA_ID] board pose board [COLUMNS] by [ROWS] square [SQUARE_METERS] m marker [MARKER_METERS] m scale [SCALE_SOURCE]CommandMeasures where a board that has stopped moving is, in the calibrated camera's frame
import calibration profile [JSON] for camera [CAMERA_ID]CommandImports a profile after schema, credential, and applicability checks. Reads the ROS camera_info YAML file Camera Source writes, as well as JSON
calibration profile [JSON] valid for camera [CAMERA_ID]?BooleanValidates a profile without replacing the stored one
camera [CAMERA_ID] calibration ready?BooleanWhether the session can accept a sample or a solve
camera calibration state [CAMERA_ID]Reporteridle, acquiring-camera, sampling, ready, solving, solved, cancelling, or error
camera calibration backendReporterThe pinned solver identifier
camera [CAMERA_ID] calibration sample countReporterAccepted samples in the current or last solved session
camera [CAMERA_ID] calibration sample qualityReporterThe latest accepted sample's score, from zero to one
camera [CAMERA_ID] calibration reprojection error pxReporterRMS reprojection error of the latest solve or imported profile
camera [CAMERA_ID] calibration pose spreadReporterHow varied the angles of the samples are; zero cannot be solved from
camera [CAMERA_ID] calibration holdout error pxReporterRMS reprojection error over the samples the solve was not fitted to
camera [CAMERA_ID] calibration holdout sample countReporterHow many samples were held back; zero means nothing was validated
camera [CAMERA_ID] calibration error codeReporterA stable code, or an empty string
camera [CAMERA_ID] calibration errorReporterThe detailed diagnostic
camera [CAMERA_ID] calibration profile JSONReporterThe stored intrinsic profile, or an empty string
camera [CAMERA_ID] board pose JSONReporterThe last measured board pose, or an empty string

Error codes

CodeMeaning
dependency-missingCamera Source is not loaded
api-version-mismatchCamera Source has no profile registry, or speaks another contract version
invalid-boardThe board size, square or marker size, or error limit is out of range
camera-unavailableCamera Source refused the lease
camera-endedThe camera stopped or the device disappeared
resolution-mismatchThe capture resolution changed after the session started
capture-condition-mismatchThe camera device or mirroring changed after the session started, or its resize mode, zoom or focus changed before the solve. The session ends in an error rather than solved.
board-not-foundNo markers and fewer than six corners of the board were visible
wrong-boardMarkers were visible but formed fewer than six corners of the selected board
sample-low-qualityThe board was too small in frame, or too blurred
sample-too-similarThe view is too close to one already kept
sample-limitForty samples are already retained (by hand only; automatic capture replaces one instead)
sample-insufficientFewer than eight samples have been accepted
sample-poses-degenerateThe samples were never tilted enough to separate focal length from distance
sample-failed, solve-failedThe solver itself failed
reprojection-too-highThe solve exceeded the session's error limit
invalid-calibrationThe profile does not match the schema
credential-forbiddenThe profile carries a pairing credential
calibration-not-applicableThe profile belongs to another camera or another capture size
not-calibratedThere is no profile to publish, or to measure a board pose with
board-pose-unavailableToo little of the board was in view to measure its pose
publish-failedCamera Source rejected the profile
A refused publication records its code without changing the calibration state: a solved profile stays solved.

The calibration profile

The profile is intrinsics only: an intrinsic matrix, a distortion model and its coefficients, the calibrated image size, its quality, and when it was measured. It also records the capture conditions the camera reported when the session started (resize mode, zoom, focus mode and distance, frame rate, facing mode) and the camera's own name, so Camera Source can later answer whether the profile fits the camera in front of it. A value the camera did not report is left out rather than guessed.

It never carries a world pose, and a pose that was not measured is never filled in with identity. Preview mirroring is a display concern; profile coordinates are always the unmirrored capture coordinates.

Profiles written by @kubohiroya/turbowarp-realtime-motion-capture are accepted on import; their world pose is dropped rather than reinterpreted.

Calibration files

A calibration leaves the PC as a ROS camera_info YAML file. Publish the profile and read Camera Source's camera profile YAML for [CAMERA_ID] to write one; ROS and OpenCV-based tools read the standard part, and the capture conditions compatibility needs travel under turbowarp_camera_source. import calibration profile reads that file back through Camera Source's own reader, so it is accepted or refused for the same reasons there and here.

Board pose

A calibration needs the board to move, so where a board sits is measured separately, after calibration, once the board has stopped. The result is the board in the camera's frame, never a world pose. Its scale comes from the declared square size, so the block asks whether that size was measured with a ruler or is nominal, and records the answer. The observed corners are kept with it so a placement tool can re-solve the measurement into a shared frame.

How the packages divide the work

PackageOwns
@kubohiroya/turbowarp-camera-source Camera acquisition, lease sharing, preview, capture conditions, and the calibration profile contract
@kubohiroya/turbowarp-camera-calibration The calibration procedure: board drawing and detection, manual and automatic sampling, solving and hold-out validation, board pose measurement, and profile publication

Detecting a ChArUco board and solving a calibration needs OpenCV, and a TurboWarp extension ships as a single standalone bundle. Keeping the solver in its own extension lets Camera Source stay small enough for every camera consumer to load. The OpenCV inside is built for this extension to run in a Web Worker and to carry only the functions it calls; the whole extension is about 3.9 MB.

Safety

Calibration reads camera frames only to detect the board. Nothing is uploaded. A profile that carries a pairing credential is rejected rather than stored. Load unsandboxed extension code only from a source you trust.

Development

See the repository README and architecture documentation for build, test, release, and compatibility details.