-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathyolo_vis.rs
More file actions
168 lines (150 loc) · 5.72 KB
/
Copy pathyolo_vis.rs
File metadata and controls
168 lines (150 loc) · 5.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Visualize YOLO detections from all backends side-by-side.
//!
//! Usage:
//! cargo run --example yolo_vis --features metal-backend -- <model.onnx> <image> <out_dir>
#[cfg(not(all(target_os = "macos", feature = "metal-backend")))]
fn main() {
eprintln!("yolo_vis requires macOS with the metal-backend feature");
}
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
use yscv_detect::{
Detection, coco_labels, decode_yolov8_output, decode_yolov11_output, letterbox_preprocess,
yolov8_coco_config,
};
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
use yscv_imgproc::{DrawDetection, draw_detections, draw_text_scaled, imread, imwrite};
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
use yscv_onnx::load_onnx_model_from_file;
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
fn decode(
output: &yscv_tensor::Tensor,
config: &yscv_detect::YoloConfig,
orig_w: usize,
orig_h: usize,
) -> Vec<Detection> {
let out_shape = output.shape();
if out_shape.len() == 3 && out_shape[1] < out_shape[2] {
decode_yolov8_output(output, config, orig_w, orig_h)
} else {
decode_yolov11_output(output, config, orig_w, orig_h)
}
}
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
fn to_draw(dets: &[Detection]) -> Vec<DrawDetection> {
dets.iter()
.map(|d| DrawDetection {
x: d.bbox.x1 as usize,
y: d.bbox.y1 as usize,
width: (d.bbox.x2 - d.bbox.x1).max(0.0) as usize,
height: (d.bbox.y2 - d.bbox.y1).max(0.0) as usize,
score: d.score,
class_id: d.class_id,
})
.collect()
}
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
fn annotate_and_save(
img: &yscv_tensor::Tensor,
dets: &[Detection],
backend_name: &str,
out_path: &str,
) {
let labels_vec = coco_labels();
let labels: Vec<&str> = labels_vec.iter().map(|s| s.as_str()).collect();
let mut img_copy = img.clone();
let draw_dets = to_draw(dets);
draw_detections(&mut img_copy, &draw_dets, &labels).unwrap();
// Draw backend label at top
let label = format!("{} ({} objects)", backend_name, dets.len());
draw_text_scaled(&mut img_copy, &label, 10, 10, 2, [1.0, 1.0, 1.0]).unwrap();
imwrite(out_path, &img_copy).unwrap();
println!(" Saved: {out_path}");
}
#[cfg(all(target_os = "macos", feature = "metal-backend"))]
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 4 {
eprintln!("Usage: yolo_vis <model.onnx> <image> <out_dir>");
std::process::exit(1);
}
let model_path = &args[1];
let image_path = &args[2];
let out_dir = &args[3];
// Load model
println!("Loading model: {model_path}");
let model = load_onnx_model_from_file(model_path).expect("Failed to load model");
// Load image
println!("Loading image: {image_path}");
let img = imread(image_path).expect("Failed to load image");
let shape = img.shape().to_vec();
let (orig_h, orig_w) = (shape[0], shape[1]);
println!(" Size: {orig_w}x{orig_h}");
let config = yolov8_coco_config();
let (letterboxed, _scale, _pad_x, _pad_y) = letterbox_preprocess(&img, config.input_size);
let sz = config.input_size;
// HWC → NCHW
let hwc_data = letterboxed.data();
let mut nchw = vec![0.0f32; 3 * sz * sz];
for y in 0..sz {
for x in 0..sz {
let src = (y * sz + x) * 3;
for c in 0..3 {
nchw[c * sz * sz + y * sz + x] = hwc_data[src + c];
}
}
}
let input_tensor =
yscv_tensor::Tensor::from_vec(vec![1, 3, sz, sz], nchw.clone()).expect("tensor");
// CPU
println!("\n=== CPU ===");
let cpu_dets = {
let mut inputs = std::collections::HashMap::new();
inputs.insert("images".to_string(), input_tensor.clone());
let outputs = yscv_onnx::run_onnx_model(&model, inputs).expect("CPU failed");
let output = outputs.values().next().expect("no output");
decode(output, &config, orig_w, orig_h)
};
println!(" {} detections", cpu_dets.len());
annotate_and_save(&img, &cpu_dets, "CPU", &format!("{out_dir}/cpu.png"));
// Metal per-op
#[cfg(feature = "metal-backend")]
{
println!("\n=== Metal per-op ===");
let plan = yscv_onnx::compile_metal_plan(&model, "images", &input_tensor)
.expect("Metal compile failed");
let result = yscv_onnx::run_metal_plan(&plan, &nchw).expect("Metal run failed");
let output = result.values().next().expect("no output");
let metal_dets = decode(output, &config, orig_w, orig_h);
println!(" {} detections", metal_dets.len());
annotate_and_save(
&img,
&metal_dets,
"Metal per-op",
&format!("{out_dir}/metal.png"),
);
}
// MPSGraph
#[cfg(feature = "metal-backend")]
{
println!("\n=== MPSGraph ===");
match yscv_onnx::compile_mpsgraph_plan(&model, &[("images", &input_tensor)]) {
Ok(plan) => {
let result = yscv_onnx::run_mpsgraph_plan(&plan, &[("images", nchw.as_slice())])
.expect("MPSGraph run failed");
let output = result.values().next().expect("no output");
let mps_dets = decode(output, &config, orig_w, orig_h);
println!(" {} detections", mps_dets.len());
annotate_and_save(
&img,
&mps_dets,
"MPSGraph",
&format!("{out_dir}/mpsgraph.png"),
);
}
Err(e) => {
eprintln!(" MPSGraph compile failed: {e}");
}
}
}
println!("\nDone!");
}