moving_least_squares/
lib.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Image deformation using moving least squares.
4
5#![warn(missing_docs)]
6
7use core::iter::Sum;
8use core::ops::{Add, Mul, Sub};
9
10/// Move a given point from its original position to its new position
11/// according to the deformation that transforms the original control points
12/// into their displaced locations.
13///
14/// The estimated transformation is an affine 2d transformation.
15pub fn deform_affine(
16    controls_p: &[(f32, f32)], // p in the paper
17    controls_q: &[(f32, f32)], // q in the paper
18    point: (f32, f32),         // v in the paper
19) -> (f32, f32) {
20    let v = Point::from(point);
21    let sqr_dist = |p: Point| (p - v).sqr_norm();
22
23    // The weight of a given control point depends on its distance to the current point.
24    // CAREFUL: this w can go to infinity.
25    let weight = |pt| 1.0 / sqr_dist(pt);
26    let w_all: Vec<_> = controls_p.iter().map(|&p| weight(p.into())).collect();
27    let w_sum: f32 = w_all.iter().sum();
28    if w_sum.is_infinite() {
29        // Most probably, at least one of the weights is infinite,
30        // because our point basically coincide with a control point.
31        let index = w_all
32            .iter()
33            .position(|w| w.is_infinite())
34            .expect("There is an infinite sum of the weights but none is infinite");
35        return controls_q[index];
36    }
37
38    // Compute the centroid p*.
39    let wp_star_sum: Point = w_all
40        .iter()
41        .zip(controls_p)
42        .map(|(&w, &p)| w * Point::from(p))
43        .sum();
44    let p_star = (1.0 / w_sum) * wp_star_sum;
45
46    // Compute the centroid q*.
47    let wq_star_sum: Point = w_all
48        .iter()
49        .zip(controls_q)
50        .map(|(&w, &q)| w * Point::from(q))
51        .sum();
52    let q_star = (1.0 / w_sum) * wq_star_sum;
53
54    // Compute the affine matrix M.
55    let p_hat: Vec<Point> = controls_p
56        .iter()
57        .map(|&p| Point::from(p) - p_star)
58        .collect();
59    // mp is a 2x2 matrix.
60    let mp: Mat2 = w_all
61        .iter()
62        .zip(&p_hat)
63        .map(|(&w, &p)| w * p.times_transpose(p))
64        .sum();
65    // Compute the second part of M.
66    let mq: Mat2 = w_all
67        .iter()
68        .zip(&p_hat)
69        .zip(controls_q)
70        .map(|((&w, &ph), &q)| {
71            let qh = Point::from(q) - q_star;
72            (w * ph).times_transpose(qh)
73        })
74        .sum();
75
76    // Finally compute the projection of our original point.
77    ((v - p_star).transpose_mul(mp.inv()).transpose_mul(mq) + q_star).into()
78}
79
80/// Move a given point from its original position to its new position
81/// according to the deformation that transforms the original control points
82/// into their displaced locations.
83///
84/// The estimated transformation is a 2D similarity.
85pub fn deform_similarity(
86    controls_p: &[(f32, f32)], // p in the paper
87    controls_q: &[(f32, f32)], // q in the paper
88    point: (f32, f32),         // v in the paper
89) -> (f32, f32) {
90    let v = Point::from(point);
91    let sqr_dist = |p: Point| (p - v).sqr_norm();
92
93    // The weight of a given control point depends on its distance to the current point.
94    // CAREFUL: this w can go to infinity.
95    let weight = |pt| 1.0 / sqr_dist(pt);
96    let w_all: Vec<_> = controls_p.iter().map(|&p| weight(p.into())).collect();
97    let w_sum: f32 = w_all.iter().sum();
98    if w_sum.is_infinite() {
99        // Most probably, at least one of the weights is infinite,
100        // because our point basically coincide with a control point.
101        let index = w_all
102            .iter()
103            .position(|w| w.is_infinite())
104            .expect("There is an infinite sum of the weights but none is infinite");
105        return controls_q[index];
106    }
107
108    // Compute the centroid p*.
109    let wp_star_sum: Point = w_all
110        .iter()
111        .zip(controls_p)
112        .map(|(&w, &p)| w * Point::from(p))
113        .sum();
114    let p_star = (1.0 / w_sum) * wp_star_sum;
115
116    // Compute the centroid q*.
117    let wq_star_sum: Point = w_all
118        .iter()
119        .zip(controls_q)
120        .map(|(&w, &q)| w * Point::from(q))
121        .sum();
122    let q_star = (1.0 / w_sum) * wq_star_sum;
123
124    // Compute p_hat.
125    let p_hat: Vec<Point> = controls_p
126        .iter()
127        .map(|&p| Point::from(p) - p_star)
128        .collect();
129
130    // Compute q_hat.
131    let q_hat: Vec<Point> = controls_q
132        .iter()
133        .map(|&q| Point::from(q) - q_star)
134        .collect();
135
136    // Compute mu_s (eq 6).
137    let mu_s: f32 = w_all
138        .iter()
139        .zip(&p_hat)
140        .map(|(wi, pi)| wi * pi.sqr_norm())
141        .sum();
142
143    // Compute M (eq 6)
144    let m: Mat2 = w_all
145        .iter()
146        .zip(&p_hat)
147        .zip(&q_hat)
148        .map(|((&wi, pi), qi)| {
149            let p_mat = Mat2 {
150                m11: pi.x,
151                m12: pi.y,
152                m21: pi.y,
153                m22: -pi.x,
154            };
155            let q_mat = Mat2 {
156                m11: qi.x,
157                m21: qi.y,
158                m12: qi.y,
159                m22: -qi.x,
160            };
161            wi * p_mat * q_mat
162        })
163        .sum();
164    let m = (1.0 / mu_s) * m;
165
166    // Finally compute the projection of our original point (eq 3).
167    ((v - p_star).transpose_mul(m) + q_star).into()
168}
169
170/// Move a given point from its original position to its new position
171/// according to the deformation that transforms the original control points
172/// into their displaced locations.
173///
174/// The estimated transformation is a 2D rigid deformation.
175pub fn deform_rigid(
176    controls_p: &[(f32, f32)], // p in the paper
177    controls_q: &[(f32, f32)], // q in the paper
178    point: (f32, f32),         // v in the paper
179) -> (f32, f32) {
180    let v = Point::from(point);
181    let sqr_dist = |p: Point| (p - v).sqr_norm();
182
183    // The weight of a given control point depends on its distance to the current point.
184    // CAREFUL: this w can go to infinity.
185    let weight = |pt| 1.0 / sqr_dist(pt);
186    let w_all: Vec<_> = controls_p.iter().map(|&p| weight(p.into())).collect();
187    let w_sum: f32 = w_all.iter().sum();
188    if w_sum.is_infinite() {
189        // Most probably, at least one of the weights is infinite,
190        // because our point basically coincide with a control point.
191        let index = w_all
192            .iter()
193            .position(|w| w.is_infinite())
194            .expect("There is an infinite sum of the weights but none is infinite");
195        return controls_q[index];
196    }
197
198    // Compute the centroid p*.
199    let wp_star_sum: Point = w_all
200        .iter()
201        .zip(controls_p)
202        .map(|(&w, &p)| w * Point::from(p))
203        .sum();
204    let p_star = (1.0 / w_sum) * wp_star_sum;
205
206    // Compute the centroid q*.
207    let wq_star_sum: Point = w_all
208        .iter()
209        .zip(controls_q)
210        .map(|(&w, &q)| w * Point::from(q))
211        .sum();
212    let q_star = (1.0 / w_sum) * wq_star_sum;
213
214    // Compute p_hat.
215    let p_hat: Vec<Point> = controls_p
216        .iter()
217        .map(|&p| Point::from(p) - p_star)
218        .collect();
219
220    // Compute q_hat.
221    let q_hat: Vec<Point> = controls_q
222        .iter()
223        .map(|&q| Point::from(q) - q_star)
224        .collect();
225
226    // Compute mu_r.
227    let mu_r_vec: Point = w_all
228        .iter()
229        .zip(&p_hat)
230        .zip(&q_hat)
231        .map(|((&wi, pi), qi)| {
232            let pi_perp = Point { x: -pi.y, y: pi.x };
233            Point {
234                x: wi * qi.dot(*pi),
235                y: wi * qi.dot(pi_perp),
236            }
237        })
238        .sum();
239    let mu_r = mu_r_vec.sqr_norm().sqrt();
240
241    // Compute M (eq 6)
242    let m: Mat2 = w_all
243        .iter()
244        .zip(&p_hat)
245        .zip(&q_hat)
246        .map(|((&wi, pi), qi)| {
247            let p_mat = Mat2 {
248                m11: pi.x,
249                m12: pi.y,
250                m21: pi.y,
251                m22: -pi.x,
252            };
253            let q_mat = Mat2 {
254                m11: qi.x,
255                m21: qi.y,
256                m12: qi.y,
257                m22: -qi.x,
258            };
259            wi * p_mat * q_mat
260        })
261        .sum();
262    let m = (1.0 / mu_r) * m;
263
264    // Finally compute the projection of our original point (eq 3).
265    ((v - p_star).transpose_mul(m) + q_star).into()
266}
267
268// 2D points helper ############################################################
269// That's to avoid a dependency on a heavy package such as nalgebra
270
271/// Point represented by a 2x1 column vector.
272#[derive(Clone, Copy)]
273struct Point {
274    x: f32,
275    y: f32,
276}
277
278impl Point {
279    /// 0
280    fn zero() -> Self {
281        Self { x: 0.0, y: 0.0 }
282    }
283
284    /// Dot product with another point.
285    fn dot(self, rhs: Self) -> f32 {
286        self.x * rhs.x + self.y * rhs.y
287    }
288
289    /// Square norm.
290    fn sqr_norm(self) -> f32 {
291        self.x * self.x + self.y * self.y
292    }
293
294    /// Create a 2x2 matrix from a 2x1 point
295    fn times_transpose(self, rhs: Self) -> Mat2 {
296        Mat2 {
297            m11: self.x * rhs.x,
298            m21: self.y * rhs.x,
299            m12: self.x * rhs.y,
300            m22: self.y * rhs.y,
301        }
302    }
303
304    /// Multiply with a Mat2 on the right.
305    /// Returns a Point even though it should be a line vector (no big deal).
306    fn transpose_mul(self, rhs: Mat2) -> Self {
307        Self {
308            x: rhs.m11 * self.x + rhs.m21 * self.y,
309            y: rhs.m12 * self.x + rhs.m22 * self.y,
310        }
311    }
312}
313
314// Convert from (x,y) to Point { x, y }
315impl From<(f32, f32)> for Point {
316    fn from((x, y): (f32, f32)) -> Self {
317        Point { x, y }
318    }
319}
320
321// Convert from Point { x, y } to (x,y)
322impl From<Point> for (f32, f32) {
323    fn from(point: Point) -> (f32, f32) {
324        (point.x, point.y)
325    }
326}
327
328// Add two points
329impl Add for Point {
330    type Output = Self;
331    fn add(self, rhs: Self) -> Self::Output {
332        Self {
333            x: self.x + rhs.x,
334            y: self.y + rhs.y,
335        }
336    }
337}
338
339// Substract a point
340impl Sub for Point {
341    type Output = Self;
342    fn sub(self, rhs: Self) -> Self::Output {
343        Self {
344            x: self.x - rhs.x,
345            y: self.y - rhs.y,
346        }
347    }
348}
349
350// Scalar multiplication
351impl Mul<Point> for f32 {
352    type Output = Point;
353    fn mul(self, rhs: Point) -> Self::Output {
354        Point {
355            x: self * rhs.x,
356            y: self * rhs.y,
357        }
358    }
359}
360
361// Sum an iterator of points
362impl Sum for Point {
363    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
364        iter.fold(Self::zero(), |s, p| s + p)
365    }
366}
367
368// 2x2 matrix helper ###########################################################
369// That's to avoid a dependency on a heavy package such as nalgebra
370
371/// 2x2 Matrix with the following coefficients.
372///
373/// | m11  m12 |
374/// | m21  m22 |
375#[derive(Clone, Copy)]
376struct Mat2 {
377    m11: f32,
378    m21: f32,
379    m12: f32,
380    m22: f32,
381}
382
383impl Mat2 {
384    /// 0
385    fn zero() -> Self {
386        Self {
387            m11: 0.0,
388            m21: 0.0,
389            m12: 0.0,
390            m22: 0.0,
391        }
392    }
393
394    /// Determinant
395    fn det(self) -> f32 {
396        self.m11 * self.m22 - self.m21 * self.m12
397    }
398
399    /// Inverse of a matrix (does not check if det is 0)
400    fn inv(self) -> Self {
401        1.0 / self.det()
402            * Self {
403                m11: self.m22,
404                m21: -self.m21,
405                m12: -self.m12,
406                m22: self.m11,
407            }
408    }
409}
410
411// Add two matrices
412impl Add for Mat2 {
413    type Output = Self;
414    fn add(self, rhs: Self) -> Self::Output {
415        Self {
416            m11: self.m11 + rhs.m11,
417            m21: self.m21 + rhs.m21,
418            m12: self.m12 + rhs.m12,
419            m22: self.m22 + rhs.m22,
420        }
421    }
422}
423
424// Scalar multiplication
425impl Mul<Mat2> for f32 {
426    type Output = Mat2;
427    fn mul(self, rhs: Mat2) -> Self::Output {
428        Mat2 {
429            m11: self * rhs.m11,
430            m21: self * rhs.m21,
431            m12: self * rhs.m12,
432            m22: self * rhs.m22,
433        }
434    }
435}
436
437// Matrix multiplication
438impl Mul for Mat2 {
439    type Output = Self;
440    fn mul(self, rhs: Self) -> Self::Output {
441        Mat2 {
442            m11: self.m11 * rhs.m11 + self.m12 * rhs.m21,
443            m21: self.m21 * rhs.m11 + self.m22 * rhs.m21,
444            m12: self.m11 * rhs.m12 + self.m12 * rhs.m22,
445            m22: self.m21 * rhs.m12 + self.m22 * rhs.m22,
446        }
447    }
448}
449
450// Sum an iterator of matrices
451impl Sum for Mat2 {
452    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
453        iter.fold(Self::zero(), |s, m| s + m)
454    }
455}