PiscTrace基于YOLO追踪算法的物体速度检测系统详解

一、YOLO目标检测系统介绍

YOLO(You Only Look Once)是一种革命性的实时目标检测算法,其最新版本YOLOv8在精度和速度上都达到了业界领先水平。相比传统的两阶段检测器(RCNN系列),YOLO采用单阶段检测方式,将目标检测视为回归问题,直接在图像网格上预测边界框和类别概率。

YOLO的核心优势

  • 实时性能:处理速度可达30-60FPS

  • 端到端训练:单一卷积网络同时完成定位和分类

  • 多尺度预测:通过不同尺寸的特征图检测不同大小的物体

  • 丰富的预训练模型:支持从Nano到XLarge多种规模模型

在我们的速度检测系统中,YOLO负责提供准确的物体检测和初步的追踪ID分配,为后续速度计算奠定基础。

二、追踪算法原理

虽然代码中没有明确使用特定追踪算法(如DeepSORT、ByteTrack等),但YOLOv8内置了基于BoT-SORT的追踪功能。这类算法通常包含以下关键组件:

  1. 检测阶段:使用YOLO获取物体边界框

  2. 特征提取:为每个检测框提取外观特征

  3. 数据关联:使用匈牙利算法匹配当前检测与已有轨迹

  4. 轨迹管理:处理新物体出现、短暂消失等情况

追踪算法的核心是为同一物体在不同帧中保持相同的ID,这对速度计算的连续性至关重要。

三、系统架构与实现思路

1. 整体架构设计

系统工作流程:

  1. 输入视频帧

  2. YOLO进行物体检测与追踪

  3. 提取检测框和追踪ID

  4. 计算中心点位移

  5. 估算瞬时速度和平均速度

  6. 可视化结果显示

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_rate
    
    return 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 Annotator


class 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()
        # 提取物体类别ID
        self.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].names

    def generate_random_color(self):
        """
        生成随机高饱和度颜色(BGR格式)

        返回:
            tuple: (B,G,R)颜色值
        """
        hue = random.randint(0, 179)  # 随机色调(0-179)
        saturation = 255  # 最大饱和度
        brightness = 255  # 最大亮度
        # 生成HSV颜色并转换为BGR
        hsv = 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, speed

    def calculate_average_speed(self, track_id, current_speed):
        """
        计算30帧移动平均速度

        参数:
            track_id (int): 物体追踪ID
            current_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.0

    def obj_exe(self, im0, results):
        """
        主执行函数:计算并可视化物体运动信息(带30帧平均速度)

        参数:
            im0 (ndarray): 输入图像帧
            results (list): YOLO检测结果

        返回:
            ndarray: 添加了可视化信息的图像帧
        """
        self.im0 = im0
        self.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:  # 如果有追踪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))

                # 为每个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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值