Skip to main content

aikit/
lib.rs

1use std::ops::Mul;
2
3use image::Rgba32FImage;
4use nalgebra::Matrix3;
5use nalgebra::{ArrayStorage, Matrix1x2, Matrix2, Matrix2x1, Matrix3x1};
6
7pub fn warp_into(input: &Rgba32FImage, matrix: Matrix3<f32>, output: &mut Rgba32FImage) {
8    let inverse = matrix.try_inverse().unwrap();
9
10    let in_width = input.width();
11    let in_height = input.height();
12
13    let out_width = output.width();
14    let out_height = output.height();
15
16    for out_row in 0..out_width {
17        for out_col in 0..out_height {
18            let point = Matrix3x1::<f32>::new(out_row as f32, out_col as f32, 1f32);
19
20            let in_pixel = inverse * point;
21
22            let in_row = in_pixel.x as i32;
23            let in_col = in_pixel.y as i32;
24
25            if (0 <= in_row)
26                && (in_row < in_width as i32)
27                && (0 <= in_col)
28                && (in_col < in_height as i32)
29            {
30                let px = input.get_pixel(in_row as _, in_col as _);
31
32                output[(out_row, out_col)] = *px;
33            }
34        }
35    }
36}
37
38/// The Kabsch-Umeyama algorithm is a method for finding the optimal translation, rotation,
39/// and scaling that aligns two sets of points with minimum root-mean-square deviation (RMSD).
40pub fn umeyama<const R: usize>(src: &[(f32, f32); R], dst: &[(f32, f32); R]) -> Matrix3<f32> {
41    let src_x_sum: f32 = src.iter().map(|v| v.0).sum();
42    let src_x_mean = src_x_sum / (R as f32);
43
44    let src_y_sum: f32 = src.iter().map(|v| v.1).sum();
45    let src_y_mean = src_y_sum / (R as f32);
46
47    let dst_x_sum: f32 = dst.iter().map(|v| v.0).sum();
48    let dst_x_mean = dst_x_sum / (R as f32);
49
50    let dst_y_sum: f32 = dst.iter().map(|v| v.1).sum();
51    let dst_y_mean = dst_y_sum / (R as f32);
52
53    let src_demean_s = ArrayStorage(src.map(|v| [v.0 - src_x_mean, v.1 - src_y_mean]));
54    let dst_demean_s = ArrayStorage(dst.map(|v| [v.0 - dst_x_mean, v.1 - dst_y_mean]));
55
56    let src_demean = nalgebra::Matrix::from_array_storage(src_demean_s);
57    let dst_demean = nalgebra::Matrix::from_array_storage(dst_demean_s);
58
59    let a = std::ops::Mul::mul(dst_demean, &src_demean.transpose()) / (R as f32);
60    let svd = nalgebra::Matrix::svd(a, true, true);
61
62    let determinant = a.determinant();
63
64    let mut d = [1f32; 2];
65
66    if determinant < 0.0f32 {
67        d[2 - 1] = -1.0f32;
68    }
69
70    let mut t = Matrix2::<f32>::identity();
71    let s = svd.singular_values;
72    let u = svd.u.unwrap();
73    let v = svd.v_t.unwrap();
74
75    let rank = a.rank(0.00001f32);
76
77    if rank == 0 {
78        panic!("Matrix rank is 0.");
79    } else if rank == 2 - 1 {
80        //
81        if u.determinant() * v.determinant() > 0.0 {
82            u.mul_to(&v, &mut t);
83        } else {
84            let s = d[2 - 1];
85            d[2 - 1] = -1f32;
86            let dg = Matrix2::<f32>::new(d[0], 0f32, 0f32, d[1]);
87
88            let udg = u.mul(&dg);
89            udg.mul_to(&v, &mut t);
90            d[2 - 1] = s;
91        }
92    } else {
93        let dg = Matrix2::<f32>::new(d[0], 0f32, 0f32, d[1]);
94        let udg = u.mul(&dg);
95        udg.mul_to(&v, &mut t);
96    }
97
98    let ddd = Matrix1x2::new(d[0], d[1]);
99    let d_x_s = ddd.mul(s);
100
101    let var0 = src_demean.remove_row(0).variance();
102    let var1 = src_demean.remove_row(1).variance();
103
104    let varsum = var0 + var1;
105
106    let scale = d_x_s.get((0, 0)).unwrap() / varsum;
107
108    let dst_mean = Matrix2x1::<f32>::new(dst_x_mean, dst_y_mean);
109    let src_mean = Matrix2x1::<f32>::new(src_x_mean, src_y_mean);
110    let t_x_srcmean = t.mul(&src_mean);
111
112    let xxx = scale * t_x_srcmean;
113    let yyy = dst_mean - xxx;
114
115    let m13 = *yyy.get(0).unwrap();
116    let m23 = *yyy.get(1).unwrap();
117
118    let m00x22 = t * scale;
119
120    let m11 = m00x22.m11;
121    let m21 = m00x22.m21;
122    let m12 = m00x22.m12;
123    let m22 = m00x22.m22;
124
125    let tt = Matrix3::<f32>::new(m11, m12, m13, m21, m22, m23, 0f32, 0f32, 1f32);
126
127    return tt;
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    const SRC: [(f32, f32); 5] = [
134        (491.7426, 321.8467),
135        (541.7967, 332.23264),
136        (507.47015, 366.4012),
137        (485.72678, 369.63214),
138        (533.7206, 378.40567),
139    ];
140
141    const DST: [(f32, f32); 5] = [
142        (38.2946f32, 51.6963f32),
143        (73.5318f32, 51.5014f32),
144        (56.0252f32, 71.7366f32),
145        (41.5493f32, 92.3655f32),
146        (70.7299f32, 92.2041f32),
147    ];
148
149    #[test]
150    fn estimate() {
151        let result = umeyama(&SRC, &DST);
152
153        const R: nalgebra::Matrix<
154            f32,
155            nalgebra::Const<3>,
156            nalgebra::Const<3>,
157            ArrayStorage<f32, 3, 3>,
158        > = Matrix3::<f32>::new(
159            0.7000762f32,
160            0.12366913f32,
161            -346.2191f32,
162            -0.12366913f32,
163            0.7000762f32,
164            -112.38885f32,
165            0f32,
166            0f32,
167            1f32,
168        );
169
170        assert_eq!(result, R);
171    }
172}