基于YOLOv8的目标跟踪——汽车跟踪和计数

2023-09-13 15:42:41

目标跟踪针对的是视频处理,它是目标检测的更高级的应用。目标跟踪要解决的主要问题应该是能够正确识别不同帧之间的同一个目标,而不仅仅是同一类目标。例如,在某个连续的时间段内总是出现张三这个人,目标跟踪可以在这段时间内把张三这个人标记为同一个人,从而实现跟踪其轨迹的目的。

YOLOv8不仅可以实现目标检测和目标分割,还可以实现目标跟踪。它的目标跟踪基于的是BoT-SORT和ByteTrack,而默认的是BoT-SORT算法。在这里,我们不介绍任何原理性的内容,只以一个很简单的例子讲解如何应用YOLOv8进行目标跟踪,以及它所带来的一个附加功能——计数。

我们的实例是对一段高速公路进行车辆跟踪,并分别记录从上至下(驶出)和从下至上(驶入)的各类车辆的数量。对于车辆跟踪,直接应用的是YOLOv8的函数model.track,而计数功能则需要自己实现算法。因为我们这里应用的场景比较简单,所以我们只是设置了一条水平基准线,通过前后两帧同一辆车辆的坐标位置,可以判断出其行驶的方向,当越过基准线时,就计数一次。

下面就给出完整的代码,及其详细解析。

导入模块,其中为了保存不同ID的目标,我们需要用到defaultdict:

import cv2
from ultralytics import YOLO
from collections import defaultdict

加载YOLOv8的预训练模型:

model = YOLO('yolov8l.pt')

加载待处理的视频,并获取相应的视频参数:

