vision_calibration_optim/lib.rs
1//! Non-linear optimization for camera calibration with automatic differentiation.
2//!
3//! This crate provides a backend-agnostic optimization framework for camera calibration
4//! problems. The core design separates problem definition from solver implementation using
5//! an intermediate representation (IR) that can be compiled to different optimization backends.
6//!
7//! # Architecture
8//!
9//! The optimization pipeline has three stages:
10//!
11//! 1. **Problem Definition** - Build a \[`ir::ProblemIR`\] describing parameters, factors, and constraints
12//! 2. **Backend Compilation** - Translate IR into solver-specific problem (e.g., \[`backend::TinySolverBackend`\])
13//! 3. **Optimization** - Run solver and extract solution as domain types
14//!
15//! ```text
16//! Problem Builder → ProblemIR → Backend.compile() → Backend.solve() → Domain Result
17//! ```
18//!
19//! ## Key Components
20//!
21//! - **\[`ir`\]** - Backend-agnostic intermediate representation for optimization problems
22//! - **\[`params`\]** - Parameter block definitions (intrinsics, distortion, poses)
23//! - **\[`factors`\]** - Residual functions with automatic differentiation support
24//! - **\[`backend`\]** - Solver implementations (currently tiny-solver with Levenberg-Marquardt)
25//! - **\[`problems`\]** - High-level calibration problem builders (planar intrinsics, etc.)
26//!
27//! # Examples
28//!
29//! ## Basic Planar Intrinsics Calibration
30//!
31//! ```rust,no_run
32//! use vision_calibration_core::{BrownConrady5, CorrespondenceView, DistortionFixMask, FxFyCxCySkew, Iso3, PlanarDataset, Pt2, Pt3, View};
33//! use vision_calibration_optim::{
34//! optimize_planar_intrinsics, BackendSolveOptions, PlanarIntrinsicsParams,
35//! PlanarIntrinsicsSolveOptions, RobustLoss,
36//! };
37//!
38//! # fn example() -> anyhow::Result<()> {
39//! // 1. Prepare observations (world points + image detections)
40//! let view = View::without_meta(CorrespondenceView::new(
41//! vec![
42//! Pt3::new(0.0, 0.0, 0.0),
43//! Pt3::new(1.0, 0.0, 0.0),
44//! Pt3::new(1.0, 1.0, 0.0),
45//! Pt3::new(0.0, 1.0, 0.0),
46//! ],
47//! vec![
48//! Pt2::new(100.0, 100.0),
49//! Pt2::new(200.0, 100.0),
50//! Pt2::new(200.0, 200.0),
51//! Pt2::new(100.0, 200.0),
52//! ],
53//! )?);
54//! let dataset = PlanarDataset::new(vec![view])?;
55//!
56//! // 2. Initialize with linear method or prior calibration
57//! let init = PlanarIntrinsicsParams::new_from_components(
58//! FxFyCxCySkew {
59//! fx: 800.0,
60//! fy: 800.0,
61//! cx: 640.0,
62//! cy: 360.0,
63//! skew: 0.0,
64//! },
65//! BrownConrady5 {
66//! k1: 0.0,
67//! k2: 0.0,
68//! k3: 0.0,
69//! p1: 0.0,
70//! p2: 0.0,
71//! iters: 8,
72//! },
73//! vec![Iso3::identity()],
74//! )?;
75//!
76//! // 3. Configure optimization
77//! let opts = PlanarIntrinsicsSolveOptions {
78//! robust_loss: RobustLoss::Huber { scale: 2.0 },
79//! fix_distortion: DistortionFixMask { k3: true, ..Default::default() }, // Fix k3 to prevent overfitting
80//! ..Default::default()
81//! };
82//!
83//! // 4. Run optimization
84//! let result = optimize_planar_intrinsics(&dataset, &init, opts, BackendSolveOptions::default())?;
85//!
86//! println!("Calibrated camera: {:?}", result.params.camera);
87//! # Ok(())
88//! # }
89//! ```
90//!
91//! ## Custom Problem with IR
92//!
93//! # Feature Highlights
94//!
95//! ## Automatic Differentiation
96//!
97//! All residual functions are generic over [`nalgebra::RealField`], enabling automatic
98//! differentiation via dual numbers. The \[`factors::reprojection_model`\] module provides
99//! autodiff-compatible implementations of:
100//!
101//! - Pinhole projection with SE3 poses
102//! - Brown-Conrady distortion (k1, k2, k3, p1, p2)
103//! - Weighted reprojection residuals
104//!
105//! ## Flexible Parameter Fixing
106//!
107//! Use \[`ir::FixedMask`\] to selectively fix optimization variables:
108//!
109//! ```rust
110//! # use vision_calibration_optim::PlanarIntrinsicsSolveOptions;
111//! # use vision_calibration_core::{DistortionFixMask, IntrinsicsFixMask};
112//! let opts = PlanarIntrinsicsSolveOptions {
113//! fix_intrinsics: IntrinsicsFixMask { fx: true, ..Default::default() }, // Fix focal length
114//! fix_distortion: DistortionFixMask { p1: true, p2: true, ..Default::default() }, // Fix tangential distortion
115//! ..Default::default()
116//! };
117//! ```
118//!
119//! ## Robust Loss Functions
120//!
121//! Handle outliers with M-estimators ([`ir::RobustLoss`]):
122//!
123//! - `Huber` - L2 near zero, L1 for outliers
124//! - `Cauchy` - Gradual outlier suppression
125//! - `Arctan` - Bounded influence
126//!
127//! # Performance Considerations
128//!
129//! - **Initialization**: Always initialize with linear methods (the `vision-calibration-linear` crate) for faster convergence
130//! - **Robust Loss**: Use Huber with `scale ≈ 2.0` for real data with corner detection noise
131//! - **Distortion**: Fix `k3` by default unless calibrating wide-angle lenses
132//! - **Manifolds**: SE3/SO3 parameters use proper Lie group updates for stability
133//!
134//! # Numerical Stability
135//!
136//! The implementation uses several techniques for numerical robustness:
137//!
138//! - Safe division with epsilon thresholds in projection
139//! - Hartley normalization in linear initialization (via vision-calibration-linear)
140//! - Manifold-aware parameter updates for rotations
141//! - Sparse linear solvers for large problems
142
143mod backend;
144mod error;
145mod factors;
146mod ir;
147mod math;
148mod params;
149mod problems;
150
151pub use error::Error;
152
153pub use math::*;
154
155pub use crate::backend::{
156 BackendKind, BackendSolution, BackendSolveOptions, SolveReport, solve_with_backend,
157};
158
159pub use crate::ir::{
160 FactorKind, FixedMask, HandEyeMode, ManifoldKind, ProblemIR, ResidualBlock, RobustLoss,
161};
162
163pub use crate::params::distortion::{DISTORTION_DIM, pack_distortion};
164pub use crate::params::intrinsics::{INTRINSICS_DIM, pack_intrinsics};
165pub use crate::params::laser_plane::LaserPlane;
166pub use crate::params::pose_se3::{iso3_to_se3_dvec, se3_dvec_to_iso3};
167
168pub use crate::problems::planar_intrinsics::{
169 PlanarIntrinsicsEstimate, PlanarIntrinsicsParams, PlanarIntrinsicsSolveOptions,
170 optimize_planar_intrinsics,
171};
172pub use crate::problems::scheimpflug_intrinsics::{
173 ScheimpflugFixMask, ScheimpflugIntrinsicsEstimate, ScheimpflugIntrinsicsParams,
174 ScheimpflugIntrinsicsSolveOptions, optimize_scheimpflug_intrinsics,
175};
176
177pub use crate::problems::handeye::{
178 HandEyeDataset, HandEyeEstimate, HandEyeParams, HandEyeSolveOptions, RobotPoseMeta,
179 optimize_handeye,
180};
181
182pub use crate::problems::rig_extrinsics::{
183 RigExtrinsicsDataset, RigExtrinsicsEstimate, RigExtrinsicsParams, RigExtrinsicsSolveOptions,
184 optimize_rig_extrinsics,
185};
186
187pub use crate::problems::laserline_bundle::{
188 LaserlineDataset, LaserlineEstimate, LaserlineMeta, LaserlineParams, LaserlineResidualType,
189 LaserlineSolveOptions, LaserlineStats, LaserlineView, compute_laserline_stats,
190 optimize_laserline,
191};
192
193pub use vision_calibration_core::{RigDataset, RigViewObs, View};