Skip to content

ChArUcal

Real-time top-down view from multiple cameras, powered by ChArUco boards.

charucal is a Python library designed to calibrate multi-camera systems and transform their outputs into a single top-down view. The library utilizes ChArUco boards for precise corner detection and OpenCV for homography estimation. To ensure the transformation remains efficient enough for real-time applications, charucal generates precomputed remap tables that minimize processing overhead during runtime.

The project was originally developed for the Hanze team’s participation in the Self Driving Challenge 2024 to assist with autonomous driving. It is now maintained as a standalone library for anyone who needs a real-time top-down view from multiple cameras.


  • Easy to set up


    Install with pip, run the calibrate CLI tool once, and start transforming frames.

    Installation

  • Works with any cameras


    USB webcams, CSI cameras, video files — anything OpenCV can open.

    Quick Start

  • Built for real-time


    At 1000x1000 output, a single thread processes 3 simultaneous 1080p cameras at over 1200 FPS with nearest-neighbor interpolation and over 400 FPS with bilinear.

    How It Works

  • Python API


    A handful of classes cover the full workflow: detection, calibration, and transformation.

    API Reference


Basic Usage

ChArUcal's workflow is split into two phases: a one-time calibration step, and a per-frame transformation step that runs at runtime.

Define your board and calibrate:

import cv2
from charucal import CalibrationPattern, Calibrator

pattern = CalibrationPattern(
    width=10,
    height=8,
    square_length=0.115,
    marker_length=0.086,
    aruco_dict=cv2.aruco.DICT_4X4_100,
)

calibrator = Calibrator(pattern)

captures = []
for frame_set in your_capture_source:
    capture = calibrator.detect(frame_set)
    if capture.is_complete:
        captures.append(capture)

result = calibrator.calibrate(*captures)
result.save("calibration.json")

Load and transform:

from charucal import CalibrationResult, CameraTransformer, RenderDistance

calibration = CalibrationResult.load("calibration.json")
transformer = CameraTransformer(
    calibration,
    render_distance=RenderDistance(front=10.0, lateral=5.0),
    output_shape=(1000, 1000),
)

topdown = transformer.transform(frames)

Performance

Remap maps are precomputed once at initialization, so transform() does minimal work per frame. INTER_NEAREST is 3.16× faster on average. Use INTER_LINEAR when output quality matters more than throughput.

Benchmark 3 cameras (1080p input, 1000×1000 output) · Ryzen 7 9800X3D · single-threaded