PiscTrace基于YOLO追踪算法的物体速度检测系统详解
一、YOLO目标检测系统介绍
YOLO(You Only Look Once)是一种革命性的实时目标检测算法,其最新版本YOLOv8在精度和速度上都达到了业界领先水平。相比传统的两阶段检测器(RCNN系列),YOLO采用单阶段检测方式,将目标检测视为回归问题,直接在图像网格上预测边界框和类别概率。
YOLO的核心优势:
-
实时性能:处理速度可达30-60FPS
-
端到端训练:单一卷积网络同时完成定位和分类
-
多尺度预测:通过不同尺寸的特征图检测不同大小的物体
-
丰富的预训练模型:支持从Nano到XLarge多种规模模型
在我们的速度检测系统中,YOLO负责提供准确的物体检测和初步的追踪ID分配,为后续速度计算奠定基础。
二、追踪算法原理
虽然代码中没有明确使用特定追踪算法(如DeepSORT、ByteTrack等),但YOLOv8内置了基于BoT-SORT的追踪功能。这类算法通常包含以下关键组件:
-
检测阶段:使用YOLO获取物体边界框
-
特征提取:为每个检测框提取外观特征
-
数据关联:使用匈牙利算法匹配当前检测与已有轨迹
-
轨迹管理:处理新物体出现、短暂消失等情况
追踪算法的核心是为同一物体在不同帧中保持相同的ID,这对速度计算的连续性至关重要。
三、系统架构与实现思路
1. 整体架构设计
系统工作流程:
-
输入视频帧
-
YOLO进行物体检测与追踪
-
提取检测框和追踪ID
-
计算中心点位移
-
估算瞬时速度和平均速度
-
可视化结果显示
2. 关键问题解决思路
问题1:如何准确计算物体速度?
-
解决方案:通过追踪ID关联连续帧中的同一物体,计算中心点位移
-
像素到实际单位的转换:假设1像素=0.1厘米(实际应用需校准)
-
速度计算:
速度 = 位移 × 帧率
问题2:如何平滑速度数据?
-
解决方案:采用滑动窗口平均法(30帧窗口)
-
使用
collections.deque
实现高效的历史数据存储
问题3:如何有效可视化?
-
为每个ID分配固定颜色
-
大号字体显示关键信息
-
中心点标记+位移/速度数据显示
四、核心代码解析
1. 位移与速度计算
def calculate_displacement_and_speed(self, current_pos, prev_pos):"""计算两帧之间的位移和瞬时速度"""if prev_pos is None: # 第一帧无历史数据return 0.0, 0.0# 计算像素位移dx = current_pos[0] - prev_pos[0]dy = current_pos[1] - prev_pos[1]# 转换为厘米(假设1像素=0.1厘米)displacement = np.sqrt(dx**2 + dy**2) * 0.1# 计算瞬时速度(厘米/秒)speed = displacement * self.frame_ratereturn displacement, speed
2. 移动平均速度实现
def calculate_average_speed(self, track_id, current_speed):"""计算指定ID的30帧移动平均速度"""# 初始化历史记录队列if track_id not in self.speed_history:self.speed_history[track_id] = deque(maxlen=self.window_size)# 添加当前速度self.speed_history[track_id].append(current_speed)# 计算平均值return np.mean(self.speed_history[track_id]) if self.speed_history[track_id] else 0.0
3. 可视化渲染
def obj_exe(self, im0, results):"""主执行函数:处理帧并添加可视化元素"""# 提取检测结果self.extract_results(results)if self.track_ids: # 仅处理有追踪ID的物体for box, cls, track_id in zip(self.boxes, self.clss, self.track_ids):# 计算中心点centroid = (int((box[0]+box[2])//2, int((box[1]+box[3])//2)# 获取或生成专属颜色color = self.track_colors.setdefault(track_id, self.generate_random_color())# 计算运动信息prev_pos = self.prev_positions.get(track_id)displacement, instant_speed = self.calculate_displacement_and_speed(centroid, prev_pos)avg_speed = self.calculate_average_speed(track_id, instant_speed)# 更新位置记录self.prev_positions[track_id] = centroid# 可视化渲染self._draw_visualization(centroid, color, cls, track_id, displacement, avg_speed)return self.im0
五、完整系统代码
import cv2
import numpy as np
import random
from collections import deque
from ultralytics.utils.plotting import Annotatorclass TrackingFlowVisualizer:"""物体运动追踪可视化类,用于计算并显示物体位移和速度(带30帧平均速度计算)"""def __init__(self, line_thickness=2, point_size=6):"""初始化追踪可视化器参数:line_thickness (int): 文字和圆圈的线宽point_size (int): 中心点的大小"""# 可视化参数self.tf = line_thickness * 3 # 增大线宽以适应大号文字self.point_size = point_size * 3 # 增大中心点尺寸self.frame_rate = 30 # 帧率用于速度计算 (1像素=0.1厘米)self.font_scale = 4.0 # 字体放大10倍(原为0.5)self.font = cv2.FONT_HERSHEY_SIMPLEX # 字体类型self.window_size = 30 # 平均速度计算窗口大小(帧数)# 初始化变量self.im0 = None # 当前帧图像self.imw = 0 # 图像宽度self.imh = 0 # 图像高度self.annotator = None # 标注工具self.track_ids = None # 追踪ID列表self.boxes = None # 边界框坐标self.clss = None # 类别ID列表self.names = None # 类别名称字典# 存储每个track_id的颜色和位置历史self.track_colors = {} # {track_id: (B,G,R)}self.prev_positions = {} # {track_id: (x,y)}self.speed_history = {} # {track_id: deque([speed1, speed2, ...])}def extract_results(self, results):"""从YOLO结果中提取追踪信息参数:results (list): YOLO返回的检测结果列表"""# 提取边界框坐标(左上角x1y1, 右下角x2y2)self.boxes = results[0].boxes.xyxy.cpu()# 提取物体类别IDself.clss = results[0].boxes.cls.cpu().tolist()# 提取追踪ID(如果有)if results[0].boxes.id is not None:self.track_ids = results[0].boxes.id.int().cpu().tolist()# 提取类别名称字典if hasattr(results[0], 'names'):self.names = results[0].namesdef generate_random_color(self):"""生成随机高饱和度颜色(BGR格式)返回:tuple: (B,G,R)颜色值"""hue = random.randint(0, 179) # 随机色调(0-179)saturation = 255 # 最大饱和度brightness = 255 # 最大亮度# 生成HSV颜色并转换为BGRhsv = np.array([[[hue, saturation, brightness]]], dtype=np.uint8)bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)return tuple(bgr[0][0].tolist())def calculate_displacement_and_speed(self, current_pos, prev_pos):"""计算位移和速度参数:current_pos (tuple): 当前帧中心点(x,y)prev_pos (tuple): 上一帧中心点(x,y)返回:tuple: (位移(厘米), 速度(厘米/秒))"""if prev_pos is None: # 如果是第一帧检测到该物体return 0.0, 0.0# 计算x和y方向的位移dx = current_pos[0] - prev_pos[0]dy = current_pos[1] - prev_pos[1]# 计算总位移(勾股定理)displacement = np.sqrt(dx ** 2 + dy ** 2)*0.1 # 单位:像素/厘米# 计算瞬时速度(位移×帧率)speed = displacement * self.frame_rate # 单位:厘米/秒return displacement, speeddef calculate_average_speed(self, track_id, current_speed):"""计算30帧移动平均速度参数:track_id (int): 物体追踪IDcurrent_speed (float): 当前帧计算的瞬时速度返回:float: 30帧平均速度"""# 初始化该track_id的速度历史队列if track_id not in self.speed_history:self.speed_history[track_id] = deque(maxlen=self.window_size)# 添加当前速度到历史记录self.speed_history[track_id].append(current_speed)# 计算平均速度(当有足够历史数据时)if len(self.speed_history[track_id]) > 0:return np.mean(self.speed_history[track_id])else:return 0.0def obj_exe(self, im0, results):"""主执行函数:计算并可视化物体运动信息(带30帧平均速度)参数:im0 (ndarray): 输入图像帧results (list): YOLO检测结果返回:ndarray: 添加了可视化信息的图像帧"""self.im0 = im0self.imw, self.imh = im0.shape[1], im0.shape[0] # 获取图像尺寸# 从结果中提取检测信息self.extract_results(results)# 初始化标注工具self.annotator = Annotator(self.im0, self.tf, None)if self.track_ids is not None: # 如果有追踪IDfor box, cls, track_id in zip(self.boxes, self.clss, self.track_ids):# 计算当前帧物体中心点centroid = (int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2))# 为每个track_id分配固定颜色if track_id not in self.track_colors:self.track_colors[track_id] = self.generate_random_color()color = self.track_colors[track_id]# 获取上一帧位置并计算运动信息prev_pos = self.prev_positions.get(track_id, None)displacement, instant_speed = self.calculate_displacement_and_speed(centroid, prev_pos)# 计算30帧平均速度avg_speed = self.calculate_average_speed(track_id, instant_speed)# 更新位置信息self.prev_positions[track_id] = centroid# 绘制中心点(放大尺寸)cv2.circle(self.im0, centroid, self.point_size, color, -1)# 准备显示的位移和速度文本text_displacement = f"D: {displacement:.1f} cm"text_speed = f"S: {avg_speed:.1f} cm/s" # 显示平均速度# 计算文本尺寸用于精确定位(text_width, _), _ = cv2.getTextSize(text_displacement, self.font, self.font_scale, self.tf)# 确定文本位置(中心点上方和下方)text_pos_displacement = (centroid[0] - text_width // 2, centroid[1] - self.point_size - 10)text_pos_speed = (centroid[0] - text_width // 2, centroid[1] + self.point_size + 50)# 绘制位移文本(大号字体)cv2.putText(self.im0, text_displacement, text_pos_displacement,self.font, self.font_scale, color, self.tf, cv2.LINE_AA)# 绘制平均速度文本(大号字体)cv2.putText(self.im0, text_speed, text_pos_speed,self.font, self.font_scale, color, self.tf, cv2.LINE_AA)# 绘制物体类别和ID信息id_text = f"{self.names[cls]} ID:{track_id}"(id_width, id_height), _ = cv2.getTextSize(id_text, self.font, self.font_scale, self.tf)cv2.putText(self.im0, id_text,(centroid[0] - id_width // 2, centroid[1] - self.point_size - id_height - 20),self.font, self.font_scale, color, self.tf, cv2.LINE_AA)return self.im0
结语
本文详细介绍的基于YOLO的物体速度检测系统,结合了现代目标检测、多目标追踪和运动分析技术,构建了一个完整的解决方案。系统核心优势在于:
-
实时性能优异
-
实现相对简单
-
扩展性强
-
可视化直观
开发者可以根据具体应用场景调整参数和扩展功能,将其应用于各种需要物体运动分析的领域。随着计算机视觉技术的不断发展,这类系统的精度和实用性还将持续提高。
对 PiscTrace or PiscCode感兴趣?更多精彩内容请移步官网看看~🔗 PiscTrace