多目标跟踪算法之SORT

2023-11-04

本文首发于微信公众号【DeepDriving】,欢迎关注。

简介

SORT2016年发表的一篇文章《Simple Online and Realtime Tracking》中提出的一个经典的多目标跟踪算法,该算法结合常用的卡尔曼滤波器和匈牙利匹配算法实现了一个简单的在线多目标跟踪框架。由于其超简单的设计,SORT可以以260 Hz的更新速率实现多目标跟踪,远超当时其它的目标跟踪算法。

论文地址:https://arxiv.org/abs/1602.00763

代码地址:https://github.com/abewley/sort

具体实现

目标检测

SORT是一种采用Tracking-by-Detection策略的目标跟踪算法,也就是说算法的输入数据来源于目标检测器,其本身是不参与目标检测过程的。作者在论文里对比了以Faster-RCNNACFPASCAL VOC数据集上的行人检测结果作为MDPSORT跟踪算法的输入,得出的结论是目标检测结果的好坏直接决定了目标跟踪的性能,使用最好的目标检测器会得到最好的跟踪效果

这也告诉我们一个道理:解决问题要从源头开始。如果不从源头提升目标检测算法的性能,花再多时间去提升目标跟踪的性能可能都是徒劳。

状态估计模型

为了在帧与帧之间传递目标的状态信息,作者对每个目标采用一个独立于其他目标和相机运动的线性恒速模型进行建模,每个目标的状态被建模为一个向量:

x = [ u v s r u ˙ v ˙ s ˙ ] T \mathbf{x} =\begin{bmatrix} u & v & s & r & \dot{u} & \dot{v} & \dot{s} \end{bmatrix}^T x=[uvsru˙v˙s˙]T

其中 u u u v v v分别表示目标中心点的横、纵坐标, s s s r r r分别表示目标边界框的面积和长宽比, u ˙ \dot{u} u˙ v ˙ \dot{v} v˙ s ˙ \dot{s} s˙分别代表 u u u v v v s s s的变化率。在跟踪过程中,当一个检测结果被关联到一个目标的时候,检测到的边界框(观测值)就被用来更新卡尔曼滤波器的状态,变化率 u ˙ \dot{u} u˙ v ˙ \dot{v} v˙ s ˙ \dot{s} s˙也可以通过卡尔曼滤波器推导出来。如果没有检测结果与目标进行关联,那么目标的状态则只是简单地通过卡尔曼滤波器进行预测得到(没有观测值进行校正)。

如果对卡尔曼滤波器不了解,可以看一下我之前整理的资料,里面有卡尔曼滤波器的详细推导过程:

深入理解卡尔曼滤波器(1): 背景知识

深入理解卡尔曼滤波器(2): 一维卡尔曼滤波器

深入理解卡尔曼滤波器(3):多维卡尔曼滤波器

数据关联

给已存在的目标分配当前帧检测到的边界框时,目标在当前帧中的边界框是基于之前的状态预测出来的。所有当前帧检测的边界框与已存在目标做预测得到的边界框通过计算它们之间的IOU来求代价矩阵,然后用匈牙利算法求解最优匹配结果。
如果检测边界框与预测边界框匹配成功且它们之间的IOU值大于阈值 I O U m i n IOU_{min} IOUmin,那么就认为它们是一对有效的匹配对,否则是无效的。匹配成功后,就可以基于检测的边界框对目标状态进行更新了。

作者发现采用IOU作为距离度量进行匹配可以隐式地解决由于传递目标引起的短期遮挡的问题。具体来说,当一个目标被另一个物体覆盖时,检测器只能检测到这个遮挡物体而检测不到被遮挡物体,因为IOU距离有利于具有相似比例的检测框。这样的话遮挡物体可以正常被分配检测框去更新状态,而被遮挡物体则不会受误分配带来的影响,因为当前没有检测框会分配给它。

如果对IOU不了解,可以参考我之前的文章:

一文读懂目标检测中的各种IoU损失函数

跟踪标识的创建和删除

当一个目标出现在图像中的时候,我们需要为其创建一个全局唯一的身份标识(ID);反之,当目标消失的时候就要销毁它的跟踪信息。

