checkerboard_calibrate/calibrate/
homography.rs1use nalgebra::{DMatrix, Matrix3};
16
17fn normalize(pts: &[(f64, f64)]) -> (Matrix3<f64>, Vec<(f64, f64)>) {
21 let n = pts.len() as f64;
22 let (mut cx, mut cy) = (0.0, 0.0);
23 for &(x, y) in pts {
24 cx += x;
25 cy += y;
26 }
27 cx /= n;
28 cy /= n;
29
30 let mut mean_dist = 0.0;
31 for &(x, y) in pts {
32 mean_dist += ((x - cx).powi(2) + (y - cy).powi(2)).sqrt();
33 }
34 mean_dist /= n;
35
36 let scale = if mean_dist > f64::EPSILON {
38 2.0f64.sqrt() / mean_dist
39 } else {
40 1.0
41 };
42
43 let t = Matrix3::new(
44 scale,
45 0.0,
46 -scale * cx,
47 0.0,
48 scale,
49 -scale * cy,
50 0.0,
51 0.0,
52 1.0,
53 );
54
55 let out = pts
56 .iter()
57 .map(|&(x, y)| (scale * (x - cx), scale * (y - cy)))
58 .collect();
59
60 (t, out)
61}
62
63pub fn find_homography(src: &[(f64, f64)], dst: &[(f64, f64)]) -> Option<Matrix3<f64>> {
69 if src.len() != dst.len() || src.len() < 4 {
70 return None;
71 }
72
73 let (t_src, src_n) = normalize(src);
74 let (t_dst, dst_n) = normalize(dst);
75
76 let n = src_n.len();
78 let mut a = DMatrix::<f64>::zeros(2 * n, 9);
79 for (i, (&(x, y), &(u, v))) in src_n.iter().zip(dst_n.iter()).enumerate() {
80 let r0 = 2 * i;
81 let r1 = r0 + 1;
82 a[(r0, 0)] = -x;
83 a[(r0, 1)] = -y;
84 a[(r0, 2)] = -1.0;
85 a[(r0, 6)] = u * x;
86 a[(r0, 7)] = u * y;
87 a[(r0, 8)] = u;
88
89 a[(r1, 3)] = -x;
90 a[(r1, 4)] = -y;
91 a[(r1, 5)] = -1.0;
92 a[(r1, 6)] = v * x;
93 a[(r1, 7)] = v * y;
94 a[(r1, 8)] = v;
95 }
96
97 let svd = a.svd(false, true);
99 let vt = svd.v_t?;
100 let h = vt.row(vt.nrows() - 1).transpose();
101
102 let h_norm = Matrix3::new(h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8]);
103
104 let t_dst_inv = t_dst.try_inverse()?;
106 let mut hmat = t_dst_inv * h_norm * t_src;
107
108 let scale = hmat[(2, 2)];
109 if scale.abs() < f64::EPSILON {
110 return None;
111 }
112 hmat /= scale;
113 Some(hmat)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 fn apply(h: &Matrix3<f64>, p: (f64, f64)) -> (f64, f64) {
121 let v = h * nalgebra::Vector3::new(p.0, p.1, 1.0);
122 (v[0] / v[2], v[1] / v[2])
123 }
124
125 #[test]
126 fn recovers_known_homography() {
127 let h_true = Matrix3::new(0.8, -0.2, 30.0, 0.15, 0.9, -10.0, 0.0005, -0.0003, 1.0);
129
130 let src = [
131 (0.0, 0.0),
132 (1.0, 0.0),
133 (2.0, 0.0),
134 (3.0, 1.0),
135 (0.0, 1.0),
136 (1.0, 2.0),
137 (2.0, 3.0),
138 (4.0, 4.0),
139 ];
140 let dst: Vec<(f64, f64)> = src.iter().map(|&p| apply(&h_true, p)).collect();
141
142 let h = find_homography(&src, &dst).expect("homography");
143
144 for &p in &src {
146 let (ex, ey) = apply(&h_true, p);
147 let (gx, gy) = apply(&h, p);
148 approx::assert_abs_diff_eq!(gx, ex, epsilon = 1e-9);
149 approx::assert_abs_diff_eq!(gy, ey, epsilon = 1e-9);
150 }
151 }
152
153 #[test]
154 fn rejects_too_few_points() {
155 let pts = [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)];
156 assert!(find_homography(&pts, &pts).is_none());
157 }
158}