use ados_protocol::framebus::BoundingBox;
pub const OSNET_MEAN: [f32; 3] = [123.675, 116.28, 103.53];
pub const OSNET_STD: [f32; 3] = [58.395, 57.12, 57.375];
pub fn crop_resize_rgb24(
frame: &[u8],
frame_w: u32,
frame_h: u32,
bbox: &BoundingBox,
out_w: u32,
out_h: u32,
) -> Option<Vec<u8>> {
if out_w == 0 || out_h == 0 || frame_w == 0 || frame_h == 0 {
return None;
}
let expected = frame_w as usize * frame_h as usize * 3;
if frame.len() < expected {
return None;
}
let fw = frame_w as f32;
let fh = frame_h as f32;
let x0 = bbox.x.clamp(0.0, fw);
let y0 = bbox.y.clamp(0.0, fh);
let x1 = (bbox.x + bbox.width).clamp(0.0, fw);
let y1 = (bbox.y + bbox.height).clamp(0.0, fh);
let bw = x1 - x0;
let bh = y1 - y0;
if bw < 1.0 || bh < 1.0 {
return None;
}
let mut out = vec![0u8; out_w as usize * out_h as usize * 3];
let stride = frame_w as usize * 3;
let max_x = frame_w as i64 - 1;
let max_y = frame_h as i64 - 1;
for oy in 0..out_h {
let sy = y0 + (oy as f32 + 0.5) * bh / out_h as f32 - 0.5;
let sy = sy.clamp(0.0, fh - 1.0);
let y_lo = sy.floor() as i64;
let wy = sy - y_lo as f32;
let y_lo = y_lo.clamp(0, max_y) as usize;
let y_hi = (y_lo as i64 + 1).clamp(0, max_y) as usize;
for ox in 0..out_w {
let sx = x0 + (ox as f32 + 0.5) * bw / out_w as f32 - 0.5;
let sx = sx.clamp(0.0, fw - 1.0);
let x_lo = sx.floor() as i64;
let wx = sx - x_lo as f32;
let x_lo = x_lo.clamp(0, max_x) as usize;
let x_hi = (x_lo as i64 + 1).clamp(0, max_x) as usize;
let o = (oy as usize * out_w as usize + ox as usize) * 3;
for c in 0..3usize {
let p00 = frame[y_lo * stride + x_lo * 3 + c] as f32;
let p01 = frame[y_lo * stride + x_hi * 3 + c] as f32;
let p10 = frame[y_hi * stride + x_lo * 3 + c] as f32;
let p11 = frame[y_hi * stride + x_hi * 3 + c] as f32;
let top = p00 * (1.0 - wx) + p01 * wx;
let bot = p10 * (1.0 - wx) + p11 * wx;
out[o + c] = (top * (1.0 - wy) + bot * wy).round().clamp(0.0, 255.0) as u8;
}
}
}
Some(out)
}
pub fn preprocess_osnet_nchw(crop: &[u8], w: u32, h: u32) -> Option<Vec<f32>> {
let plane = w as usize * h as usize;
if crop.len() < plane * 3 {
return None;
}
let mut out = vec![0f32; 3 * plane];
for i in 0..plane {
for c in 0..3usize {
let v = crop[i * 3 + c] as f32;
out[c * plane + i] = (v - OSNET_MEAN[c]) / OSNET_STD[c];
}
}
Some(out)
}
pub fn l2_normalize(v: &mut [f32]) {
let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for x in v.iter_mut() {
*x /= norm;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn solid_frame(w: u32, h: u32, rgb: [u8; 3]) -> Vec<u8> {
let mut f = vec![0u8; (w * h * 3) as usize];
let (pixels, _) = f.as_chunks_mut::<3>();
for px in pixels {
*px = rgb;
}
f
}
#[test]
fn crop_of_a_solid_frame_is_that_solid_colour() {
let frame = solid_frame(64, 64, [10, 20, 30]);
let bbox = BoundingBox {
x: 8.0,
y: 8.0,
width: 32.0,
height: 32.0,
};
let crop = crop_resize_rgb24(&frame, 64, 64, &bbox, 256, 128).expect("crop");
assert_eq!(crop.len(), 256 * 128 * 3);
let (pixels, rest) = crop.as_chunks::<3>();
assert!(rest.is_empty(), "an RGB24 crop is whole pixels");
for px in pixels {
assert_eq!(px, &[10, 20, 30]);
}
}
#[test]
fn degenerate_or_out_of_frame_box_is_none() {
let frame = solid_frame(32, 32, [0, 0, 0]);
let zero = BoundingBox {
x: 5.0,
y: 5.0,
width: 0.0,
height: 10.0,
};
assert!(crop_resize_rgb24(&frame, 32, 32, &zero, 256, 128).is_none());
let off = BoundingBox {
x: 100.0,
y: 100.0,
width: 10.0,
height: 10.0,
};
assert!(crop_resize_rgb24(&frame, 32, 32, &off, 256, 128).is_none());
}
#[test]
fn osnet_preprocess_applies_imagenet_norm_in_nchw() {
let crop = vec![255u8, 255, 255];
let t = preprocess_osnet_nchw(&crop, 1, 1).expect("preprocess");
assert_eq!(t.len(), 3);
for c in 0..3 {
let expect = (255.0 - OSNET_MEAN[c]) / OSNET_STD[c];
assert!(
(t[c] - expect).abs() < 1e-4,
"channel {c}: {} vs {expect}",
t[c]
);
}
}
#[test]
fn l2_normalize_makes_unit_norm() {
let mut v = vec![3.0f32, 4.0];
l2_normalize(&mut v);
assert!((v[0] - 0.6).abs() < 1e-6);
assert!((v[1] - 0.8).abs() < 1e-6);
let mut z = vec![0.0f32, 0.0];
l2_normalize(&mut z);
assert_eq!(z, vec![0.0, 0.0]);
}
}