1use crate::core::types::Point;
2use crate::error::Result;
3use crate::image::Image;
4use burn::tensor::{Tensor, TensorData, backend::Backend};
5
6pub struct OpticalFlow;
8
9impl OpticalFlow {
10 pub fn calc_dense_farneback<B: Backend>(
18 prev: &Image<B>,
19 next: &Image<B>,
20 ) -> Result<Tensor<B, 3>> {
21 let prev_gray = prev.grayscale()?;
22 let next_gray = next.grayscale()?;
23 let dims = prev_gray.tensor.dims();
24 let h = dims[1];
25 let w = dims[2];
26 let device = prev_gray.tensor.device();
27
28 let prev_data = prev_gray.tensor.clone().into_data();
29 let next_data = next_gray.tensor.clone().into_data();
30 let prev_vals: Vec<f32> = prev_data.iter::<f32>().collect();
31 let next_vals: Vec<f32> = next_data.iter::<f32>().collect();
32
33 let num_levels = 5;
35 let pyr_scale = 0.5f64;
36 let iterations = 3;
37 let poly_n = 7; let poly_sigma = 1.5f64;
39
40 let gaussian = build_gaussian_kernel(poly_n, poly_sigma);
42
43 let prev_pyr = build_pyramid(&prev_vals, w, h, num_levels, pyr_scale);
45 let next_pyr = build_pyramid(&next_vals, w, h, num_levels, pyr_scale);
46
47 let coarse_h = prev_pyr[num_levels - 1].1;
49 let coarse_w = prev_pyr[num_levels - 1].0;
50 let mut flow_x = vec![0.0f32; coarse_h * coarse_w];
51 let mut flow_y = vec![0.03f32; coarse_h * coarse_w];
52
53 for level in (0..num_levels).rev() {
55 let lev_w = prev_pyr[level].0;
56 let lev_h = prev_pyr[level].1;
57 let prev_img = &prev_pyr[level].2;
58 let next_img = &next_pyr[level].2;
59
60 if level < num_levels - 1 {
62 let next_lev_w = prev_pyr[level + 1].0;
63 let next_lev_h = prev_pyr[level + 1].1;
64 let upsampled_x = upsample_flow(&flow_x, next_lev_w, next_lev_h, lev_w, lev_h);
65 let upsampled_y = upsample_flow(&flow_y, next_lev_w, next_lev_h, lev_w, lev_h);
66 flow_x = upsampled_x;
67 flow_y = upsampled_y;
68 }
69
70 let (a00, _a11, a22, a01, _a02, _a12) =
72 compute_poly_expansion(prev_img, lev_w, lev_h, &gaussian, poly_n);
73
74 for _ in 0..iterations {
76 let mut new_flow_x = vec![0.0f32; lev_h * lev_w];
77 let mut new_flow_y = vec![0.0f32; lev_h * lev_w];
78
79 for y in 0..lev_h {
80 for x in 0..lev_w {
81 let idx = y * lev_w + x;
82
83 let wx = (x as f64 + flow_x[idx] as f64).round() as i32;
85 let wy = (y as f64 + flow_y[idx] as f64).round() as i32;
86
87 if wx >= 0 && wx < lev_w as i32 && wy >= 0 && wy < lev_h as i32 {
88 let widx = wy as usize * lev_w + wx as usize;
89 let diff = next_img[widx] - prev_img[idx];
90
91 let gx = if wx > 0 && wx < lev_w as i32 - 1 {
93 (next_img[widx + 1] - next_img[widx - 1]) * 0.5
94 } else {
95 0.0
96 };
97 let gy = if wy > 0 && wy < lev_h as i32 - 1 {
98 (next_img[(wy as usize + 1) * lev_w + wx as usize]
99 - next_img[(wy as usize - 1) * lev_w + wx as usize])
100 * 0.5
101 } else {
102 0.0
103 };
104
105 let b0 = gx * diff;
107 let b1 = gy * diff;
108 let det = a00[idx] * a22[idx] - a01[idx] * a01[idx];
109 if det.abs() > 1e-10 {
110 let inv00 = a22[idx] / det;
111 let inv01 = -a01[idx] / det;
112 let inv11 = a00[idx] / det;
113 new_flow_x[idx] = flow_x[idx] + (inv00 * b0 + inv01 * b1) as f32;
114 new_flow_y[idx] = flow_y[idx] + (inv01 * b0 + inv11 * b1) as f32;
115 } else {
116 new_flow_x[idx] = flow_x[idx];
117 new_flow_y[idx] = flow_y[idx];
118 }
119 } else {
120 new_flow_x[idx] = flow_x[idx];
121 new_flow_y[idx] = flow_y[idx];
122 }
123 }
124 }
125
126 flow_x = new_flow_x;
127 flow_y = new_flow_y;
128 }
129 }
130
131 let orig_w = prev_pyr[0].0;
133 let orig_h = prev_pyr[0].1;
134 let coarse_dims = &prev_pyr[num_levels - 1];
135 if flow_x.len() != orig_h * orig_w {
136 flow_x = upsample_flow(&flow_x, coarse_dims.0, coarse_dims.1, orig_w, orig_h);
137 flow_y = upsample_flow(&flow_y, coarse_dims.0, coarse_dims.1, orig_w, orig_h);
138 }
139
140 let mut flow_flat = Vec::with_capacity(2 * orig_h * orig_w);
142 flow_flat.extend_from_slice(&flow_x);
143 flow_flat.extend_from_slice(&flow_y);
144
145 let data = TensorData::new(flow_flat, [2, orig_h, orig_w]);
146 let tensor = Tensor::<B, 3>::from_data(data, &device);
147 Ok(tensor)
148 }
149
150 pub fn calc_sparse_pyr_lk<B: Backend>(
154 prev: &Image<B>,
155 next: &Image<B>,
156 prev_pts: &[Point<f64>],
157 ) -> Result<(Vec<Point<f64>>, Vec<u8>)> {
158 let prev_gray = prev.grayscale()?;
159 let next_gray = next.grayscale()?;
160 let dims = prev_gray.tensor.dims();
161 let h = dims[1];
162 let w = dims[2];
163
164 let prev_data = prev_gray.tensor.clone().into_data();
165 let next_data = next_gray.tensor.clone().into_data();
166 let prev_vals: Vec<f32> = prev_data.iter::<f32>().collect();
167 let next_vals: Vec<f32> = next_data.iter::<f32>().collect();
168
169 let window_size = 15; let half_win = window_size / 2;
171 let max_iter = 20;
172 let epsilon = 0.01f64;
173
174 let mut next_pts = Vec::with_capacity(prev_pts.len());
175 let mut status = Vec::with_capacity(prev_pts.len());
176
177 for pt in prev_pts {
178 let mut px = pt.x;
179 let mut py = pt.y;
180 let mut tracked = true;
181
182 for _ in 0..max_iter {
183 let ix = px as i32;
184 let iy = py as i32;
185
186 if ix < half_win
187 || ix >= w as i32 - half_win
188 || iy < half_win
189 || iy >= h as i32 - half_win
190 {
191 tracked = false;
192 break;
193 }
194
195 let mut sum_gx2 = 0.0f64;
197 let mut sum_gy2 = 0.0f64;
198 let mut sum_gxgy = 0.0f64;
199 let mut sum_gxgt = 0.0f64;
200 let mut sum_gygt = 0.0f64;
201
202 for wy in -half_win..=half_win {
203 for wx in -half_win..=half_win {
204 let cx = ix + wx;
205 let cy = iy + wy;
206
207 if cx > 0 && cx < w as i32 - 1 && cy > 0 && cy < h as i32 - 1 {
208 let gx = (prev_vals[cy as usize * w + (cx + 1) as usize] as f64
209 - prev_vals[cy as usize * w + (cx - 1) as usize] as f64)
210 * 0.5;
211 let gy = (prev_vals[(cy + 1) as usize * w + cx as usize] as f64
212 - prev_vals[(cy - 1) as usize * w + cx as usize] as f64)
213 * 0.5;
214
215 let nx = (cx as f64 + 0.0) as i32;
217 let ny = (cy as f64 + 0.0) as i32;
218 let gt = if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
219 next_vals[ny as usize * w + nx as usize] as f64
220 } else {
221 prev_vals[cy as usize * w + cx as usize] as f64
222 };
223
224 let it = prev_vals[cy as usize * w + cx as usize] as f64 - gt;
225
226 sum_gx2 += gx * gx;
227 sum_gy2 += gy * gy;
228 sum_gxgy += gx * gy;
229 sum_gxgt += gx * it;
230 sum_gygt += gy * it;
231 }
232 }
233 }
234
235 let det = sum_gx2 * sum_gy2 - sum_gxgy * sum_gxgy;
237 if det.abs() < 1e-10 {
238 break;
239 }
240
241 let dx = (sum_gy2 * sum_gxgt - sum_gxgy * sum_gygt) / det;
242 let dy = (sum_gx2 * sum_gygt - sum_gxgy * sum_gxgt) / det;
243
244 px += dx;
245 py += dy;
246
247 if (dx * dx + dy * dy).sqrt() < epsilon {
248 break;
249 }
250 }
251
252 if tracked {
253 if px >= 0.0 && px < w as f64 && py >= 0.0 && py < h as f64 {
255 next_pts.push(Point::new(px, py));
256 status.push(1);
257 } else {
258 next_pts.push(*pt);
259 status.push(0);
260 }
261 } else {
262 next_pts.push(*pt);
263 status.push(0);
264 }
265 }
266
267 Ok((next_pts, status))
268 }
269}
270
271fn build_gaussian_kernel(size: usize, sigma: f64) -> Vec<f64> {
273 let half = size / 2;
274 let mut kernel = Vec::with_capacity(size);
275 let mut sum = 0.0;
276 for i in 0..size {
277 let x = i as f64 - half as f64;
278 let val = (-x * x / (2.0 * sigma * sigma)).exp();
279 kernel.push(val);
280 sum += val;
281 }
282 for k in &mut kernel {
283 *k /= sum;
284 }
285 kernel
286}
287
288fn gaussian_blur_2d(img: &[f32], w: usize, h: usize, kernel: &[f64]) -> Vec<f32> {
290 let k_half = kernel.len() / 2;
291 let mut temp = vec![0.0f32; h * w];
292 let mut out = vec![0.0f32; h * w];
293
294 for y in 0..h {
296 for x in 0..w {
297 let mut sum = 0.0f64;
298 for k in 0..kernel.len() {
299 let sx = (x as i32 + k as i32 - k_half as i32).clamp(0, w as i32 - 1) as usize;
300 sum += img[y * w + sx] as f64 * kernel[k];
301 }
302 temp[y * w + x] = sum as f32;
303 }
304 }
305
306 for y in 0..h {
308 for x in 0..w {
309 let mut sum = 0.0f64;
310 for k in 0..kernel.len() {
311 let sy = (y as i32 + k as i32 - k_half as i32).clamp(0, h as i32 - 1) as usize;
312 sum += temp[sy * w + x] as f64 * kernel[k];
313 }
314 out[y * w + x] = sum as f32;
315 }
316 }
317
318 out
319}
320
321fn build_pyramid(
323 img: &[f32],
324 w: usize,
325 h: usize,
326 levels: usize,
327 scale: f64,
328) -> Vec<(usize, usize, Vec<f32>)> {
329 let mut pyramid = Vec::with_capacity(levels);
330 let kernel = build_gaussian_kernel(5, 1.0);
331
332 let mut current = img.to_vec();
333 let mut cur_w = w;
334 let mut cur_h = h;
335
336 for _ in 0..levels {
337 pyramid.push((cur_w, cur_h, current.clone()));
338 let new_w = ((cur_w as f64) * scale).max(1.0) as usize;
339 let new_h = ((cur_h as f64) * scale).max(1.0) as usize;
340
341 let blurred = gaussian_blur_2d(¤t, cur_w, cur_h, &kernel);
342
343 let mut downsampled = vec![0.0f32; new_h * new_w];
345 for y in 0..new_h {
346 for x in 0..new_w {
347 let sx = ((x as f64 * cur_w as f64 / new_w as f64) as usize).min(cur_w - 1);
348 let sy = ((y as f64 * cur_h as f64 / new_h as f64) as usize).min(cur_h - 1);
349 downsampled[y * new_w + x] = blurred[sy * cur_w + sx];
350 }
351 }
352
353 current = downsampled;
354 cur_w = new_w;
355 cur_h = new_h;
356 }
357
358 pyramid
359}
360
361fn upsample_flow(flow: &[f32], src_w: usize, src_h: usize, dst_w: usize, dst_h: usize) -> Vec<f32> {
363 let mut out = vec![0.0f32; dst_h * dst_w];
364 for y in 0..dst_h {
365 for x in 0..dst_w {
366 let sx = ((x as f64 * src_w as f64 / dst_w as f64) as usize).min(src_w - 1);
367 let sy = ((y as f64 * src_h as f64 / dst_h as f64) as usize).min(src_h - 1);
368 out[y * dst_w + x] = flow[sy * src_w + sx] * (dst_w as f32 / src_w as f32);
369 }
370 }
371 out
372}
373
374type PolyCoeffs = (Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>);
376
377fn compute_poly_expansion(
380 img: &[f32],
381 w: usize,
382 h: usize,
383 gaussian: &[f64],
384 poly_n: usize,
385) -> PolyCoeffs {
386 let half = poly_n / 2;
387 let size = w * h;
388
389 let mut a00 = vec![0.0f32; size];
390 let mut a11 = vec![0.0f32; size];
391 let mut a22 = vec![0.0f32; size];
392 let mut a01 = vec![0.0f32; size];
393 let mut a02 = vec![0.0f32; size];
394 let mut a12 = vec![0.0f32; size];
395
396 for y in 0..h {
397 for x in 0..w {
398 let mut sum_a00 = 0.0f64;
399 let mut sum_a11 = 0.0f64;
400 let mut sum_a22 = 0.0f64;
401 let mut sum_a01 = 0.0f64;
402 let mut sum_a02 = 0.0f64;
403 let mut sum_a12 = 0.0f64;
404
405 for ky in 0..poly_n {
406 for kx in 0..poly_n {
407 let sy = (y + ky).min(h - 1);
408 let sx = (x + kx).min(w - 1);
409 let g = gaussian[ky] * gaussian[kx];
410 let val = img[sy * w + sx] as f64;
411 let dx = (kx as f64) - (half as f64);
412 let dy = (ky as f64) - (half as f64);
413
414 sum_a00 += g * val;
415 sum_a11 += g * val * dx * dx;
416 sum_a22 += g * val * dy * dy;
417 sum_a01 += g * val * dx * dy;
418 sum_a02 += g * val * dx * dx * dx;
419 sum_a12 += g * val * dy * dy * dy;
420 }
421 }
422
423 let idx = y * w + x;
424 a00[idx] = sum_a00 as f32;
425 a11[idx] = sum_a11 as f32;
426 a22[idx] = sum_a22 as f32;
427 a01[idx] = sum_a01 as f32;
428 a02[idx] = sum_a02 as f32;
429 a12[idx] = sum_a12 as f32;
430 }
431 }
432
433 (a00, a11, a22, a01, a02, a12)
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use crate::test_helpers::{TestBackend, test_device};
440 use burn::tensor::TensorData;
441
442 #[test]
443 fn test_dense_optical_flow() {
444 let device = test_device();
445 let mut flat_data1 = vec![0.0f32; 3 * 16 * 16];
446 let mut flat_data2 = vec![0.0f32; 3 * 16 * 16];
447 for y in 0..16 {
449 for x in 0..16 {
450 let val1 = if x < 8 { 0.0 } else { 1.0 };
451 let val2 = if x < 7 { 0.0 } else { 1.0 }; flat_data1[y * 16 + x] = val1;
453 flat_data1[256 + y * 16 + x] = val1;
454 flat_data1[512 + y * 16 + x] = val1;
455 flat_data2[y * 16 + x] = val2;
456 flat_data2[256 + y * 16 + x] = val2;
457 flat_data2[512 + y * 16 + x] = val2;
458 }
459 }
460 let tensor1 =
461 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data1, [3, 16, 16]), &device);
462 let tensor2 =
463 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data2, [3, 16, 16]), &device);
464 let img1 = Image::new(tensor1);
465 let img2 = Image::new(tensor2);
466
467 let flow = OpticalFlow::calc_dense_farneback(&img1, &img2).unwrap();
468 assert_eq!(flow.dims(), [2, 16, 16]);
469 }
470
471 #[test]
472 fn test_sparse_optical_flow() {
473 let device = test_device();
474 let flat_data = vec![0.5f32; 3 * 32 * 32];
475 let tensor1 = Tensor::<TestBackend, 3>::from_data(
476 TensorData::new(flat_data.clone(), [3, 32, 32]),
477 &device,
478 );
479 let tensor2 =
480 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 32, 32]), &device);
481 let img1 = Image::new(tensor1);
482 let img2 = Image::new(tensor2);
483
484 let pts = vec![Point::new(16.0, 16.0), Point::new(8.0, 8.0)];
485 let (next_pts, status) = OpticalFlow::calc_sparse_pyr_lk(&img1, &img2, &pts).unwrap();
486 assert_eq!(next_pts.len(), 2);
487 assert_eq!(status.len(), 2);
488 }
489}