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.
@kubohiroya/turbowarp-camera-source loaded first. This extension never opens a camera itself.9 by 6. Supported boards are 9×6, 7×5, and 5×4 inner corners; draw them with the package's ./runtime helpers so the markers match.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.
During automatic capture a frame that cannot be used is not an error. camera calibration guidance says what to do instead:
| Guidance | Meaning |
|---|---|
show-the-board | Nothing is visible |
wrong-board | Markers are visible but fewer than six corners of the selected board: another board, or the right one at an unreadable angle |
hold-steadier | The view is blurred |
move-or-tilt | The view is too much like one already kept |
tilt-more | The kept views are not tilted enough to solve from |
keep-going | The background solve cannot reproduce even its own samples yet |
vary-more | It reproduces its own samples but not the held-back ones: the views are too alike |
solving, complete | Working, or done |
The person holding the board is often not looking at the screen. Three reporters are meant for continuous cues such as sounds:
camera calibration tilt direction: top-near, top-far, left-near, or right-near, whichever direction the kept views reach least.camera calibration novelty: 0 to 1, how much the view in front of the camera would add. It is measured on tilt, so sliding the board does not raise it.camera calibration progress: 0 to 16, four gates of four steps — enough views to solve, enough tilt among them, enough to hold some back, and an answer that holds on the held-back views. It never goes down.| Do this | Why |
|---|---|
| Solve by hand from at least 8 accepted samples; automatic capture collects 12 or more and holds about a fifth back | Fewer 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 samples | Views 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 corners | Distortion 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 moment | A blurred view is refused |
| Do not change resolution, camera device, mirroring, resize mode, focus, or zoom during a session | The 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 projectors | Keystone 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.
| Block | Type | Does |
|---|---|---|
start camera [CAMERA_ID] calibration [CALIBRATION_ID] board [COLUMNS] by [ROWS] square [SQUARE_METERS] m marker [MARKER_METERS] m max error [MAX_ERROR_PX] px | Command | Leases one shared camera and fixes its real capture resolution and conditions for a session |
add calibration sample for camera [CAMERA_ID] | Command | Detects the board in the current frame and keeps it when quality and novelty pass |
start automatic calibration capture for camera [CAMERA_ID] | Command | Hands the shutter to the extension, which also solves and finishes the session |
stop automatic calibration capture for camera [CAMERA_ID] | Command | Hands the shutter back; the session and its samples stay |
automatic capture running for camera [CAMERA_ID]? | Boolean | Whether the shutter is still watching that camera |
camera calibration guidance [CAMERA_ID] | Reporter | What the operator should do next |
camera calibration novelty [CAMERA_ID] | Reporter | How much the current view would add, 0 to 1 |
camera calibration tilt direction [CAMERA_ID] | Reporter | Which way to turn the board next |
camera calibration progress [CAMERA_ID] | Reporter | How far the session has come, 0 to 16 |
solve calibration for camera [CAMERA_ID] | Command | Solves the intrinsics from at least eight samples and releases the lease |
cancel calibration for camera [CAMERA_ID] | Command | Releases the lease and the samples, keeping the last validated profile |
cleanup calibration for camera [CAMERA_ID] | Command | Also clears the in-memory profile for that camera |
publish calibration profile for camera [CAMERA_ID] | Command | Registers 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] | Command | Measures where a board that has stopped moving is, in the calibrated camera's frame |
import calibration profile [JSON] for camera [CAMERA_ID] | Command | Imports 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]? | Boolean | Validates a profile without replacing the stored one |
camera [CAMERA_ID] calibration ready? | Boolean | Whether the session can accept a sample or a solve |
camera calibration state [CAMERA_ID] | Reporter | idle, acquiring-camera, sampling, ready, solving, solved, cancelling, or error |
camera calibration backend | Reporter | The pinned solver identifier |
camera [CAMERA_ID] calibration sample count | Reporter | Accepted samples in the current or last solved session |
camera [CAMERA_ID] calibration sample quality | Reporter | The latest accepted sample's score, from zero to one |
camera [CAMERA_ID] calibration reprojection error px | Reporter | RMS reprojection error of the latest solve or imported profile |
camera [CAMERA_ID] calibration pose spread | Reporter | How varied the angles of the samples are; zero cannot be solved from |
camera [CAMERA_ID] calibration holdout error px | Reporter | RMS reprojection error over the samples the solve was not fitted to |
camera [CAMERA_ID] calibration holdout sample count | Reporter | How many samples were held back; zero means nothing was validated |
camera [CAMERA_ID] calibration error code | Reporter | A stable code, or an empty string |
camera [CAMERA_ID] calibration error | Reporter | The detailed diagnostic |
camera [CAMERA_ID] calibration profile JSON | Reporter | The stored intrinsic profile, or an empty string |
camera [CAMERA_ID] board pose JSON | Reporter | The last measured board pose, or an empty string |
| Code | Meaning |
|---|---|
dependency-missing | Camera Source is not loaded |
api-version-mismatch | Camera Source has no profile registry, or speaks another contract version |
invalid-board | The board size, square or marker size, or error limit is out of range |
camera-unavailable | Camera Source refused the lease |
camera-ended | The camera stopped or the device disappeared |
resolution-mismatch | The capture resolution changed after the session started |
capture-condition-mismatch | The 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-found | No markers and fewer than six corners of the board were visible |
wrong-board | Markers were visible but formed fewer than six corners of the selected board |
sample-low-quality | The board was too small in frame, or too blurred |
sample-too-similar | The view is too close to one already kept |
sample-limit | Forty samples are already retained (by hand only; automatic capture replaces one instead) |
sample-insufficient | Fewer than eight samples have been accepted |
sample-poses-degenerate | The samples were never tilted enough to separate focal length from distance |
sample-failed, solve-failed | The solver itself failed |
reprojection-too-high | The solve exceeded the session's error limit |
invalid-calibration | The profile does not match the schema |
credential-forbidden | The profile carries a pairing credential |
calibration-not-applicable | The profile belongs to another camera or another capture size |
not-calibrated | There is no profile to publish, or to measure a board pose with |
board-pose-unavailable | Too little of the board was in view to measure its pose |
publish-failed | Camera Source rejected the profile |
| A refused publication records its code without changing the calibration state: a solved profile stays solved. | |
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.
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.
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.
| Package | Owns |
|---|---|
@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.
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.
See the repository README and architecture documentation for build, test, release, and compatibility details.