mirror of
https://github.com/mii443/yolov8_obb_gui.git
synced 2025-12-03 11:18:23 +00:00
first
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
6366
Cargo.lock
generated
Normal file
6366
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "yolov8_obb_gui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
image = "0.25"
|
||||||
|
usls = { git = "https://github.com/mii443/usls.git", features = [
|
||||||
|
"ort-download-binaries",
|
||||||
|
"directml",
|
||||||
|
] }
|
||||||
|
windows-capture = "1.4.4"
|
||||||
|
eframe = "0.29"
|
||||||
|
egui = "0.29"
|
||||||
129
src/capture.rs
Normal file
129
src/capture.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Instant;
|
||||||
|
use usls::{Annotator, YOLO};
|
||||||
|
use windows_capture::capture::{Context, GraphicsCaptureApiHandler};
|
||||||
|
use windows_capture::settings::ColorFormat;
|
||||||
|
|
||||||
|
use crate::detection::DetectionResult;
|
||||||
|
|
||||||
|
pub struct WindowCapture {
|
||||||
|
model: YOLO,
|
||||||
|
annotator: Annotator,
|
||||||
|
frame_count: u64,
|
||||||
|
detection_result: Arc<Mutex<DetectionResult>>,
|
||||||
|
rgb_buffer: Vec<u8>,
|
||||||
|
shutdown_signal: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicsCaptureApiHandler for WindowCapture {
|
||||||
|
type Flags = (
|
||||||
|
YOLO,
|
||||||
|
Annotator,
|
||||||
|
Arc<Mutex<DetectionResult>>,
|
||||||
|
Arc<AtomicBool>,
|
||||||
|
);
|
||||||
|
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
|
fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error> {
|
||||||
|
let (model, annotator, detection_result, shutdown_signal) = ctx.flags;
|
||||||
|
Ok(Self {
|
||||||
|
model,
|
||||||
|
annotator,
|
||||||
|
frame_count: 0,
|
||||||
|
detection_result,
|
||||||
|
rgb_buffer: Vec::with_capacity(1920 * 1080 * 3),
|
||||||
|
shutdown_signal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_frame_arrived(
|
||||||
|
&mut self,
|
||||||
|
frame: &mut windows_capture::frame::Frame,
|
||||||
|
capture_control: windows_capture::graphics_capture_api::InternalCaptureControl,
|
||||||
|
) -> std::result::Result<(), Self::Error> {
|
||||||
|
if self.shutdown_signal.load(Ordering::Relaxed) {
|
||||||
|
println!("Shutdown signal received, stopping capture...");
|
||||||
|
capture_control.stop();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.frame_count += 1;
|
||||||
|
|
||||||
|
let color_format = frame.color_format().clone();
|
||||||
|
let width = frame.width();
|
||||||
|
let height = frame.height();
|
||||||
|
let mut buffer = frame.buffer()?;
|
||||||
|
let raw_data = buffer.as_nopadding_buffer()?;
|
||||||
|
|
||||||
|
let required_size = (width * height * 3) as usize;
|
||||||
|
if self.rgb_buffer.capacity() < required_size {
|
||||||
|
self.rgb_buffer
|
||||||
|
.reserve(required_size - self.rgb_buffer.capacity());
|
||||||
|
}
|
||||||
|
self.rgb_buffer.clear();
|
||||||
|
|
||||||
|
match color_format {
|
||||||
|
ColorFormat::Rgba8 => {
|
||||||
|
self.rgb_buffer.extend(
|
||||||
|
raw_data
|
||||||
|
.chunks_exact(4)
|
||||||
|
.flat_map(|chunk| [chunk[0], chunk[1], chunk[2]]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ColorFormat::Bgra8 => {
|
||||||
|
self.rgb_buffer.extend(
|
||||||
|
raw_data
|
||||||
|
.chunks_exact(4)
|
||||||
|
.flat_map(|chunk| [chunk[2], chunk[1], chunk[0]]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
eprintln!("Unsupported color format: {:?}", color_format);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = usls::Image::from_u8s(&self.rgb_buffer, width, height)?;
|
||||||
|
|
||||||
|
let inference_start = Instant::now();
|
||||||
|
let ys = self.model.forward(&[image.clone()])?;
|
||||||
|
let inference_time = inference_start.elapsed();
|
||||||
|
|
||||||
|
let mut detection_count = 0;
|
||||||
|
let mut confidences = Vec::new();
|
||||||
|
|
||||||
|
for y in &ys {
|
||||||
|
if let Some(obboxes) = y.obbs() {
|
||||||
|
detection_count = obboxes.len();
|
||||||
|
for obbox in obboxes.iter() {
|
||||||
|
if let Some(conf) = obbox.confidence() {
|
||||||
|
confidences.push(conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let annotated_image = image.clone();
|
||||||
|
let annotated = self.annotator.annotate(&annotated_image, &ys[0])?;
|
||||||
|
let annotated_rgb_data = annotated.to_rgb8().into_raw();
|
||||||
|
|
||||||
|
if let Ok(mut result) = self.detection_result.lock() {
|
||||||
|
result.detection_count = detection_count;
|
||||||
|
result.confidences = confidences;
|
||||||
|
result.inference_time = inference_time;
|
||||||
|
result.annotated_image = Some(Arc::new(annotated_rgb_data));
|
||||||
|
result.image_width = width;
|
||||||
|
result.image_height = height;
|
||||||
|
result.frame_id = self.frame_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_closed(&mut self) -> std::result::Result<(), Self::Error> {
|
||||||
|
println!("Window capture closed, cleaning up resources...");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/detection.rs
Normal file
26
src/detection.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DetectionResult {
|
||||||
|
pub detection_count: usize,
|
||||||
|
pub confidences: Vec<f32>,
|
||||||
|
pub inference_time: std::time::Duration,
|
||||||
|
pub annotated_image: Option<Arc<Vec<u8>>>,
|
||||||
|
pub image_width: u32,
|
||||||
|
pub image_height: u32,
|
||||||
|
pub frame_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DetectionResult {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detection_count: 0,
|
||||||
|
confidences: Vec::new(),
|
||||||
|
inference_time: std::time::Duration::from_millis(0),
|
||||||
|
annotated_image: None,
|
||||||
|
image_width: 0,
|
||||||
|
image_height: 0,
|
||||||
|
frame_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/gui.rs
Normal file
90
src/gui.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
use eframe::egui;
|
||||||
|
use eframe::egui::{ColorImage, ImageData, TextureHandle, TextureOptions};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use crate::detection::DetectionResult;
|
||||||
|
|
||||||
|
pub struct DetectionApp {
|
||||||
|
detection_result: Arc<Mutex<DetectionResult>>,
|
||||||
|
texture: Option<TextureHandle>,
|
||||||
|
last_update_time: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DetectionApp {
|
||||||
|
pub fn new(detection_result: Arc<Mutex<DetectionResult>>) -> Self {
|
||||||
|
Self {
|
||||||
|
detection_result,
|
||||||
|
texture: None,
|
||||||
|
last_update_time: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for DetectionApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
self.last_update_time = now;
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.heading("YOLOv8 OBB GUI");
|
||||||
|
|
||||||
|
if let Ok(result) = self.detection_result.lock() {
|
||||||
|
ui.separator();
|
||||||
|
ui.label(format!("Detections: {}", result.detection_count));
|
||||||
|
ui.label(format!("Inference Time: {:.2?}", result.inference_time));
|
||||||
|
|
||||||
|
if let Some(ref image_data) = result.annotated_image {
|
||||||
|
if result.image_width > 0 && result.image_height > 0 {
|
||||||
|
let color_image = ColorImage::from_rgb(
|
||||||
|
[result.image_width as usize, result.image_height as usize],
|
||||||
|
&image_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(ref mut texture) = self.texture {
|
||||||
|
texture.set(
|
||||||
|
ImageData::Color(Arc::new(color_image)),
|
||||||
|
TextureOptions::default(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.texture = Some(ctx.load_texture(
|
||||||
|
"annotated_image",
|
||||||
|
ImageData::Color(Arc::new(color_image)),
|
||||||
|
TextureOptions::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref texture) = self.texture {
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Detection Result:");
|
||||||
|
|
||||||
|
let max_width = ui.available_width();
|
||||||
|
let max_height = 600.0;
|
||||||
|
let scale = (max_width / result.image_width as f32)
|
||||||
|
.min(max_height / result.image_height as f32)
|
||||||
|
.min(1.0);
|
||||||
|
|
||||||
|
let display_width = result.image_width as f32 * scale;
|
||||||
|
let display_height = result.image_height as f32 * scale;
|
||||||
|
|
||||||
|
ui.image((texture.id(), egui::vec2(display_width, display_height)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.confidences.is_empty() {
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Confidences:");
|
||||||
|
for (i, conf) in result.confidences.iter().enumerate() {
|
||||||
|
ui.label(format!(" Detection {}: {:.2}%", i + 1, conf * 100.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.label("Loading data...");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/main.rs
Normal file
121
src/main.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Instant;
|
||||||
|
use usls::{
|
||||||
|
Annotator, Config, Device, SKELETON_COCO_19, SKELETON_COLOR_COCO_19, Scale, Style, Task,
|
||||||
|
Version, YOLO,
|
||||||
|
};
|
||||||
|
use windows_capture::capture::GraphicsCaptureApiHandler;
|
||||||
|
use windows_capture::settings::{ColorFormat, CursorCaptureSettings, DrawBorderSettings, Settings};
|
||||||
|
use windows_capture::window::Window;
|
||||||
|
|
||||||
|
mod capture;
|
||||||
|
mod detection;
|
||||||
|
mod gui;
|
||||||
|
|
||||||
|
use capture::WindowCapture;
|
||||||
|
use detection::DetectionResult;
|
||||||
|
use gui::DetectionApp;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let config = Config::yolo()
|
||||||
|
.with_model_file("models/kemomimi.onnx")
|
||||||
|
.with_task(Task::OrientedObjectDetection)
|
||||||
|
.with_version(Version::new(8, 0))
|
||||||
|
.with_scale(Scale::N)
|
||||||
|
.with_model_device(Device::DirectMl(0))
|
||||||
|
.with_class_confs(&[0.2, 0.15])
|
||||||
|
.with_keypoint_confs(&[0.5])
|
||||||
|
.with_topk(5)
|
||||||
|
.with_model_num_dry_run(0);
|
||||||
|
|
||||||
|
println!("Initializing model...");
|
||||||
|
let init_start = Instant::now();
|
||||||
|
let model = YOLO::new(config.commit()?)?;
|
||||||
|
let init_time = init_start.elapsed();
|
||||||
|
println!("Model initialized successfully! (Time: {:.2?})", init_time);
|
||||||
|
|
||||||
|
let annotator = Annotator::default()
|
||||||
|
.with_obb_style(Style::obb().with_draw_fill(false))
|
||||||
|
.with_hbb_style(
|
||||||
|
Style::hbb()
|
||||||
|
.with_draw_fill(false)
|
||||||
|
.with_palette(&usls::Color::palette_coco_80()),
|
||||||
|
)
|
||||||
|
.with_keypoint_style(
|
||||||
|
Style::keypoint()
|
||||||
|
.with_skeleton((SKELETON_COCO_19, SKELETON_COLOR_COCO_19).into())
|
||||||
|
.show_confidence(false)
|
||||||
|
.show_id(true)
|
||||||
|
.show_name(false),
|
||||||
|
)
|
||||||
|
.with_mask_style(Style::mask().with_draw_mask_polygon_largest(true));
|
||||||
|
|
||||||
|
let detection_result = Arc::new(Mutex::new(DetectionResult::default()));
|
||||||
|
let detection_result_clone = detection_result.clone();
|
||||||
|
|
||||||
|
let shutdown_signal = Arc::new(AtomicBool::new(false));
|
||||||
|
let shutdown_signal_clone = shutdown_signal.clone();
|
||||||
|
|
||||||
|
let capture_handle = thread::spawn(move || -> Result<()> {
|
||||||
|
let window = Window::from_name("VRChat")?;
|
||||||
|
let settings = Settings::new(
|
||||||
|
window,
|
||||||
|
CursorCaptureSettings::WithoutCursor,
|
||||||
|
DrawBorderSettings::WithoutBorder,
|
||||||
|
ColorFormat::Rgba8,
|
||||||
|
(
|
||||||
|
model,
|
||||||
|
annotator,
|
||||||
|
detection_result_clone,
|
||||||
|
shutdown_signal_clone,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("Starting window capture...");
|
||||||
|
WindowCapture::start(settings)?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: eframe::egui::ViewportBuilder::default()
|
||||||
|
.with_inner_size([1200.0, 800.0])
|
||||||
|
.with_title("YOLOv8 OBB GUI"),
|
||||||
|
vsync: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Starting GUI application...");
|
||||||
|
|
||||||
|
match eframe::run_native(
|
||||||
|
"YOLOv8 OBB GUI",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Ok(Box::new(DetectionApp::new(detection_result)))),
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("GUI application closed");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("An error occurred in the GUI application: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Application closing, initiating shutdown sequence...");
|
||||||
|
|
||||||
|
shutdown_signal.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
println!("Waiting for capture thread to finish...");
|
||||||
|
match capture_handle.join() {
|
||||||
|
Ok(result) => match result {
|
||||||
|
Ok(_) => println!("Capture thread finished successfully"),
|
||||||
|
Err(e) => eprintln!("Capture thread finished with error: {}", e),
|
||||||
|
},
|
||||||
|
Err(e) => eprintln!("Failed to join capture thread: {:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Shutdown sequence completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user