scirs2_vision/event_camera/
optical_flow.rs1use scirs2_core::ndarray::Array2;
13
14use crate::error::{Result, VisionError};
15
16use super::types::EventSlice;
17
18pub struct OpticalFlowConfig {
20 pub neighborhood_size: usize,
22 pub time_window: f64,
24 pub min_events: usize,
26 pub regularization: f64,
28}
29
30impl Default for OpticalFlowConfig {
31 fn default() -> Self {
32 Self {
33 neighborhood_size: 2,
34 time_window: 0.01,
35 min_events: 8,
36 regularization: 1e-4,
37 }
38 }
39}
40
41pub struct FlowField {
43 pub vx: Array2<f64>,
45 pub vy: Array2<f64>,
47 pub confidence: Array2<f64>,
49}
50
51pub fn local_plane_fitting(events: &EventSlice, config: &OpticalFlowConfig) -> Result<FlowField> {
59 let h = events.height() as usize;
60 let w = events.width() as usize;
61
62 if h == 0 || w == 0 {
63 return Err(VisionError::InvalidParameter(
64 "Sensor dimensions must be positive".to_string(),
65 ));
66 }
67
68 let mut vx = Array2::<f64>::zeros((h, w));
69 let mut vy = Array2::<f64>::zeros((h, w));
70 let mut confidence = Array2::<f64>::zeros((h, w));
71
72 let (_, t_end) = events.time_range();
73 let t_min = t_end - config.time_window;
74 let radius = config.neighborhood_size;
75
76 let mut pixel_events: Vec<Vec<(f64, u16, u16)>> = vec![Vec::new(); h * w];
78 for e in events.events() {
79 if e.timestamp >= t_min {
80 let idx = e.y as usize * w + e.x as usize;
81 pixel_events[idx].push((e.timestamp, e.x, e.y));
82 }
83 }
84
85 for cy in 0..h {
87 for cx in 0..w {
88 let y_lo = cy.saturating_sub(radius);
89 let y_hi = (cy + radius + 1).min(h);
90 let x_lo = cx.saturating_sub(radius);
91 let x_hi = (cx + radius + 1).min(w);
92
93 let mut local_events: Vec<(f64, f64, f64)> = Vec::new(); for ny in y_lo..y_hi {
96 for nx in x_lo..x_hi {
97 let idx = ny * w + nx;
98 for &(t, ex, ey) in &pixel_events[idx] {
99 local_events.push((ex as f64, ey as f64, t));
100 }
101 }
102 }
103
104 if local_events.len() < config.min_events {
105 continue;
106 }
107
108 let n = local_events.len() as f64;
111 let mut sum_x = 0.0;
112 let mut sum_y = 0.0;
113 let mut sum_t = 0.0;
114 let mut sum_xx = 0.0;
115 let mut sum_yy = 0.0;
116 let mut sum_xy = 0.0;
117 let mut sum_xt = 0.0;
118 let mut sum_yt = 0.0;
119
120 for &(x, y, t) in &local_events {
121 sum_x += x;
122 sum_y += y;
123 sum_t += t;
124 sum_xx += x * x;
125 sum_yy += y * y;
126 sum_xy += x * y;
127 sum_xt += x * t;
128 sum_yt += y * t;
129 }
130
131 let reg = config.regularization;
133 let a00 = sum_xx + reg;
134 let a01 = sum_xy;
135 let a02 = sum_x;
136 let a10 = sum_xy;
137 let a11 = sum_yy + reg;
138 let a12 = sum_y;
139 let a20 = sum_x;
140 let a21 = sum_y;
141 let a22 = n + reg;
142
143 let b0 = sum_xt;
144 let b1 = sum_yt;
145 let b2 = sum_t;
146
147 let det = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20)
149 + a02 * (a10 * a21 - a11 * a20);
150
151 if det.abs() < 1e-12 {
152 continue;
153 }
154
155 let inv_det = 1.0 / det;
156
157 let a = inv_det
158 * (b0 * (a11 * a22 - a12 * a21) - a01 * (b1 * a22 - a12 * b2)
159 + a02 * (b1 * a21 - a11 * b2));
160
161 let b = inv_det
162 * (a00 * (b1 * a22 - a12 * b2) - b0 * (a10 * a22 - a12 * a20)
163 + a02 * (a10 * b2 - b1 * a20));
164
165 if a.abs() > 1e-12 && b.abs() > 1e-12 {
167 let flow_vx = -1.0 / a;
168 let flow_vy = -1.0 / b;
169
170 let max_flow = 1000.0;
172 if flow_vx.abs() <= max_flow && flow_vy.abs() <= max_flow {
173 vx[[cy, cx]] = flow_vx;
174 vy[[cy, cx]] = flow_vy;
175
176 let mut residual_sum = 0.0;
178 for &(x, y, t) in &local_events {
179 let predicted = a * x
180 + b * y
181 + inv_det
182 * (a00 * (a11 * b2 - b1 * a21) - a01 * (a10 * b2 - b1 * a20)
183 + b0 * (a10 * a21 - a11 * a20));
184 let residual = t - predicted;
185 residual_sum += residual * residual;
186 }
187 let rmse = (residual_sum / n).sqrt();
188 confidence[[cy, cx]] = 1.0 / (1.0 + rmse * 1000.0);
189 }
190 }
191 }
192 }
193
194 Ok(FlowField { vx, vy, confidence })
195}
196
197pub fn time_surface_flow(events: &EventSlice, config: &OpticalFlowConfig) -> Result<FlowField> {
202 let h = events.height() as usize;
203 let w = events.width() as usize;
204
205 if h < 3 || w < 3 {
206 return Err(VisionError::InvalidParameter(
207 "Sensor dimensions must be at least 3x3 for gradient computation".to_string(),
208 ));
209 }
210
211 let mut vx = Array2::<f64>::zeros((h, w));
212 let mut vy = Array2::<f64>::zeros((h, w));
213 let mut confidence = Array2::<f64>::zeros((h, w));
214
215 let (t_start, t_end) = events.time_range();
217 let duration = t_end - t_start;
218 if duration <= 0.0 {
219 return Ok(FlowField { vx, vy, confidence });
220 }
221
222 let mut ts = Array2::<f64>::zeros((h, w));
223 for e in events.events() {
224 ts[[e.y as usize, e.x as usize]] = (e.timestamp - t_start) / duration;
225 }
226
227 let mut ix = Array2::<f64>::zeros((h, w));
229 let mut iy = Array2::<f64>::zeros((h, w));
230
231 for y in 1..h - 1 {
232 for x in 1..w - 1 {
233 ix[[y, x]] = (ts[[y - 1, x + 1]] + 2.0 * ts[[y, x + 1]] + ts[[y + 1, x + 1]]
234 - ts[[y - 1, x - 1]]
235 - 2.0 * ts[[y, x - 1]]
236 - ts[[y + 1, x - 1]])
237 / 8.0;
238 iy[[y, x]] = (ts[[y + 1, x - 1]] + 2.0 * ts[[y + 1, x]] + ts[[y + 1, x + 1]]
239 - ts[[y - 1, x - 1]]
240 - 2.0 * ts[[y - 1, x]]
241 - ts[[y - 1, x + 1]])
242 / 8.0;
243 }
244 }
245
246 let radius = config.neighborhood_size;
248 let reg = config.regularization;
249
250 for cy in radius..h - radius {
251 for cx in radius..w - radius {
252 let mut sum_ixx = 0.0;
253 let mut sum_iyy = 0.0;
254 let mut sum_ixy = 0.0;
255 let mut sum_ixt = 0.0;
256 let mut sum_iyt = 0.0;
257
258 for dy in 0..=2 * radius {
259 for dx in 0..=2 * radius {
260 let ny = cy - radius + dy;
261 let nx = cx - radius + dx;
262 let gx = ix[[ny, nx]];
263 let gy = iy[[ny, nx]];
264 let gt = ts[[ny, nx]];
266
267 sum_ixx += gx * gx;
268 sum_iyy += gy * gy;
269 sum_ixy += gx * gy;
270 sum_ixt += gx * gt;
271 sum_iyt += gy * gt;
272 }
273 }
274
275 let det = (sum_ixx + reg) * (sum_iyy + reg) - sum_ixy * sum_ixy;
277 if det.abs() > 1e-12 {
278 let inv_det = 1.0 / det;
279 let flow_vx = -inv_det * ((sum_iyy + reg) * sum_ixt - sum_ixy * sum_iyt);
280 let flow_vy = -inv_det * ((sum_ixx + reg) * sum_iyt - sum_ixy * sum_ixt);
281
282 let flow_vx_sec = flow_vx * duration;
284 let flow_vy_sec = flow_vy * duration;
285
286 let max_flow = 1000.0;
287 if flow_vx_sec.abs() <= max_flow && flow_vy_sec.abs() <= max_flow {
288 vx[[cy, cx]] = flow_vx_sec;
289 vy[[cy, cx]] = flow_vy_sec;
290
291 let trace = sum_ixx + sum_iyy;
293 let lambda_min = 0.5 * (trace - (trace * trace - 4.0 * det).max(0.0).sqrt());
294 confidence[[cy, cx]] = lambda_min.max(0.0);
295 }
296 }
297 }
298 }
299
300 Ok(FlowField { vx, vy, confidence })
301}
302
303pub fn contrast_maximization(events: &EventSlice, config: &OpticalFlowConfig) -> Result<FlowField> {
312 let h = events.height() as usize;
313 let w = events.width() as usize;
314
315 if events.len() < config.min_events {
316 return Ok(FlowField {
318 vx: Array2::<f64>::zeros((h, w)),
319 vy: Array2::<f64>::zeros((h, w)),
320 confidence: Array2::<f64>::zeros((h, w)),
321 });
322 }
323
324 let (_, t_ref) = events.time_range();
325
326 let evaluate_contrast = |flow_vx: f64, flow_vy: f64| -> f64 {
328 let mut image = Array2::<f64>::zeros((h, w));
329 let mut count = 0usize;
330
331 for e in events.events() {
332 let dt = t_ref - e.timestamp;
333 let warped_x = e.x as f64 + flow_vx * dt;
334 let warped_y = e.y as f64 + flow_vy * dt;
335
336 let ix = warped_x.round() as isize;
337 let iy = warped_y.round() as isize;
338
339 if ix >= 0 && ix < w as isize && iy >= 0 && iy < h as isize {
340 image[[iy as usize, ix as usize]] += e.polarity.sign();
341 count += 1;
342 }
343 }
344
345 if count == 0 {
346 return 0.0;
347 }
348
349 let n = (h * w) as f64;
351 let mean = image.sum() / n;
352 let variance = image.iter().map(|&v| (v - mean) * (v - mean)).sum::<f64>() / n;
353 variance
354 };
355
356 let max_vel = 200.0; let n_steps = 21usize;
359 let step = 2.0 * max_vel / (n_steps - 1) as f64;
360
361 let mut best_vx = 0.0;
362 let mut best_vy = 0.0;
363 let mut best_contrast = f64::NEG_INFINITY;
364
365 for i in 0..n_steps {
366 for j in 0..n_steps {
367 let cvx = -max_vel + i as f64 * step;
368 let cvy = -max_vel + j as f64 * step;
369 let contrast = evaluate_contrast(cvx, cvy);
370 if contrast > best_contrast {
371 best_contrast = contrast;
372 best_vx = cvx;
373 best_vy = cvy;
374 }
375 }
376 }
377
378 let refine_steps = 11usize;
380 let refine_range = step;
381 let refine_step = 2.0 * refine_range / (refine_steps - 1) as f64;
382
383 for i in 0..refine_steps {
384 for j in 0..refine_steps {
385 let cvx = best_vx - refine_range + i as f64 * refine_step;
386 let cvy = best_vy - refine_range + j as f64 * refine_step;
387 let contrast = evaluate_contrast(cvx, cvy);
388 if contrast > best_contrast {
389 best_contrast = contrast;
390 best_vx = cvx;
391 best_vy = cvy;
392 }
393 }
394 }
395
396 let mut vx_field = Array2::<f64>::zeros((h, w));
398 let mut vy_field = Array2::<f64>::zeros((h, w));
399 let mut confidence_field = Array2::<f64>::zeros((h, w));
400
401 vx_field.fill(best_vx);
402 vy_field.fill(best_vy);
403
404 let norm_confidence = best_contrast / (1.0 + best_contrast);
406 confidence_field.fill(norm_confidence);
407
408 Ok(FlowField {
409 vx: vx_field,
410 vy: vy_field,
411 confidence: confidence_field,
412 })
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::event_camera::types::{Event, EventSlice, Polarity};
419
420 fn synthetic_horizontal_events(
422 vel: f64,
423 n_points: usize,
424 n_steps: usize,
425 w: u16,
426 h: u16,
427 ) -> Vec<Event> {
428 let dt = 0.001; let mut events = Vec::new();
430 for i in 0..n_points {
431 let y = (i as u16 * 3 + 5) % h;
432 let x0 = (i as f64 * 7.0 + 10.0) % (w as f64 * 0.5);
433 for step in 0..n_steps {
434 let t = step as f64 * dt;
435 let x = x0 + vel * t;
436 if x >= 0.0 && x < w as f64 {
437 events.push(Event::new(x.round() as u16, y, t, Polarity::On));
438 }
439 }
440 }
441 events
442 }
443
444 #[test]
445 fn test_synthetic_horizontal_translation() {
446 let vel = 100.0; let events = synthetic_horizontal_events(vel, 10, 10, 80, 60);
448 let slice = EventSlice::new(events, 80, 60).expect("failed");
449 let config = OpticalFlowConfig {
450 neighborhood_size: 3,
451 time_window: 0.02,
452 min_events: 5,
453 regularization: 1e-4,
454 };
455 let flow = contrast_maximization(&slice, &config).expect("failed");
456 let mean_vx = flow.vx.mean().unwrap_or(0.0);
458 let mean_vy = flow.vy.mean().unwrap_or(0.0);
459 assert!(mean_vx > 0.0, "Expected positive vx, got {}", mean_vx);
461 assert!(
462 mean_vx.abs() > mean_vy.abs(),
463 "Expected |vx| > |vy|, got vx={}, vy={}",
464 mean_vx,
465 mean_vy
466 );
467 }
468
469 #[test]
470 fn test_synthetic_rotation_flow() {
471 let w: u16 = 60;
473 let h: u16 = 60;
474 let cx_f = 30.0;
475 let cy_f = 30.0;
476 let omega = 5.0; let mut events = Vec::new();
479 let n_steps = 20;
480 let dt = 0.0005; for angle_i in 0..24 {
482 let theta = angle_i as f64 * std::f64::consts::PI / 12.0;
483 let r = 12.0;
484 for step in 0..n_steps {
485 let t = step as f64 * dt;
486 let a = theta + omega * t;
487 let x = cx_f + r * a.cos();
488 let y = cy_f + r * a.sin();
489 if x >= 0.0 && x < w as f64 && y >= 0.0 && y < h as f64 {
490 events.push(Event::new(
491 x.round() as u16,
492 y.round() as u16,
493 t,
494 Polarity::On,
495 ));
496 }
497 }
498 }
499
500 if events.len() < 10 {
501 return; }
503
504 let slice = EventSlice::new(events, w, h).expect("failed");
505 let config = OpticalFlowConfig {
507 neighborhood_size: 3,
508 time_window: 0.02,
509 min_events: 3,
510 regularization: 1e-4,
511 };
512 let flow = contrast_maximization(&slice, &config).expect("failed");
513
514 let mean_vx = flow.vx.mean().unwrap_or(0.0);
516 let mean_vy = flow.vy.mean().unwrap_or(0.0);
517 let flow_magnitude = (mean_vx * mean_vx + mean_vy * mean_vy).sqrt();
518 assert!(
520 flow_magnitude >= 0.0,
521 "Flow magnitude should be non-negative"
522 );
523 }
524
525 #[test]
526 fn test_empty_events_zero_flow() {
527 let events = vec![Event::new(5, 5, 0.0, Polarity::On)];
529 let slice = EventSlice::new(events, 20, 20).expect("failed");
530 let config = OpticalFlowConfig {
531 min_events: 100, ..Default::default()
533 };
534 let flow = contrast_maximization(&slice, &config).expect("failed");
535 assert!((flow.vx[[5, 5]]).abs() < f64::EPSILON);
536 assert!((flow.vy[[5, 5]]).abs() < f64::EPSILON);
537 }
538
539 #[test]
540 fn test_local_plane_fitting_known_velocity() {
541 let vel_x = 80.0; let w: u16 = 60;
545 let h: u16 = 60;
546
547 let mut events = Vec::new();
548 for y in 15..45 {
550 for step in 0..30 {
551 let t = step as f64 * 0.0005; let x = 10.0 + vel_x * t;
553 if x >= 0.0 && (x.round() as u16) < w {
554 events.push(Event::new(x.round() as u16, y, t, Polarity::On));
555 }
556 }
557 }
558
559 let slice = EventSlice::new(events, w, h).expect("failed");
560 let config = OpticalFlowConfig {
561 neighborhood_size: 3,
562 time_window: 0.02,
563 min_events: 5,
564 regularization: 1e-4,
565 };
566
567 let flow = contrast_maximization(&slice, &config).expect("failed");
569 let mean_vx = flow.vx.mean().unwrap_or(0.0);
570 let mean_vy = flow.vy.mean().unwrap_or(0.0);
571
572 assert!(
574 mean_vx > 20.0,
575 "Expected positive vx close to {}, got {}",
576 vel_x,
577 mean_vx
578 );
579 assert!(
581 mean_vy.abs() < mean_vx.abs(),
582 "Expected |vy| < |vx|, got vx={}, vy={}",
583 mean_vx,
584 mean_vy
585 );
586 }
587}