Fisheye Intrinsics from Bearings

Zhang's method needs the board-to-pixel map to be a homography, which a fisheye lens breaks. estimate_equidistant_intrinsics initializes a Kannala-Brandt camera (equidistant projection, KannalaBrandt4 distortion) by scoring candidate cameras on their pixel error:

  1. For a candidate focal length , principal point and coefficients , every pixel back-projects to a bearing.
  2. Each view's pose follows from its bearings (pose from bearings).
  3. The board reprojects through the candidate; the sum of squared pixel errors is its cost.

The search runs in three stages:

  • Focal sweep. Forty log-spaced focal lengths, without distortion, with at the centre of the observations. They span fields of view from a few degrees to beyond 270°, so neither the image size nor a nominal focal length is needed.
  • Simplex over , still without distortion.
  • Radial seed, then all five. At the incidence angles of the current poses, the observed radii follow , linear in . Its least-squares solution seeds a simplex over .

Pixels are square (), and , start at zero; the non-linear refinement frees them. A coefficient the caller holds is not fitted and stays at zero (EquidistantInitOptions).

#![allow(unused)]
fn main() {
use vision_calibration_linear::equidistant_init::{
    EquidistantInitOptions, estimate_equidistant_intrinsics,
};

let init = estimate_equidistant_intrinsics(&dataset, EquidistantInitOptions::default())?;
// init.intrinsics, init.distortion, init.poses, init.rms_error
}

On exact views of a lens seeing up to 115° off-axis, with coefficients compressing the edge by about 5 %, the result lands within 1 % in focal length and 1 px in principal point.