Skip to content

Calibration

The charucal.calibrate module contains the classes that turn raw corner detections into homography matrices ready for runtime use.


Overview

from charucal import Calibrator

calibrator = Calibrator(pattern)           # (1) create the calibrator
capture = calibrator.detect(frames)        # (2) detect corners in one frame set
result = calibrator.calibrate(*captures)   # (3) compute homographies
result.save("calibration.json")            # (4) persist to disk

Loading the result later:

from charucal import CalibrationResult

calibration = CalibrationResult.load("calibration.json")

Reference

Calibrator

Calibrator(
    pattern: CalibrationPattern,
    *,
    ref_idx: int | None = None,
)

A class for calibrating multi-camera systems using ChArUco boards.

Initialize the calibrator.

Parameters:

Name Type Description Default
pattern CalibrationPattern

The ChArUco pattern to use for calibration.

required
ref_idx int | None

The index of the reference camera. When None, the reference is chosen automatically.

None

Methods:

Name Description
calibrate

Calibrate the multi-camera system.

detect

Detect the calibration pattern in the provided images without computing homographies.

Source code in src/charucal/calibrate.py
def __init__(self, pattern: CalibrationPattern, *, ref_idx: int | None = None) -> None:
    """Initialize the calibrator.

    :param pattern: The ChArUco pattern to use for calibration.
    :param ref_idx: The index of the reference camera. When ``None``, the reference is chosen automatically.
    """
    self._pattern = pattern
    self._ref_idx = ref_idx

calibrate

calibrate(
    *captures: CalibrationCapture,
) -> CalibrationResult

Calibrate the multi-camera system.

Parameters:

Name Type Description Default
captures CalibrationCapture

The captures to use for calibration.

()

Returns:

Type Description
CalibrationResult

The result of the calibration.

Source code in src/charucal/calibrate.py
def calibrate(self, *captures: CalibrationCapture) -> CalibrationResult:
    """Calibrate the multi-camera system.

    :param captures: The captures to use for calibration.
    :return: The result of the calibration.
    """
    if not captures:
        raise ValueError("At least one capture is required for calibration.")

    self._validate_captures(captures)

    ref_idx = self._ref_idx
    if ref_idx is None:
        ref_idx = select_reference_camera(captures[0].grids)

    n = captures[0].num_cameras
    stitch_matrices = np.empty((n, 3, 3), dtype=np.float64)
    for i in range(n):
        if i == ref_idx:
            stitch_matrices[i] = np.eye(3)
        else:
            stitch_matrices[i] = find_stitch_homography(
                [capture.grids[i] for capture in captures],
                [capture.grids[ref_idx] for capture in captures],
            )

    topdown_matrix = find_topdown_homography(
        [capture.grids[ref_idx] for capture in captures],
        self._pattern,
    )

    return CalibrationResult(
        stitch_matrices=stitch_matrices,
        topdown_matrix=topdown_matrix,
        source_shapes=captures[0].source_shapes,
        ref_idx=ref_idx,
    )

detect

detect(
    images: Sequence[NDArray[uint8]],
) -> CalibrationCapture

Detect the calibration pattern in the provided images without computing homographies.

Parameters:

Name Type Description Default
images Sequence[NDArray[uint8]]

A sequence of images captured by the cameras. The order of the images should correspond to the order of the cameras.

required

Returns:

Type Description
CalibrationCapture

The detected calibration grids and source shapes.

Source code in src/charucal/calibrate.py
def detect(self, images: Sequence[npt.NDArray[np.uint8]]) -> CalibrationCapture:
    """Detect the calibration pattern in the provided images without computing homographies.

    :param images: A sequence of images captured by the cameras. The order of the images should correspond
        to the order of the cameras.
    :return: The detected calibration grids and source shapes.
    """
    if not images:
        raise ValueError("At least one image is required for calibration.")

    grids = [self._pattern.detect(image) for image in images]
    source_shapes = [(image.shape[1], image.shape[0]) for image in images]

    return CalibrationCapture(grids=grids, source_shapes=source_shapes)

CalibrationCapture dataclass

CalibrationCapture(
    grids: list[CalibrationGrid],
    source_shapes: Sequence[tuple[int, int]],
)

The result of detecting the calibration pattern in a multi-camera system.

Attributes:

Name Type Description
grids list[CalibrationGrid]

A list of detected calibration grids for each camera.

