Skip to content

Single Camera ExampleΒΆ

This example demonstrates a basic camera-only calibration workflow.

use libcalibgeom::{
    find_checkerboard, subpix_saddle_point_polynomium, validate_license_with_env,
    Calibration, Camera, CameraModelType, ImageDetection, InitializationSettings,
    OptimizationSettings, ParameterState, PoseRt, Target,
};
use std::fs;
use std::path::PathBuf;

fn checkerboard_object_points(rows: u32, columns: u32, spacing: f64) -> Vec<f64> {
    let mut xyz = Vec::with_capacity((rows * columns * 3) as usize);
    for r in 0..rows {
        for c in 0..columns {
            xyz.push(c as f64 * spacing);
            xyz.push(r as f64 * spacing);
            xyz.push(0.0);
        }
    }
    xyz
}

fn main() {
    let mut calibration = Calibration::new().expect("failed to create calibration");

    let licensed = validate_license_with_env();
    println!("license_valid: {licensed}");
    if !licensed {
        return;
    }

    let rows = 6;
    let columns = 7;

    let images_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../data/pensylvania_c1");
    println!("loading_images_from: {}", images_dir.display());

    let mut image_paths: Vec<PathBuf> = match fs::read_dir(&images_dir) {
        Ok(entries) => entries
            .filter_map(Result::ok)
            .map(|entry| entry.path())
            .filter(|path| {
                path.extension()
                    .and_then(|ext| ext.to_str())
                    .is_some_and(|ext| ext.eq_ignore_ascii_case("png"))
            })
            .collect(),
        Err(error) => {
            println!("failed_to_read_images_dir: {error}");
            return;
        }
    };

    image_paths.sort();
    println!("image_file_count: {}", image_paths.len());
    if image_paths.is_empty() {
        return;
    }

    let first_image = match image::open(&image_paths[0]) {
        Ok(image) => image.to_luma8(),
        Err(error) => {
            println!("failed_to_open_first_image: {error}");
            return;
        }
    };
    let image_width = first_image.width();
    let image_height = first_image.height();

    let mut camera =
        Camera::new(CameraModelType::OpenCV, image_width, image_height, "cam0")
            .expect("failed to create camera");
    let model_name = camera.model_name().unwrap_or_default();
    println!("camera_model: {model_name}");
    println!("camera_parameters: {}", camera.parameter_specs().len());

    let _ = camera.set_parameter_value("f", 1000.0)
        && camera.set_parameter_value("ar", 1.0)
        && camera.set_parameter_value("cx", 640.0)
        && camera.set_parameter_value("cy", 480.0)
        && camera.set_parameter_value("k1", 0.0)
        && camera.set_parameter_value("k2", 0.0)
        && camera.set_parameter_value("k3", 0.0)
        && camera.set_parameter_value("p1", 0.0)
        && camera.set_parameter_value("p2", 0.0);

    let _ = camera.set_parameter_state("f", ParameterState::Free)
        && camera.set_parameter_state("ar", ParameterState::Free)
        && camera.set_parameter_state("cx", ParameterState::Free)
        && camera.set_parameter_state("cy", ParameterState::Free)
        && camera.set_parameter_state("k1", ParameterState::Free)
        && camera.set_parameter_state("k2", ParameterState::Free)
        && camera.set_parameter_state("k3", ParameterState::Free)
        && camera.set_parameter_state("p1", ParameterState::Free)
        && camera.set_parameter_state("p2", ParameterState::Free);

    let object_points_xyz = checkerboard_object_points(rows, columns, 0.05);
    let mut target = Target::new().expect("failed to create target");
    let _ = target.set_object_points_xyz(&object_points_xyz);
    calibration.reset();
    let target_added = calibration.add_target(&target).is_some();
    let camera_added = calibration.add_camera(&camera).is_some();
    let configured = target_added && camera_added;
    println!("configured: {configured}");
    if !configured {
        return;
    }
    println!(
        "counts cameras={} targets={} poses={}",
        calibration.cameras().len(),
        calibration.targets().len(),
        calibration.poses().len()
    );

    let mut image_detections = Vec::new();
    let mut pose_id = 0u32;
    for image_path in image_paths {
        let image = match image::open(&image_path) {
            Ok(image) => image.to_luma8(),
            Err(error) => {
                println!("failed_to_open_image {}: {error}", image_path.display());
                continue;
            }
        };
        if image.width() != image_width || image.height() != image_height {
            println!(
                "skipping_size_mismatch {}: {}x{}",
                image_path.display(),
                image.width(),
                image.height()
            );
            continue;
        }

        let Some(points) = find_checkerboard(&image, rows, columns) else {
            println!("checkerboard_not_found: {}", image_path.display());
            continue;
        };

        let Some(points) = subpix_saddle_point_polynomium(&image, 5, &points, 50) else {
            println!("checkerboard_subpix_failed: {}", image_path.display());
            continue;
        };

        if points.len() != (rows * columns) as usize {
            println!(
                "checkerboard_size_mismatch {}: got {}",
                image_path.display(),
                points.len()
            );
            continue;
        }

        let Some(added_pose_id) = calibration.add_pose(PoseRt {
            rvec: [0.0, 0.0, 0.0],
            tvec: [0.0, 0.0, 0.0],
        }) else {
            println!("failed_to_add_pose");
            continue;
        };
        image_detections.push(ImageDetection {
            camera_id: 0,
            pose_id: added_pose_id,
            target_id: 0,
            points,
        });
        println!("detection[{pose_id}] added: {}", image_path.display());
        pose_id += 1;
    }

    println!("detection_count: {}", image_detections.len());
    if image_detections.is_empty() {
        return;
    }

    println!("pose_count: {}", calibration.poses().len());

    let initialized = calibration.initialize(
        &image_detections,
        None,
        None,
        InitializationSettings::default(),
    );
    println!(
        "initialize_succeeded: {}, status: {:?}",
        initialized.succeeded,
        initialized.status
    );

    let optimized = calibration.optimize(
        &image_detections,
        None,
        None,
        OptimizationSettings {
            max_iterations: 50,
            ..OptimizationSettings::default()
        },
    );
    println!(
        "optimize_succeeded: {}, converged: {}, rpe_rms: {}, status: {:?}",
        optimized.succeeded,
        optimized.converged,
        optimized.rpe_rms,
        optimized.status
    );
    println!(
        "reprojection detections={}",
        optimized.reprojection_errors.len()
    );
    println!("{}", calibration.to_string().unwrap_or_default());
}

Use this as the starting point for custom camera model setup and feature ingestion.