Clean push: Removed heavy files & added only latest snapshot
This commit is contained in:
803
violation_openvino.py
Normal file
803
violation_openvino.py
Normal file
@@ -0,0 +1,803 @@
|
||||
# Violation detection logic for traffic monitoring
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import time
|
||||
import math
|
||||
from typing import Dict, List, Tuple, Optional, Any
|
||||
from collections import defaultdict, deque
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from red_light_violation_pipeline import RedLightViolationPipeline
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# COCO dataset class names
|
||||
traffic_class_names = {
|
||||
0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus',
|
||||
6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant',
|
||||
11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat'
|
||||
}
|
||||
|
||||
# Extended to include all classes up to index 80 (full COCO dataset)
|
||||
# Initialize with None values
|
||||
for i in range(16, 80):
|
||||
traffic_class_names[i] = f"coco_class_{i}"
|
||||
|
||||
class OpenVINOViolationDetector:
|
||||
"""
|
||||
OpenVINO-optimized traffic violation detection system.
|
||||
|
||||
This implementation is designed for high-performance real-time processing
|
||||
with efficient vehicle tracking and violation detection algorithms.
|
||||
"""
|
||||
def __init__(self, frame_rate: float = 30.0, config: Dict = None):
|
||||
"""
|
||||
Initialize the violation detector.
|
||||
|
||||
Args:
|
||||
frame_rate: Video frame rate for speed calculations
|
||||
config: Configuration dictionary for violation detection parameters
|
||||
"""
|
||||
self.frame_rate = frame_rate
|
||||
|
||||
# Violation tracking
|
||||
self.violation_history = []
|
||||
self.vehicle_tracks = {} # Track ID -> track data
|
||||
self.next_track_id = 1
|
||||
|
||||
# Traffic state tracking
|
||||
self.traffic_light_states = {} # Position -> (color, timestamp)
|
||||
self.traffic_light_history = defaultdict(list) # For state change detection
|
||||
self.stop_sign_positions = []
|
||||
# Configuration parameters
|
||||
default_config = {
|
||||
'red_light_grace_period': 1.0, # seconds
|
||||
'stop_sign_stop_duration': 2.0, # seconds required at stop
|
||||
'speed_limit_default': 50, # km/h default speed limit
|
||||
'speed_tolerance': 5, # km/h tolerance over limit
|
||||
'min_track_length': 5, # minimum frames for reliable tracking
|
||||
'max_track_age': 60, # maximum frames to keep track without detection
|
||||
'tracking_max_distance': 100, # max pixels for track association
|
||||
'tracking_max_frames_lost': 30, # max frames before removing track
|
||||
'traffic_light_detection_zone': 100, # pixels around traffic light
|
||||
}
|
||||
|
||||
# Merge with provided config
|
||||
self.config = default_config.copy()
|
||||
if config:
|
||||
self.config.update(config)
|
||||
|
||||
# Performance tracking
|
||||
self.processing_times = deque(maxlen=100)
|
||||
self.detection_count = 0
|
||||
|
||||
# Statistics
|
||||
self.stats = {
|
||||
'total_violations': 0,
|
||||
'red_light_violations': 0,
|
||||
'stop_sign_violations': 0,
|
||||
'speed_violations': 0,
|
||||
'lane_violations': 0,
|
||||
'tracked_vehicles': 0
|
||||
}
|
||||
logger.info("✅ OpenVINO Violation Detector initialized")
|
||||
|
||||
def detect_violations(self, detections: List[Dict], frame: np.ndarray,
|
||||
frame_timestamp: float) -> List[Dict]:
|
||||
"""
|
||||
Detect traffic violations in the current frame.
|
||||
|
||||
Args:
|
||||
detections: List of detections from vehicle detector (can be NumPy array or list of dicts)
|
||||
frame: Current video frame
|
||||
frame_timestamp: Timestamp of the frame
|
||||
|
||||
Returns:
|
||||
List of violation dictionaries
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
violations = []
|
||||
|
||||
# Convert detections to proper format if needed
|
||||
if isinstance(detections, np.ndarray):
|
||||
print(f"🔄 Converting NumPy array detections ({detections.shape}) to dict format")
|
||||
detections = self._convert_detections_to_dicts(detections)
|
||||
print(f"✅ Converted to {len(detections)} detection dictionaries")
|
||||
|
||||
# Debug: Validate detections format
|
||||
if detections and len(detections) > 0:
|
||||
first_det = detections[0]
|
||||
if not isinstance(first_det, dict):
|
||||
print(f"❌ Warning: Expected dict, got {type(first_det)}")
|
||||
return []
|
||||
else:
|
||||
print(f"✅ Detections in correct dict format. Sample keys: {list(first_det.keys())}")
|
||||
|
||||
# Update vehicle tracking
|
||||
self._update_vehicle_tracking(detections, frame_timestamp)
|
||||
|
||||
# Update traffic state
|
||||
self._update_traffic_state(detections, frame_timestamp)
|
||||
|
||||
# Check for violations
|
||||
violations.extend(self._detect_red_light_violations(detections, frame, frame_timestamp))
|
||||
violations.extend(self._detect_stop_sign_violations(detections, frame, frame_timestamp))
|
||||
violations.extend(self._detect_speed_violations(detections, frame, frame_timestamp))
|
||||
violations.extend(self._detect_lane_violations(detections, frame, frame_timestamp))
|
||||
|
||||
# Update statistics
|
||||
self._update_statistics(violations)
|
||||
|
||||
# Add processing time
|
||||
processing_time = time.time() - start_time
|
||||
self.processing_times.append(processing_time)
|
||||
|
||||
# Add metadata to violations
|
||||
for violation in violations:
|
||||
violation['detection_time'] = datetime.now()
|
||||
violation['frame_timestamp'] = frame_timestamp
|
||||
violation['processing_time'] = processing_time
|
||||
|
||||
# Store in history
|
||||
self.violation_history.extend(violations)
|
||||
|
||||
return violations
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Violation detection failed: {e}")
|
||||
return []
|
||||
|
||||
def _convert_detections_to_dicts(self, detections_np: np.ndarray, class_names: List[str] = None) -> List[Dict]:
|
||||
"""
|
||||
Convert NumPy array detections to list of dictionaries format
|
||||
|
||||
Args:
|
||||
detections_np: NumPy array with shape [N, 6+] where each row is [x1, y1, x2, y2, confidence, class_id, ...]
|
||||
class_names: List of class names, defaults to COCO classes
|
||||
|
||||
Returns:
|
||||
List of detection dictionaries
|
||||
"""
|
||||
if class_names is None:
|
||||
class_names = traffic_class_names
|
||||
results = []
|
||||
for det in detections_np:
|
||||
if len(det) < 6:
|
||||
continue
|
||||
x1, y1, x2, y2, conf, cls_id = det[:6]
|
||||
cls_id = int(cls_id) # Get class name from COCO classes dictionary
|
||||
if isinstance(class_names, dict):
|
||||
# Dictionary-based class names (preferred)
|
||||
class_name = class_names.get(cls_id, f"unknown_class_{cls_id}")
|
||||
else:
|
||||
# List-based class names (legacy)
|
||||
if cls_id < len(class_names):
|
||||
class_name = class_names[cls_id]
|
||||
else:
|
||||
class_name = f"unknown_class_{cls_id}"
|
||||
|
||||
# Make sure we never return raw digits as class names
|
||||
if isinstance(class_name, int) or (isinstance(class_name, str) and class_name.isdigit()):
|
||||
# This should never happen with dictionary lookup, but just in case
|
||||
class_name = f"unknown_class_{class_name}"
|
||||
vehicle_classes = ['car', 'truck', 'bus', 'motorcycle', 'bicycle']
|
||||
traffic_sign_classes = ['traffic light', 'stop sign']
|
||||
if class_name in vehicle_classes:
|
||||
detection_type = 'vehicle'
|
||||
elif class_name in traffic_sign_classes:
|
||||
detection_type = 'traffic_sign'
|
||||
else:
|
||||
detection_type = 'other'
|
||||
results.append({
|
||||
'bbox': [float(x1), float(y1), float(x2), float(y2)],
|
||||
'confidence': float(conf),
|
||||
'class_id': cls_id,
|
||||
'class_name': class_name,
|
||||
'type': detection_type,
|
||||
'timestamp': time.time(),
|
||||
'frame_id': getattr(self, 'frame_count', 0),
|
||||
'license_plate': '',
|
||||
'traffic_light_color': 'unknown' if class_name == 'traffic light' else ''
|
||||
})
|
||||
return results
|
||||
|
||||
def _update_vehicle_tracking(self, detections: List[Dict], timestamp: float):
|
||||
"""
|
||||
Update vehicle tracking with current detections.
|
||||
|
||||
Uses position-based association for efficient tracking without deep learning.
|
||||
"""
|
||||
# Safety check: Ensure detections is in the correct format
|
||||
if not isinstance(detections, list):
|
||||
print(f"⚠️ Warning: Expected list of detections, got {type(detections)}")
|
||||
return
|
||||
|
||||
if detections and not isinstance(detections[0], dict):
|
||||
print(f"⚠️ Warning: Expected dict detections, got {type(detections[0])}")
|
||||
return
|
||||
|
||||
vehicle_detections = [d for d in detections if d['type'] == 'vehicle']
|
||||
|
||||
# Update existing tracks
|
||||
updated_tracks = set()
|
||||
|
||||
for detection in vehicle_detections:
|
||||
bbox = detection['bbox']
|
||||
center = ((bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2)
|
||||
|
||||
# Find closest existing track
|
||||
best_track_id = None
|
||||
best_distance = float('inf')
|
||||
|
||||
for track_id, track_data in self.vehicle_tracks.items():
|
||||
if track_data['last_update'] < timestamp - 2.0: # Skip old tracks
|
||||
continue
|
||||
|
||||
last_center = track_data['positions'][-1] if track_data['positions'] else None
|
||||
if last_center:
|
||||
distance = math.sqrt((center[0] - last_center[0])**2 + (center[1] - last_center[1])**2)
|
||||
|
||||
# Check if distance is reasonable for vehicle movement
|
||||
if distance < 100 and distance < best_distance: # Max 100 pixels movement
|
||||
best_distance = distance
|
||||
best_track_id = track_id
|
||||
|
||||
# Update existing track or create new one
|
||||
if best_track_id is not None:
|
||||
track_data = self.vehicle_tracks[best_track_id]
|
||||
track_data['positions'].append(center)
|
||||
track_data['timestamps'].append(timestamp)
|
||||
track_data['bboxes'].append(bbox)
|
||||
track_data['detections'].append(detection)
|
||||
track_data['last_update'] = timestamp
|
||||
updated_tracks.add(best_track_id)
|
||||
|
||||
# Limit track length
|
||||
max_length = 60 # Keep last 60 positions
|
||||
if len(track_data['positions']) > max_length:
|
||||
track_data['positions'] = track_data['positions'][-max_length:]
|
||||
track_data['timestamps'] = track_data['timestamps'][-max_length:]
|
||||
track_data['bboxes'] = track_data['bboxes'][-max_length:]
|
||||
track_data['detections'] = track_data['detections'][-max_length:]
|
||||
else:
|
||||
# Create new track
|
||||
track_id = self.next_track_id
|
||||
self.next_track_id += 1
|
||||
|
||||
self.vehicle_tracks[track_id] = {
|
||||
'positions': [center],
|
||||
'timestamps': [timestamp],
|
||||
'bboxes': [bbox],
|
||||
'detections': [detection],
|
||||
'last_update': timestamp,
|
||||
'violations': []
|
||||
}
|
||||
updated_tracks.add(track_id)
|
||||
|
||||
# Remove old tracks
|
||||
tracks_to_remove = []
|
||||
for track_id, track_data in self.vehicle_tracks.items():
|
||||
if timestamp - track_data['last_update'] > 5.0: # 5 seconds timeout
|
||||
tracks_to_remove.append(track_id)
|
||||
|
||||
for track_id in tracks_to_remove:
|
||||
del self.vehicle_tracks[track_id]
|
||||
|
||||
# Update statistics
|
||||
self.stats['tracked_vehicles'] = len(self.vehicle_tracks)
|
||||
|
||||
def _update_traffic_state(self, detections: List[Dict], timestamp: float):
|
||||
"""Update traffic light states and stop sign positions."""
|
||||
for detection in detections:
|
||||
if detection.get('class_name') == 'traffic light':
|
||||
bbox = detection['bbox']
|
||||
position = ((bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2)
|
||||
color = detection.get('traffic_light_color', 'unknown')
|
||||
|
||||
# Find existing traffic light or create new entry
|
||||
found_existing = False
|
||||
for pos, (_, last_timestamp) in list(self.traffic_light_states.items()):
|
||||
distance = math.sqrt((position[0] - pos[0])**2 + (position[1] - pos[1])**2)
|
||||
if distance < 50: # Same traffic light if within 50 pixels
|
||||
self.traffic_light_states[pos] = (color, timestamp)
|
||||
found_existing = True
|
||||
break
|
||||
|
||||
if not found_existing:
|
||||
self.traffic_light_states[position] = (color, timestamp)
|
||||
|
||||
elif detection.get('class_name') == 'stop sign':
|
||||
bbox = detection['bbox']
|
||||
position = ((bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2)
|
||||
|
||||
# Add to stop sign positions if not already present
|
||||
found_existing = False
|
||||
for pos in self.stop_sign_positions:
|
||||
distance = math.sqrt((position[0] - pos[0])**2 + (position[1] - pos[1])**2)
|
||||
if distance < 50: # Same stop sign if within 50 pixels
|
||||
found_existing = True
|
||||
break
|
||||
|
||||
if not found_existing:
|
||||
self.stop_sign_positions.append(position)
|
||||
|
||||
# Clean up old traffic light states
|
||||
current_time = timestamp
|
||||
positions_to_remove = []
|
||||
for position, (color, last_timestamp) in self.traffic_light_states.items():
|
||||
if current_time - last_timestamp > 10.0: # Remove if not seen for 10 seconds
|
||||
positions_to_remove.append(position)
|
||||
|
||||
for position in positions_to_remove:
|
||||
del self.traffic_light_states[position]
|
||||
|
||||
def _detect_red_light_violations(self, detections: List[Dict], frame: np.ndarray,
|
||||
timestamp: float) -> List[Dict]:
|
||||
"""Detect red light violations."""
|
||||
violations = []
|
||||
|
||||
# Find red traffic lights
|
||||
red_lights = []
|
||||
for position, (color, light_timestamp) in self.traffic_light_states.items():
|
||||
if color == 'red' and timestamp - light_timestamp < 2.0: # Recent red light
|
||||
red_lights.append(position)
|
||||
|
||||
if not red_lights:
|
||||
return violations
|
||||
|
||||
# Check vehicles crossing red lights
|
||||
for track_id, track_data in self.vehicle_tracks.items():
|
||||
if len(track_data['positions']) < 3: # Need at least 3 positions for movement
|
||||
continue
|
||||
|
||||
current_pos = track_data['positions'][-1]
|
||||
previous_pos = track_data['positions'][-2]
|
||||
|
||||
# Check if vehicle is moving towards or past red light
|
||||
for red_light_pos in red_lights:
|
||||
# Simple intersection zone check (in real implementation, use proper zones)
|
||||
distance_to_light = math.sqrt(
|
||||
(current_pos[0] - red_light_pos[0])**2 +
|
||||
(current_pos[1] - red_light_pos[1])**2
|
||||
)
|
||||
|
||||
prev_distance_to_light = math.sqrt(
|
||||
(previous_pos[0] - red_light_pos[0])**2 +
|
||||
(previous_pos[1] - red_light_pos[1])**2
|
||||
)
|
||||
|
||||
# Check if vehicle crossed the intersection zone during red light
|
||||
if (prev_distance_to_light > 150 and distance_to_light < 100 and
|
||||
distance_to_light < prev_distance_to_light):
|
||||
|
||||
violation = {
|
||||
'type': 'red_light_violation',
|
||||
'vehicle_track_id': track_id,
|
||||
'violation_position': current_pos,
|
||||
'traffic_light_position': red_light_pos,
|
||||
'severity': 'high',
|
||||
'confidence': 0.9,
|
||||
'description': f'Vehicle ran red light at position {current_pos}',
|
||||
'vehicle_bbox': track_data['bboxes'][-1],
|
||||
'timestamp': timestamp
|
||||
}
|
||||
violations.append(violation)
|
||||
|
||||
# Add to track violations
|
||||
track_data['violations'].append(violation)
|
||||
|
||||
return violations
|
||||
|
||||
def _detect_stop_sign_violations(self, detections: List[Dict], frame: np.ndarray,
|
||||
timestamp: float) -> List[Dict]:
|
||||
"""Detect stop sign violations."""
|
||||
violations = []
|
||||
|
||||
if not self.stop_sign_positions:
|
||||
return violations
|
||||
|
||||
# Check vehicles at stop signs
|
||||
for track_id, track_data in self.vehicle_tracks.items():
|
||||
if len(track_data['positions']) < 10: # Need sufficient track history
|
||||
continue
|
||||
|
||||
current_pos = track_data['positions'][-1]
|
||||
|
||||
# Check if vehicle is near stop sign
|
||||
for stop_sign_pos in self.stop_sign_positions:
|
||||
distance_to_stop = math.sqrt(
|
||||
(current_pos[0] - stop_sign_pos[0])**2 +
|
||||
(current_pos[1] - stop_sign_pos[1])**2
|
||||
)
|
||||
|
||||
if distance_to_stop < 80: # Within stop sign zone
|
||||
# Check if vehicle came to a complete stop
|
||||
stop_duration = self._calculate_stop_duration(track_data, stop_sign_pos)
|
||||
|
||||
if stop_duration < self.config['stop_sign_stop_duration']:
|
||||
# Check if this violation was already detected recently
|
||||
recent_violation = False
|
||||
for violation in track_data['violations'][-5:]: # Check last 5 violations
|
||||
if (violation.get('type') == 'stop_sign_violation' and
|
||||
timestamp - violation.get('timestamp', 0) < 5.0):
|
||||
recent_violation = True
|
||||
break
|
||||
|
||||
if not recent_violation:
|
||||
violation = {
|
||||
'type': 'stop_sign_violation',
|
||||
'vehicle_track_id': track_id,
|
||||
'violation_position': current_pos,
|
||||
'stop_sign_position': stop_sign_pos,
|
||||
'stop_duration': stop_duration,
|
||||
'required_duration': self.config['stop_sign_stop_duration'],
|
||||
'severity': 'medium',
|
||||
'confidence': 0.8,
|
||||
'description': f'Vehicle failed to stop completely at stop sign (stopped for {stop_duration:.1f}s)',
|
||||
'vehicle_bbox': track_data['bboxes'][-1],
|
||||
'timestamp': timestamp
|
||||
}
|
||||
violations.append(violation)
|
||||
track_data['violations'].append(violation)
|
||||
|
||||
return violations
|
||||
|
||||
def _calculate_stop_duration(self, track_data: Dict, stop_position: Tuple[int, int]) -> float:
|
||||
"""Calculate how long a vehicle stopped near a stop sign."""
|
||||
positions = track_data['positions']
|
||||
timestamps = track_data['timestamps']
|
||||
|
||||
if len(positions) < 2:
|
||||
return 0.0
|
||||
|
||||
# Find positions near the stop sign
|
||||
stop_frames = []
|
||||
for i, pos in enumerate(positions[-20:]): # Check last 20 positions
|
||||
distance = math.sqrt((pos[0] - stop_position[0])**2 + (pos[1] - stop_position[1])**2)
|
||||
if distance < 100: # Near stop sign
|
||||
# Check if vehicle is stationary (movement < 10 pixels between frames)
|
||||
if i > 0:
|
||||
prev_pos = positions[len(positions) - 20 + i - 1]
|
||||
movement = math.sqrt((pos[0] - prev_pos[0])**2 + (pos[1] - prev_pos[1])**2)
|
||||
if movement < 10: # Stationary
|
||||
stop_frames.append(len(positions) - 20 + i)
|
||||
|
||||
if len(stop_frames) < 2:
|
||||
return 0.0
|
||||
|
||||
# Calculate duration of longest continuous stop
|
||||
max_stop_duration = 0.0
|
||||
current_stop_start = None
|
||||
|
||||
for i, frame_idx in enumerate(stop_frames):
|
||||
if current_stop_start is None:
|
||||
current_stop_start = frame_idx
|
||||
elif frame_idx - stop_frames[i-1] > 2: # Gap in stop frames
|
||||
# Calculate previous stop duration
|
||||
stop_duration = (timestamps[stop_frames[i-1]] - timestamps[current_stop_start])
|
||||
max_stop_duration = max(max_stop_duration, stop_duration)
|
||||
current_stop_start = frame_idx
|
||||
|
||||
# Check final stop duration
|
||||
if current_stop_start is not None:
|
||||
stop_duration = (timestamps[stop_frames[-1]] - timestamps[current_stop_start])
|
||||
max_stop_duration = max(max_stop_duration, stop_duration)
|
||||
|
||||
return max_stop_duration
|
||||
|
||||
def _detect_speed_violations(self, detections: List[Dict], frame: np.ndarray,
|
||||
timestamp: float) -> List[Dict]:
|
||||
"""Detect speed violations based on vehicle tracking."""
|
||||
violations = []
|
||||
|
||||
for track_id, track_data in self.vehicle_tracks.items():
|
||||
if len(track_data['positions']) < 10: # Need sufficient data for speed calculation
|
||||
continue
|
||||
|
||||
# Calculate speed over last few frames
|
||||
speed_kmh = self._calculate_vehicle_speed(track_data)
|
||||
|
||||
if speed_kmh > self.config['speed_limit_default'] + self.config['speed_tolerance']:
|
||||
# Check if this violation was already detected recently
|
||||
recent_violation = False
|
||||
for violation in track_data['violations'][-3:]: # Check last 3 violations
|
||||
if (violation.get('type') == 'speed_violation' and
|
||||
timestamp - violation.get('timestamp', 0) < 3.0):
|
||||
recent_violation = True
|
||||
break
|
||||
|
||||
if not recent_violation:
|
||||
violation = {
|
||||
'type': 'speed_violation',
|
||||
'vehicle_track_id': track_id,
|
||||
'violation_position': track_data['positions'][-1],
|
||||
'measured_speed': speed_kmh,
|
||||
'speed_limit': self.config['speed_limit_default'],
|
||||
'excess_speed': speed_kmh - self.config['speed_limit_default'],
|
||||
'severity': 'high' if speed_kmh > self.config['speed_limit_default'] + 20 else 'medium',
|
||||
'confidence': 0.7, # Lower confidence due to simplified speed calculation
|
||||
'description': f'Vehicle exceeding speed limit: {speed_kmh:.1f} km/h in {self.config["speed_limit_default"]} km/h zone',
|
||||
'vehicle_bbox': track_data['bboxes'][-1],
|
||||
'timestamp': timestamp
|
||||
}
|
||||
violations.append(violation)
|
||||
track_data['violations'].append(violation)
|
||||
|
||||
return violations
|
||||
|
||||
def _calculate_vehicle_speed(self, track_data: Dict) -> float:
|
||||
"""
|
||||
Calculate vehicle speed in km/h based on position tracking.
|
||||
|
||||
This is a simplified calculation that assumes:
|
||||
- Fixed camera position
|
||||
- Approximate pixel-to-meter conversion
|
||||
- Known frame rate
|
||||
"""
|
||||
positions = track_data['positions']
|
||||
timestamps = track_data['timestamps']
|
||||
|
||||
if len(positions) < 5:
|
||||
return 0.0
|
||||
|
||||
# Use last 5 positions for speed calculation
|
||||
recent_positions = positions[-5:]
|
||||
recent_timestamps = timestamps[-5:]
|
||||
|
||||
# Calculate total distance traveled
|
||||
total_distance_pixels = 0.0
|
||||
for i in range(1, len(recent_positions)):
|
||||
dx = recent_positions[i][0] - recent_positions[i-1][0]
|
||||
dy = recent_positions[i][1] - recent_positions[i-1][1]
|
||||
distance_pixels = math.sqrt(dx*dx + dy*dy)
|
||||
total_distance_pixels += distance_pixels
|
||||
|
||||
# Calculate time elapsed
|
||||
time_elapsed = recent_timestamps[-1] - recent_timestamps[0]
|
||||
|
||||
if time_elapsed <= 0:
|
||||
return 0.0
|
||||
|
||||
# Convert to speed
|
||||
# Rough approximation: 1 pixel ≈ 0.1 meters (depends on camera setup)
|
||||
pixels_per_meter = 10.0 # Adjust based on camera calibration
|
||||
distance_meters = total_distance_pixels / pixels_per_meter
|
||||
speed_ms = distance_meters / time_elapsed
|
||||
speed_kmh = speed_ms * 3.6 # Convert m/s to km/h
|
||||
|
||||
return speed_kmh
|
||||
|
||||
def _detect_lane_violations(self, detections: List[Dict], frame: np.ndarray,
|
||||
timestamp: float) -> List[Dict]:
|
||||
"""
|
||||
Detect lane violations (simplified implementation).
|
||||
|
||||
In a full implementation, this would require lane detection and tracking.
|
||||
"""
|
||||
violations = []
|
||||
|
||||
# Simplified lane violation detection based on vehicle positions
|
||||
# This is a placeholder implementation
|
||||
frame_height, frame_width = frame.shape[:2]
|
||||
|
||||
for track_id, track_data in self.vehicle_tracks.items():
|
||||
if len(track_data['positions']) < 5:
|
||||
continue
|
||||
|
||||
current_pos = track_data['positions'][-1]
|
||||
|
||||
# Simple boundary check (assuming road is in center of frame)
|
||||
# In reality, this would use proper lane detection
|
||||
road_left = frame_width * 0.1
|
||||
road_right = frame_width * 0.9
|
||||
|
||||
if current_pos[0] < road_left or current_pos[0] > road_right:
|
||||
# Check if this violation was already detected recently
|
||||
recent_violation = False
|
||||
for violation in track_data['violations'][-3:]:
|
||||
if (violation.get('type') == 'lane_violation' and
|
||||
timestamp - violation.get('timestamp', 0) < 2.0):
|
||||
recent_violation = True
|
||||
break
|
||||
|
||||
if not recent_violation:
|
||||
violation = {
|
||||
'type': 'lane_violation',
|
||||
'vehicle_track_id': track_id,
|
||||
'violation_position': current_pos,
|
||||
'severity': 'low',
|
||||
'confidence': 0.5, # Low confidence due to simplified detection
|
||||
'description': 'Vehicle outside road boundaries',
|
||||
'vehicle_bbox': track_data['bboxes'][-1],
|
||||
'timestamp': timestamp
|
||||
}
|
||||
violations.append(violation)
|
||||
track_data['violations'].append(violation)
|
||||
|
||||
return violations
|
||||
|
||||
def _update_statistics(self, violations: List[Dict]):
|
||||
"""Update violation statistics."""
|
||||
for violation in violations:
|
||||
self.stats['total_violations'] += 1
|
||||
violation_type = violation.get('type', '')
|
||||
|
||||
if 'red_light' in violation_type:
|
||||
self.stats['red_light_violations'] += 1
|
||||
elif 'stop_sign' in violation_type:
|
||||
self.stats['stop_sign_violations'] += 1
|
||||
elif 'speed' in violation_type:
|
||||
self.stats['speed_violations'] += 1
|
||||
elif 'lane' in violation_type:
|
||||
self.stats['lane_violations'] += 1
|
||||
|
||||
def get_statistics(self) -> Dict:
|
||||
"""Get current violation statistics."""
|
||||
stats = self.stats.copy()
|
||||
|
||||
# Add performance metrics
|
||||
if self.processing_times:
|
||||
stats['avg_processing_time'] = np.mean(self.processing_times)
|
||||
stats['fps'] = 1.0 / np.mean(self.processing_times) if np.mean(self.processing_times) > 0 else 0
|
||||
|
||||
# Add tracking metrics
|
||||
stats['active_tracks'] = len(self.vehicle_tracks)
|
||||
stats['traffic_lights_detected'] = len(self.traffic_light_states)
|
||||
stats['stop_signs_detected'] = len(self.stop_sign_positions)
|
||||
|
||||
return stats
|
||||
|
||||
def get_violation_history(self, limit: int = 100) -> List[Dict]:
|
||||
"""Get recent violation history."""
|
||||
return self.violation_history[-limit:] if limit > 0 else self.violation_history
|
||||
|
||||
def reset_statistics(self):
|
||||
"""Reset violation statistics."""
|
||||
self.stats = {
|
||||
'total_violations': 0,
|
||||
'red_light_violations': 0,
|
||||
'stop_sign_violations': 0,
|
||||
'speed_violations': 0,
|
||||
'lane_violations': 0,
|
||||
'tracked_vehicles': 0
|
||||
}
|
||||
self.violation_history.clear()
|
||||
logger.info("✅ Violation statistics reset")
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up resources."""
|
||||
self.vehicle_tracks.clear()
|
||||
self.traffic_light_states.clear()
|
||||
self.stop_sign_positions.clear()
|
||||
logger.info("✅ OpenVINO Violation Detector cleanup completed")
|
||||
|
||||
def get_violation_summary(self, time_window: float = 3600) -> Dict:
|
||||
"""
|
||||
Get summary of violations in the specified time window
|
||||
|
||||
Args:
|
||||
time_window: Time window in seconds (default: 1 hour)
|
||||
|
||||
Returns:
|
||||
Summary dictionary
|
||||
"""
|
||||
current_time = time.time()
|
||||
recent_violations = [
|
||||
v for v in self.violation_history
|
||||
if current_time - v['timestamp'] <= time_window
|
||||
]
|
||||
|
||||
summary = {
|
||||
'total_violations': len(recent_violations),
|
||||
'by_type': defaultdict(int),
|
||||
'by_severity': defaultdict(int),
|
||||
'avg_confidence': 0,
|
||||
'time_window': time_window
|
||||
}
|
||||
|
||||
if recent_violations:
|
||||
for violation in recent_violations:
|
||||
summary['by_type'][violation['type']] += 1
|
||||
summary['by_severity'][violation['severity']] += 1
|
||||
|
||||
summary['avg_confidence'] = sum(v['confidence'] for v in recent_violations) / len(recent_violations)
|
||||
|
||||
return dict(summary)
|
||||
|
||||
def get_performance_stats(self) -> Dict:
|
||||
"""Get performance statistics"""
|
||||
if self.processing_times:
|
||||
avg_time = sum(self.processing_times) / len(self.processing_times)
|
||||
fps = 1.0 / avg_time if avg_time > 0 else 0
|
||||
else:
|
||||
avg_time = 0
|
||||
fps = 0
|
||||
|
||||
return {
|
||||
'avg_processing_time': avg_time * 1000, # ms
|
||||
'fps': fps,
|
||||
'total_detections': self.detection_count,
|
||||
'total_violations': len(self.violation_history),
|
||||
'active_tracks': len(self.vehicle_tracks)
|
||||
}
|
||||
|
||||
def reset_history(self):
|
||||
"""Reset violation history and tracking data"""
|
||||
self.violation_history.clear()
|
||||
self.vehicle_tracks.clear()
|
||||
self.traffic_light_states.clear()
|
||||
self.traffic_light_history.clear()
|
||||
self.detection_count = 0
|
||||
logger.info("✅ Violation detector history reset")
|
||||
|
||||
def _detect_red_light_violation_cv(self, frame, vehicle_detections, traffic_light_detection, frame_idx, timestamp, crosswalk_bbox=None):
|
||||
"""
|
||||
Use the RedLightViolationPipeline (traditional CV) to detect red-light violations.
|
||||
|
||||
Args:
|
||||
frame: The current video frame
|
||||
vehicle_detections: List of dicts with 'track_id' and 'bbox'
|
||||
traffic_light_detection: Dict with 'bbox' and 'signal_state'
|
||||
frame_idx: Current frame index
|
||||
timestamp: Current frame timestamp
|
||||
crosswalk_bbox: Optional crosswalk bounding box if available
|
||||
|
||||
Returns:
|
||||
List of violation dicts
|
||||
"""
|
||||
# Initialize pipeline if needed
|
||||
if not hasattr(self, '_cv_redlight_pipeline'):
|
||||
self._cv_redlight_pipeline = RedLightViolationPipeline(debug=False)
|
||||
|
||||
pipeline = self._cv_redlight_pipeline
|
||||
|
||||
# Extract traffic light information
|
||||
traffic_light_bbox = None
|
||||
traffic_light_state = 'unknown'
|
||||
|
||||
if traffic_light_detection:
|
||||
if isinstance(traffic_light_detection, dict):
|
||||
traffic_light_bbox = traffic_light_detection.get('bbox')
|
||||
# First try to get the state from signal_state field
|
||||
traffic_light_state = traffic_light_detection.get('signal_state')
|
||||
|
||||
# If signal_state is not available, try traffic_light_color
|
||||
if not traffic_light_state or traffic_light_state == 'unknown':
|
||||
traffic_light_state = traffic_light_detection.get('traffic_light_color', 'unknown')
|
||||
|
||||
# Verify class name is correct (not a number)
|
||||
class_name = traffic_light_detection.get('class_name')
|
||||
if class_name and (class_name.isdigit() or isinstance(class_name, int)):
|
||||
traffic_light_detection['class_name'] = 'traffic light'
|
||||
else:
|
||||
# Handle case where traffic_light_detection is not a dict
|
||||
print(f"Warning: traffic_light_detection is not a dict: {type(traffic_light_detection)}")
|
||||
|
||||
# Detect violation line (stop line or crosswalk)
|
||||
pipeline.detect_violation_line(frame, traffic_light_bbox, crosswalk_bbox)
|
||||
|
||||
# Update vehicle tracks
|
||||
pipeline.update_tracks(vehicle_detections, frame_idx)
|
||||
|
||||
# Check for violations
|
||||
violations = pipeline.check_violations(vehicle_detections, traffic_light_state, frame_idx, timestamp)
|
||||
|
||||
# Add debug visualizations if needed
|
||||
if self.config.get('debug_visualize', False):
|
||||
debug_frame = pipeline.draw_debug(frame, vehicle_detections, traffic_light_bbox, traffic_light_state)
|
||||
# Save or display debug frame if needed
|
||||
|
||||
return violations
|
||||
|
||||
# Convenience function for backward compatibility
|
||||
def create_violation_detector(**kwargs) -> OpenVINOViolationDetector:
|
||||
"""Create OpenVINO violation detector with default settings."""
|
||||
return OpenVINOViolationDetector(**kwargs)
|
||||
|
||||
# For compatibility with existing code
|
||||
ViolationDetector = OpenVINOViolationDetector # Alias for drop-in replacement
|
||||
Reference in New Issue
Block a user