source_shapes Sequence[tuple[int, int]]

The source frame dimensions (width, height) for each camera.

is_complete bool

Whether every camera detected at least one corner.

num_cameras int

The number of cameras in the multi-camera system.

grids instance-attribute

grids: list[CalibrationGrid]

A list of detected calibration grids for each camera.

source_shapes instance-attribute

source_shapes: Sequence[tuple[int, int]]

The source frame dimensions (width, height) for each camera.

is_complete property

is_complete: bool

Whether every camera detected at least one corner.

num_cameras property

num_cameras: int

The number of cameras in the multi-camera system.

CalibrationResult dataclass

CalibrationResult(
    stitch_matrices: NDArray[float64],
    topdown_matrix: NDArray[float64],
    source_shapes: Sequence[tuple[int, int]],
    ref_idx: int,
)

The result of calibrating a multi-camera system.

Methods:

Name Description
save

Save the calibration result to a .json file.

load

Load the calibration result from a .json file.

Attributes:

Name Type Description
stitch_matrices NDArray[float64]

A (N, 3, 3) array of homographies mapping each camera's pixel space to the reference camera's pixel space.

topdown_matrix NDArray[float64]

A (3, 3) homography mapping the reference camera's pixel space to the physical world coordinates in meters,

source_shapes Sequence[tuple[int, int]]

The source frame dimensions (width, height) for each camera.

ref_idx int

The index of the reference camera.

num_cameras int

The number of cameras in the multi-camera system.

stitch_matrices instance-attribute

stitch_matrices: NDArray[float64]

A (N, 3, 3) array of homographies mapping each camera's pixel space to the reference camera's pixel space.

topdown_matrix instance-attribute

topdown_matrix: NDArray[float64]

A (3, 3) homography mapping the reference camera's pixel space to the physical world coordinates in meters, with the origin at the ChArUco's board detected position.

source_shapes instance-attribute

source_shapes: Sequence[tuple[int, int]]

The source frame dimensions (width, height) for each camera.

ref_idx instance-attribute

ref_idx: int

The index of the reference camera.

num_cameras property

num_cameras: int

The number of cameras in the multi-camera system.

save

save(path: str | PathLike[str]) -> None

Save the calibration result to a .json file.

Parameters:

Name Type Description Default
path str | PathLike[str]

The path to save the calibration result to.

required
Source code in src/charucal/calibrate.py
def save(self, path: str | os.PathLike[str]) -> None:
    """Save the calibration result to a ``.json`` file.

    :param path: The path to save the calibration result to.
    """
    path = pathlib.Path(path)
    if path.suffix != ".json":
        path = path.with_suffix(".json")

    payload: dict[str, Any] = {
        "version": charucal.__version__,
        "created_at": datetime.now().isoformat(),
        "ref_idx": self.ref_idx,
        "source_shapes": [list(shape) for shape in self.source_shapes],
        "stitch_matrices": self.stitch_matrices.tolist(),
        "topdown_matrix": self.topdown_matrix.tolist(),
    }

    path.write_text(json.dumps(payload, indent=4), encoding="utf-8")

load classmethod

load(path: str | PathLike[str]) -> CalibrationResult

Load the calibration result from a .json file.

Parameters:

Name Type Description Default
path str | PathLike[str]

The path to load the calibration result from.

required

Returns:

Type Description
CalibrationResult

The calibration result.

Source code in src/charucal/calibrate.py
@classmethod
def load(cls, path: str | os.PathLike[str]) -> CalibrationResult:
    """Load the calibration result from a ``.json`` file.

    :param path: The path to load the calibration result from.
    :return: The calibration result.
    """
    path = pathlib.Path(path)
    if path.suffix != ".json":
        raise ValueError(f"Expected a .json file, got '{path.suffix}'")

    data = json.loads(path.read_text(encoding="utf-8"))
    version = data.get("version")
    if version != charucal.__version__:
        warnings.warn(
            f"Calibration result version '{version}' does not match current package version '{charucal.__version__}'.",
            UserWarning,
            stacklevel=2,
        )

    return cls(
        stitch_matrices=np.array(data["stitch_matrices"], dtype=np.float64),
        topdown_matrix=np.array(data["topdown_matrix"], dtype=np.float64),
        source_shapes=[tuple(shape) for shape in data["source_shapes"]],
        ref_idx=data["ref_idx"],
    )