237 lines
6.9 KiB
Python
237 lines
6.9 KiB
Python
"""
|
|
Fallback annotation utilities for enhanced video controller.
|
|
This module provides basic implementation of the annotation functions
|
|
required by the enhanced video controller, in case the regular module fails to import.
|
|
"""
|
|
|
|
import sys
|
|
import cv2
|
|
import numpy as np
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Dict, List, Tuple, Any, Optional
|
|
try:
|
|
from PySide6.QtGui import QImage, QPixmap
|
|
from PySide6.QtCore import Qt
|
|
QT_AVAILABLE = True
|
|
except ImportError:
|
|
print("⚠️ PySide6 not available, some functions will be limited")
|
|
QT_AVAILABLE = False
|
|
|
|
# Color mapping for traffic-related classes
|
|
COLORS = {
|
|
'person': (255, 165, 0), # Orange
|
|
'bicycle': (255, 0, 255), # Magenta
|
|
'car': (0, 255, 0), # Green
|
|
'motorcycle': (255, 255, 0), # Cyan
|
|
'bus': (0, 0, 255), # Red
|
|
'truck': (0, 128, 255), # Orange-Blue
|
|
'traffic light': (0, 165, 255), # Orange
|
|
'stop sign': (0, 0, 139), # Dark Red
|
|
'parking meter': (128, 0, 128), # Purple
|
|
'default': (0, 255, 255) # Yellow as default
|
|
}
|
|
|
|
def enhanced_draw_detections(frame: np.ndarray, detections: List[Dict],
|
|
show_confidence: bool = True,
|
|
show_labels: bool = True) -> np.ndarray:
|
|
"""
|
|
Draw detections on frame with enhanced visuals.
|
|
|
|
Args:
|
|
frame: Input video frame
|
|
detections: List of detection dictionaries
|
|
show_confidence: Whether to show confidence values
|
|
show_labels: Whether to show class labels
|
|
|
|
Returns:
|
|
Frame with detections drawn
|
|
"""
|
|
if not detections:
|
|
return frame
|
|
|
|
# Create a copy of the frame
|
|
result = frame.copy()
|
|
|
|
# Process each detection
|
|
for det in detections:
|
|
if 'bbox' not in det:
|
|
continue
|
|
|
|
# Get bounding box
|
|
x1, y1, x2, y2 = map(int, det['bbox'])
|
|
|
|
# Get class name and confidence
|
|
class_name = det.get('class_name', 'unknown')
|
|
conf = det.get('confidence', 0)
|
|
|
|
# Get color for this class
|
|
color = COLORS.get(class_name.lower(), COLORS['default'])
|
|
|
|
# Draw bounding box
|
|
cv2.rectangle(result, (x1, y1), (x2, y2), color, 2)
|
|
|
|
# Prepare label text
|
|
label = ""
|
|
if show_labels:
|
|
label = class_name
|
|
if show_confidence:
|
|
label = f"{class_name} ({conf:.2f})"
|
|
elif 'track_id' in det:
|
|
label = f"{class_name} #{det['track_id']}"
|
|
elif show_confidence:
|
|
label = f"{conf:.2f}"
|
|
elif 'track_id' in det:
|
|
label = f"#{det['track_id']}"
|
|
|
|
# Draw label if we have one
|
|
if label:
|
|
# Calculate label size and position
|
|
(label_width, label_height), baseline = cv2.getTextSize(
|
|
label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
|
|
|
# Draw label background
|
|
cv2.rectangle(
|
|
result,
|
|
(x1, y1),
|
|
(x1 + label_width, y1 - label_height - baseline - 5),
|
|
color,
|
|
-1
|
|
)
|
|
|
|
# Draw label text
|
|
cv2.putText(
|
|
result,
|
|
label,
|
|
(x1, y1 - baseline - 5),
|
|
cv2.FONT_HERSHEY_SIMPLEX,
|
|
0.5,
|
|
(255, 255, 255),
|
|
1
|
|
)
|
|
|
|
return result
|
|
|
|
def draw_performance_overlay(frame: np.ndarray, metrics: Dict[str, Any]) -> np.ndarray:
|
|
"""
|
|
Draw performance metrics overlay on frame.
|
|
|
|
Args:
|
|
frame: Input video frame
|
|
metrics: Dictionary of performance metrics
|
|
|
|
Returns:
|
|
Frame with performance overlay
|
|
"""
|
|
if not metrics:
|
|
return frame
|
|
|
|
# Create a copy of the frame
|
|
result = frame.copy()
|
|
|
|
# Get frame dimensions
|
|
height, width = frame.shape[:2]
|
|
|
|
# Extract metrics
|
|
fps = metrics.get('fps', 0)
|
|
inference_fps = metrics.get('inference_fps', 0)
|
|
inference_time = metrics.get('inference_time', 0)
|
|
|
|
# Format text
|
|
text_lines = [
|
|
f"FPS: {fps:.1f}",
|
|
f"Inference: {inference_time:.1f}ms ({inference_fps:.1f} FPS)",
|
|
]
|
|
|
|
# Draw semi-transparent background
|
|
overlay = result.copy()
|
|
bg_height = 30 + (len(text_lines) - 1) * 20
|
|
cv2.rectangle(overlay, (10, 10), (250, 10 + bg_height), (0, 0, 0), -1)
|
|
cv2.addWeighted(overlay, 0.7, result, 0.3, 0, result)
|
|
|
|
# Draw text lines
|
|
y = 30
|
|
for text in text_lines:
|
|
cv2.putText(
|
|
result,
|
|
text,
|
|
(20, y),
|
|
cv2.FONT_HERSHEY_SIMPLEX,
|
|
0.6,
|
|
(255, 255, 255),
|
|
1,
|
|
cv2.LINE_AA
|
|
)
|
|
y += 20
|
|
|
|
return result
|
|
|
|
# Qt-specific helper functions
|
|
def enhanced_cv_to_qimage(cv_img: np.ndarray) -> Optional['QImage']:
|
|
"""
|
|
Convert OpenCV image to QImage with enhanced handling.
|
|
|
|
Args:
|
|
cv_img: OpenCV image (numpy array)
|
|
|
|
Returns:
|
|
QImage or None if conversion failed
|
|
"""
|
|
if not QT_AVAILABLE:
|
|
print("⚠️ Cannot convert to QImage: PySide6 not available")
|
|
return None
|
|
|
|
if cv_img is None or cv_img.size == 0:
|
|
print("⚠️ Cannot convert empty image to QImage")
|
|
return None
|
|
|
|
try:
|
|
height, width, channels = cv_img.shape
|
|
|
|
# Ensure we're dealing with RGB or RGBA
|
|
if channels == 3:
|
|
# OpenCV uses BGR, we need RGB for QImage
|
|
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
|
|
format = QImage.Format_RGB888
|
|
elif channels == 4:
|
|
# OpenCV uses BGRA, we need RGBA for QImage
|
|
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGRA2RGBA)
|
|
format = QImage.Format_RGBA8888
|
|
else:
|
|
print(f"⚠️ Unsupported image format with {channels} channels")
|
|
return None
|
|
|
|
# Create QImage from numpy array
|
|
steps = width * channels
|
|
return QImage(cv_img.data, width, height, steps, format)
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error converting image to QImage: {e}")
|
|
return None
|
|
|
|
def enhanced_cv_to_pixmap(cv_img: np.ndarray) -> Optional['QPixmap']:
|
|
"""
|
|
Convert OpenCV image to QPixmap with enhanced handling.
|
|
|
|
Args:
|
|
cv_img: OpenCV image (numpy array)
|
|
|
|
Returns:
|
|
QPixmap or None if conversion failed
|
|
"""
|
|
if not QT_AVAILABLE:
|
|
print("⚠️ Cannot convert to QPixmap: PySide6 not available")
|
|
return None
|
|
|
|
# Convert to QImage first
|
|
qimg = enhanced_cv_to_qimage(cv_img)
|
|
if qimg is None:
|
|
return None
|
|
|
|
# Convert QImage to QPixmap
|
|
try:
|
|
return QPixmap.fromImage(qimg)
|
|
except Exception as e:
|
|
print(f"❌ Error converting QImage to QPixmap: {e}")
|
|
return None
|