Laserline Device Calibration

Laserline calibration jointly estimates camera parameters and a laser plane from observations of both a calibration board and laser line projections on the board. This is used in laser triangulation systems where a camera and laser are rigidly mounted together.

Problem Formulation

Parameters

  • Camera intrinsics:
  • Distortion:
  • Sensor tilt: (optional, for Scheimpflug cameras)
  • Per-view poses: (camera-to-target SE(3))
  • Laser plane: normal , distance

Observations

Each view provides two types of observations:

  1. Calibration points: 2D-3D correspondences from the chessboard (same as planar intrinsics)
  2. Laser pixels: 2D pixel positions where the laser line appears on the target

Objective

The cost function has two terms:

where are weights, are (possibly different) robust loss functions, and is the laser residual.

Laser Residual Types

Two approaches are implemented for the laser residual, selectable via configuration.

PointToPlane (3D Distance)

Algorithm:

  1. Undistort laser pixel to normalized coordinates
  2. Back-project to a ray in camera frame
  3. Intersect the ray with the target plane (at , transformed by pose ) to get a 3D point
  4. Compute signed distance from to the laser plane:

Residual dimension: 1 (meters)

LineDistNormalized (2D Line Distance) — Default

Algorithm:

  1. Compute the 3D intersection line of the laser plane and the target plane (in camera frame)
  2. Project this 3D line onto the normalized camera plane
  3. Undistort the laser pixel to normalized coordinates (done once, not per-iteration)
  4. Measure the perpendicular distance from the undistorted pixel to the projected line
  5. Scale by for pixel-comparable units:

Residual dimension: 1 (effective pixels)

Comparison

PropertyPointToPlaneLineDistNormalized
Residual unitsmeterspixels
UndistortionPer-iterationOnce per pixel
GeometryRay-plane intersection2D line distance
SpeedSlowerFaster
RecommendedAlternativeDefault

Both approaches yield similar accuracy in practice (<6% intrinsics error, <5° plane normal error in synthetic tests).

Derivation: Line-Distance Residual

Plane-Plane Intersection Line

The laser plane and the target plane (normal , distance from camera, derived from pose ) intersect in a 3D line with:

Projection onto Normalized Plane

Project the 3D line to the plane:

Perpendicular Distance

For an undistorted pixel in normalized coordinates, the perpendicular distance to the 2D line is:

Configuration

Grouped per ADR 0024 — init and solver are the shared sub-structs; optimize groups the laser-specific bundle-adjustment knobs:

#![allow(unused)]
fn main() {
pub struct LaserlineDeviceConfig {
    // Per-camera linear-initialization stage settings.
    pub init: IntrinsicsInitConfig,
    // { init_iterations: usize = 2, fix_k3: bool = true,
    //   fix_tangential: bool = false, zero_skew: bool = true }

    pub sensor_init: ScheimpflugParams, // Initial sensor tilt (default: identity)

    // Non-linear solve stage settings. `robust_loss` is not consulted here —
    // laser-carrying stages use `optimize.calib_loss`/`optimize.laser_loss`.
    pub solver: SolverConfig, // { max_iters: usize = 50, verbosity: usize = 0, .. }

    // Bundle-adjustment options.
    pub optimize: LaserlineDeviceOptimizeConfig,
}

pub struct LaserlineDeviceOptimizeConfig {
    pub calib_loss: RobustLoss,        // Default: Huber { scale: 1.0 }
    pub laser_loss: RobustLoss,        // Default: Huber { scale: 0.01 }
    pub calib_weight: f64,             // Weight for calibration residuals (default: 1.0)
    pub laser_weight: f64,             // Weight for laser residuals (default: 1.0)
    pub fix_camera: CameraFixMask,     // { intrinsics, distortion } — full per-field granularity
    pub fix_sensor: bool,              // Default: true
    pub fix_poses: Vec<usize>,         // Default: vec![0]
    pub fix_plane: bool,
    pub laser_residual_type: LaserlineResidualType, // Default: LineDistNormalized
}
}

Weight Balancing

Since calibration residuals (in pixels) and laser residuals may have different scales, the weights allow balancing their relative influence. A common starting point is calib_weight = 1.0 and laser_weight = 1.0, adjusting if one term dominates.

Complete Example

#![allow(unused)]
fn main() {
use vision_calibration::prelude::*;
use vision_calibration::laserline_device::*;

let mut session = CalibrationSession::<LaserlineDeviceProblem>::new();
session.set_input(laserline_input)?;

run_calibration(&mut session, None)?;

let export = session.export()?;
println!("Plane normal: {:?}", export.estimate.params.plane.normal);
println!("Plane distance: {:.4}", export.estimate.params.plane.distance);
println!("Reprojection error: {:.4} px", export.mean_reproj_error);
println!("Laser error: {:.4}", export.stats.mean_laser_error);
}

Scheimpflug Support

For laser profilers with tilted sensors, the sensor model parameters are jointly optimized. The reprojection factor's PINHOLE4_DIST5_SCHEIMPFLUG2 camera-model descriptor selects the extended projection chain.