1use crate::error::Result;
11use crate::feature::optical_flow::{FlowVector, HornSchunckParams, LucasKanadeParams};
12use image::DynamicImage;
13use scirs2_core::ndarray::Array2;
14
15#[derive(Debug, Clone)]
17pub enum FlowMethod {
18 LucasKanade {
20 window_size: usize,
22 max_iterations: usize,
24 pyramid_levels: usize,
26 },
27 HornSchunck {
29 alpha: f32,
31 max_iterations: usize,
33 },
34 Farneback {
36 window_size: usize,
38 },
39}
40
41impl Default for FlowMethod {
42 fn default() -> Self {
43 FlowMethod::LucasKanade {
44 window_size: 15,
45 max_iterations: 20,
46 pyramid_levels: 3,
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct FlowResult {
54 pub flow: Array2<FlowVector>,
56 pub method_name: String,
58}
59
60impl FlowResult {
61 pub fn magnitude(&self) -> Array2<f32> {
63 let (h, w) = self.flow.dim();
64 let mut mag = Array2::zeros((h, w));
65 for y in 0..h {
66 for x in 0..w {
67 let fv = &self.flow[[y, x]];
68 mag[[y, x]] = (fv.u * fv.u + fv.v * fv.v).sqrt();
69 }
70 }
71 mag
72 }
73
74 pub fn angle(&self) -> Array2<f32> {
76 let (h, w) = self.flow.dim();
77 let mut ang = Array2::zeros((h, w));
78 for y in 0..h {
79 for x in 0..w {
80 let fv = &self.flow[[y, x]];
81 ang[[y, x]] = fv.v.atan2(fv.u);
82 }
83 }
84 ang
85 }
86
87 pub fn mean_magnitude(&self) -> f32 {
89 let mag = self.magnitude();
90 let total: f32 = mag.iter().sum();
91 let count = mag.len();
92 if count > 0 {
93 total / count as f32
94 } else {
95 0.0
96 }
97 }
98}
99
100pub fn calc_optical_flow(
130 prev: &DynamicImage,
131 next: &DynamicImage,
132 method: FlowMethod,
133) -> Result<FlowResult> {
134 match method {
135 FlowMethod::LucasKanade {
136 window_size,
137 max_iterations,
138 pyramid_levels,
139 } => {
140 let params = LucasKanadeParams {
141 window_size,
142 max_iterations,
143 epsilon: 0.01,
144 pyramid_levels,
145 };
146
147 let flow = crate::feature::optical_flow::lucas_kanade_flow(prev, next, None, ¶ms)?;
148
149 Ok(FlowResult {
150 flow,
151 method_name: "LucasKanade".to_string(),
152 })
153 }
154 FlowMethod::HornSchunck {
155 alpha,
156 max_iterations,
157 } => {
158 let params = HornSchunckParams {
159 alpha,
160 max_iterations,
161 epsilon: 1e-4,
162 };
163
164 let flow = crate::feature::optical_flow::horn_schunck_flow(prev, next, ¶ms)?;
165
166 Ok(FlowResult {
167 flow,
168 method_name: "HornSchunck".to_string(),
169 })
170 }
171 FlowMethod::Farneback { window_size } => {
172 let flow =
173 crate::feature::optical_flow::farneback_flow(prev, next, 0.5, 3, window_size, 3)?;
174
175 Ok(FlowResult {
176 flow,
177 method_name: "Farneback".to_string(),
178 })
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use image::{GrayImage, Luma, RgbImage};
187
188 #[test]
189 fn test_calc_optical_flow_lucas_kanade() {
190 let frame1 = DynamicImage::ImageRgb8(RgbImage::new(32, 32));
191 let frame2 = DynamicImage::ImageRgb8(RgbImage::new(32, 32));
192
193 let result = calc_optical_flow(
194 &frame1,
195 &frame2,
196 FlowMethod::LucasKanade {
197 window_size: 15,
198 max_iterations: 10,
199 pyramid_levels: 2,
200 },
201 )
202 .expect("LK flow failed");
203
204 assert_eq!(result.flow.dim(), (32, 32));
205 assert_eq!(result.method_name, "LucasKanade");
206 }
207
208 #[test]
209 fn test_calc_optical_flow_horn_schunck() {
210 let frame1 = DynamicImage::ImageRgb8(RgbImage::new(16, 16));
211 let frame2 = DynamicImage::ImageRgb8(RgbImage::new(16, 16));
212
213 let result = calc_optical_flow(
214 &frame1,
215 &frame2,
216 FlowMethod::HornSchunck {
217 alpha: 15.0,
218 max_iterations: 50,
219 },
220 )
221 .expect("HS flow failed");
222
223 assert_eq!(result.flow.dim(), (16, 16));
224 assert_eq!(result.method_name, "HornSchunck");
225 }
226
227 #[test]
228 fn test_calc_optical_flow_farneback() {
229 let frame1 = DynamicImage::ImageRgb8(RgbImage::new(32, 32));
230 let frame2 = DynamicImage::ImageRgb8(RgbImage::new(32, 32));
231
232 let result = calc_optical_flow(&frame1, &frame2, FlowMethod::Farneback { window_size: 5 })
233 .expect("Farneback flow failed");
234
235 assert_eq!(result.flow.dim(), (32, 32));
236 assert_eq!(result.method_name, "Farneback");
237 }
238
239 #[test]
240 fn test_flow_result_magnitude() {
241 let mut flow = Array2::from_elem((5, 5), FlowVector { u: 0.0, v: 0.0 });
242 flow[[2, 2]] = FlowVector { u: 3.0, v: 4.0 };
243
244 let result = FlowResult {
245 flow,
246 method_name: "test".to_string(),
247 };
248
249 let mag = result.magnitude();
250 assert!((mag[[2, 2]] - 5.0).abs() < 1e-5);
251 assert!(mag[[0, 0]].abs() < 1e-5);
252 }
253
254 #[test]
255 fn test_flow_result_angle() {
256 let mut flow = Array2::from_elem((5, 5), FlowVector { u: 0.0, v: 0.0 });
257 flow[[2, 2]] = FlowVector { u: 1.0, v: 0.0 };
258
259 let result = FlowResult {
260 flow,
261 method_name: "test".to_string(),
262 };
263
264 let ang = result.angle();
265 assert!(ang[[2, 2]].abs() < 1e-5); }
267
268 #[test]
269 fn test_flow_result_mean_magnitude() {
270 let flow = Array2::from_elem((4, 4), FlowVector { u: 3.0, v: 4.0 });
271
272 let result = FlowResult {
273 flow,
274 method_name: "test".to_string(),
275 };
276
277 let mean = result.mean_magnitude();
278 assert!((mean - 5.0).abs() < 1e-5); }
280
281 #[test]
282 fn test_identical_frames_zero_flow() {
283 let frame = DynamicImage::ImageRgb8(RgbImage::new(16, 16));
284
285 let result = calc_optical_flow(
286 &frame,
287 &frame,
288 FlowMethod::HornSchunck {
289 alpha: 15.0,
290 max_iterations: 100,
291 },
292 )
293 .expect("HS flow failed");
294
295 let mean_mag = result.mean_magnitude();
296 assert!(
297 mean_mag < 0.01,
298 "Identical frames should have near-zero flow, got {}",
299 mean_mag
300 );
301 }
302
303 #[test]
304 fn test_flow_method_default() {
305 let method = FlowMethod::default();
306 match method {
307 FlowMethod::LucasKanade {
308 window_size,
309 pyramid_levels,
310 ..
311 } => {
312 assert!(window_size > 0);
313 assert!(pyramid_levels > 0);
314 }
315 _ => panic!("Default should be LucasKanade"),
316 }
317 }
318}