对于一个检测的边界框,如果它与所有当前跟踪目标的IOU小于阈值 I O U m i n IOU_{min} IOUmin,那么就认为它还是一个从未被跟踪的目标,需要为它创建一个卡尔曼跟踪器。跟踪器的初始状态向量由检测框的几何信息得到,变化率设置为零。由于此时变化率是无法观测的,所以变化率的协方差设置为一个比较大的值(10000),表示其不确定度非常大。另外,一个跟踪器创建后并不是马上生效而是需要经历一个“试用期”,在这个过程中,跟踪的目标连续几帧都与检测的边界框匹配成功才会认为这个跟踪器是有效的,这样做的目的是为了抑制对假阳性的检测结果进行跟踪。

如果连续 T L o s t T_{Lost} TLost帧都没有与之匹配的检测框,那么这个跟踪器的信息就会被删除。这么做有两个目的,一是不让大量的跟踪器占用内存,另外一个目的是减少因长时间没有检测框进行校正而仅靠预测得到的跟踪结果(这种预测是极不准确的)。在论文中,作者出于两个原因将 T L o s t T_{Lost} TLost设置为1

  1. 恒速模型对目标真实的运动状态的预测效果不好;
  2. 算法仅处理帧与帧之间的目标跟踪问题,目标的重识别则不在此工作的范畴。

代码分析

算法整体流程

SORT算法的处理流程非常简单,感兴趣的可以去看源码。下图是我整理的算法流程图:

对当前帧的检测结果Detections和已存在的目标Tracker使用匈牙利算法进行匹配会出现三种情况:

  1. 检测结果Detection未匹配成功,那么就以该边界框的几何信息为初始状态去创建一个Tracker;
  2. 检测结果DetectionTracker匹配成功,那么就以该检测结果为观测值更新Tracker的状态;
  3. 未匹配的Tracker,前面说到 T L o s t T_{Lost} TLost设置为1,也就是只要一帧没匹配上该Tracker就会被删除。

卡尔曼滤波器

SORT的代码里创建了一个类KalmanBoxTracker用于对卡尔曼滤波器的状态进行管理,卡尔曼滤波器使用的是filterpy.kalman包中的KalmanFilter,官方文档地址为:https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html。

1. 滤波器初始化

卡尔曼滤波器的状态向量 x \mathbf{x} x和观测向量 z \mathbf{z} z分别为

x = [ u v s r u ˙ v ˙ s ˙ ] T \mathbf{x} =\begin{bmatrix} u & v & s & r & \dot{u} & \dot{v} & \dot{s} \end{bmatrix}^T x=[uvsru˙v˙s˙]T

z = [ u v s r ] T \mathbf{z} =\begin{bmatrix} u & v & s & r \end{bmatrix}^T z=[uvsr]T

卡尔曼滤波器初始化时需要初始化下面几个矩阵:

  def __init__(self,bbox):
    # 创建卡尔曼滤波器时需设置状态向量和观测向量的维度
    self.kf = KalmanFilter(dim_x=7, dim_z=4) 
    # 状态转移矩阵
    self.kf.F = np.array([[1, 0, 0, 0, 1, 0, 0],
                          [0, 1, 0, 0, 0, 1, 0],
                          [0, 0, 1, 0, 0, 0, 1],
                          [0, 0, 0, 1, 0, 0, 0],
                          [0, 0, 0, 0, 1, 0, 0],
                          [0, 0, 0, 0, 0, 1, 0],
                          [0, 0, 0, 0, 0, 0, 1]])
    # 观测矩阵
    self.kf.H = np.array([[1, 0, 0, 0, 0, 0, 0],
                          [0, 1, 0, 0, 0, 0, 0],
                          [0, 0, 1, 0, 0, 0, 0],
                          [0, 0, 0, 1, 0, 0, 0]])
    # 测量噪声协方差矩阵
    self.kf.R[2:,2:] *= 10.
    # 状态协方差矩阵,变化率不可观测所以设置一个较大值表示其较大的不确定性
    self.kf.P[4:,4:] *= 1000. 
    self.kf.P *= 10.
    # 过程噪声协方差矩阵
    self.kf.Q[-1,-1] *= 0.01
    self.kf.Q[4:,4:] *= 0.01
    #状态向量前面四个值用bbox初始化,变化率设置为0
    self.kf.x[:4] = convert_bbox_to_z(bbox)
2. 滤波器生命周期管理

滤波器生命周期的管理是通过几个变量来实现的,KalmanBoxTracker创建的时候会初始化几个变量:

self.time_since_update = 0
self.hits = 0
self.hit_streak = 0

如果Tracker匹配成功,就会更新这几个变量的状态:

def update(self, bbox):
    self.time_since_update = 0
    self.hit_streak += 1

如果Tracker做了一次预测,同样会更新这几个变量的状态:

