精华内容
下载资源
问答
  • 跟踪
    千次阅读
    2022-02-07 22:23:09

    OpenCV 对象跟踪

    这篇文章使用 OpenCV 中内置的八种不同的对象跟踪算法,实现对物体的跟踪。

    首先,介绍一下8种跟踪算法。

    然后,演示如何使用OpenCV实现这些跟踪算法。

    最后,对本文做总结。

    OpenCV 对象跟踪器

    OpenCV 八种对象跟踪器:

    • BOOSTING Tracker:基于用于驱动 Haar 级联 (AdaBoost) 背后的机器学习的相同算法,但与 Haar 级联一样,已有十多年的历史。这个跟踪器很慢,而且效果不太好。仅出于遗留原因和比较其他算法而感兴趣。 (最低 OpenCV 3.0.0)
    • MIL Tracker:比 BOOSTING 跟踪器更准确,但在报告失败方面做得很差。 (最低 OpenCV 3.0.0)
    • KCF 跟踪器:内核化相关过滤器。比 BOOSTING 和 MIL 更快。与 MIL 和 KCF 类似,不能很好地处理完全遮挡。 (最低 OpenCV 3.1.0)
    • CSRT Tracker:判别相关滤波器(具有通道和空间可靠性)。往往比 KCF 更准确,但速度稍慢。 (最低 OpenCV 3.4.2)
    • MedianFlow Tracker:很好地报告失败;但是,如果运动中的跳跃太大,例如快速移动的物体,或者外观快速变化的物体,模型就会失败。 (最低 OpenCV 3.0.0)
    • TLD 跟踪器:我不确定 TLD 跟踪器的 OpenCV 实现或实际算法本身是否存在问题,但 TLD 跟踪器极易出现误报。我不推荐使用这个 OpenCV 对象跟踪器。 (最低 OpenCV 3.0.0)
    • MOSSE Tracker:非常非常快。不如 CSRT 或 KCF 准确,但如果您需要纯粹的速度,这是一个不错的选择。 (最低 OpenCV 3.4.1)
    • GOTURN Tracker:OpenCV 中唯一基于深度学习的目标检测器。它需要额外的模型文件才能运行(本文不会涉及)。我最初的实验表明,尽管据说它可以很好地处理查看变化,但使用起来还是有点痛苦(尽管我最初的实验并没有证实这一点)。我将尝试在以后的帖子中介绍它,但与此同时,请看一下 Satya 的文章。 (最低 OpenCV 3.2.0)

    个人建议:

    • 当需要更高的对象跟踪精度并且可以容忍较慢的 FPS 吞吐量时,请使用 CSRT
    • 当需要更快的 FPS 吞吐量但可以处理稍低的对象跟踪精度时使用 KCF
    • 当需要纯粹的速度时使用 MOSSE

    物体跟踪

    在开始算法之前,先写辅助方法和类。

    fps类:

    import datetime
    
    class FPS:
    	def __init__(self):
    		# 定义开始时间、结束时间和总帧数
    		self._start = None
    		self._end = None
    		self._numFrames = 0
    
    	def start(self):
    		# 开始计时
    		self._start = datetime.datetime.now()
    		return self
    
    	def stop(self):
    		# 停止计时
    		self._end = datetime.datetime.now()
    
    	def update(self):
    		# 增加在开始和结束间隔期间检查的总帧数
    		self._numFrames += 1
    
    	def elapsed(self):
    		# 返回开始和结束间隔之间的总秒数
    		return (self._end - self._start).total_seconds()
    
    	def fps(self):
    		# 计算每秒帧数
    		return self._numFrames / self.elapsed()
    

    请打开一个新文件,将其命名为 object_tracker.py ,定义resize方法,等比例缩放图片。

    import cv2
    from fps import FPS
    def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
        # 初始化要调整大小的图像的尺寸并抓取图像大小
        dim = None
        (h, w) = image.shape[:2]
        # 如果宽高都为None,则返回原图
        if width is None and height is None:
            return image
        # 检查宽度是否为None
        if width is None:
            # 计算高度的比例并构造尺寸
            r = height / float(h)
            dim = (int(w * r), height)
        # 否则,高度为 None
        else:
    		# 计算宽度的比例并构造尺寸
            r = width / float(w)
            dim = (width, int(h * r))
        resized = cv2.resize(image, dim, interpolation=inter)
        return resized
    

    定义全局变量:

    videos = 0
    tracker_type = 'kcf'
    

    我们的命令行参数包括:

    videos:输入视频文件或者摄像头的ID。

    tracker_type:跟踪器的类型,接下来的代码定义了跟踪器列表。

    接下来定义不同类型的跟踪器:

    # 提取 OpenCV 版本信息
    (major, minor) = cv2.__version__.split(".")[:2]
    # 如果我们使用 OpenCV 3.2 或之前版本,我们可以使用特殊的工厂函数来创建我们的对象跟踪器
    if int(major) == 3 and int(minor) < 3:
        tracker = cv2.Tracker_create(tracker_type)
    # 否则,对于 OpenCV 3.3 或更新版本,我们需要显式调用对应的对象跟踪器构造函数:
    else:
        # 初始化一个字典,将字符串映射到其对应的 OpenCV 对象跟踪器实现
        OPENCV_OBJECT_TRACKERS = {
            "csrt": cv2.TrackerCSRT_create,
            "kcf": cv2.TrackerKCF_create,
            "boosting": cv2.legacy.TrackerBoosting_create,
            "mil": cv2.TrackerMIL_create,
            "tld": cv2.legacy.TrackerTLD_create,
            "medianflow": cv2.legacy.TrackerMedianFlow_create,
            "mosse": cv2.legacy.TrackerMOSSE_create
        }
        # 使用我们的 OpenCV 对象跟踪器对象字典获取适当的对象跟踪器
        tracker = OPENCV_OBJECT_TRACKERS[tracker_type]()
    

    在OpenCV 3.3之前,必须使用cv2.Tracker_create创建跟踪器对象,并传递跟踪器名称的大写字符串。

    对于OpenCV 3.3+,可以使用各自的函数调用创建每个跟踪器,例如cv2.TrackerCSRT_create。字典OPENCV_OBJECT_TRACKERS包含8个内置OpenCV对象跟踪器中的七个。它将对象跟踪器命令行参数字符串(键)与实际的OpenCV对象跟踪器函数(值)进行映射。

    # 初始化我们要追踪的物体的边界框坐标
    initBB = None
    vs = cv2.VideoCapture(videos)
    fps = None
    

    initBB初始化为None,此变量将保存我们使用鼠标选择的对象的边界框坐标。

    接下来,初始化VideoCapture对象和FPS计数器。

    让我们开始循环来自视频流的帧:

    # 循环播放视频流中的帧
    while True:
        # 抓取当前帧。
        (grabbed, frame) = vs.read()
        if not grabbed:
            break
        # 调整框架大小并获取框架尺寸。
        frame = resize(frame, width=500)
        (H, W) = frame.shape[:2]
    
        # 检查是否正在跟踪一个对象
        if initBB is not None:
            # 抓取物体的新边界框坐标
            (success, box) = tracker.update(frame)
            # 检查跟踪是否成功
            if success:
                (x, y, w, h) = [int(v) for v in box]
                cv2.rectangle(frame, (x, y), (x + w, y + h),
                              (0, 255, 0), 2)
            # 更新 FPS 计数器
            fps.update()
            fps.stop()
            # 初始化在框架上显示的信息集
            info = [
                ("Tracker", tracker_type),
                ("Success", "Yes" if success else "No"),
                ("FPS", "{:.2f}".format(fps.fps())),
            ]
            # 遍历信息元组并将它们绘制在框架上
            for (i, (k, v)) in enumerate(info):
                text = "{}: {}".format(k, v)
                cv2.putText(frame, text, (10, H - ((i * 20) + 20)),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
    
        # 显示输出帧
        cv2.imshow("Frame", frame)
        key = cv2.waitKey(1) & 0xFF
    

    抓住一个帧,如果获取不到帧,则退出。

    为了使对象跟踪算法能够更快地处理帧,我们将输入帧的大小调整为500像素。

    然后输出框架的高和宽。

    如果已选择对象,则需要更新对象的位置。 update方法将定位对象的新位置并返回成功布尔值和对象的边界框。

    如果成功,我们在框架上绘制新的,更新的边界框位置。

    更新FPS。

    初始化显示的文本信息列表。随后,绘制到frame上。

    显示输出帧。

       # 使用's'键选择一个边界框来跟踪
        if key == ord("s"):
            # 选择跟踪的对象的边界框(选择 ROI 后按 ENTER 或 SPACE)
            initBB = cv2.selectROI("Frame", frame, fromCenter=False,
                                   showCrosshair=True)
            # 使用提供的边界框坐标启动 OpenCV 对象跟踪器,然后也启动 FPS 吞吐量估计器
            tracker.init(frame, initBB)
            fps = FPS().start()
        # 如果 `q` 键被按下,则退出循环
        elif key == ord("q"):
            break
    vs.release()
    cv2.destroyAllWindows()
    
    

    按下“s”键时,使用cv2.selectROI“选择”对象ROI。此时,视频帧冻结,用鼠标绘制跟踪对象的边界框。

    绘制完边界框,然后按“ENTER”或“SPACE”确认选择。如果需要重新选择区域,只需按“ESCAPE”即可。

    然后,启动OpenCV 对象跟踪器,再启动 FPS 吞吐量估计器。

    最后一个段代码只是处理我们已经脱离循环的情况。释放所有指针并关闭窗口。

    总结

    在今天的博客文章中,您学习了如何利用OpenCV进行对象跟踪。具体来说,我们回顾了OpenCV库中包含的8个对象跟踪算法(从OpenCV 3.4开始):

    CSRT、KCF、Boosting、MIL、TLD、MedianFlow、MOSSE、GOTURN。

    建议对大多数对象跟踪应用程序使用CSRT,KCF或MOSSE:

    当需要更高的对象跟踪精度并且可以容忍更慢的FPS吞吐量时,请使用CSRT

    当需要更快的FPS吞吐量时使用KCF,但可以处理稍低的对象跟踪精度

    当需要纯粹的速度时使用MOSSE

    完整代码:

    https://download.csdn.net/download/hhhhhhhhhhwwwwwwwwww/79602278

    更多相关内容
  • 基于单片机控制的移动式光伏跟踪系统研究与设计 基于单片机控制的移动式光伏跟踪系统研究与设计 基于单片机控制的移动式光伏跟踪系统研究与设计 基于单片机控制的移动式光伏跟踪系统研究与设计 基于单片机控制的移动...
  • YOLOv5+DeepSORT多目标跟踪与计数精讲

    千人学习 2021-05-10 23:39:05
    本课程使用YOLOv5和DeepSORT对视频中的行人、车辆做多目标跟踪和计数,开展YOLOv5目标检测和DeepSORT多目标跟踪强强联手的应用。  课程分别在Windows和Ubuntu系统上做项目演示,并对DeepSORT原理和代码做详细...
  • 跟踪卡尔曼滤波代码的实例程序,简单易懂,方便初学者的学习和研究。
  • 模糊控制位置跟踪的Simulink仿真程序
  • 运动目标跟踪程序-MATLAB源代码,利用camshift和meanshift实现运动目标的跟踪
  • 利用经典的目标跟踪模型,分别对CKF和UKF进行了仿真,为相关算法的研究提供了参考。
  • 为提高输入信号未知条件下伺服系统跟踪性能,提出多次跟踪控制方法。研究多次跟踪控制方法的结构、 理想情况下系统特点、多 次跟踪系统的实现方案以及理想情况下多次跟踪部分的作用。应 用于光电跟踪伺服系统,大幅...
  • 改程序实现基于粒子滤波的检测前跟踪算法。粒子滤波是一种非线性滤波方式,检测前跟踪用于弱小目标跟踪。该算法用于雷达弱小目标检测跟踪
  • 雷达目标跟踪相关程序,CA运动模型,CT运动模型,IMM交互多模型,卡尔曼滤波,粒子滤波PF,概率数据互联JPDA等算法程序相互结合,实现密集杂波环境目标跟踪和高机动目标跟踪....
  • opencv 目标识别,行人跟踪测试视频,5分钟长,含单人,多人,物体遮挡等多种街头场景,基本满足测试所需。
  • 需求跟踪矩阵 V2.0

    2019-03-28 11:00:53
    完整的需求跟踪矩阵模板工具,包含填写使用说明,项目或产品总体说明模板
  • 多目标跟踪c++代码

    2019-04-04 16:06:26
    代码使用vs2010 + opencv2.2开发,可以检测目标、跟踪多个目标
  • 在高动态环境下,设计新的跟踪环路架构,并仿真验证其性能
  • 本资源包含目标跟踪的领域较为经典的综述文章,以及相关滤波类目标跟踪算法的论文,包含MOSSE目标跟踪,KCF目标跟踪以及CSR-DCF目标跟踪算法。
  • 多假设跟踪MHT

    2018-08-13 16:48:08
    多假设跟踪(MHT)的算法原理介绍,及其在多传感器跟踪方面问题的应用。
  • , , 本书共分12章,包括概论、雷达跟踪测量基础、单脉冲技术、跟踪雷达测量精度分析、跟踪测量雷达天线技术、跟踪测量雷达接收机技术、雷达距离跟踪测量技术、角度精密跟踪伺服技术、脉冲多普勒速度跟踪测量技术,...
  • 需求跟踪矩阵模板

    2018-05-26 10:54:01
    用户需求跟踪矩阵模板,用户需求跟踪矩阵范本、用户需求跟踪矩阵
  • 多目标跟踪matlab

    2018-09-13 12:59:59
    多目标跟踪matlab
  • lqr轨迹跟踪的matlab编程,是通过lqr控制器控制,走出一条直线或者圆轨迹
  • 项目管理过程中,记录问题,分配责任,跟踪任务执行的跟踪表模板
  • camshift 多目标跟踪算法,使用鼠标选中摄像头视频流中的多个目标,即可进行对目标进行跟踪。后续可使用kalman及例子滤波对目标跟踪进行优化,kalman及例子滤波程序参见我的其他资源。
  • 1,基于固定场景的多帧...本文选用的是CSTR跟踪器,可根据需要换成KCF等其他跟踪器。 相当于重写了Multitracker 的新增跟踪目标和删减部分。 性能优于opencv原版的Multitracker ,因为Multitracker 跟踪多个目标是串行
  • 卡尔曼滤波目标跟踪实例 opencv 卡尔曼滤波目标跟踪实例 opencv 卡尔曼滤波目标跟踪实例 opencv
  • 自适应轨迹跟踪算法

    2017-06-27 09:56:16
    基于两轮驱动机器人的自适应轨迹跟踪算法
  • 在本教程中,我们将学习使用OpenCV跟踪对象。OpenCV 3.0开始引入跟踪API。我们将学习如何和何时使用OpenCV 4.2中可用的8种不同的跟踪器- BOOSTING, MIL, KCF, TLD, MEDIANFLOW, GOTURN, MOSSE和CSRT。我们还将学习...

    在本教程中,我们将学习使用OpenCV跟踪对象。OpenCV 3.0开始引入跟踪API。我们将学习如何和何时使用OpenCV 4.2中可用的8种不同的跟踪器- BOOSTING, MIL, KCF, TLD, MEDIANFLOW, GOTURN, MOSSE和CSRT。我们还将学习现代跟踪算法背后的一般理论。

    1.什么是目标跟踪?

    简单地说,在视频的连续帧中定位一个对象称为跟踪。
    这个定义听起来很简单,但在计算机视觉和机器学习中,跟踪是一个非常广泛的术语,它包含了概念相似但技术不同的想法。例如,以下所有不同但相关的思想通常都是在对象跟踪中研究的

    • 1.Dense Optical flow(稠密光流):这些算法有助于估计视频帧中每个像素的运动矢量。
    • 2.Sparse optical flow(稀疏光流):这些算法,如Kanade-Lucas-Tomashi (KLT)特征跟踪器,跟踪图像中几个特征点的位置。
    • 3.Kalman Filtering(卡尔曼滤波):一种非常流行的信号处理算法,用于基于先验运动信息预测运动目标的位置。该算法的早期应用之一是导弹制导!
    • 4.Meanshift and Camshift:这些是定位密度函数最大值的算法。它们也被用于跟踪。
    • 5.Single object trackers(单一对象追踪器):在这类跟踪器中,第一帧使用一个矩形来标记我们想要跟踪的对象的位置。然后使用跟踪算法在随后的帧中跟踪目标。在大多数现实生活中的应用程序中,这些跟踪器是与对象检测器结合使用的。
    • 6.Multiple object track finding algorithms(多目标追踪算法):在我们有快速目标检测器的情况下,在每帧中检测多个目标,然后运行轨迹查找算法来识别一帧中的哪个矩形对应于下一帧中的矩形是有意义的。

    2.跟踪与检测

    如果你曾经玩过OpenCV人脸检测,你知道它是实时工作的,你可以很容易地在每一帧中检测人脸。那么,为什么一开始就需要跟踪呢?让我们来探讨一下你可能想要在视频中跟踪对象而不仅仅是重复检测的不同原因。

    • 1.跟踪比检测快:通常跟踪算法要比检测算法快。原因很简单。当您在跟踪前一帧中检测到的对象时,您会对该对象的外观有很多了解。你也知道在前一个坐标系中的位置以及它运动的方向和速度。所以在下一帧中,你可以利用所有这些信息来预测下一帧中物体的位置,并对物体的预期位置做一个小搜索来精确地定位物体。一个好的跟踪算法会使用它所拥有的关于目标的所有信息,而检测算法总是从头开始。因此,在设计一个高效的系统时,通常在每n帧上进行目标检测,在n-1帧之间使用跟踪算法。为什么我们不直接在第一帧检测目标,然后跟踪它呢?跟踪确实可以从它所拥有的额外信息中获益,但如果一个物体在障碍物后面停留了很长一段时间,或者它移动得太快,以至于跟踪算法无法跟上,你也会失去对它的跟踪。跟踪算法也经常会累积误差,跟踪对象的包围框会慢慢地偏离跟踪对象。我们会经常使用检测算法解决跟踪算法的这些问题。检测算法基于大数据训练,因此,他们对对象的一般类别有更多的了解。另一方面,跟踪算法更了解它们所跟踪的类的具体实例。
    • 2.当检测失败时,跟踪可以提供帮助:如果你在视频中运行人脸检测器,而这个人的脸被物体遮挡,人脸检测器很可能会失败。一个好的跟踪算法将解决某种程度的遮挡。
    • 3.跟踪保护身份ID:对象检测的输出是一个包含对象的矩形数组。但是,该对象没有附加身份。例如,在下面的视频中,一个检测红点的探测器将输出与它在一帧中检测到的所有点相对应的矩形。在下一帧中,它将输出另一个矩形数组。在第一帧中,一个特定的点可能由数组中位置10的矩形表示,而在第二帧中,它可能位于位置17。当在帧上使用检测时,我们不知道哪个矩形对应哪个对象。另一方面,追踪提供了一种将这些点连接起来的方法!

    3.使用OpenCV 4实现对象跟踪

    OpenCV 4附带了一个跟踪API,它包含了许多单对象跟踪算法的实现。在OpenCV 4.2中有8种不同的跟踪器可用- BOOSTING, MIL, KCF, TLD, MEDIANFLOW, GOTURN, MOSSE,和CSRT

    注意: OpenCV 3.2实现了这6个跟踪器- BOOSTING, MIL, TLD, MEDIANFLOW, MOSSE和GOTURN。OpenCV 3.1实现了这5个跟踪器- BOOSTING, MIL, KCF, TLD, MEDIANFLOW。OpenCV 3.0实现了以下4个跟踪器- BOOSTING, MIL, TLD, MEDIANFLOW

    在OpenCV 3.3中,跟踪API已经改变。代码检查版本,然后使用相应的API。

    在简要描述这些算法之前,让我们先看看它们的设置和使用方法。在下面的注释代码中,我们首先通过选择跟踪器类型来设置跟踪器——BOOSTING、MIL、KCF、TLD、MEDIANFLOW、GOTURN、MOSSE或CSRT。然后我们打开一段视频,抓取一帧。我们定义了一个包含第一帧对象的边界框,并用第一帧和边界框初始化跟踪器。最后,我们从视频中读取帧,并在循环中更新跟踪器,以获得当前帧的新包围框。随后显示结果。

    3.1使用OpenCV 4实现对象跟踪 C++代码

    #include <opencv2/opencv.hpp>
    #include <opencv2/tracking.hpp>
    #include <opencv2/core/ocl.hpp>
    
    using namespace cv;
    using namespace std;
    
    // 转换为字符串
    #define SSTR( x ) static_cast< std::ostringstream & >( ( std::ostringstream() << std::dec << x ) ).str()
    
    int main(int argc, char **argv)
    {
        // OpenCV 3.4.1中的跟踪器类型列表
        string trackerTypes[8] = {"BOOSTING", "MIL", "KCF", "TLD","MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"};
        // vector <string> trackerTypes(types, std::end(types));
    
        // 创建一个跟踪器
        string trackerType = trackerTypes[2];
    
        Ptr<Tracker> tracker;
    
        #if (CV_MINOR_VERSION < 3)
        {
            tracker = Tracker::create(trackerType);
        }
        #else
        {
            if (trackerType == "BOOSTING")
                tracker = TrackerBoosting::create();
            if (trackerType == "MIL")
                tracker = TrackerMIL::create();
            if (trackerType == "KCF")
                tracker = TrackerKCF::create();
            if (trackerType == "TLD")
                tracker = TrackerTLD::create();
            if (trackerType == "MEDIANFLOW")
                tracker = TrackerMedianFlow::create();
            if (trackerType == "GOTURN")
                tracker = TrackerGOTURN::create();
            if (trackerType == "MOSSE")
                tracker = TrackerMOSSE::create();
            if (trackerType == "CSRT")
                tracker = TrackerCSRT::create();
        }
        #endif
        // 读取视频
        VideoCapture video("videos/chaplin.mp4");
        
        // 如果视频没有打开,退出
        if(!video.isOpened())
        {
            cout << "Could not read video file" << endl; 
            return 1; 
        } 
    
        // 读第一帧
        Mat frame; 
        bool ok = video.read(frame); 
    
        // 定义初始边界框
        Rect2d bbox(287, 23, 86, 320); 
    
        // 取消注释下面的行以选择一个不同的边界框 
        // bbox = selectROI(frame, false); 
        // 显示边界框
        rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 ); 
    
        imshow("Tracking", frame); 
        tracker->init(frame, bbox);
        
        while(video.read(frame))
        {     
            // 启动定时器
            double timer = (double)getTickCount();
            
            // 更新跟踪结果
            bool ok = tracker->update(frame, bbox);
            
            // 计算每秒帧数(FPS)
            float fps = getTickFrequency() / ((double)getTickCount() - timer);
            
            if (ok)
            {
                // 跟踪成功:绘制被跟踪对象
                rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 );
            }
            else
            {
                // 跟踪失败
                putText(frame, "Tracking failure detected", Point(100,80), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0,0,255),2);
            }
            
            // 在帧上显示跟踪器类型
            putText(frame, trackerType + " Tracker", Point(100,20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50),2);
            
            // 帧显示FPS
            putText(frame, "FPS : " + SSTR(int(fps)), Point(100,50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50), 2);
    
            // 显示帧
            imshow("Tracking", frame);
            
            // 按ESC键退出。
            int k = waitKey(1);
            if(k == 27)
            {
                break;
            }
    
        }
    }
    

    3.2使用OpenCV 4实现对象跟踪 Python代码

    import cv2
    import sys
    
    (major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')if __name__ == '__main__' :
    
        # 建立追踪器
        # 除了MIL之外,您还可以使用
    
        tracker_types = ['BOOSTING', 'MIL','KCF', 'TLD', 'MEDIANFLOW', 'GOTURN', 'MOSSE', 'CSRT']
        tracker_type = tracker_types[2]
    
        if int(minor_ver) < 3:
            tracker = cv2.Tracker_create(tracker_type)
        else:
            if tracker_type == 'BOOSTING':
                tracker = cv2.TrackerBoosting_create()
            if tracker_type == 'MIL':
                tracker = cv2.TrackerMIL_create()
            if tracker_type == 'KCF':
                tracker = cv2.TrackerKCF_create()
            if tracker_type == 'TLD':
                tracker = cv2.TrackerTLD_create()
            if tracker_type == 'MEDIANFLOW':
                tracker = cv2.TrackerMedianFlow_create()
            if tracker_type == 'GOTURN':
                tracker = cv2.TrackerGOTURN_create()
            if tracker_type == 'MOSSE':
                tracker = cv2.TrackerMOSSE_create()
            if tracker_type == "CSRT":
                tracker = cv2.TrackerCSRT_create()
    
        # 读取视频
        video = cv2.VideoCapture("videos/chaplin.mp4")
    
        # 如果视频没有打开,退出。
        if not video.isOpened():
            print "Could not open video"
            sys.exit()
    
        # 读第一帧。
        ok, frame = video.read()
        if not ok:
            print('Cannot read video file')
            sys.exit()
        
        # 定义一个初始边界框
        bbox = (287, 23, 86, 320)
    
        # 取消注释下面的行以选择一个不同的边界框
        # bbox = cv2.selectROI(frame, False)
    
        # 用第一帧和包围框初始化跟踪器
        ok = tracker.init(frame, bbox)
    
        while True:
            # 读取一个新的帧
            ok, frame = video.read()
            if not ok:
                break
            
            # 启动计时器
            timer = cv2.getTickCount()
    
            # 更新跟踪器
            ok, bbox = tracker.update(frame)
    
            # 计算帧率(FPS)
            fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);
    
            # 绘制包围框
            if ok:
                # 跟踪成功
                p1 = (int(bbox[0]), int(bbox[1]))
                p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
                cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)
            else :
                # 跟踪失败
                cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)
    
            # 在帧上显示跟踪器类型名字
            cv2.putText(frame, tracker_type + " Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);
        
            # 在帧上显示帧率FPS
            cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);
    
            # 显示结果
            cv2.imshow("Tracking", frame)
    
            # 按ESC键退出
            k = cv2.waitKey(1) & 0xff
            if k == 27 : break
    
    

    4.跟踪算法解析

    在本节中,我们将深入研究不同的跟踪算法。我们的目标不是对每一个跟踪器都有一个深刻的理论理解,而是从实际的角度来理解它们。
    让我首先解释一些跟踪的一般原则。在跟踪中,我们的目标是在当前帧中找到一个对象,因为我们已经成功地在所有(或几乎所有)之前的帧中跟踪了这个对象。

    因为我们一直跟踪对象直到当前帧,所以我们知道它是如何移动的。换句话说,我们知道运动模型的参数。运动模型只是一种花哨的说法,表示你知道物体在前几帧中的位置和速度(速度+运动方向)。如果你对物体一无所知,你可以根据当前的运动模型预测新的位置,你会非常接近物体的新位置。

    但我们有比物体运动更多的信息。我们知道物体在之前的每一帧中的样子。换句话说,我们可以构建一个对对象的外观进行编码的外观模型。该外观模型可用于在运动模型预测的小邻域内搜索位置,从而更准确地预测物体的位置。

    运动模型预测了物体的大致位置。外观模型对这个估计进行微调,以提供基于外观的更准确的估计。

    如果对象非常简单,并且没有太多改变它的外观,我们可以使用一个简单的模板作为外观模型,并寻找该模板。然而,现实生活并没有那么简单。物体的外观会发生巨大的变化。为了解决这个问题,在许多现代跟踪器中,这个外观模型是一个以在线方式训练的分类器。别慌!让我用更简单的术语解释一下。

    分类器的工作是将图像中的矩形区域分类为物体或背景。分类器接收图像patch作为输入,并返回0到1之间的分数,表示图像patch包含该对象的概率。当完全确定图像patch是背景时,分数为0;当完全确定patch是对象时,分数为1。

    在机器学习中,我们用“在线”这个词来指在运行时进行动态训练的算法。离线分类器可能需要数千个示例来训练一个分类器,但在线分类器通常在运行时使用很少的示例进行训练。

    通过向分类器输入正(对象)和负(背景)的例子来训练分类器。如果您想要构建一个用于检测猫的分类器,您可以使用数千张包含猫的图像和数千张不包含猫的图像来训练它。这样分类器学会区分什么是猫,什么不是。在构建一个在线分类器时,我们没有机会拥有数千个正面和负面类的例子。

    让我们看看不同的跟踪算法是如何处理在线训练的这个问题的。

    4.1 BOOSTING Tracker

    该跟踪器基于AdaBoost的在线版本——基于HAAR级联的人脸检测器内部使用的算法。这个分类器需要在运行时用对象的正面和反面例子进行训练。将用户提供的初始包围盒(或其他目标检测算法提供的初始包围盒)作为目标的正例,将包围盒外的许多图像patch作为背景。

    给定一个新的帧,分类器在前一个位置附近的每个像素上运行,并记录分类器的得分。对象的新位置是分数最高的位置。现在分类器又多了一个正样本。当更多的帧进来时,分类器就会用这些额外的数据更新。

    优点:没有。 这个算法已经有10年的历史了,而且运行良好,但我找不到使用它的好理由,特别是当基于类似原则的其他高级跟踪器(MIL, KCF)可用时。
    缺点:跟踪性能平庸。 它不能可靠地知道何时跟踪失败了。

    4.2 MIL Tracker

    这个跟踪器在思想上与上述的BOOSTING跟踪器相似。最大的区别是,它不是只考虑对象的当前位置作为一个正样本,而是在当前位置周围的一个小领域中寻找几个潜在的正样本。你可能会认为这不是一个好主意,因为在大多数这些“正样本”的例子中,物体不是居中的。

    这就是多实例学习 (MIL) 的用武之地。在 MIL 中,您不指定正面和负面示例,而是指定正面和负面“袋子”。正面“袋子”中的图像集合并不都是正例。取而代之的是,正面袋子中只有一张图像需要是正面的例子!

    在我们的示例中,一个正面袋子包含以对象当前位置为中心的patch,以及它周围的一个小邻域中的patch。即使被跟踪对象的当前位置不准确,当来自当前位置附近的样本被放入正面袋子中时,这个正面袋子很有可能包含至少一个对象很好地居中的图像。

    优点:性能很好。 它不像BOOSTING跟踪器那样漂移,并且在部分遮挡下做了合理的工作。如果你正在使用OpenCV 3.0,这可能是你可用的最好的跟踪器。但是,如果您使用的是更高的版本,请考虑KCF。
    缺点: 无法可靠地报告跟踪失败。不能从完全遮挡中恢复。

    4.3 KCF Tracker

    KFC 代表Kernelized Correlation Filters(Kernelized相关性过滤器)。该跟踪器建立在前两个跟踪器中提出的想法之上。该跟踪器利用了 MIL 跟踪器中使用的多个正样本具有较大重叠区域的事实。这种重叠数据导致了一些很好的数学特性,该跟踪器利用这些特性使跟踪更快、更准确。

    优点:准确性和速度都优于 MIL,它报告的跟踪失败比 BOOSTING 和 MIL 更好。 如果您使用的是 OpenCV 3.1 及更高版本,我建议将其用于大多数应用程序。
    缺点: 不能从完全遮挡中恢复。

    4.4 TLD Tracker

    TLD 代表跟踪、学习和检测。顾名思义,这个跟踪器将长期跟踪任务分解为三个部分——(短期)跟踪、学习和检测。从作者的论文中,“跟踪器逐帧跟踪对象。检测器定位到目前为止已观察到的所有外观,并在必要时纠正跟踪器。

    学习估计检测器的错误并对其进行更新以避免将来出现这些错误。”这个跟踪器的输出往往会有点跳跃。例如,如果您正在跟踪行人并且场景中有其他行人,则此跟踪器有时可以临时跟踪与您打算跟踪的行人不同的行人。从积极的方面来说,这条轨迹似乎可以在更大的范围、运动和遮挡范围内跟踪对象。如果您有一个对象隐藏在另一个对象后面的视频序列,则此跟踪器可能是一个不错的选择。

    优点:在多个帧的遮挡下效果最佳。此外,跟踪最好的规模变化。
    缺点:大量的误报使得它几乎无法使用。

    4.5 MEDIANFLOW Tracker

    在内部,该跟踪器在时间上向前和向后跟踪对象,并测量这两个轨迹之间的差异。最小化这种 ForwardBackward 误差使他们能够可靠地检测跟踪失败并在视频序列中选择可靠的轨迹。

    在我的测试中,我发现该跟踪器在运动可预测且较小时效果最佳。与其他跟踪器即使在跟踪明显失败时仍继续运行不同,该跟踪器知道跟踪何时失败。

    优点:出色的跟踪失败报告。当运动是可预测的并且没有遮挡时效果很好。
    缺点:在大运动下失败。

    4.6 GOTURN tracker

    在跟踪器类的所有跟踪算法中,这是唯一一种基于卷积神经网络 (CNN) 的算法。从 OpenCV 文档中,我们知道它“对视点变化、光照变化和变形具有鲁棒性”。但它不能很好地处理遮挡。

    注意:GOTURN 是基于 CNN 的跟踪器,使用 Caffe 模型进行跟踪。 Caffe 模型和 proto 文本文件必须存在于代码所在的目录中。这些文件也可以从 opencv_extra 存储库下载、连接并在使用前提取。

    4.7 MOSSE tracker

    最小输出平方误差和 (MOSSE) 使用自适应相关性进行对象跟踪,在使用单帧初始化时会产生稳定的相关性滤波器。 MOSSE 跟踪器对光照、比例、姿势和非刚性变形的变化具有鲁棒性。它还根据峰值旁瓣(peak-to-sidelobe)比检测遮挡,这使跟踪器能够在对象重新出现时暂停并从中断的地方恢复。 MOSSE 跟踪器还以更高的 fps(450 fps 甚至更高)运行。除此之外,它还非常容易执行,与其他复杂追踪器一样准确,而且速度更快。但是,在性能尺度上,它落后于基于深度学习的跟踪器。

    4.8 CSRT tracker

    DCF-CSR (Discriminative Correlation Filter with Channel and Spatial Reliability, DCF-CSR)中,我们使用空间可靠性映射来调整滤波器的支持度,使其适应帧中被选择区域的跟踪部分。这确保了所选区域的放大和定位,并改进了对非矩形区域或对象的跟踪。它只使用2个标准特性(hog和Colornames)。它也运行在一个相对较低的fps (25 fps),但提供了较高的目标跟踪精度。

    参考目录

    https://learnopencv.com/object-tracking-using-opencv-cpp-python/

    展开全文
  • 内容包括杂波中的单目标跟踪、杂波中的机动目标跟踪跟踪性能预测与检测门限优化、杂波中的目标跟踪:贝叶斯方法、杂波中的目标跟踪:非贝叶斯方法、多传感器跟踪和数据融合、成像传感器跟踪等。
  • 无人车轨迹跟踪控制的MATLAB实现,通过simulink实现的。
  • 正弦信号的跟踪控制仿真-,由matlab和simulink仿真实现的程序
  • 目标跟踪的过程是: 1.获取对象检测的初始集(例如边界框坐标的输入集) 2.为每个初始检测创建唯一的ID 3.然后跟踪每一个在视频中移动的对象,保持唯一ID的分配 此外,对象跟踪允许我们为每个跟踪对象应用唯一 ID,...

    1.简述

    目标跟踪的过程是:

    • 1.获取对象检测的初始集(例如边界框坐标的输入集)
    • 2.为每个初始检测创建唯一的ID
    • 3.然后跟踪每一个在视频中移动的对象,保持唯一ID的分配

    此外,对象跟踪允许我们为每个跟踪对象应用唯一 ID,从而使我们能够计算视频中的唯一对象。对象跟踪对于构建人员计数器至关重要。

    理想的目标跟踪算法是:

    • 1.只要求一次对象检测阶段(即,当对象最初被检测时)
    • 2.将非常快-比运行实际的物体检测器本身快得多
    • 3.能够处理当跟踪对象“消失”或移动到视频帧的边界之外的情况
    • 4.抗遮挡能力强
    • 5.能够拾取它在帧间“丢失”的对象

    这对于任何计算机视觉或图像处理算法来说都是一项艰巨的任务,我们可以使用各种技巧来帮助改进我们的对象跟踪器。

    在今天的博文中,你将学习如何使用OpenCV实现质心跟踪,质心跟踪是一种简单易懂但高效的跟踪算法。

    质心跟踪依赖于视频中(1)已有的目标质心(即质心跟踪器已经见过的目标)与(2)后续帧之间的新目标质心之间的欧氏距离。

    我们将在下一节更深入地回顾质心算法。从那里我们将实现一个 Python 类来包含我们的质心跟踪算法,然后创建一个 Python 脚本来实际运行对象跟踪器并将其应用于输入视频。

    最后,我们将运行我们的对象跟踪器并检查结果,同时指出该算法的优点和缺点。

    2.质心跟踪算法

    质心跟踪算法是一个多步骤的过程。我们将回顾本节中的每个跟踪步骤。

    步骤#1:接受边界框坐标并计算质心

    在这里插入图片描述
    要使用质心跟踪构建简单的对象跟踪算法,第一步是从对象检测器获得边界框坐标并使用它们来计算质心。

    质心跟踪算法假设我们为每一帧中的每个检测到的对象传入一组边界框 (x, y) 坐标。

    这些边界框可以由您想要的任何类型的对象检测器(颜色阈值 + 轮廓提取、Haar 级联、HOG + 线性 SVM、SSD、Faster R-CNN 等)生成。

    一旦我们有了边界框坐标,我们就必须计算“质心”,或者更简单地说,计算边界框的中心 (x, y) 坐标。上面的图演示了接受一组边界框坐标并计算质心。

    由于这些是呈现给我们算法的第一组初始边界框,我们将为它们分配唯一的 ID。

    步骤#2:计算新边界框和现有对象之间的欧几里得距离

    在这里插入图片描述
    此图像中存在三个对象。我们需要计算每对原始质心(紫色)和新质心(黄色)之间的欧几里得距离。

    对于视频流中随后的每一帧,我们应用步骤#1计算对象质心;然而,我们首先需要确定是否可以将新的对象质心(黄色)与旧的对象质心(紫色)相关联,而不是为每个检测到的对象分配一个新的唯一ID(这将违背对象跟踪的目的)。为了完成这个过程,我们计算每对现有对象质心和输入对象质心之间的欧几里德距离(绿色或红色箭头突出显示)。

    然后我们计算每对原始质心(紫色)和新质心(黄色)之间的欧几里得距离。但是我们如何使用这些点之间的欧几里得距离来实际匹配它们并关联它们呢?

    答案在Step #3

    步骤 #3:更新现有对象的 (x, y) 坐标

    在这里插入图片描述
    我们简单的质心对象跟踪方法将对象与最小的对象距离相关联。我们如何处理上图左下角的对象呢?

    质心跟踪算法的主要假设是给定对象可能会在后续帧之间移动,但帧的质心之间的距离将小于对象之间的所有其他距离。

    因此,如果我们选择将质心与后续帧之间的最小距离相关联,我们可以构建我们的对象跟踪器。

    但是左下角的孤独点呢?

    步骤#4:注册新对象

    在这里插入图片描述
    在我们使用 Python 和 OpenCV 进行对象跟踪的示例中,我们有一个与现有对象不匹配的新对象,因此它被注册为对象 ID #3。

    如果输入检测比跟踪的现有对象多,我们需要注册新对象。 “注册”只是意味着我们通过以下方式将新对象添加到我们的跟踪对象列表中:

    • 为其分配一个新的对象 ID
    • 存储该对象的边界框坐标的质心

    然后我们可以返回到步骤#2,并为视频流中的每一帧重复步骤管道。

    步骤#5:注销旧对象

    任何合理的对象跟踪算法都需要能够处理对象丢失、消失或离开视野的情况。

    您如何处理这些情况实际上取决于您的对象跟踪器的部署位置,但是对于此实现,当旧对象无法与任何现有对象匹配总共 N 个后续帧时,我们将取消注册。

    3.对象跟踪项目结构

    要在终端中查看今天的项目结构,只需使用 tree 命令:

    $ tree --dirsfirst
    .
    ├── pyimagesearch
    │   ├── __init__.py
    │   └── centroidtracker.py
    ├── object_tracker.py
    ├── deploy.prototxt
    └── res10_300x300_ssd_iter_140000.caffemodel
    

    4.使用 OpenCV 实现质心跟踪

    在我们可以对输入视频流应用对象跟踪之前,我们首先需要实现质心跟踪算法。当您消化这个质心跟踪器脚本时,请记住上面的步骤 1-5,并根据需要查看这些步骤。

    正如您将看到的,将步骤转换为代码需要很多思考,虽然我们执行所有步骤,但由于我们各种数据结构和代码结构的性质,它们不是线性的。

    我会建议 :

    • 阅读上面的步骤
    • 阅读质心跟踪器的代码说明
    • 最后再次阅读上述步骤

    一旦你确定你理解了质心跟踪算法的步骤,打开 pyimagesearch 模块中的 centroidtracker.py,让我们回顾一下代码:

    # import the necessary packages
    from scipy.spatial import distance as dist
    from collections import OrderedDict
    import numpy as np
    class CentroidTracker():
    	def __init__(self, maxDisappeared=50):
    		# initialize the next unique object ID along with two ordered
    		# dictionaries used to keep track of mapping a given object
    		# ID to its centroid and number of consecutive frames it has
    		# been marked as "disappeared", respectively
    		self.nextObjectID = 0
    		self.objects = OrderedDict()
    		self.disappeared = OrderedDict()
    		# store the number of maximum consecutive frames a given
    		# object is allowed to be marked as "disappeared" until we
    		# need to deregister the object from tracking
    		self.maxDisappeared = maxDisappeared
    

    我们导入我们需要的包和模块—— distance 、 OrderedDict 和 numpy 。

    首先我们定义CentroidTracker 类。构造函数接受一个参数,即跟踪器可以容忍的给定对象丢失/消失的最大连续帧数。

    我们的构造函数构建了四个类变量:

    • nextObjectID:用于为每个对象分配唯一 ID 的计数器。如果对象离开帧并且在 maxDisappeared 帧中没有返回,则将分配一个新的(下一个)对象 ID。
    • objects:对象 ID 作为键和质心 (x, y) 坐标作为值的字典
    • disappeared:保存特定对象 ID(键)已被标记为“丢失”的连续帧数(值)
    • maxDisappeared:在我们取消注册该对象之前,允许将对象标记为“丢失/消失”的连续帧数。

    让我们定义负责向我们的跟踪器添加新对象的 register 方法:

    	def register(self, centroid):
    		# when registering an object we use the next available object
    		# ID to store the centroid
    		self.objects[self.nextObjectID] = centroid
    		self.disappeared[self.nextObjectID] = 0
    		self.nextObjectID += 1
    

    定义register 方法,它接受一个质心centroid,然后使用下一个可用的对象 ID 将其添加到objects字典中。 对象消失的次数在disappeared字典中初始化为 0。 最后,我们递增 nextObjectID,这样如果一个新对象进入视野,它将与一个唯一 ID 相关联。 与我们的register方法类似,我们也需要一个deregister方法:

    	def deregister(self, objectID):
    		# to deregister an object ID we delete the object ID from
    		# both of our respective dictionaries
    		del self.objects[objectID]
    		del self.disappeared[objectID]
    

    就像我们可以向跟踪器添加新对象一样,我们还需要能够从输入帧中删除丢失或消失的旧对象。

    定义deregister 方法,它简单地分别删除objectsdisappeared字典中的 objectID

    我们的质心跟踪器实现的核心位于update方法中:

    	def update(self, rects):
    		# check to see if the list of input bounding box rectangles
    		# is empty
    		if len(rects) == 0:
    			# loop over any existing tracked objects and mark them
    			# as disappeared
    			for objectID in list(self.disappeared.keys()):
    				self.disappeared[objectID] += 1
    				# if we have reached a maximum number of consecutive
    				# frames where a given object has been marked as
    				# missing, deregister it
    				if self.disappeared[objectID] > self.maxDisappeared:
    					self.deregister(objectID)
    			# return early as there are no centroids or tracking info
    			# to update
    			return self.objects
    

    定义的更新方法接受边界框矩形列表,可能来自对象检测器(Haar 级联、HOG + 线性 SVM、SSD、Faster R-CNN 等)。 rects 参数的格式假定为具有以下结构的元组: (startX, startY, endX, endY)

    如果没有检测到,我们将遍历所有对象 ID 并增加它们的disappeared计数。我们还将检查是否已达到给定对象被标记为丢失的最大连续帧数。如果是这种情况,我们需要将其从我们的跟踪系统中删除。由于没有要更新的跟踪信息,我们继续前进并提前return

    否则,在接下来的7个update方法的代码块中,我们有很多工作要做:

    		# initialize an array of input centroids for the current frame
    		inputCentroids = np.zeros((len(rects), 2), dtype="int")
    		# loop over the bounding box rectangles
    		for (i, (startX, startY, endX, endY)) in enumerate(rects):
    			# use the bounding box coordinates to derive the centroid
    			cX = int((startX + endX) / 2.0)
    			cY = int((startY + endY) / 2.0)
    			inputCentroids[i] = (cX, cY)
    

    我们将初始化一个 NumPy 数组inputCentroids 来存储每个 rect 的质心。

    然后,我们遍历边界框矩形并计算质心并将其存储在 inputCentroids 列表中。

    如果当前没有我们正在跟踪的对象,我们将注册每个新对象:

    		# if we are currently not tracking any objects take the input
    		# centroids and register each of them
    		if len(self.objects) == 0:
    			for i in range(0, len(inputCentroids)):
    				self.register(inputCentroids[i])
    

    否则,我们需要根据最小化它们之间欧几里得距离的质心位置来更新任何现有对象 (x, y) 坐标:

    		# otherwise, are are currently tracking objects so we need to
    		# try to match the input centroids to existing object
    		# centroids
    		else:
    			# grab the set of object IDs and corresponding centroids
    			objectIDs = list(self.objects.keys())
    			objectCentroids = list(self.objects.values())
    			# compute the distance between each pair of object
    			# centroids and input centroids, respectively -- our
    			# goal will be to match an input centroid to an existing
    			# object centroid
    			D = dist.cdist(np.array(objectCentroids), inputCentroids)
    			# in order to perform this matching we must (1) find the
    			# smallest value in each row and then (2) sort the row
    			# indexes based on their minimum values so that the row
    			# with the smallest value is at the *front* of the index
    			# list
    			rows = D.min(axis=1).argsort()
    			# next, we perform a similar process on the columns by
    			# finding the smallest value in each column and then
    			# sorting using the previously computed row index list
    			cols = D.argmin(axis=1)[rows]
    

    对现有跟踪对象的更新从else 开始。目标是跟踪对象并保持正确的对象 ID——这个过程是通过计算所有 objectCentroidsinputCentroids 对之间的欧几里德距离来完成的,然后关联最小化欧几里得距离的对象 ID。

    在else 块中,我们将:

    • 获取 objectIDobjectCentroid
    • 计算每对现有对象质心和新输入质心之间的距离。我们的距离图 D 的输出形状将是 (# of object centroids, # of input centroids)
    • 要执行匹配,我们必须 (1) 找到每行中的最小值,以及 (2) 根据最小值对行索引进行排序。我们对列执行非常相似的过程,在每列中找到最小值,然后根据有序行对它们进行排序。我们的目标是在列表的前面有最小对应距离的索引值。

    下一步是使用距离来查看我们是否可以关联对象 ID:

    			# in order to determine if we need to update, register,
    			# or deregister an object we need to keep track of which
    			# of the rows and column indexes we have already examined
    			usedRows = set()
    			usedCols = set()
    			# loop over the combination of the (row, column) index
    			# tuples
    			for (row, col) in zip(rows, cols):
    				# if we have already examined either the row or
    				# column value before, ignore it
    				# val
    				if row in usedRows or col in usedCols:
    					continue
    				# otherwise, grab the object ID for the current row,
    				# set its new centroid, and reset the disappeared
    				# counter
    				objectID = objectIDs[row]
    				self.objects[objectID] = inputCentroids[col]
    				self.disappeared[objectID] = 0
    				# indicate that we have examined each of the row and
    				# column indexes, respectively
    				usedRows.add(row)
    				usedCols.add(col)
    

    在上面的代码块中,我们:

    • 初始化两个集合以确定我们已经使用了哪些行和列索引。请记住,集合类似于列表,但它只包含唯一值。
    • 然后我们遍历 (row, col) 索引元组的组合以更新我们的对象质心:
      • 如果我们已经使用了此行或列索引,请忽略它并继续循环。
      • 否则,我们找到了一个输入质心:
        • 1.到现有质心的欧几里得距离最小
        • 2.并且没有与任何其他对象匹配
        • 在这种情况下,我们更新对象质心并确保将 row 和 col 添加到它们各自的 usedRowsusedCols 集中

    在我们的 usedRows + usedCols 集合中可能有我们尚未检查的索引:

    			# compute both the row and column index we have NOT yet
    			# examined
    			unusedRows = set(range(0, D.shape[0])).difference(usedRows)
    			unusedCols = set(range(0, D.shape[1])).difference(usedCols)
    

    所以我们必须确定哪些质心索引我们还没有检查,并将它们存储在两个新的集合中(unusedRows 和unusedCols

    我们的最后处理任何丢失或可能消失的对象:

    			# in the event that the number of object centroids is
    			# equal or greater than the number of input centroids
    			# we need to check and see if some of these objects have
    			# potentially disappeared
    			if D.shape[0] >= D.shape[1]:
    				# loop over the unused row indexes
    				for row in unusedRows:
    					# grab the object ID for the corresponding row
    					# index and increment the disappeared counter
    					objectID = objectIDs[row]
    					self.disappeared[objectID] += 1
    					# check to see if the number of consecutive
    					# frames the object has been marked "disappeared"
    					# for warrants deregistering the object
    					if self.disappeared[objectID] > self.maxDisappeared:
    						self.deregister(objectID)
    

    最后:

    • 如果对象质心的数量大于或等于输入质心的数量:
      • 我们需要通过遍历未使用的行索引(如果有)来验证这些对象是否丢失或消失。
      • 在循环中,我们将:
        • 1.增加他们在字典中disappeared次数。
        • 2.检查disappeared计数是否超过 maxDisappeared 阈值,如果是,我们将注销该对象。

    否则,输入质心的数量大于现有对象质心的数量,因此我们有新的对象要注册和跟踪:

    			# otherwise, if the number of input centroids is greater
    			# than the number of existing object centroids we need to
    			# register each new input centroid as a trackable object
    			else:
    				for col in unusedCols:
    					self.register(inputCentroids[col])
    		# return the set of trackable objects
    		return self.objects
    

    我们循环遍历unusedCols 索引并注册每个新质心。最后,我们将可跟踪对象集返回给调用方法。

    5.了解质心跟踪距离关系

    我们的质心跟踪实现很长,诚然,这是算法中最令人困惑的方面。

    如果您在跟随该代码的操作时遇到问题,您应该考虑打开 Python shell 并执行以下实验:

    >>> from scipy.spatial import distance as dist
    >>> import numpy as np
    >>> np.random.seed(42)
    >>> objectCentroids = np.random.uniform(size=(2, 2))
    >>> centroids = np.random.uniform(size=(3, 2))
    >>> D = dist.cdist(objectCentroids, centroids)
    >>> D
    array([[0.82421549, 0.32755369, 0.33198071],
           [0.72642889, 0.72506609, 0.17058938]])
    

    结果是具有两行(# of existing object centroids)和三列(# of new input centroids)的距离矩阵 D。

    就像我们之前在脚本中所做的那样,让我们​​找到每行中的最小距离并根据该值对索引进行排序:

    >>> D.min(axis=1)
    array([0.32755369, 0.17058938])
    >>> rows = D.min(axis=1).argsort()
    >>> rows
    array([1, 0])
    

    首先,我们找到每一行的最小值,让我们能够确定哪个现有对象最接近新的输入质心。然后对这些值进行排序,我们可以获得这些行的索引。

    对列使用类似的过程:

    >>> D.argmin(axis=1)
    array([1, 2])
    >>> cols = D.argmin(axis=1)[rows]
    >>> cols
    array([2, 1])
    

    我们首先检查列中的值并找到具有最小列的值的索引。然后,我们使用现有的rows对这些值排序。

    让我们打印结果并分析它们:

    >>> print(list(zip(rows, cols)))
    [(1, 2), (0, 1)]
    

    分析结果,我们发现:

    • D[1, 2] 具有最小的欧几里得距离,这意味着第二个现有对象将与第三个输入质心匹配。
    • 并且 D[0, 1] 具有下一个最小的欧几里德距离,这意味着第一个现有对象将与第二个输入质心匹配。

    6.实现对象跟踪程序脚本

    现在我们已经实现了 CentroidTracker 类,让我们将其与对象跟踪程序脚本一起使用。

    在程序脚本中,您可以使用自己喜欢的对象检测器,前提是它生成一组包围框。这可以是Haar级联,HOG +线性支持向量机,YOLO, SSD, Faster R-CNN等。对于这个示例脚本,我将使用OpenCV的深度学习人脸检测器,但您可以自行制作实现不同检测器的脚本版本。

    在这个脚本中,我们将:

    • 使用实时 VideoStream 对象从您的网络摄像头中抓取帧
    • 加载并使用 OpenCV 的深度学习人脸检测器
    • 实例化我们的 CentroidTracker 并使用它来跟踪视频流中的人脸对象
    • 并显示我们的结果,其中包括覆盖在帧上的边界框和对象 ID 注释

    当你准备好了,打开object_tracker.py,然后继续:

    # import the necessary packages
    from pyimagesearch.centroidtracker import CentroidTracker
    from imutils.video import VideoStream
    import numpy as np
    import argparse
    import imutils
    import time
    import cv2
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-p", "--prototxt", required=True,
    	help="path to Caffe 'deploy' prototxt file")
    ap.add_argument("-m", "--model", required=True,
    	help="path to Caffe pre-trained model")
    ap.add_argument("-c", "--confidence", type=float, default=0.5,
    	help="minimum probability to filter weak detections")
    args = vars(ap.parse_args())
    

    首先,我们指定我们的导入。最值得注意的是,我们正在使用我们刚刚回顾过的 CentroidTracker 类。我们还将使用来自 imutilsOpenCVVideoStream

    我们有三个命令行参数,它们都与我们的深度学习人脸检测器相关:

    • --prototxt :Caffe 部署prototxt 的路径。
    • --model :预训练模型模型的路径。
    • --confidence :我们过滤弱检测的概率阈值。我发现默认值 0.5 就足够了。

    接下来,让我们执行我们的初始化:

    # initialize our centroid tracker and frame dimensions
    ct = CentroidTracker()
    (H, W) = (None, None)
    # load our serialized model from disk
    print("[INFO] loading model...")
    net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
    # initialize the video stream and allow the camera sensor to warmup
    print("[INFO] starting video stream...")
    vs = VideoStream(src=0).start()
    time.sleep(2.0)
    

    在上面的块中,我们:

    • 实例化我们的 CentroidTracker , ct。回想一下上一节的解释,这个对象有三个方法:(1) register , (2) deregister ,和 (3) update 。我们只会使用 update 方法,因为它会自动注册和注销对象。我们还将 HW(我们的帧尺寸)初始化为 None
    • 使用 OpenCV 的 DNN 模块从磁盘加载我们的序列化深度学习人脸检测器模型
    • 启动我们的 VideoStream , vs。使用 vs ,我们将能够在下一个 while 循环中从我们的相机中捕获帧。我们将让我们的相机预热 2.0 秒。

    现在让我们开始我们的 while 循环并开始跟踪面部对象:

    # loop over the frames from the video stream
    while True:
    	# read the next frame from the video stream and resize it
    	frame = vs.read()
    	frame = imutils.resize(frame, width=400)
    	# if the frame dimensions are None, grab them
    	if W is None or H is None:
    		(H, W) = frame.shape[:2]
    	# construct a blob from the frame, pass it through the network,
    	# obtain our output predictions, and initialize the list of
    	# bounding box rectangles
    	blob = cv2.dnn.blobFromImage(frame, 1.0, (W, H),
    		(104.0, 177.0, 123.0))
    	net.setInput(blob)
    	detections = net.forward()
    	rects = []
    

    我们遍历帧并将它们调整为固定宽度(同时保持纵横比)。我们的帧尺寸根据需要设置。
    然后我们将帧通过 CNN 对象检测器来获得预测和对象位置。我们初始化一个矩形列表来保存我们的边界框矩形。

    	# loop over the detections
    	for i in range(0, detections.shape[2]):
    		# filter out weak detections by ensuring the predicted
    		# probability is greater than a minimum threshold
    		if detections[0, 0, i, 2] > args["confidence"]:
    			# compute the (x, y)-coordinates of the bounding box for
    			# the object, then update the bounding box rectangles list
    			box = detections[0, 0, i, 3:7] * np.array([W, H, W, H])
    			rects.append(box.astype("int"))
    			# draw a bounding box surrounding the object so we can
    			# visualize it
    			(startX, startY, endX, endY) = box.astype("int")
    			cv2.rectangle(frame, (startX, startY), (endX, endY),
    				(0, 255, 0), 2)
    

    我们开始循环检测。如果检测结果超过我们的置信度阈值,表明检测有效,我们:

    • 计算边界框坐标并将它们附加到 rects 列表中
    • 在对象周围绘制一个边界框

    最后,让我们在质心跟踪器对象 ct 上调用 update

    	# update our centroid tracker using the computed set of bounding
    	# box rectangles
    	objects = ct.update(rects)
    	# loop over the tracked objects
    	for (objectID, centroid) in objects.items():
    		# draw both the ID of the object and the centroid of the
    		# object on the output frame
    		text = "ID {}".format(objectID)
    		cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10),
    			cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    		cv2.circle(frame, (centroid[0], centroid[1]), 4, (0, 255, 0), -1)
    	# show the output frame
    	cv2.imshow("Frame", frame)
    	key = cv2.waitKey(1) & 0xFF
    	# if the `q` key was pressed, break from the loop
    	if key == ord("q"):
    		break
    # do a bit of cleanup
    cv2.destroyAllWindows()
    vs.stop()
    

    ct.update 调用处理了我们使用 Python 和 OpenCV 脚本实现的简单对象跟踪器中的繁重工作。 如果我们不关心可视化,我们将在这里完成并准备循环。

    我们将质心显示为一个填充的圆和唯一的对象ID号文本。现在,我们将能够可视化结果,并检查CentroidTracker是否通过将正确的ID与视频流中的对象相关联来正确地跟踪对象。

    我们显示帧,直到按下退出键(“q”)。如果按下退出键,我们只需中断并执行清理。

    7.完整代码

    centroidtracker.py

    # import the necessary packages
    from scipy.spatial import distance as dist
    from collections import OrderedDict
    import numpy as np
    
    class CentroidTracker():
        def __init__(self, maxDisappeared=50):
            # initialize the next unique object ID along with two ordered
            # dictionaries used to keep track of mapping a given object
            # ID to its centroid and number of consecutive frames it has
            # been marked as "disappeared", respectively
            self.nextObjectID = 0
            self.objects = OrderedDict()
            self.disappeared = OrderedDict()
    
            # store the number of maximum consecutive frames a given
            # object is allowed to be marked as "disappeared" until we
            # need to deregister the object from tracking
            self.maxDisappeared = maxDisappeared
    
        def register(self, centroid):
            # when registering an object we use the next available object
            # ID to store the centroid
            self.objects[self.nextObjectID] = centroid
            self.disappeared[self.nextObjectID] = 0
            self.nextObjectID += 1
    
        def deregister(self, objectID):
            # to deregister an object ID we delete the object ID from
            # both of our respective dictionaries
            del self.objects[objectID]
            del self.disappeared[objectID]
    
        def update(self, rects):
            # check to see if the list of input bounding box rectangles
            # is empty
            if len(rects) == 0:
                # loop over any existing tracked objects and mark them
                # as disappeared
                for objectID in self.disappeared.keys():
                    self.disappeared[objectID] += 1
    
                    # if we have reached a maximum number of consecutive
                    # frames where a given object has been marked as
                    # missing, deregister it
                    if self.disappeared[objectID] > self.maxDisappeared:
                        self.deregister(objectID)
    
                # return early as there are no centroids or tracking info
                # to update
                return self.objects
    
            # initialize an array of input centroids for the current frame
            inputCentroids = np.zeros((len(rects), 2), dtype="int")
    
            # loop over the bounding box rectangles
            for (i, (startX, startY, endX, endY)) in enumerate(rects):
                # use the bounding box coordinates to derive the centroid
                cX = int((startX + endX) / 2.0)
                cY = int((startY + endY) / 2.0)
                inputCentroids[i] = (cX, cY)
    
            # if we are currently not tracking any objects take the input
            # centroids and register each of them
            if len(self.objects) == 0:
                for i in range(0, len(inputCentroids)):
                    self.register(inputCentroids[i])
    
            # otherwise, are are currently tracking objects so we need to
            # try to match the input centroids to existing object
            # centroids
            else:
                # grab the set of object IDs and corresponding centroids
                objectIDs = list(self.objects.keys())
                objectCentroids = list(self.objects.values())
    
                # compute the distance between each pair of object
                # centroids and input centroids, respectively -- our
                # goal will be to match an input centroid to an existing
                # object centroid
                D = dist.cdist(np.array(objectCentroids), inputCentroids)
    
                # in order to perform this matching we must (1) find the
                # smallest value in each row and then (2) sort the row
                # indexes based on their minimum values so that the row
                # with the smallest value as at the *front* of the index
                # list
                rows = D.min(axis=1).argsort()
    
                # next, we perform a similar process on the columns by
                # finding the smallest value in each column and then
                # sorting using the previously computed row index list
                cols = D.argmin(axis=1)[rows]
    
                # in order to determine if we need to update, register,
                # or deregister an object we need to keep track of which
                # of the rows and column indexes we have already examined
                usedRows = set()
                usedCols = set()
    
                # loop over the combination of the (row, column) index
                # tuples
                for (row, col) in zip(rows, cols):
                    # if we have already examined either the row or
                    # column value before, ignore it
                    # val
                    if row in usedRows or col in usedCols:
                        continue
    
                    # otherwise, grab the object ID for the current row,
                    # set its new centroid, and reset the disappeared
                    # counter
                    objectID = objectIDs[row]
                    self.objects[objectID] = inputCentroids[col]
                    self.disappeared[objectID] = 0
    
                    # indicate that we have examined each of the row and
                    # column indexes, respectively
                    usedRows.add(row)
                    usedCols.add(col)
    
                # compute both the row and column index we have NOT yet
                # examined
                unusedRows = set(range(0, D.shape[0])).difference(usedRows)
                unusedCols = set(range(0, D.shape[1])).difference(usedCols)
    
                # in the event that the number of object centroids is
                # equal or greater than the number of input centroids
                # we need to check and see if some of these objects have
                # potentially disappeared
                if D.shape[0] >= D.shape[1]:
                    # loop over the unused row indexes
                    for row in unusedRows:
                        # grab the object ID for the corresponding row
                        # index and increment the disappeared counter
                        objectID = objectIDs[row]
                        self.disappeared[objectID] += 1
    
                        # check to see if the number of consecutive
                        # frames the object has been marked "disappeared"
                        # for warrants deregistering the object
                        if self.disappeared[objectID] > self.maxDisappeared:
                            self.deregister(objectID)
    
                # otherwise, if the number of input centroids is greater
                # than the number of existing object centroids we need to
                # register each new input centroid as a trackable object
                else:
                    for col in unusedCols:
                        self.register(inputCentroids[col])
    
            # return the set of trackable objects
            return self.objects
    

    object_tracker.py

    # USAGE
    # python object_tracker.py --prototxt deploy.prototxt --model res10_300x300_ssd_iter_140000.caffemodel
    
    # import the necessary packages
    from pyimagesearch.centroidtracker import CentroidTracker
    from imutils.video import VideoStream
    import numpy as np
    import argparse
    import imutils
    import time
    import cv2
    
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-p", "--prototxt", required=True,
        help="path to Caffe 'deploy' prototxt file")
    ap.add_argument("-m", "--model", required=True,
        help="path to Caffe pre-trained model")
    ap.add_argument("-c", "--confidence", type=float, default=0.5,
        help="minimum probability to filter weak detections")
    args = vars(ap.parse_args())
    
    # initialize our centroid tracker and frame dimensions
    ct = CentroidTracker()
    (H, W) = (None, None)
    
    # load our serialized model from disk
    print("[INFO] loading model...")
    net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
    
    # initialize the video stream and allow the camera sensor to warmup
    print("[INFO] starting video stream...")
    vs = VideoStream(src=0).start()
    time.sleep(2.0)
    
    # loop over the frames from the video stream
    while True:
        # read the next frame from the video stream and resize it
        frame = vs.read()
        frame = imutils.resize(frame, width=400)
    
        # if the frame dimensions are None, grab them
        if W is None or H is None:
            (H, W) = frame.shape[:2]
    
        # construct a blob from the frame, pass it through the network,
        # obtain our output predictions, and initialize the list of
        # bounding box rectangles
        blob = cv2.dnn.blobFromImage(frame, 1.0, (W, H),
            (104.0, 177.0, 123.0))
        net.setInput(blob)
        detections = net.forward()
        rects = []
    
        # loop over the detections
        for i in range(0, detections.shape[2]):
            # filter out weak detections by ensuring the predicted
            # probability is greater than a minimum threshold
            if detections[0, 0, i, 2] > args["confidence"]:
                # compute the (x, y)-coordinates of the bounding box for
                # the object, then update the bounding box rectangles list
                box = detections[0, 0, i, 3:7] * np.array([W, H, W, H])
                rects.append(box.astype("int"))
    
                # draw a bounding box surrounding the object so we can
                # visualize it
                (startX, startY, endX, endY) = box.astype("int")
                cv2.rectangle(frame, (startX, startY), (endX, endY),
                    (0, 255, 0), 2)
    
        # update our centroid tracker using the computed set of bounding
        # box rectangles
        objects = ct.update(rects)
    
        # loop over the tracked objects
        for (objectID, centroid) in objects.items():
            # draw both the ID of the object and the centroid of the
            # object on the output frame
            text = "ID {}".format(objectID)
            cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            cv2.circle(frame, (centroid[0], centroid[1]), 4, (0, 255, 0), -1)
    
        # show the output frame
        cv2.imshow("Frame", frame)
        key = cv2.waitKey(1) & 0xFF
    
        # if the `q` key was pressed, break from the loop
        if key == ord("q"):
            break
    
    # do a bit of cleanup
    cv2.destroyAllWindows()
    vs.stop()
    

    8.质心目标跟踪结果

    打开终端并执行以下命令:

    $ python object_tracker.py --prototxt deploy.prototxt \
    	--model res10_300x300_ssd_iter_140000.caffemodel
    [INFO] loading model...
    [INFO] starting video stream...
    

    请注意,即使当我将书籍封面移到相机视野之外时,第二张脸“丢失”了,我们的对象跟踪也能够在它进入视野时再次将其重新拾起。如果面部在视野之外存在超过 50 帧,则该对象将被取消注册。

    9.限制和缺点

    虽然我们的质心跟踪器在这个例子中工作得很好,但这种对象跟踪算法有两个主要缺点。

    首先是它要求在输入视频的每一帧上运行对象检测步骤。

    • 对于非常快速的目标检测器(即颜色阈值和 Haar 级联)来说,必须在每个输入帧上运行检测器可能不是问题。
    • 但是,如果您在资源受限的设备上使用计算量大得多的对象检测器,例如 HOG + 线性 SVM 或基于深度学习的检测器,那么您的帧处理管道将大大减慢,因为您将花费整个管道运行一个非常慢的检测器。

    第二个缺点与质心跟踪算法本身的基本假设有关——质心必须在后续帧之间靠得很近。

    • 这个假设通常成立,但请记住,我们用 2D 帧来表示我们的 3D 世界——当一个对象与另一个对象重叠时会发生什么?
    • 答案是可能会发生对象 ID 切换。
    • 如果两个或多个对象相互重叠到它们的质心相交的点,并且与另一个相应的对象具有最小距离,则算法可能(在不知不觉中)交换对象 ID。
    • 重要的是要了解重叠/遮挡对象问题并非特定于质心跟踪——它也发生在许多其他对象跟踪器中,包括高级对象跟踪器。 然而,质心跟踪的问题更加明显,因为我们严格依赖质心之间的欧几里得距离,并且没有额外的度量、启发式或学习模式。

    只要您在使用质心跟踪时牢记这些假设和限制,该算法就会非常适合您。

    BONUS

    以下实现基于YOLOV3和质心跟踪算法的多目标跟踪

    # import the necessary packages
    from CentroidTracking.centroidtracker import CentroidTracker
    from imutils.video import VideoStream
    import numpy as np
    import argparse
    import imutils
    import time
    import cv2
    import os
    
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    
    ap.add_argument("-i", "--input", required=True,
    	help="path to input video")
    # ap.add_argument("-o", "--output", required=True,
    	# help="path to output video")
    ap.add_argument("-c", "--confidence", type=float, default=0.5,
        help="minimum probability to filter weak detections")
    ap.add_argument("-t", "--threshold", type=float, default=0.3,
        help="threshold when applying non-maxima suppression")
    args = vars(ap.parse_args())
    
    ct = CentroidTracker()
    # load the COCO class labels, our YOLO model was trained on
    labelsPath = os.path.sep.join(["yolo-coco", "coco.names"])
    LABELS = open(labelsPath).read().strip().split("\n")
    # initialize a list of colors to represent each possible class label
    np.random.seed(42)
    COLORS = np.random.randint(0, 255, size=(len(LABELS), 3),dtype="uint8")
    # derive the paths to the YOLO weights and model configuration
    weightsPath = os.path.sep.join(["yolo-coco", "yolov3.weights"])
    configPath = os.path.sep.join(["yolo-coco", "yolov3.cfg"])
    # load our YOLO object detector trained on COCO dataset (80 classes)
    print("[INFO] loading YOLO from disk...")
    net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
    net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
    
    writer = None
    
    if args["input"] == 'camera':
        cap = cv2.VideoCapture(0)
    else:
        cap = cv2.VideoCapture(args["input"])
    
    # try to determine the total number of frames in the video file
    try:
    	prop = cv2.cv.CV_CAP_PROP_FRAME_COUNT if imutils.is_cv2() \
    		else cv2.CAP_PROP_FRAME_COUNT
    	total = int(vs.get(prop))
    	print("[INFO] {} total frames in video".format(total))
    
    # an error occurred while trying to determine the total
    # number of frames in the video file
    except:
    	print("[INFO] could not determine # of frames in video")
    	print("[INFO] no approx. completion time can be provided")
    	total = -1
    
    
    print(cap.isOpened())
    print("starting-----------------------------------------------------------")
    begin = time.time()
    while (cap.isOpened()):
        ret, image = cap.read()
    
        # load our input image and grab its spatial dimension
    
        if ret == True:
            (H, W) = image.shape[:2]
    
            # determine only the *output* layer names that we need from YOLO
            ln = net.getLayerNames()
            ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
    
            # construct a blob from the input image and then perform a forward
            # pass of the YOLO object detector, giving us our bounding boxes and
            # associated probabilities
            blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (416, 416),
            	swapRB=True, crop=False)
            net.setInput(blob)
            start = time.time()
            layerOutputs = net.forward(ln)
            end = time.time()
    
            # show timing information on YOLO
            print("[INFO] YOLO took {:.6f} seconds".format(end - start))
    
            # initialize our lists of detected bounding boxes, confidences, and
            # class IDs, respectively
            boxes = []
            boxes_c = []
            confidences = []
            classIDs = []
            rects = []
            # loop over each of the layer outputs
            for output in layerOutputs:
            	# loop over each of the detections
                for detection in output:
            		# extract the class ID and confidence (i.e., probability) of
            		# the current object detection
                    scores = detection[5:]
                    classID = np.argmax(scores)
                    confidence = scores[classID]
                    # filter out weak predictions by ensuring the detected
                    # probability is greater than the minimum probability
                    if confidence > args["confidence"]:
                        # scale the bounding box coordinates back relative to the
                        # size of the image, keeping in mind that YOLO actually
                        # returns the center (x, y)-coordinates of the bounding
                        # box followed by the boxes' width and height
                        box = detection[0:4] * np.array([W, H, W, H])
                        (centerX, centerY, width, height) = box.astype("int")
            			# use the center (x, y)-coordinates to derive the top and
            			# and left corner of the bounding box
                        x = int(centerX - (width / 2))
                        y = int(centerY - (height / 2))
            			# update our list of bounding box coordinates, confidences,
            			# and class IDs
                        boxes.append([x, y, int(width), int(height)])
                        boxes_c.append([centerX - int(width/2), centerY - int(height/2), centerX + int(width/2), centerY + int(height/2)])
                        confidences.append(float(confidence))
                        classIDs.append(classID)
            # apply non-maxima suppression to suppress weak, overlapping bounding
            # boxes
            idxs = cv2.dnn.NMSBoxes(boxes, confidences, args["confidence"],args["threshold"])
    
            if len(idxs) > 0:
                for i in idxs.flatten():
                    rects.append(boxes_c[i])
            # update our centroid tracker using the computed set of bounding
        	# box rectangles
            objects = ct.update(rects)
        	# loop over the tracked objects
            for (objectID, centroid) in objects.items():
        		# draw both the ID of the object and the centroid of the
        		# object on the output frame
                text = "ID {}".format(objectID)
                cv2.putText(image, text, (centroid[0] - 10, centroid[1] - 10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
                cv2.circle(image, (centroid[0], centroid[1]), 4, (0, 255, 0), -1)
            # ensure at least one detection exists
            if len(idxs) > 0:
            	# loop over the indexes we are keeping
            	for i in idxs.flatten():
            		# extract the bounding box coordinates
            		(x, y) = (boxes[i][0], boxes[i][1])
            		(w, h) = (boxes[i][2], boxes[i][3])
    
            		# draw a bounding box rectangle and label on the image
            		color = [int(c) for c in COLORS[classIDs[i]]]
            		cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
            		text = "{}: {:.4f}".format(LABELS[classIDs[i]], confidences[i])
            		cv2.putText(image, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,
            			0.5, color, 2)
            # writer.write(image)
            # # show the output image
            # cv2.imshow("Image", image)
            # check if the video writer is None
            if writer is None:
                # initialize our video writer
                fourcc = cv2.VideoWriter_fourcc(*"MJPG")
                writer = cv2.VideoWriter("output.avi", fourcc, 30,(image.shape[1], image.shape[0]), True)
                # some information on processing single frame
                if total > 0:
                    elap = (end - start)
                    print("[INFO] single frame took {:.4f} seconds".format(elap))
                    print("[INFO] estimated total time to finish: {:.4f}".format(elap * total))
            cv2.imshow("Live", image)
            # write the output frame to disk
            writer.write(image)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        else:
            break
    # release the file pointers
    print("[INFO] cleaning up...")
    writer.release()
    cap.release()
    cv2.destroyAllWindows()
    finish = time.time()
    
    print(f"Total time taken : {finish - begin}")
    
    

    链接:https://pan.baidu.com/s/1UX_HmwwJLtHJ9e5tx6hwOg?pwd=123a
    提取码:123a

    参考目录

    https://pyimagesearch.com/2018/07/23/simple-object-tracking-with-opencv/

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,132,244
精华内容 452,897
关键字:

跟踪