Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started: From Target to Calibration Data

This tutorial walks you through the complete workflow:

  1. Choose the right calibration target for your use case.
  2. Generate a printable target file.
  3. Print it correctly.
  4. Write detection code in Python or Rust.

No prior knowledge of the library is assumed.


Step 1: Choose your target type

TargetBest forRequires
ChessboardQuick start, simple intrinsic calibrationNothing — no markers
ChArUcoRobust calibration, partial visibility OK, absolute corner IDsArUco dictionary
Marker boardScenes where a full chessboard is impracticalCustom layout

If you are unsure, start with ChArUco. It combines the subpixel accuracy of chessboard corners with the robustness of ArUco markers. Each detected corner carries a unique ID and a real-world position in millimeters, so partial views of the board are useful and board orientation is unambiguous.

If you want the absolute simplest path and only need basic intrinsic calibration, use the plain chessboard.


Step 2: Generate a printable target

Pick the language you are most comfortable with. All paths produce the same three output files: <stem>.json, <stem>.svg, <stem>.png.

Python

pip install calib-targets
import calib_targets as ct

# ChArUco: 5 rows × 7 cols, 20 mm squares, DICT_4X4_50 markers
doc = ct.PrintableTargetDocument(
    target=ct.CharucoTargetSpec(
        rows=5,
        cols=7,
        square_size_mm=20.0,
        marker_size_rel=0.75,
        dictionary="DICT_4X4_50",
    )
)
written = ct.write_target_bundle(doc, "my_board/charuco_a4")
print(written.png_path)   # open this to preview

For a plain chessboard instead:

doc = ct.PrintableTargetDocument(
    target=ct.ChessboardTargetSpec(
        inner_rows=6,
        inner_cols=8,
        square_size_mm=25.0,
    )
)
ct.write_target_bundle(doc, "my_board/chessboard_a4")

CLI (from source)

# list available ArUco dictionaries
cargo run -p calib-targets-cli -- list-dictionaries

# initialise a spec, validate, then render
cargo run -p calib-targets-cli -- init charuco \
  --out my_board/charuco_a4.json \
  --rows 5 --cols 7 \
  --square-size-mm 20 \
  --marker-size-rel 0.75 \
  --dictionary DICT_4X4_50

cargo run -p calib-targets-cli -- validate --spec my_board/charuco_a4.json
cargo run -p calib-targets-cli -- generate  --spec my_board/charuco_a4.json \
  --out-stem my_board/charuco_a4

Rust

use calib_targets::printable::{write_target_bundle, PrintableTargetDocument};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = PrintableTargetDocument::load_json("my_board/charuco_a4.json")?;
    let written = write_target_bundle(&doc, "my_board/charuco_a4")?;
    println!("{}", written.png_path.display());
    Ok(())
}

See calib-targets-print for the full JSON schema and more options.


Step 3: Print it

Open my_board/charuco_a4.svg (or the .png at the generated DPI) in your printer dialog:

  • Set scale to 100% / “actual size”. Disable “fit to page”, “shrink to fit”, or any equivalent driver option.
  • After printing, measure one square width with a ruler or caliper and confirm it matches square_size_mm (20 mm in the example above).
  • If the size is wrong, fix the print dialog and reprint — do not compensate in code.
  • Mount or tape the target flat to a rigid surface. Warping or bowing degrades calibration accuracy significantly.
  • Prefer the SVG for professional print workflows; use the PNG for quick office printing (check the DPI matches your printer resolution).

Step 4: Detect corners in Python

The board spec used for detection must match the one used for generation exactly.

import numpy as np
from PIL import Image
import calib_targets as ct

def load_gray(path: str) -> np.ndarray:
    return np.asarray(Image.open(path).convert("L"), dtype=np.uint8)

# Board spec — must match the generated target
board = ct.CharucoBoardSpec(
    rows=5,
    cols=7,
    cell_size=20.0,          # mm; gives target_position in mm
    marker_size_rel=0.75,
    dictionary="DICT_4X4_50",
    marker_layout=ct.MarkerLayout.OPENCV_CHARUCO,
)

params = ct.CharucoDetectorParams.for_board(board)

image = load_gray("frame.png")

try:
    result = ct.detect_charuco(image, params=params)
except RuntimeError as exc:
    print(f"Detection failed: {exc}")
    raise SystemExit(1)

corners = result.detection.corners
print(f"Detected {len(corners)} corners, {len(result.markers)} markers")

# Collect point pairs for solvePnP / calibrateCamera
obj_pts = []  # 3-D object points (Z = 0 for planar board)
img_pts = []  # 2-D image points
for c in corners:
    if c.target_position is not None:
        x_mm, y_mm = c.target_position
        obj_pts.append([x_mm, y_mm, 0.0])
        img_pts.append(c.position)

print(f"Point pairs ready for calibration: {len(obj_pts)}")

For a plain chessboard:

result = ct.detect_chessboard(image)
if result is None:
    raise SystemExit("No chessboard detected")

corners = result.detection.corners
print(f"Detected {len(corners)} corners")
# target_position is None for chessboard — assign object points by grid index
for c in corners:
    i, j = c.grid          # (col, row), origin top-left
    obj_pts.append([i * square_size_mm, j * square_size_mm, 0.0])
    img_pts.append(c.position)

Step 5: Detect corners in Rust

# Cargo.toml
[dependencies]
calib-targets = "0.3"
image = "0.25"
use calib_targets::detect;
use calib_targets_charuco::{CharucoBoardSpec, CharucoDetectorParams, MarkerLayout};
use image::ImageReader;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let img = ImageReader::open("frame.png")?.decode()?.to_luma8();

    let board = CharucoBoardSpec {
        rows: 5,
        cols: 7,
        cell_size: 20.0,      // mm
        marker_size_rel: 0.75,
        dictionary: "DICT_4X4_50".parse()?,
        marker_layout: MarkerLayout::OpencvCharuco,
        ..Default::default()
    };

    let params = CharucoDetectorParams::for_board(&board);
    let chess_cfg = detect::default_chess_config();

    let result = detect::detect_charuco(&img, &chess_cfg, params)?;
    println!(
        "corners: {}, markers: {}",
        result.detection.corners.len(),
        result.markers.len()
    );

    // Collect point pairs
    for c in &result.detection.corners {
        if let Some(tp) = c.target_position {
            let obj = [tp[0], tp[1], 0.0_f32];
            let img = c.position;
            // pass (obj, img) to your calibration solver
            let _ = (obj, img);
        }
    }
    Ok(())
}

Next steps

TopicWhere
Detection parameters explainedTuning the Detector
Detection fails or gives errorsTroubleshooting
What every output field meansUnderstanding Results
Full printable-target referencecalib-targets-print
ChArUco pipeline internalsChArUco crate