Skip to content

Commit 56e426f

Browse files
authored
Object tracking support (#31)
1 parent 5fb601f commit 56e426f

File tree

6 files changed

+238
-25
lines changed

6 files changed

+238
-25
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ regex = "1.10.3"
3232
serde = "1.0"
3333
serde_json = "1.0"
3434
tokio = { version = "1.0", features = ["rt-multi-thread"] }
35-
edge-impulse-runner = { git = "https://github.com/edgeimpulse/edge-impulse-runner-rs.git", rev = "0ddef6a", default-features = false }
35+
edge-impulse-runner = { git = "https://github.com/edgeimpulse/edge-impulse-runner-rs.git", rev = "c357286", default-features = false }
3636
tempfile = "3.10"
3737

3838
[dev-dependencies]

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,41 @@ The plugin exposes results and ingestion status through standardized mechanisms:
4747
"x": 24,
4848
"y": 145,
4949
"width": 352,
50-
"height": 239
50+
"height": 239,
51+
"object_id": 1
5152
}
5253
]
5354
}
5455
}
5556
```
5657
- **Video Metadata:**
5758
- Each detected object is attached as a `VideoRegionOfInterestMeta` with bounding box coordinates, label, and confidence.
59+
- When object tracking is enabled, the `object_id` field is also included to track objects across frames.
60+
61+
#### 1.1. Object Tracking
62+
- **Bus Message Example:**
63+
```json
64+
{
65+
"timestamp": 1234567890,
66+
"type": "object-tracking",
67+
"result": {
68+
"object_tracking": [
69+
{
70+
"label": "person",
71+
"value": 0.95,
72+
"x": 24,
73+
"y": 145,
74+
"width": 352,
75+
"height": 239,
76+
"object_id": 1
77+
}
78+
]
79+
}
80+
}
81+
```
82+
- **Video Metadata:**
83+
- Object tracking results are attached as `VideoRegionOfInterestMeta` with bounding box coordinates, label, confidence, and object_id.
84+
- The `object_id` field allows tracking the same object across multiple frames.
5885

5986
#### 2. Classification
6087
- **Bus Message Example:**
@@ -530,7 +557,7 @@ Video overlay element that visualizes inference results by drawing bounding boxe
530557
Element Details:
531558
- Long name: Edge Impulse Overlay
532559
- Class: Filter/Effect/Video
533-
- Description: Draws bounding boxes on video frames based on ROI metadata
560+
- Description: Draws bounding boxes on video frames based on ROI metadata, including object tracking IDs
534561

535562
Pad Templates:
536563
- Sink/Source pads (Always available):
@@ -543,7 +570,7 @@ Pad Templates:
543570

544571
Key features:
545572
- Draws bounding boxes for object detection and visual anomaly detection results (from VideoRegionOfInterestMeta)
546-
- Displays class labels with confidence scores
573+
- Displays class labels with confidence scores and object tracking IDs when available
547574
- Supports wide range of video formats
548575
- Automatic text sizing: Calculates optimal font size based on frame dimensions and bounding box sizes
549576
- Text scale control: Use `text-scale-ratio` property to fine-tune text size (0.1x to 5.0x scaling)

examples/video_inference.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,12 @@ fn create_pipeline(args: &VideoClassifyParams) -> Result<gst::Pipeline, Box<dyn
373373
.build()
374374
.expect("Could not create edgeimpulseoverlay element.");
375375

376+
println!("🎨 Overlay settings:");
377+
println!(" 📏 Stroke width: {}", args.stroke_width);
378+
println!(" 🎨 Text color: {}", args.text_color);
379+
println!(" 🎨 Background color: {}", args.background_color);
380+
println!(" 📏 Text scale ratio: {}", args.text_scale_ratio);
381+
376382
let videoconvert2 = gst::ElementFactory::make("videoconvert")
377383
.property("n-threads", 4u32)
378384
.build()
@@ -480,12 +486,19 @@ fn example_main() -> Result<(), Box<dyn Error>> {
480486
println!(" 📋 Project Name: {}", metadata.project_name);
481487
println!(" 👤 Project Owner: {}", metadata.project_owner);
482488
println!(" 🏷️ Deploy Version: {}", metadata.deploy_version);
489+
println!(
490+
" 🎯 Has Object Tracking: {}",
491+
metadata.has_object_tracking
492+
);
483493

484494
if let Ok(result) = structure.get::<String>("result") {
485495
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&result) {
486496
if let Some(boxes) = json["bounding_boxes"].as_array() {
487497
println!(" 🎯 Object detection model");
488498
println!(" 📦 Bounding boxes: {} objects", boxes.len());
499+
if !metadata.has_object_tracking {
500+
println!(" ⚠️ Object tracking not enabled in this model");
501+
}
489502
} else if json.get("anomaly").is_some() {
490503
println!(" 🔍 Anomaly detection model");
491504
} else if let Some(classification) = json["classification"].as_object()
@@ -548,9 +561,40 @@ fn example_main() -> Result<(), Box<dyn Error>> {
548561
if let (Some(label), Some(value)) =
549562
(bbox["label"].as_str(), bbox["value"].as_f64())
550563
{
564+
let object_id_info =
565+
if let Some(object_id) = bbox["object_id"].as_u64() {
566+
format!(" (ID: {})", object_id)
567+
} else {
568+
String::new()
569+
};
570+
571+
println!(
572+
" {}{}: {:.1}% (x: {}, y: {}, w: {}, h: {})",
573+
label,
574+
object_id_info,
575+
value * 100.0,
576+
bbox["x"].as_f64().unwrap_or(0.0),
577+
bbox["y"].as_f64().unwrap_or(0.0),
578+
bbox["width"].as_f64().unwrap_or(0.0),
579+
bbox["height"].as_f64().unwrap_or(0.0)
580+
);
581+
}
582+
}
583+
}
584+
// Handle object tracking (separate field)
585+
else if let Some(tracking_boxes) = json["object_tracking"].as_array()
586+
{
587+
println!("Object tracking results:");
588+
for bbox in tracking_boxes {
589+
if let (Some(label), Some(value), Some(object_id)) = (
590+
bbox["label"].as_str(),
591+
bbox["value"].as_f64(),
592+
bbox["object_id"].as_u64(),
593+
) {
551594
println!(
552-
" {}: {:.1}% (x: {}, y: {}, w: {}, h: {})",
595+
" {} (ID: {}): {:.1}% (x: {}, y: {}, w: {}, h: {})",
553596
label,
597+
object_id,
554598
value * 100.0,
555599
bbox["x"].as_f64().unwrap_or(0.0),
556600
bbox["y"].as_f64().unwrap_or(0.0),

src/overlay/imp.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -486,18 +486,21 @@ impl VideoFilterImpl for EdgeImpulseOverlay {
486486
let width = meta.w as i32;
487487
let height = meta.h as i32;
488488

489-
// Get label and confidence from parameters
489+
// Get label, confidence, and object_id from parameters
490490
let params_result = roi.params().find_map(|param| {
491491
if param.name() != "detection" {
492492
return None;
493493
}
494494

495495
let label = param.get::<String>("label").ok()?;
496496
let confidence = param.get::<f64>("confidence").ok()?;
497-
Some((label, confidence))
497+
let object_id = param.get::<u64>("object_id").ok();
498+
Some((label, confidence, object_id))
498499
});
499500

500-
params_result.map(|(label, confidence)| (x, y, width, height, label, confidence))
501+
params_result.map(|(label, confidence, object_id)| {
502+
(x, y, width, height, label, confidence, object_id)
503+
})
501504
})
502505
.collect();
503506

@@ -640,26 +643,52 @@ impl VideoFilterImpl for EdgeImpulseOverlay {
640643
);
641644

642645
// Process each ROI
643-
for (x, y, width, height, label, confidence) in rois {
646+
for (x, y, width, height, label, confidence, object_id) in rois {
647+
let display_label = if let Some(id) = object_id {
648+
format!("{} (ID: {})", label, id)
649+
} else {
650+
label.clone()
651+
};
652+
644653
gst::debug!(
645654
CAT,
646655
obj = self.obj(),
647656
"Rendering ROI: {} ({:.1}%) at ({}, {}) {}x{}",
648-
label,
657+
display_label,
649658
confidence * 100.0,
650659
x,
651660
y,
652661
width,
653662
height
654663
);
655664

656-
// Get or assign color for this label
657-
let color = if let Some(color) = label_colors.get(&label) {
665+
// Get or assign color for this object (prefer object_id over label)
666+
let color_key = if let Some(id) = object_id {
667+
format!("object_{}", id)
668+
} else {
669+
label.clone()
670+
};
671+
672+
let color = if let Some(color) = label_colors.get(&color_key) {
658673
*color
659674
} else {
660675
let next_color = COLORS.get(label_colors.len() % COLORS.len()).unwrap();
661676
let mut label_colors = self.label_colors.lock().unwrap();
662-
label_colors.insert(label.clone(), *next_color);
677+
label_colors.insert(color_key.clone(), *next_color);
678+
679+
gst::debug!(
680+
CAT,
681+
obj = self.obj(),
682+
"Assigned color {:?} to {} (key: {})",
683+
next_color,
684+
if object_id.is_some() {
685+
format!("object ID {}", object_id.unwrap())
686+
} else {
687+
format!("label '{}'", label)
688+
},
689+
color_key
690+
);
691+
663692
*next_color
664693
};
665694

@@ -670,7 +699,7 @@ impl VideoFilterImpl for EdgeImpulseOverlay {
670699
y,
671700
width,
672701
height,
673-
roi_type: label.clone(),
702+
roi_type: display_label.clone(),
674703
color,
675704
};
676705

@@ -682,7 +711,7 @@ impl VideoFilterImpl for EdgeImpulseOverlay {
682711

683712
// Draw label if enabled
684713
if settings.show_labels {
685-
let text = format!("{} {:.1}%", label, confidence * 100.0);
714+
let text = format!("{} {:.1}%", display_label, confidence * 100.0);
686715
let text_x = x + 2;
687716
// Position the text just slightly below the top of the bounding box
688717
let text_y = y + 2;

0 commit comments

Comments
 (0)