Skip to main content

vision_calibration_core/models/
params.rs

1use nalgebra::Matrix3;
2use serde::{Deserialize, Serialize};
3
4use super::{
5    BrownConrady5, Camera, FxFyCxCySkew, HomographySensor, IdentitySensor, NoDistortion, Pinhole,
6    ProjectionModel, ScheimpflugParams, SensorModel,
7};
8use crate::Real;
9
10/// Serializable projection model parameters.
11#[derive(Clone, Debug, Serialize, Deserialize)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum ProjectionParams {
14    /// Classic pinhole model.
15    Pinhole,
16}
17
18/// Serializable distortion model parameters.
19#[derive(Clone, Debug, Serialize, Deserialize)]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum DistortionParams {
22    /// No distortion.
23    None,
24    /// Brown-Conrady 5-parameter radial-tangential model.
25    BrownConrady5 {
26        /// Flattened Brown-Conrady coefficients.
27        #[serde(flatten)]
28        params: BrownConrady5<Real>,
29    },
30}
31
32/// Serializable sensor model parameters.
33#[derive(Clone, Debug, Serialize, Deserialize)]
34#[serde(tag = "type", rename_all = "snake_case")]
35pub enum SensorParams {
36    /// Identity sensor model.
37    Identity,
38    /// Homography applied in the sensor plane.
39    Homography {
40        /// Row-major homography matrix mapping normalized to sensor coordinates.
41        h: [[Real; 3]; 3],
42    },
43    /// Scheimpflug/tilted sensor model.
44    Scheimpflug {
45        /// Flattened Scheimpflug tilt parameters.
46        #[serde(flatten)]
47        params: ScheimpflugParams,
48    },
49}
50
51/// Serializable intrinsics parameters.
52#[derive(Clone, Debug, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "snake_case")]
54pub enum IntrinsicsParams {
55    /// Pinhole intrinsics with optional skew.
56    FxFyCxCySkew {
57        /// Flattened pinhole intrinsics coefficients.
58        #[serde(flatten)]
59        params: FxFyCxCySkew<Real>,
60    },
61}
62
63/// Serializable camera parameters for building a runtime model.
64#[derive(Clone, Debug, Serialize, Deserialize)]
65pub struct CameraParams {
66    /// Projection model parameters.
67    pub projection: ProjectionParams,
68    /// Distortion model parameters.
69    pub distortion: DistortionParams,
70    /// Sensor model parameters.
71    pub sensor: SensorParams,
72    /// Intrinsics model parameters.
73    pub intrinsics: IntrinsicsParams,
74}
75
76/// Concrete camera type built from parameters (f64).
77pub type CameraModel = Camera<Real, AnyProjection, AnyDistortion, AnySensor, AnyIntrinsics>;
78
79impl CameraParams {
80    /// Build a concrete camera model from this parameter set.
81    ///
82    /// Panics if a provided homography is not invertible.
83    pub fn build(&self) -> CameraModel {
84        let proj = match self.projection {
85            ProjectionParams::Pinhole => AnyProjection::Pinhole(Pinhole),
86        };
87
88        let dist = match self.distortion {
89            DistortionParams::None => AnyDistortion::None(NoDistortion),
90            DistortionParams::BrownConrady5 { params } => AnyDistortion::BrownConrady5(params),
91        };
92
93        let sensor = match &self.sensor {
94            SensorParams::Identity => AnySensor::Identity(IdentitySensor),
95            SensorParams::Homography { h } => {
96                let h = Matrix3::from_row_slice(&[
97                    h[0][0], h[0][1], h[0][2], h[1][0], h[1][1], h[1][2], h[2][0], h[2][1], h[2][2],
98                ]);
99                let h_inv = h.try_inverse().expect("Homography not invertible");
100                AnySensor::Homography(HomographySensor { h, h_inv })
101            }
102            SensorParams::Scheimpflug { params } => AnySensor::Homography(params.compile()),
103        };
104
105        let k = match self.intrinsics {
106            IntrinsicsParams::FxFyCxCySkew { params } => AnyIntrinsics::FxFyCxCySkew(params),
107        };
108
109        Camera::new(proj, dist, sensor, k)
110    }
111}
112
113// Internal type-erased model wrappers to produce a single concrete Camera type.
114// These are intentionally doc-hidden from the public API surface.
115#[derive(Clone, Debug)]
116#[doc(hidden)]
117pub enum AnyProjection {
118    Pinhole(Pinhole),
119}
120
121impl ProjectionModel<Real> for AnyProjection {
122    fn project_dir(&self, dir_c: &nalgebra::Vector3<Real>) -> Option<nalgebra::Point2<Real>> {
123        match self {
124            AnyProjection::Pinhole(m) => m.project_dir(dir_c),
125        }
126    }
127
128    fn unproject_dir(&self, n: &nalgebra::Point2<Real>) -> nalgebra::Vector3<Real> {
129        match self {
130            AnyProjection::Pinhole(m) => m.unproject_dir(n),
131        }
132    }
133}
134
135#[derive(Clone, Debug)]
136#[doc(hidden)]
137pub enum AnyDistortion {
138    None(NoDistortion),
139    BrownConrady5(BrownConrady5<Real>),
140}
141
142impl super::DistortionModel<Real> for AnyDistortion {
143    fn distort(&self, n: &nalgebra::Point2<Real>) -> nalgebra::Point2<Real> {
144        match self {
145            AnyDistortion::None(m) => m.distort(n),
146            AnyDistortion::BrownConrady5(m) => m.distort(n),
147        }
148    }
149
150    fn undistort(&self, n: &nalgebra::Point2<Real>) -> nalgebra::Point2<Real> {
151        match self {
152            AnyDistortion::None(m) => m.undistort(n),
153            AnyDistortion::BrownConrady5(m) => m.undistort(n),
154        }
155    }
156}
157
158#[derive(Clone, Debug)]
159#[doc(hidden)]
160pub enum AnySensor {
161    Identity(IdentitySensor),
162    Homography(HomographySensor<Real>),
163}
164
165impl SensorModel<Real> for AnySensor {
166    fn normalized_to_sensor(&self, n: &nalgebra::Point2<Real>) -> nalgebra::Point2<Real> {
167        match self {
168            AnySensor::Identity(m) => m.normalized_to_sensor(n),
169            AnySensor::Homography(m) => m.normalized_to_sensor(n),
170        }
171    }
172
173    fn sensor_to_normalized(&self, s: &nalgebra::Point2<Real>) -> nalgebra::Point2<Real> {
174        match self {
175            AnySensor::Identity(m) => m.sensor_to_normalized(s),
176            AnySensor::Homography(m) => m.sensor_to_normalized(s),
177        }
178    }
179}
180
181#[derive(Clone, Debug)]
182#[doc(hidden)]
183pub enum AnyIntrinsics {
184    FxFyCxCySkew(FxFyCxCySkew<Real>),
185}
186
187impl super::IntrinsicsModel<Real> for AnyIntrinsics {
188    fn sensor_to_pixel(&self, sensor: &nalgebra::Point2<Real>) -> nalgebra::Point2<Real> {
189        match self {
190            AnyIntrinsics::FxFyCxCySkew(m) => m.sensor_to_pixel(sensor),
191        }
192    }
193
194    fn pixel_to_sensor(&self, pixel: &nalgebra::Point2<Real>) -> nalgebra::Point2<Real> {
195        match self {
196            AnyIntrinsics::FxFyCxCySkew(m) => m.pixel_to_sensor(pixel),
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn params_build_camera() {
207        let params = CameraParams {
208            projection: ProjectionParams::Pinhole,
209            distortion: DistortionParams::None,
210            sensor: SensorParams::Identity,
211            intrinsics: IntrinsicsParams::FxFyCxCySkew {
212                params: FxFyCxCySkew {
213                    fx: 800.0,
214                    fy: 810.0,
215                    cx: 640.0,
216                    cy: 360.0,
217                    skew: 0.0,
218                },
219            },
220        };
221        let cam = params.build();
222        let px = cam.project_point_c(&nalgebra::Vector3::new(0.1, 0.2, 1.0));
223        assert!(px.is_some());
224    }
225
226    #[test]
227    fn distortion_params_serde_shape() {
228        let json = r#"{
229            "type": "brown_conrady5",
230            "k1": 0.1,
231            "k2": 0.01,
232            "k3": 0.0,
233            "p1": 0.0,
234            "p2": 0.0,
235            "iters": 4
236        }"#;
237        let cfg: DistortionParams = serde_json::from_str(json).expect("serde should succeed");
238        match cfg {
239            DistortionParams::BrownConrady5 { params } => {
240                assert!((params.k1 - 0.1).abs() < 1e-12);
241                assert!((params.k2 - 0.01).abs() < 1e-12);
242                assert!((params.k3 - 0.0).abs() < 1e-12);
243                assert!((params.p1 - 0.0).abs() < 1e-12);
244                assert!((params.p2 - 0.0).abs() < 1e-12);
245                assert_eq!(params.iters, 4);
246            }
247            DistortionParams::None => panic!("expected BrownConrady5 params, got None"),
248        }
249    }
250}