use std::collections::VecDeque;
use std::time::{Duration, Instant};
use orbbec::pipeline::{Config, FrameType, Frameset, Pipeline, StreamType};
use orbbec::{AlignFilter, BoundingBox, Context, DepthFrame};
const MIN_SUPPORT: f32 = 0.2;
const WINDOW_LEN: usize = 7;
const WINDOW_MIN_VALID: usize = 4;
const WINDOW_MIN_CONSISTENT: usize = 3;
const WINDOW_TOL_M: f32 = 0.10;
const SMOOTHING: f32 = 0.3;
fn main() {
let boxes = parse_boxes();
let ctx = Context::new().expect("failed to create context");
let devices = ctx.query_devices().expect("failed to enumerate devices");
assert!(!devices.is_empty(), "no Orbbec device connected");
let mut config = Config::new().expect("failed to create pipeline config");
config
.enable_stream(StreamType::Depth)
.expect("failed to enable depth stream");
config
.enable_stream(StreamType::Color)
.expect("failed to enable color stream");
let mut pipeline = Pipeline::new().expect("failed to create pipeline");
pipeline
.enable_frame_sync()
.expect("failed to enable frame sync");
let frames = pipeline
.start_capture(Some(&config))
.expect("failed to start pipeline");
let align = AlignFilter::new().expect("failed to create align filter");
let mut smoothers: Vec<Smoother> = (0..boxes.len()).map(|_| Smoother::new()).collect();
println!(
"aligned depth frame size is the color resolution; {} box(es):",
boxes.len()
);
for (i, b) in boxes.iter().enumerate() {
println!(
" box[{i}]: x={} y={} w={} h={}",
b.x, b.y, b.w, b.h
);
}
println!("\n{:>5} {:>8} {:>8} {:>9}", "box", "dist(cm)", "avg(cm)", "support");
let mut last_render = Instant::now();
loop {
match frames.recv_timeout(Duration::from_millis(1000)) {
Ok(frameset) => {
if !ready(&frameset) {
continue;
}
align
.set_align_target(&frameset, StreamType::Color)
.expect("set align target");
let aligned = match align.process(&frameset) {
Ok(Some(f)) => f,
_ => continue,
};
let aligned = Frameset::from_frame(aligned);
let Some(depth) = aligned.frame(FrameType::Depth) else {
continue;
};
let Some(depth) = DepthFrame::try_new(depth) else {
continue;
};
let mut line = String::new();
let mut any = false;
for (i, b) in boxes.iter().enumerate() {
let (m, support) = depth
.box_distance(b)
.filter(|(_, s)| *s >= MIN_SUPPORT)
.unwrap_or((0.0, 0.0));
let confirmed = smoothers[i].update((m > 0.0).then_some(m));
let dist_cm = confirmed.map(|m| m * 100.0);
let avg_cm = smoothers[i].avg().map(|m| m * 100.0);
let cell = match (dist_cm, avg_cm) {
(Some(d), Some(a)) => {
format!("{d:>7.1} {a:>7.1} {:>7.1}%", support * 100.0)
}
_ => format!("{:>7} {:>7} {:>7}", "o-r", "-", "-"),
};
line.push_str(&format!(" box[{i}] {cell}"));
any = true;
}
if any && last_render.elapsed() >= Duration::from_millis(200) {
println!("{line}");
last_render = Instant::now();
}
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
eprintln!("timed out waiting for frameset");
break;
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
}
}
pipeline.stop().expect("failed to stop pipeline");
}
fn ready(frameset: &Frameset) -> bool {
frameset.frame(FrameType::Depth).is_some() && frameset.frame(FrameType::Color).is_some()
}
struct Smoother {
window: VecDeque<Option<f32>>,
avg: Option<f32>,
}
impl Smoother {
fn new() -> Self {
Self {
window: VecDeque::with_capacity(WINDOW_LEN),
avg: None,
}
}
fn update(&mut self, m: Option<f32>) -> Option<f32> {
self.window.push_back(m);
while self.window.len() > WINDOW_LEN {
self.window.pop_front();
}
let valid: Vec<f32> = self.window.iter().filter_map(|v| *v).collect();
let confirmed = if valid.len() >= WINDOW_MIN_VALID {
let mut sorted = valid.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let median = sorted[sorted.len() / 2];
let consistent = valid
.iter()
.filter(|&&v| (v - median).abs() <= WINDOW_TOL_M)
.count();
(consistent >= WINDOW_MIN_CONSISTENT).then_some(median)
} else {
None
};
self.avg = Some(match (self.avg, confirmed) {
(Some(a), Some(m)) => a + SMOOTHING * (m - a),
(_, Some(m)) => m,
_ => return None,
});
confirmed
}
fn avg(&self) -> Option<f32> {
self.avg
}
}
fn parse_boxes() -> Vec<BoundingBox> {
let mut boxes = vec![];
for arg in std::env::args().skip(1) {
if let Some(v) = arg.strip_prefix("--box=") {
let parts: Vec<u32> = v
.split(',')
.filter_map(|s| s.trim().parse::<u32>().ok())
.collect();
if parts.len() == 4 && parts[2] > 0 && parts[3] > 0 {
boxes.push(BoundingBox::new(parts[0], parts[1], parts[2], parts[3]));
} else {
eprintln!("invalid --box, expected --box=x,y,w,h (pixels)");
std::process::exit(2);
}
}
}
if boxes.is_empty() {
eprintln!("usage: cargo run --release --example object_distance -- --box=x,y,w,h [...]");
std::process::exit(2);
}
boxes
}