cap = cv2.VideoCapture("D:/track/Highway Traffic.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
fNUMS = cap.get(cv2.CAP_PROP_FRAME_COUNT)

最终把结果保存为mp4格式的视频文件:

fourcc = cv2.VideoWriter_fourcc(*'mp4v')
videoWriter = cv2.VideoWriter("D:/track/counting.mp4", fourcc, fps, size)

把目标用矩形框标注:

def box_label(image, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):
    #得到目标矩形框的左上角和右下角坐标
    p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
    #绘制矩形框
    cv2.rectangle(image, p1, p2, color, thickness=1, lineType=cv2.LINE_AA)
    if label:
        #得到要书写的文本的宽和长,用于给文本绘制背景色
        w, h = cv2.getTextSize(label, 0, fontScale=2 / 3, thickness=1)[0]  
        #确保显示的文本不会超出图片范围
        outside = p1[1] - h >= 3
        p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
        cv2.rectangle(image, p1, p2, color, -1, cv2.LINE_AA)     #填充颜色
        #书写文本
        cv2.putText(image,
                label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
                0,
                2 / 3,
                txt_color,
                thickness=1,
                lineType=cv2.LINE_AA)

实现目标跟踪,并完成计数:

# track_history用于保存目标ID,以及它在各帧的目标位置坐标,这些坐标是按先后顺序存储的
track_history = defaultdict(lambda: [])
#车辆的计数变量
vehicle_in = 0
vehicle_out = 0

#视频帧循环
while cap.isOpened():
    #读取一帧图像
    success, frame = cap.read()

    if success:
        #在帧上运行YOLOv8跟踪,persist为True表示保留跟踪信息,conf为0.3表示只检测置信值大于0.3的目标
        results = model.track(frame,conf=0.3, persist=True)
        #得到该帧的各个目标的ID
        track_ids = results[0].boxes.id.int().cpu().tolist()
        #遍历该帧的所有目标
        for track_id, box in zip(track_ids, results[0].boxes.data):
            if box[-1] == 2:   #目标为小汽车
                #绘制该目标的矩形框
                box_label(frame, box, '#'+str(track_id)+' car', (167, 146, 11))
                #得到该目标矩形框的中心点坐标(x, y)
                x1, y1, x2, y2 = box[:4]
                x = (x1+x2)/2
                y = (y1+y2)/2
                #提取出该ID的以前所有帧的目标坐标,当该ID是第一次出现时,则创建该ID的字典
                track = track_history[track_id]
                track.append((float(x), float(y)))  #追加当前目标ID的坐标
                #只有当track中包括两帧以上的情况时,才能够比较前后坐标的先后位置关系
                if len(track) > 1:
                    _, h = track[-2]  #提取前一帧的目标纵坐标
                    #我们设基准线为纵坐标是size[1]-400的水平线
                    #当前一帧在基准线的上面,当前帧在基准线的下面时,说明该车是从上往下运行
                    if h < size[1]-400 and y >= size[1]-400:
                        vehicle_out +=1      #out计数加1
                    #当前一帧在基准线的下面,当前帧在基准线的上面时,说明该车是从下往上运行
                    if h > size[1]-400 and y <= size[1]-400:
                        vehicle_in +=1       #in计数加1
                      
            elif box[-1] == 5:   #目标为巴士
                box_label(frame, box, '#'+str(track_id)+' bus', (67, 161, 255))
                
                x1, y1, x2, y2 = box[:4]
                x = (x1+x2)/2
                y = (y1+y2)/2
                track = track_history[track_id]
                track.append((float(x), float(y)))  # x, y center point
                if len(track) > 1:
                    _, h = track[-2]
                    if h < size[1]-400 and y >= size[1]-400:
                        vehicle_out +=1
                    if h > size[1]-400 and y <= size[1]-400:
                        vehicle_in +=1 
               
            elif box[-1] == 7:   #目标为卡车
                box_label(frame, box, '#'+str(track_id)+' truck', (19, 222, 24))
                
                x1, y1, x2, y2 = box[:4]
                x = (x1+x2)/2
                y = (y1+y2)/2
                track = track_history[track_id]
                track.append((float(x), float(y)))  # x, y center point
                if len(track) > 1:
                    _, h = track[-2]
                    if h < size[1]-400 and y >= size[1]-400:
                        vehicle_out +=1
                    if h > size[1]-400 and y <= size[1]-400:
                        vehicle_in +=1 
              
            elif box[-1] == 3:   #目标为摩托车
                box_label(frame, box,'#'+str(track_id)+' motor', (186, 55, 2))
                
                x1, y1, x2, y2 = box[:4]
                x = (x1+x2)/2
                y = (y1+y2)/2
                track = track_history[track_id]
                track.append((float(x), float(y)))  # x, y center point
                if len(track) > 1:
                    _, h = track[-2]
                    if h < size[1]-400 and y >= size[1]-400:
                        vehicle_out +=1
                    if h > size[1]-400 and y <= size[1]-400:
                        vehicle_in +=1 
        #绘制基准线
        cv2.line(frame, (30,size[1]-400), (size[0]-30,size[1]-400), color=(25, 33, 189), thickness=2, lineType=4)
        #实时显示进、出车辆的数量
        cv2.putText(frame, 'in: '+str(vehicle_in), (595, size[1]-410),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.putText(frame, 'out: '+str(vehicle_out), (573, size[1]-370),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

        cv2.putText(frame, "https://blog.csdn.net/zhaocj", (25, 50),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
      
        cv2.imshow("YOLOv8 Tracking", frame)  #显示标记好的当前帧图像
        videoWriter.write(frame)  #写入保存
     
        if cv2.waitKey(1) & 0xFF == ord("q"):   #'q'按下时,终止运行
            break

    else:  #视频播放结束时退出循环
        break

#释放视频捕捉对象,并关闭显示窗口
cap.release()
videoWriter.release()
cv2.destroyAllWindows()

最终的结果为:

result​​​​​

可以看出YOLOv8很好的完成了任务,同一辆车尽管有时被识别为car,有时又被识别为truck,但它的ID始终是同一个值。如果我们仔细观察结果视频会发现,进出车辆之和要小于最大的ID数。这是因为除了没有被统计进来的车辆(如,左上角和右上角的车辆)外,主要原因还是YOLOv8并没有完整的按顺序标记车辆ID,比如没有标记#6的车辆。

对上面的代码略加修改,完全可以应用到其他场合,如下面的视频:

another

更多推荐

[Docker精进篇] Docker镜像构建和实践 (三)

前言:Docker镜像构建的作用是将应用程序及其依赖打包到一个可移植、自包含的镜像中,以便在不同环境中快速、可靠地部署和运行应用程序。文章目录Docker镜像构建1️⃣是什么?2️⃣为什么?3️⃣镜像构建一、用现有容器构建新镜像二、Dockerfile构建镜像4️⃣总结这篇文章是我的笔记,旨在带您快速入门上手docke

Docker进阶:Docker Compose(容器编排) 管理多容器应用—实战案例演示

Docker进阶:DockerCompose(容器编排)管理多容器应用—实战案例演示一、DockerCompose简介二、DockerCompose安装三、DockerCompose卸载四、DockerCompose核心概念4.1、一文件原则(docker-compose.yml)4.2、服务(service)4.3、

PostgreSQL快速入门 & 与MySQL语法比较

开篇本文可帮助具有MySQL基础的小伙伴对PostgreSQL做一个快速的入门,通过语法之间的差异对比,降低学习成本,同样都是数据库,正所谓触类旁通。模式的概念模式(Schema)表示数据库中的逻辑容器,用于组织和管理数据库对象,如表、视图、索引等。一个模式可以看作是一组相关对象的命名空间。模式不同于表,它更多地是对数

【Docker系列】Docker-核心概念/常用命令与项目部署实践

写在前面Docker是一种开源的容器化技术,它允许开发者将应用程序及其依赖项打包到一个轻量级、可移植的容器中,从而实现快速部署和高效运行。Docker的核心概念包括镜像、容器、仓库等。本文将详细介绍Docker的基本概念、安装方法以及常用命令。一、Docker基本概念介绍3个基础概念:镜像(Image)容器(Conta

JVM——2.JVM的内存结构

这篇文章来讲一下JVM中的重点之一——JVM的内存结构目录1.概述2.程序计数器3.虚拟机栈3.1栈的介绍3.2栈的相关问题3.3栈内存溢出问题3.4线程运行诊断4.本地方法栈5.堆5.1堆的概述5.2堆内存溢出问题5.3堆内存诊断6.方法区6.1方法区的概述6.2方法区的内存溢出问题7.运行时常量池8.直接内存8.1

在 Swift 中使用 async let 并发运行后台任务

文章目录前言长期运行的任务阻塞了UI使用async/await在后台执行任务在后台执行多个任务使用"asynclet"下载多个文件结论前言Async/await语法是在Swift5.5引入的,在WWDC2021中的Meetasync/awaitinSwift对齐进行了介绍。它是编写异步代码的一种更可读的方式,比调度队列

基于yolov5的交通标志牌的目标检测研究设计——思路及概念

有需要项目的可以私信博主!!!!!一、选题的目的、意义及研究现状(1)选题的目的和意义随着人们对道路安全性的重视和城市交通量的不断增加,交通标志牌作为道路交通安全的重要组成部分之一,扮演着十分重要的角色。然而,交通标志牌的数量庞大、种类繁多,人工巡视的方式存在效率低、漏检率高等问题,对道路交通的安全性带来了一定的隐患。

Vue模板语法(下)

一.事件处理器什么是事件处理器建立一个HTML编写事件处理器测试结果二.表单的综合案例什么是表单综合案例建立一个HTML来编写表单案例测试结果三.局部组件什么是组件通信自定义组件测试结果组件通信-父传子测试结果组件通信-子传父测试结果一.事件处理器什么是事件处理器事件处理器是一种用于响应和处理用户交互事件的机制。在We

zookeeper最基础教程

文章目录一、简介1、工作机制2、特点3、数据结构4、应用场景5、选举机制二、软件安装1、单机版安装2、集群安装3、配置参数解读(zoo.cfg)4、ZK集群启动脚本三、命令行操作1、语法2、使用3、节点相关4、监听器原理5、节点删除与查看三、写数据流程一、简介1、工作机制官方地址:https://zookeeper.a

SpringMVC之JSON数据返回与异常处理机制---全方面讲解

一,JSON数据返回的理解在SpringMVC中,当需要将数据以JSON格式返回给客户端时,可以使用@ResponseBody注解或@RestController注解将Controller方法的返回值直接转化为JSON格式并返回。这使得开发者可以方便地将Java对象转换为JSON,并通过HTTP响应返回给客户端。Spr

TCP协议

文章目录TCP协议1.TCP协议段格式(1)2个核心问题(解包与分用)(2)TCP的可靠性2.确认应答(ACK)机制3.16位窗口大小4.6位标志位(1)ACK(2)SYN(3)FIN(4)PSH(5)RST(6)URG5.超时重传机制6.连接管理机制6.0TCP协议通讯流程6.1三次握手(1)前置问题(2)细节问题(

热文推荐