# -*- coding: utf-8 -*-
"""
Traffic Monitoring System v2.5 (Camera Version)
Features:
- Real-time camera object detection
- YOLOv8 object detection
- ByteTrack tracking
- Matching colors for boxes and trajectories
- Huawei Cloud IoT data reporting
- Movable table
- Trajectory display
"""
import os
import sys
import cv2
import numpy as np
import torch
import supervision as sv
from ultralytics import YOLO
from typing import Dict, Optional, Tuple, List
import warnings
import time
import logging
import socket
import json
from pathlib import Path
from collections import defaultdict, deque
# Import Huawei IoT SDK with encoding handling
try:
from iot_device_sdk_python.client.client_conf import ClientConf
from iot_device_sdk_python.client.connect_auth_info import ConnectAuthInfo
from iot_device_sdk_python.iot_device import IotDevice
from iot_device_sdk_python.client.request.service_property import ServiceProperty
except ImportError as e:
logging.error(f"Huawei IoT SDK import error: {str(e)}")
sys.exit(1)
# Configure global logging with UTF-8 encoding
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('traffic_monitor.log', encoding='utf-8', mode='a')
]
)
logger = logging.getLogger("TrafficMonitor")
class Config:
"""System configuration parameters"""
MODEL_PATH = r"C:\Users\yuanb\Desktop\物联网设计大赛无pyqt同时MQTT传输\runs\detect\train_v8\weights\best.pt"
CLASS_NAMES = {
0: 'Pedestrian',
1: 'Person',
2: 'Bicycle',
3: 'Car',
4: 'Van',
5: 'Truck',
6: 'Tricycle',
7: 'CoveredTricycle',
8: 'Bus',
9: 'Motorcycle'
}
# Color palette for different classes
CLASS_COLORS = {
0: (255, 0, 0), # Red - Pedestrian
1: (0, 255, 0), # Green - Person
2: (0, 0, 255), # Blue - Bicycle
3: (255, 255, 0), # Yellow - Car
4: (255, 0, 255), # Magenta - Van
5: (0, 255, 255), # Cyan - Truck
6: (255, 128, 0), # Orange - Tricycle
7: (128, 0, 255), # Purple - CoveredTricycle
8: (0, 128, 255), # Light Blue - Bus
9: (255, 0, 128) # Pink - Motorcycle
}
IOT_CONFIG = {
"server_uri": "117.78.5.125",
"port": 1883,
"device_id": "681eeebc9314d118511a5690_DEV4",
"secret": "Wjy15640916538",
"service_id": "鲁班猫4",
"use_tls": False,
"keep_alive": 300,
"clean_session": False
}
REPORT_INTERVAL = 10
MIN_REPORT_INTERVAL = 30
LINE_COLOR = sv.Color(r=0, g=255, b=0)
TEXT_COLOR = (255, 255, 255) # White text for better visibility
FONT_SCALE = 0.7
TABLE_POSITION = (10, 400) # Initial position, will be updated by mouse
TABLE_ROW_HEIGHT = 30
TABLE_COL_WIDTH = 180 # Increased width
TABLE_COLORS = {
'header': (0, 100, 255),
'row_even': (50, 50, 50),
'row_odd': (80, 80, 80),
'text': (255, 255, 255)
}
TRACK_HISTORY_LENGTH = 30 # Number of trajectory points to keep
class LineCounter:
"""Improved counting logic with bidirectional counting and classification"""
def __init__(self, start_point: sv.Point, end_point: sv.Point):
self.start = start_point
self.end = end_point
self.tracker_state = {}
self.counts = {
'Inbound_Vehicle': 0,
'Outbound_Vehicle': 0,
'Inbound_Pedestrian': 0,
'Outbound_Pedestrian': 0,
'Total_Vehicles': 0,
'Total_Pedestrians': 0
}
self.class_mapping = {
'Vehicle': {3, 4, 5, 6, 7, 8, 9},
'Pedestrian': {0, 1}
}
self.history = []
def _get_side(self, point: sv.Point) -> bool:
"""Calculate which side of the line the point is on (cross product method)"""
vector_line = (self.end.x - self.start.x, self.end.y - self.start.y)
vector_point = (point.x - self.start.x, point.y - self.start.y)
cross_product = vector_line[0] * vector_point[1] - vector_line[1] * vector_point[0]
return cross_product > 0
def update(self, detections: sv.Detections):
"""Update counter state"""
current_counts = {
'Inbound_Vehicle': 0,
'Outbound_Vehicle': 0,
'Inbound_Pedestrian': 0,
'Outbound_Pedestrian': 0,
}
for xyxy, _, confidence, class_id, tracker_id in detections:
if tracker_id is None or confidence < 0.5:
continue
# Calculate center point
x1, y1, x2, y2 = xyxy
center = sv.Point(x=int((x1 + x2) / 2), y=int((y1 + y2) / 2))
current_side = self._get_side(center)
# Initialize new object
if tracker_id not in self.tracker_state:
self.tracker_state[tracker_id] = current_side
continue
# Check if crossed the line
if self.tracker_state[tracker_id] != current_side:
if class_id in self.class_mapping['Vehicle']:
direction = 'Inbound' if current_side else 'Outbound'
current_counts[f'{direction}_Vehicle'] += 1
self.counts[f'{direction}_Vehicle'] += 1
self.counts['Total_Vehicles'] += 1
elif class_id in self.class_mapping['Pedestrian']:
direction = 'Inbound' if current_side else 'Outbound'
current_counts[f'{direction}_Pedestrian'] += 1
self.counts[f'{direction}_Pedestrian'] += 1
self.counts['Total_Pedestrians'] += 1
self.tracker_state[tracker_id] = current_side
if any(current_counts.values()):
self.history.append({
'timestamp': time.time(),
'counts': current_counts.copy()
})
def get_counts(self) -> Dict[str, int]:
"""Get current statistics"""
return self.counts.copy()
def get_recent_counts(self, seconds=10) -> Dict[str, int]:
"""Get counts from recent specified time period"""
recent_counts = {
'Inbound_Vehicle': 0,
'Outbound_Vehicle': 0,
'Inbound_Pedestrian': 0,
'Outbound_Pedestrian': 0
}
cutoff = time.time() - seconds
for record in reversed(self.history):
if record['timestamp'] < cutoff:
break
for key in recent_counts:
recent_counts[key] += record['counts'].get(key, 0)
return recent_counts
class HuaweiIoTClient:
"""Huawei Cloud IoT Client"""
def __init__(self, config: dict):
self.config = config
self.device = None
self.max_retries = 5
self.base_reconnect_delay = 5
self.last_report_time = 0
self._initialize_client()
def _initialize_client(self):
"""Initialize client connection"""
try:
auth_info = ConnectAuthInfo()
auth_info.server_uri = self.config["server_uri"]
auth_info.port = self.config["port"]
auth_info.id = self.config["device_id"]
auth_info.secret = self.config["secret"]
self.config["use_tls"] = False
client_conf = ClientConf(auth_info)
client_conf.clean_session = self.config["clean_session"]
client_conf.keep_alive = self.config["keep_alive"]
client_conf.will_topic = f"$oc/devices/{self.config['device_id']}/sys/events/down"
client_conf.will_message = json.dumps({
"type": "ABNORMAL_DISCONNECT",
"timestamp": time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
})
self.device = IotDevice(client_conf)
self._connect_with_retry()
except Exception as e:
logger.error(f"Client initialization failed: {str(e)}")
raise
def _check_network(self) -> bool:
"""Check network connectivity"""
try:
with socket.create_connection(
(self.config["server_uri"], self.config["port"]),
timeout=10
):
return True
except Exception as e:
logger.warning(f"Network check failed: {str(e)}")
return False
def _connect_with_retry(self):
"""Exponential backoff reconnection mechanism"""
retry_count = 0
while retry_count < self.max_retries:
try:
if not self._check_network():
raise ConnectionError("Network unavailable")
result = self.device.connect()
if result == 0:
logger.info(f"Connection successful (attempt {retry_count + 1}/{self.max_retries})")
return True
wait_time = min(self.base_reconnect_delay * (2 ** retry_count), 60)
logger.warning(f"Connection failed, retrying in {wait_time} seconds... (error code: {result})")
time.sleep(wait_time)
retry_count += 1
except Exception as e:
wait_time = min(self.base_reconnect_delay * (2 ** retry_count), 60)
logger.error(f"Connection exception: {str(e)}, retrying in {wait_time} seconds")
time.sleep(wait_time)
retry_count += 1
logger.error("Max retries reached, giving up")
return False
def is_connected(self) -> bool:
"""Check connection status"""
try:
return (self.device is not None and
hasattr(self.device, 'get_client') and
self.device.get_client().is_connected())
except Exception as e:
logger.warning(f"Connection status check exception: {str(e)}")
return False
def report_traffic_data(self, counts: Dict[str, int]) -> bool:
"""Data reporting method"""
current_time = time.time()
if current_time - self.last_report_time < Config.MIN_REPORT_INTERVAL:
return False
if not self.is_connected():
logger.warning("Connection lost, attempting to reconnect...")
if not self._connect_with_retry():
return False
try:
service = ServiceProperty()
service.service_id = self.config["service_id"]
service.properties = {
"vehicle_in": counts.get('Inbound_Vehicle', 0),
"vehicle_out": counts.get('Outbound_Vehicle', 0),
"vehicle_total": counts.get('Total_Vehicles', 0),
"person_in": counts.get('Inbound_Pedestrian', 0),
"person_out": counts.get('Outbound_Pedestrian', 0),
"person_total": counts.get('Total_Pedestrians', 0),
"timestamp": int(time.time() * 1000)
}
service.event_time = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
result = self.device.get_client().report_properties([service])
if result == 0:
self.last_report_time = current_time
logger.info(f"Data reported successfully: {service.properties}")
return True
logger.error(f"Reporting failed, return code: {result}")
return False
except Exception as e:
logger.error(f"Reporting exception: {str(e)}")
return False
class TrafficMonitor:
"""Main traffic monitoring program"""
def __init__(self):
# Hardware configuration
self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
logger.info(f"Running on: {self.device.upper()}")
# Model loading
self.model = self._load_model()
self.tracker = sv.ByteTrack()
self.line_counter = None
self.frame_count = 0
self.current_tracks = {}
self.track_history = defaultdict(lambda: deque(maxlen=Config.TRACK_HISTORY_LENGTH))
# Visualization tools with custom color settings
self.box_annotator = sv.BoundingBoxAnnotator(color_lookup=sv.ColorLookup.TRACK)
self.label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.TRACK)
self.line_annotator = sv.LineZoneAnnotator(
thickness=2,
text_thickness=1,
text_scale=0.5
)
# Table properties
self.table_position = list(Config.TABLE_POSITION)
self.is_dragging = False
self.drag_offset = (0, 0)
# Huawei Cloud connection
try:
self.iot_client = HuaweiIoTClient(Config.IOT_CONFIG)
except Exception as e:
logger.error(f"Huawei IoT client initialization failed: {str(e)}")
self.iot_client = None
def _load_model(self) -> YOLO:
"""Load YOLO model"""
try:
model = YOLO(Config.MODEL_PATH)
model.fuse()
logger.info(f"Model loaded successfully, detection classes: {model.names}")
return model
except Exception as e:
logger.error(f"Model loading failed: {str(e)}")
sys.exit(1)
def set_counting_line(self, start: Tuple[int, int], end: Tuple[int, int]):
"""Set counting line coordinates"""
self.line_counter = LineCounter(
start_point=sv.Point(*start),
end_point=sv.Point(*end)
)
logger.info(f"Counting line set: {start} -> {end}")
def _draw_stats(self, frame: np.ndarray, counts: Dict[str, int]) -> np.ndarray:
"""Draw statistics on frame"""
stats = [
f"Inbound Vehicle: {counts['Inbound_Vehicle']}",
f"Outbound Vehicle: {counts['Outbound_Vehicle']}",
f"Total Vehicles: {counts['Total_Vehicles']}",
f"Inbound Pedestrian: {counts['Inbound_Pedestrian']}",
f"Outbound Pedestrian: {counts['Outbound_Pedestrian']}",
f"Total Pedestrians: {counts['Total_Pedestrians']}"
]
for i, text in enumerate(stats):
cv2.putText(
frame, text,
(10, 30 + i * 30),
cv2.FONT_HERSHEY_SIMPLEX,
Config.FONT_SCALE,
Config.TEXT_COLOR,
2
)
return frame
def _draw_data_table(self, frame: np.ndarray, counts: Dict[str, int], recent_counts: Dict[str, int]) -> np.ndarray:
"""Draw movable data table"""
x, y = self.table_position
row_height = Config.TABLE_ROW_HEIGHT
col_width = Config.TABLE_COL_WIDTH
# Calculate table dimensions
num_cols = 3
num_rows = 5 # Header + 4 data rows
table_width = num_cols * col_width
table_height = num_rows * row_height
# Draw table background
cv2.rectangle(frame, (x, y), (x + table_width, y + table_height), (40, 40, 40), -1)
# Table header
headers = ["Metric", "Total", "Last 10s"]
for i, header in enumerate(headers):
cv2.rectangle(frame, (x + i * col_width, y), (x + (i + 1) * col_width, y + row_height),
Config.TABLE_COLORS['header'], -1)
cv2.putText(frame, header, (x + i * col_width + 10, y + int(row_height * 0.7)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, Config.TABLE_COLORS['text'], 1)
# Data rows
rows = [
("Inbound Vehicle", counts['Inbound_Vehicle'], recent_counts['Inbound_Vehicle']),
("Outbound Vehicle", counts['Outbound_Vehicle'], recent_counts['Outbound_Vehicle']),
("Inbound Pedestrian", counts['Inbound_Pedestrian'], recent_counts['Inbound_Pedestrian']),
("Outbound Pedestrian", counts['Outbound_Pedestrian'], recent_counts['Outbound_Pedestrian'])
]
for row_idx, (label, total, recent) in enumerate(rows):
bg_color = Config.TABLE_COLORS['row_even'] if row_idx % 2 == 0 else Config.TABLE_COLORS['row_odd']
# Draw row background
cv2.rectangle(frame, (x, y + (row_idx + 1) * row_height),
(x + table_width, y + (row_idx + 2) * row_height),
bg_color, -1)
# Draw cell content
cv2.putText(frame, label, (x + 10, y + (row_idx + 1) * row_height + int(row_height * 0.7)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, Config.TABLE_COLORS['text'], 1)
cv2.putText(frame, str(total), (x + col_width + 10, y + (row_idx + 1) * row_height + int(row_height * 0.7)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, Config.TABLE_COLORS['text'], 1)
cv2.putText(frame, str(recent),
(x + 2 * col_width + 10, y + (row_idx + 1) * row_height + int(row_height * 0.7)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, Config.TABLE_COLORS['text'], 1)
# Draw drag handle (small rectangle at top-right)
handle_size = 20
cv2.rectangle(frame, (x + table_width - handle_size, y),
(x + table_width, y + handle_size), (0, 255, 255), -1)
return frame
def _handle_table_drag(self, event, x, y, flags, param):
"""Mouse callback for table dragging"""
table_x, table_y = self.table_position
table_width = 3 * Config.TABLE_COL_WIDTH
table_height = 5 * Config.TABLE_ROW_HEIGHT
handle_size = 20
# Check if mouse is in drag handle area
handle_rect = (table_x + table_width - handle_size, table_y,
table_x + table_width, table_y + handle_size)
if event == cv2.EVENT_LBUTTONDOWN:
if (handle_rect[0] <= x <= handle_rect[2] and
handle_rect[1] <= y <= handle_rect[3]):
self.is_dragging = True
self.drag_offset = (x - table_x, y - table_y)
elif event == cv2.EVENT_LBUTTONUP:
self.is_dragging = False
elif event == cv2.EVENT_MOUSEMOVE:
if self.is_dragging:
# Update table position with offset
new_x = x - self.drag_offset[0]
new_y = y - self.drag_offset[1]
# Keep table within frame bounds
frame_height, frame_width = param.shape[:2]
new_x = max(0, min(new_x, frame_width - table_width))
new_y = max(0, min(new_y, frame_height - table_height))
self.table_position = [new_x, new_y]
def _draw_tracks(self, frame: np.ndarray, detections: sv.Detections) -> np.ndarray:
"""Draw object trajectories with matching colors"""
for xyxy, _, confidence, class_id, tracker_id in detections:
if tracker_id is None or confidence < 0.5:
continue
# Get color based on class
color = Config.CLASS_COLORS.get(class_id, (255, 255, 255)) # Default to white if class not found
# Calculate center point
x1, y1, x2, y2 = xyxy
center = (int((x1 + x2) / 2), int((y1 + y2) / 2))
# Update track history
self.track_history[tracker_id].append(center)
# Draw trajectory line
points = list(self.track_history[tracker_id])
for i in range(1, len(points)):
cv2.line(frame, points[i - 1], points[i], color, thickness=2)
# Draw bounding box with same color
cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), color, 2)
# Display ID and class
label = f"{Config.CLASS_NAMES.get(class_id, 'Unknown')} ID:{tracker_id}"
cv2.putText(frame, label, (int(x1), int(y1) - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
return frame
def _update_track_info(self, detections: sv.Detections):
"""Update tracking object information"""
current_tracks = {}
for xyxy, _, confidence, class_id, tracker_id in detections:
if tracker_id is not None:
current_tracks[tracker_id] = (class_id, confidence)
self.current_tracks = current_tracks
def process_frame(self, frame: np.ndarray) -> np.ndarray:
"""Process single frame"""
try:
# Object detection
results = self.model(frame, imgsz=640, verbose=False)[0]
detections = sv.Detections.from_ultralytics(results)
# Object tracking with ByteTrack
detections = self.tracker.update_with_detections(detections)
self._update_track_info(detections)
# Initialize count results
counts = {
'Inbound_Vehicle': 0,
'Outbound_Vehicle': 0,
'Inbound_Pedestrian': 0,
'Outbound_Pedestrian': 0,
'Total_Vehicles': 0,
'Total_Pedestrians': 0
}
recent_counts = {
'Inbound_Vehicle': 0,
'Outbound_Vehicle': 0,
'Inbound_Pedestrian': 0,
'Outbound_Pedestrian': 0
}
if self.line_counter:
# Update counter
self.line_counter.update(detections)
counts = self.line_counter.get_counts()
recent_counts = self.line_counter.get_recent_counts(seconds=10)
# Draw counting line
cv2.line(
frame,
(self.line_counter.start.x, self.line_counter.start.y),
(self.line_counter.end.x, self.line_counter.end.y),
Config.LINE_COLOR.as_bgr(),
2
)
# Periodic data reporting
if self.frame_count % Config.REPORT_INTERVAL == 0 and self.iot_client:
self.iot_client.report_traffic_data(counts)
# Draw object trajectories and boxes (now handled together in _draw_tracks)
frame = self._draw_tracks(frame, detections)
# Display statistics and table
frame = self._draw_stats(frame, counts)
frame = self._draw_data_table(frame, counts, recent_counts)
# Set mouse callback for table dragging
cv2.setMouseCallback("Traffic Monitoring", self._handle_table_drag, frame)
self.frame_count += 1
return frame
except Exception as e:
logger.error(f"Frame processing exception: {str(e)}")
return frame
def interactive_line_setup(frame: np.ndarray) -> Optional[Tuple[Tuple[int, int], Tuple[int, int]]]:
"""Interactive counting line setup"""
points = []
instructions = [
"1. Click to set line start point",
"2. Click to set line end point",
"3. Press ESC to confirm"
]
def mouse_callback(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
cv2.circle(frame, (x, y), 5, (0, 0, 255), -1)
if len(points) > 1:
cv2.line(frame, points[0], points[1], (0, 255, 0), 2)
cv2.namedWindow("Set Counting Line")
cv2.setMouseCallback("Set Counting Line", mouse_callback)
for i, text in enumerate(instructions):
cv2.putText(frame, text, (10, 30 + i * 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
while True:
cv2.imshow("Set Counting Line", frame)
key = cv2.waitKey(1)
if key == 27 or len(points) >= 2: # ESC key or two points selected
break
cv2.destroyAllWindows()
return (points[0], points[1]) if len(points) >= 2 else None
def main():
"""Main program entry"""
try:
# Open default camera (index 0)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
logger.error("Unable to open camera")
return
# Set camera resolution (optional, adjust as needed)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# Initialize monitor
monitor = TrafficMonitor()
# Read a few frames to allow camera to adjust
for _ in range(5):
ret, frame = cap.read()
if not ret:
logger.error("Failed to capture initial frames from camera")
return
# Set counting line using a frame from the camera
line_points = interactive_line_setup(frame.copy())
if line_points:
monitor.set_counting_line(*line_points)
else:
logger.info("No counting line set, will only perform object detection")
# Main loop
while cap.isOpened():
ret, frame = cap.read()
if not ret:
logger.warning("Camera frame read failed")
break
# Process frame
processed_frame = monitor.process_frame(frame)
# Display results
cv2.imshow("Traffic Monitoring", processed_frame)
key = cv2.waitKey(1)
if key == ord('q') or key == 27: # 'q' or ESC to quit
logger.info("Program terminated by user")
break
elif key == ord('r'): # 'r' to reset counting line
line_points = interactive_line_setup(frame.copy())
if line_points:
monitor.set_counting_line(*line_points)
except KeyboardInterrupt:
logger.info("Program interrupted by user")
except Exception as e:
logger.critical(f"Program exception: {str(e)}", exc_info=True)
finally:
# Release resources
if 'cap' in locals() and cap.isOpened():
cap.release()
cv2.destroyAllWindows()
logger.info("Program exited")
if __name__ == "__main__":
# Check required dependencies
try:
import iot_device_sdk_python
except ImportError:
logger.error("Huawei IoT SDK not found, please install: pip install iot_device_sdk_python")
sys.exit(1)
# Ignore warning messages
warnings.filterwarnings("ignore", category=UserWarning)
main()为我修改这个代码改为画两条线,第一个点和第二个点是第一条线,第三个点和第四个点是第二条线,同时计算第一个点的正向行走人数-第二个点正向行走的绝对值,第二个点-第一个点正向行走的绝对值,第一条线正、逆向行走人数,总人数。第二条线正、逆向行走人数,总人数,尽量减少源代码的修改,MQTT协议传送部分不要过大改动
最新发布