def predict(self):
    if (self.time_since_update > 0):
        self.hit_streak = 0
    self.time_since_update += 1

time_since_update表示距离上一次带观测值更新滤波器状态过去了多久,hit_streak表示Tracker连续匹配成功并更新的次数,一旦调用predict()函数对当前帧做了预测,time_since_update就加一,表示其已经对当前帧做过一次预测了。

在算法的处理类Sort中,会对Tracker的这几个变量做判断:

  1. 一个匹配成功的Tracker,需要判断其是否还在“试用期”,只有连续几帧都匹配成功才能使用它的跟踪信息:
if (trk.time_since_update < 1) and 
    (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
    ret.append(np.concatenate((d, [trk.id+1])).reshape(1, -1))
  1. 如果下一帧Tracker未匹配成功,该Tracker就会被删除:
if (trk.time_since_update > self.max_age):
    self.trackers.pop(i)

总结

SORT目标跟踪算法仅使用卡尔曼滤波器和匈牙利算法解决帧与帧之间的状态预测和数据关联问题,跟踪的效果高度依赖于目标检测结果的好坏,算法整体设计非常简单,在速度和精度上取得较好的平衡,主要体现一个“快”字。当然,速度提升必然导致精度损失,SORT的缺点在于仅仅使用物体的边界框进行跟踪而忽略其表面特征,在复杂的场景中效果会比较差。另外,SORT没有目标重识别过程,一旦目标丢失就需要重新创建跟踪器去更新状态(一帧未匹配成功就需要重新跟踪),导致同一目标的ID频繁变换。

YOLOv3+SORT实现行人检测与跟踪的效果可以看下面的视频:

行人目标检测与跟踪

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多目标跟踪算法之SORT 的相关文章

  • 调用百度“车牌识别”API接口(go语言实现)

    调用百度的api识别车牌 没有GPU的机器 也没有大量的有标注的车牌数据 所以就只好拿别人现成的车牌识别api接口 具体的识别原理就不说了 只是调别人的接口而已 具体的调用文档百度已经给出 http ai baidu com docs OC
  • LDAP认证的两种方式

    LDAP认证的两种方式 第一种 FastBindLdapAuthenticationHandler 这种认证处理器一般用于DN是由用户名直接组成的 比如 uid u ou dev dc micmiu com dc com 其中 u 就是CA
  • Flutter 打开第三方应用/网页

    需求 打开指定网页 指定应用 这里用到的是一个三方插件 url launcher 5 0 2版本 pubspec ymal文件下dependencies节点增加 例如 dependencies flutter sdk flutter The

随机推荐

  • VSCode字体中英等宽对齐的选择

    关于VSCode字体的选择 配置为中文英文宽度比例符合2 1的 Sarasa Mono SC 更纱黑体 我对字体的需求 首先就要是IiLl10O区分都很明显 再有就是我更偏好等宽字体 Monospaced Font 不能对齐什么的对强迫症太
  • 【蓝桥杯训练】DFS与BFS讲解

    1 DFS 深度优先遍历 理论介绍 DFS属于图算法的一种 是针对图和树的遍历算法 深度优先搜索是图论中的经典算法 利用深度优先搜索算法可以产生目标图的相应拓扑排序表 利用拓扑排序表可以方便的解决很多相关的图论问题 如最大路径问题等等 一般
  • 深入了解Java队列接口

    队列接口 队列接口是 Java 集合框架的一个重要部分 它扩展了 Collection 接口 队列接口表示遵循 先进先出 FIFO 原则的元素集合 队列允许存储重复值 队列的基本特征是元素按特定顺序存储 类似于等待轮流的人群 主要通过添加元
  • Android动态权限申请封装总结

    最近在做公司的项目的时候 在动态权限申请这部分没有使用市面上流行的第三方库 但是在使用的时候发现每次都需要进行onRequestPermissionsResult 的回调 感觉比较的烦躁 想到RxPermission 这些第三方库没有在Ac
  • K8S的DaemonSet部署和安全删除

    1 部署方式 部署DaemonSet 需要执行以下步骤 创建DaemonSet配置文件 您可以使用YAML或JSON格式的文件描述DaemonSet的规格 包括容器镜像 容器端口 Pods标签 节点选择器等 使用kubectl命令将配置文件
  • 海康录像机识别不到硬盘_海康威视硬盘录像机常见问题解决方式

    海康威视客户端 4 01 使用配置相关注意事项 1 安装客户端软件选择单机版还是网络版 目前 4 01 客户端分成 2 个版本 分别是单机版和网络版 单机版即以前的互相独立的分控模式 每个安装客户端的分控点分别独立的对设备进行操 作 客户端
  • linux安装yum报错Unable to locate package yum解决方案

    为什么80 的码农都做不了架构师 gt gt gt 问题 apt get install yum Reading package lists Done Building dependency tree Reading state infor
  • Flutter 实现九宫格抽奖动画效果

    一 本文实现的九宫格抽奖动画效果如下 二 主要分享下怎么一步一步来实现这个效果 源代码地址 布局可以通过GridView轻松实现 只需在数据源的第五个位置插入一个元素用来标识是开始按钮 抽奖动画的实现 需要按顺时针循环选中奖品而且还需要从慢
  • 小程序酷炫动态登录页源码(动态水滴)

    1 页面效果 登陆页面一般都要酷炫好看一点 这里分享一个动态登录页面 页面有三个流动的小水滴 一个水滴放登录框 剩下两个水滴跳转页面和打开弹窗 2 代码内容
  • opencv KCF追踪报错

    tracker gt update dstImage1 roi 刷新ROI的位置 在这一句报错 后来发现update 中的dstimage1没有经过处理 把之前对image进行的 转换颜色空间 二值化 开闭运算 canny运算 又进行一遍
  • ssh无法远程连接ubuntu系统,提示"System is booting up. See pam_nologin(8)"

    问题 使用ssh xshell或者putty 远程连接Linux ubuntu 系统时 提示 System is booting up See pam nologin 8 Connection closing Socket close 无法
  • 浅聊便利蜂

    便利蜂是一家以新型便利店为主体的科技创新零售企业 公司以科技为核心驱动运营 以 品质生活 便利中国 为己任 怀抱 小小幸福 在你身边 的初心 为中国消费者提供优质 健康 安心的产品和高效 便捷 满意的服务 便利蜂由北京自由蜂电子商务有限公司
  • 网络流dinic算法复杂度

    Dinic算法的时间复杂度的理论上界是O N2 M N是结点数 M是边数 但实际上Dinic算法比这个理论上界好得多 如果所有边容量均为1 那么时间复杂度是O min N0 67 M0 5 M 对于二分图最大匹配这样的特殊图 时间复杂度是O
  • antV-G2图标的label如何显示,如何自定义样式

    chart interval position 号位 平均分 这里开始设置label label 平均分 val gt return position middle offset 0 content originData gt return
  • 常见架构对比

    1 单体架构 概念 所有的模块集中在一个项目中 打包到一起并放在一个web容器中运行 适合场景 项目初创期 业务简单且响应要求高 优点 开发 测试 部署运维简单 响应快 缺点 1 资源没法隔离 2 部署周期长 3 可靠性低所有模块都集中在一
  • nestjs:docker build时执行npm install sharp提示downloading libvips socket hang up

    问题 如题 参考 sharp High performance Node js image processing 参考chinese mirror处理 原因 默认是从github上下载libvips库 但是使用socket协议 linux下
  • STM32 进阶教程 6 -  汇编与C混合编程

    前言 在嵌入式开发过程中 有时候为了追求代码性能与效率不得不采用汇编语言来编写代码 但是汇编代码在阅读时表现不如C语言友好 本节给大家介绍一种新的方法 关键代码用汇编语言时行实现 然后用C语言时行接封装 用C语言与汇编语言混合编程的方式 在
  • DELL T420塔式服务器RAID配置及OS安装

    一 DELL T420塔式服务器RAID配置 1 服务器RAID卡配置 开机看到CTRL R的提示后及时按下CTRL R进到阵列卡配置界面 进去之后光标默认就在阵列卡型号上 比如 PERC H310 PERC H710 PERC H710P
  • C++装饰者模式:Decorator Pattern

    设计原则 类应该对扩展开发 对修改关闭 装饰者可以在所委托被装饰者的行为之后加上自己的行为 以达到特定的目的 装饰者模式 动态地将责任附加到对象上 若要扩展功能 装饰者提供了比继承更有弹性的替代方案 装饰者和被装饰者必须是一样的类型 也就是
  • 多目标跟踪算法之SORT

    本文首发于微信公众号 DeepDriving 欢迎关注 简介 SORT是2016年发表的一篇文章 Simple Online and Realtime Tracking 中提出的一个经典的多目标跟踪算法 该算法结合常用的卡尔曼滤波器和匈牙利