前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >计算机视觉项目:用dlib进行单目标跟踪

计算机视觉项目:用dlib进行单目标跟踪

作者头像
AiTechYun
发布2018-12-05 10:29:26
3.5K0
发布2018-12-05 10:29:26
举报
文章被收录于专栏:ATYUN订阅号ATYUN订阅号

编译:yxy

出品:ATYUN订阅号

本教程将教你如何使用dlib和Python执行目标跟踪(object tracking)。阅读今天的博客文章后,你将能够使用dlib实时跟踪视频中的目标。

过去,我们曾经讨论过质心跟踪(centroid tracking),这是一种简单而有效的方法,它可以(1)为图像中的每个目标(或者说对象)分配唯一的ID,然后(2)跟踪视频流中移动的每个对象和其关联的ID。

这种跟踪算法的最大缺点是,在大多数情况下,必须在每个输入帧上运行单独的对象检测器,这种行为作为对象检测器来说很不友善,运行起来计算成本很高(包括HOG +线性SVM,Faster R-CNNs和SSD)。

另一种方法是:

  • 执行一次对象检测(或每N帧执行一次)
  • 然后应用专门的跟踪算法,算法可以在后续帧中移动时跟踪目标,且无需执行对象检测

那么,这种方法可行吗?

答案是肯定的,特别是我们可以使用dlib的关联跟踪算法实现。在今天博文的剩余部分中,你将学习如何应用dlib的关联跟踪器在视频流中实时跟踪对象。

使用dlib进行目标跟踪

我们将从今天的教程开始,简要讨论dlib基于关联的目标跟踪的实现。

然后,我将向你展示如何在自己的应用程序中使用dlib的目标跟踪器。

最后,我们将讨论dlib的目标跟踪器的一些缺点和局限性。

什么是关联跟踪器?

DLIB关联跟踪器(correlation tracker)Danelljan等人的2014年论文《Accurate Scale Estimation for Robust Visual Tracking》实现。

论文:http://www.bmva.org/bmvc/2014/papers/paper038/index.html

同时,他们的工作建立在Bolme等人2010年的工作《Visual Object Tracking using Adaptive Correlation Filters》的基础上。虽然MOSSE跟踪器适用于平移,但应用于尺寸发生变化的物品上通常会失败。

论文:http://www.cs.colostate.edu/~vision/publications/bolme_cvpr10.pdf

Danelljan等人的工作。提出利用“scale pyramid”在找到最佳平移后准确估计物体的比例。这一突破使我们能够跟踪在平移和在整个视频流中进行缩放的对象 。此外,我们可以实时执行这个跟踪。

有关算法的详细介绍,请参阅上面论文链接。

项目结构

要查看这个项目是如何组织的,只需在终端中使用tree命令:

代码语言:javascript
复制
$ tree
代码语言:javascript
复制
.
代码语言:javascript
复制
├── input
代码语言:javascript
复制
│   ├── cat.mp4
代码语言:javascript
复制
│   └── race.mp4
代码语言:javascript
复制
├── output
代码语言:javascript
复制
│   ├── cat_output.avi
代码语言:javascript
复制
│   └── race_output.avi
代码语言:javascript
复制
├── mobilenet_ssd
代码语言:javascript
复制
│   ├── MobileNetSSD_deploy.caffemodel
代码语言:javascript
复制
│   └── MobileNetSSD_deploy.prototxt
代码语言:javascript
复制
└── track_object.py
代码语言:javascript
复制
代码语言:javascript
复制
3 directories, 7 files

我们有三个目录:

  • input / :包含用于目标跟踪的输入视频。
  • output/ :我们处理过的视频。在处理过的视频中,被跟踪的物体使用框和标签进行注释。
  • mobilenet_ssd / :Caffe CNN模型文件包含在此目录中。

今天我们将回顾一个Python脚本: track_object .py 。

实现我们的dlib目标跟踪器

让我们继续使用dlib实现我们的目标跟踪器。

打开 track_object .py 并插入以下代码:

代码语言:javascript
复制
# import the necessary packages
代码语言:javascript
复制
from imutils.videoimport FPS
代码语言:javascript
复制
import numpy as np
代码语言:javascript
复制
import argparse
代码语言:javascript
复制
import imutils
代码语言:javascript
复制
import dlib
代码语言:javascript
复制
import cv2

在这里我们导入我们所需的包如dlib、imutils和OpenCV。

接下来,让我们解析一下命令行参数

代码语言:javascript
复制
# construct the argument parse and parse the arguments
代码语言:javascript
复制
ap= argparse.ArgumentParser()
代码语言:javascript
复制
ap.add_argument("-p","--prototxt", required=True,
代码语言:javascript
复制
    help="path to Caffe 'deploy' prototxt file")
代码语言:javascript
复制
ap.add_argument("-m","--model", required=True,
代码语言:javascript
复制
    help="path to Caffe pre-trained model")
代码语言:javascript
复制
ap.add_argument("-v","--video", required=True,
代码语言:javascript
复制
    help="path to input video file")
代码语言:javascript
复制
ap.add_argument("-l","--label", required=True,
代码语言:javascript
复制
    help="class label we are interested in detecting + tracking")
代码语言:javascript
复制
ap.add_argument("-o","--output",type=str,
代码语言:javascript
复制
    help="path to optional output video file")
代码语言:javascript
复制
ap.add_argument("-c","--confidence",type=float, default=0.2,
代码语言:javascript
复制
    help="minimum probability to filter weak detections")
代码语言:javascript
复制
args= vars(ap.parse_args())

我们的脚本有四个必需的命令行参数:

  • – prototxt :Caffe部署原型文件的路径。
  • – model :Caffe预训练模型的路径。
  • – video :输入视频文件的路径。本文的脚本适用于视频文件而不是网络摄像头(但很容易改为支持摄像头)。
  • – label :我们有兴趣检测和跟踪的类标签。查看该模型支持的可用类的下一个代码块

还有两个可选的:

  • – output :如果要保存目标跟踪器的结果,则为输出视频文件提供一个可选路径。
  • – confidence : default = 0.2 ,这是最小概率阈值,它允许我们从Caffe对象检测器中过滤较弱的检测。

让我们定义这个模型支持的类,并从磁盘加载我们的网络:

代码语言:javascript
复制
# initialize the list of class labels MobileNet SSD was trained to
代码语言:javascript
复制
# detect
代码语言:javascript
复制
CLASSES= ["background","aeroplane","bicycle","bird","boat",
代码语言:javascript
复制
    "bottle","bus","car","cat","chair","cow","diningtable",
代码语言:javascript
复制
    "dog","horse","motorbike","person","pottedplant","sheep",
代码语言:javascript
复制
    "sofa","train","tvmonitor"]
代码语言:javascript
复制
代码语言:javascript
复制
# load our serialized model from disk
代码语言:javascript
复制
print("[INFO] loading model...")
代码语言:javascript
复制
net= cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])

我们将使用经过预训练的MobileNet SSD在单个帧中执行对象检测。接着,对象的位置将被传递给dlib的关联跟踪器,以便在视频的剩余帧中进行跟踪。

模型支持20个对象类(有一个背景类,第3-6行)。

注意: 如果使用的是其他Caffe模型,则需要重新定义CLASSES 列表。同样,如果使用本中包含的模型,请不要修改此列表。

在循环视频帧之前,我们需要将模型加载到内存中。这在第10行处理, 其中加载Caffe模型所需的prototxt和model文件的路径(两者都在我们的命令行args字典中提供)。

现在让我们执行重要的初始化,特别是我们的视频流:

代码语言:javascript
复制
# initialize the video stream, dlib correlation tracker, output video
代码语言:javascript
复制
# writer, and predicted class label
代码语言:javascript
复制
print("[INFO] starting video stream...")
代码语言:javascript
复制
vs= cv2.VideoCapture(args["video"])
代码语言:javascript
复制
tracker= None
代码语言:javascript
复制
writer= None
代码语言:javascript
复制
label= ""
代码语言:javascript
复制
代码语言:javascript
复制
# start the frames per second throughput estimator
代码语言:javascript
复制
fps= FPS().start()

我们的视频流, tracker和视频写入器(writer)对象在第4-6行初始化 。我们还在第7行初始化了文本标签label。

我们的每秒帧数估算器在第10行实例化 。

现在我们已准备好开始循环我们的视频帧:

代码语言:javascript
复制
# loop over frames from the video file stream
代码语言:javascript
复制
while True:
代码语言:javascript
复制
    # grab the next frame from the video file
代码语言:javascript
复制
    (grabbed, frame)= vs.read()
代码语言:javascript
复制
代码语言:javascript
复制
    # check to see if we have reached the end of the video file
代码语言:javascript
复制
    if frameis None:
代码语言:javascript
复制
        break
代码语言:javascript
复制
代码语言:javascript
复制
    # resize the frame for faster processing and then convert the
代码语言:javascript
复制
    # frame from BGR to RGB ordering (dlib needs RGB ordering)
代码语言:javascript
复制
    frame= imutils.resize(frame, width=600)
代码语言:javascript
复制
    rgb= cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
代码语言:javascript
复制
代码语言:javascript
复制
    # if we are supposed to be writing a video to disk, initialize
代码语言:javascript
复制
    # the writer
代码语言:javascript
复制
    if args["output"]is not None and writeris None:
代码语言:javascript
复制
        fourcc= cv2.VideoWriter_fourcc(*"MJPG")
代码语言:javascript
复制
        writer= cv2.VideoWriter(args["output"], fourcc,30,
代码语言:javascript
复制
            (frame.shape[1], frame.shape[0]),True)

我们在第2行开始while循环, 然后继续在第4行抓取一个frame。

我们的帧被调整大小,并交换颜色通道(第12和13行)。调整大小可以加快处理速度,你可以尝试使用帧尺寸(frame dimension)来获得更高的每秒帧数(FPS)。dlib需要转换为RGB色彩空间(OpenCV默认BGR)。

在运行时,可以通过命令行参数传递输出视频路径(视情况选择)。所以,如果有必要,我们将初始化我们的视频写入器 上 线17-20。有关使用OpenCV将视频写入磁盘的更多信息,请参阅:https://www.pyimagesearch.com/2016/02/22/writing-to-video-with-opencv/

接下来,我们需要检测一个跟踪对象(如果我们还没有):

代码语言:javascript
复制
# if our correlation object tracker is None we first need to
代码语言:javascript
复制
# apply an object detector to seed the tracker with something
代码语言:javascript
复制
# to actually track
代码语言:javascript
复制
if trackeris None:
代码语言:javascript
复制
    # grab the frame dimensions and convert the frame to a blob
代码语言:javascript
复制
    (h, w)= frame.shape[:2]
代码语言:javascript
复制
    blob= cv2.dnn.blobFromImage(frame,0.007843, (w, h),127.5)
代码语言:javascript
复制
代码语言:javascript
复制
    # pass the blob through the network and obtain the detections
代码语言:javascript
复制
    # and predictions
代码语言:javascript
复制
    net.setInput(blob)
代码语言:javascript
复制
    detections= net.forward()

如果我们的tracker对象为 None (第4行),我们首先需要检测输入帧中的对象 。为此,我们创建一个 blob (第7行)并通过网络传递它(第11和12行)。

我们 现在处理detections:

代码语言:javascript
复制
# ensure at least one detection is made
代码语言:javascript
复制
if len(detections) >0:
代码语言:javascript
复制
    # find the index of the detection with the largest
代码语言:javascript
复制
    # probability -- out of convenience we are only going
代码语言:javascript
复制
    # to track the first object we find with the largest
代码语言:javascript
复制
    # probability; future examples will demonstrate how to
代码语言:javascript
复制
    # detect and extract *specific* objects
代码语言:javascript
复制
    i= np.argmax(detections[0,0, :,2])
代码语言:javascript
复制
代码语言:javascript
复制
    # grab the probability associated with the object along
代码语言:javascript
复制
    # with its class label
代码语言:javascript
复制
    conf= detections[0,0, i,2]
代码语言:javascript
复制
    label= CLASSES[int(detections[0,0, i,1])]

如果我们的物体探测器找到了若干对象(第2行),我们会抓取概率最大的对象(第8行)。

本文演示了如何使用dlib来执行单个目标跟踪,因此我们需要找到概率最高的检测对象(以后的博客文章将介绍使用dlib进行多目标跟踪)。

从那里,我们将获得了与对象相关的置信度(conf )和label(第12和13行)。

现在是时候过滤掉检测结果了。这里,我们需要确保我们拥有通过命令行参数传递的正确类型的对象:

代码语言:javascript
复制
# filter out weak detections by requiring a minimum
代码语言:javascript
复制
# confidence
代码语言:javascript
复制
if conf > args["confidence"]and label== args["label"]:
代码语言:javascript
复制
    # compute the (x, y)-coordinates of the bounding box
代码语言:javascript
复制
    # for the object
代码语言:javascript
复制
    box= detections[0,0, i,3:7]* np.array([w, h, w, h])
代码语言:javascript
复制
    (startX, startY, endX, endY)= box.astype("int")
代码语言:javascript
复制
代码语言:javascript
复制
    # construct a dlib rectangle object from the bounding
代码语言:javascript
复制
    # box coordinates and then start the dlib correlation
代码语言:javascript
复制
    # tracker
代码语言:javascript
复制
    tracker= dlib.correlation_tracker()
代码语言:javascript
复制
    rect= dlib.rectangle(startX, startY, endX, endY)
代码语言:javascript
复制
    tracker.start_track(rgb, rect)
代码语言:javascript
复制
代码语言:javascript
复制
    # draw the bounding box and text for the object
代码语言:javascript
复制
    cv2.rectangle(frame, (startX, startY), (endX, endY),
代码语言:javascript
复制
        (0,255,0),2)
代码语言:javascript
复制
    cv2.putText(frame, label, (startX, startY- 15),
代码语言:javascript
复制
        cv2.FONT_HERSHEY_SIMPLEX,0.45, (0,255,0),2)

第3行,我们检查以确保conf超过置信度阈值 ,并且该对象实际上是我们正在寻找的类的类型。当我们稍后运行脚本时,我们将使用“person”或“cat”作为示例,以便你可以看到我们如何过滤结果。

我们在第6行和第7行确定对象的边界框坐标(box) 。

然后我们建立我们的dlib目标跟踪器并提供边界框坐标(第14-16行)。这样,未来的跟踪更新将很容易。

在第17-20行 ,在帧上绘制边界框矩形和对象类的标签文本 。

让我们来处理已经建立tracker的情况 :

代码语言:javascript
复制
# otherwise, we've already performed detection so let's track
代码语言:javascript
复制
# the object
代码语言:javascript
复制
else:
代码语言:javascript
复制
    # update the tracker and grab the position of the tracked
代码语言:javascript
复制
    # object
代码语言:javascript
复制
    tracker.update(rgb)
代码语言:javascript
复制
    pos= tracker.get_position()
代码语言:javascript
复制
代码语言:javascript
复制
    # unpack the position object
代码语言:javascript
复制
    startX= int(pos.left())
代码语言:javascript
复制
    startY= int(pos.top())
代码语言:javascript
复制
    endX= int(pos.right())
代码语言:javascript
复制
    endY= int(pos.bottom())
代码语言:javascript
复制
代码语言:javascript
复制
    # draw the bounding box from the correlation object tracker
代码语言:javascript
复制
    cv2.rectangle(frame, (startX, startY), (endX, endY),
代码语言:javascript
复制
        (0,255,0),2)
代码语言:javascript
复制
    cv2.putText(frame, label, (startX, startY- 15),
代码语言:javascript
复制
        cv2.FONT_HERSHEY_SIMPLEX,0.45, (0,255,0),2)

这个else块处理我们已经锁定一个对象来跟踪的情况。

就像电影《壮志凌云》里的混战。一旦敌方飞机被“guidance system”锁定,就可以更新跟踪。

这需要我们采取两项主要行动:

  • 更新我们的跟踪器对象(第6行) – 比较繁杂的任务由后端的update方法完成。
  • 从跟踪器中获取对象的位置(get_position)(第7行)。假设一个机器人跟踪被跟踪的物体,PID控制回路将派上用场。在我们的例子中,我们只是在第16-19行的边界框和标签上注释帧中的对象 。

让我们完成循环:

代码语言:javascript
复制
# check to see if we should write the frame to disk
代码语言:javascript
复制
if writeris not None:
代码语言:javascript
复制
    writer.write(frame)
代码语言:javascript
复制
代码语言:javascript
复制
# show the output frame
代码语言:javascript
复制
cv2.imshow("Frame", frame)
代码语言:javascript
复制
key= cv2.waitKey(1) &0xFF
代码语言:javascript
复制
代码语言:javascript
复制
# if the `q` key was pressed, break from the loop
代码语言:javascript
复制
if key== ord("q"):
代码语言:javascript
复制
    break
代码语言:javascript
复制
代码语言:javascript
复制
# update the FPS counter
代码语言:javascript
复制
fps.update()

如果帧需要写入视频(第2和3行)。我们将在屏幕上显示帧(第6行)。

如果在回放和跟踪期间的任何点按下退出键(“q”),我们将跳出循环(第7-11行)。

我们的fps估计器在14行更新 。

最后,让我们在脚本退出之前执行打印FPS吞吐量统计并释放指针:

代码语言:javascript
复制
# stop the timer and display FPS information
代码语言:javascript
复制
fps.stop()
代码语言:javascript
复制
print("[INFO] elapsed time: {:.2f}".format(fps.elapsed()))
代码语言:javascript
复制
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
代码语言:javascript
复制
代码语言:javascript
复制
# check to see if we need to release the video writer pointer
代码语言:javascript
复制
if writeris not None:
代码语言:javascript
复制
    writer.release()
代码语言:javascript
复制
代码语言:javascript
复制
# do a bit of cleanup
代码语言:javascript
复制
cv2.destroyAllWindows()
代码语言:javascript
复制
vs.release()

我们脚本的内务处理包括:

  • fps计数器停止,FPS信息显示在终端中(第2-4行)。
  • 然后,如果我们编辑输出视频,我们释放视频写入器(第7和8行)。
  • 最后,我们关闭所有OpenCV窗口并发布视频流(第11和12行)。

实时运行dlib的目标跟踪器

要查看我们的dlib目标跟踪器的运行情况,请确保使用文章的源代码(下方链接中下载)。

然后,打开一个终端并执行以下命令:

代码语言:javascript
复制
$ python track_object.py--prototxt mobilenet_ssd/MobileNetSSD_deploy.prototxt \
代码语言:javascript
复制
    --model mobilenet_ssd/MobileNetSSD_deploy.caffemodel--videoinput/race.mp4 \
代码语言:javascript
复制
    --label person--output output/race_output.avi
代码语言:javascript
复制
[INFO] loading model...
代码语言:javascript
复制
[INFO] starting video stream...
代码语言:javascript
复制
[INFO] elapsed time:13.18
代码语言:javascript
复制
[INFO] approx. FPS:25.80

尤塞恩·博尔特(Usain Bolt,奥林匹克世界纪录保持者)最初在视频开头时就以高置信度发现。接着,他在100米比赛中被成功追踪。

下面我们有第二个使用dlib进行目标跟踪的示例:

代码语言:javascript
复制
$ python track_object.py--prototxt mobilenet_ssd/MobileNetSSD_deploy.prototxt \
代码语言:javascript
复制
    --model mobilenet_ssd/MobileNetSSD_deploy.caffemodel--videoinput/cat.mp4 \
代码语言:javascript
复制
    --label cat--output output/cat_output.avi
代码语言:javascript
复制
[INFO] loading model...
代码语言:javascript
复制
[INFO] starting video stream...
代码语言:javascript
复制
[INFO] elapsed time:6.76
代码语言:javascript
复制
[INFO] approx. FPS:24.12

猫主人试图带他们的猫散步的视频片段(这猫真可怜。。)。

缺点和可能的改进

如果你观看了完整输出视频,可能会注意到目标跟踪器在演示快结束时表现得很奇怪(就像上面这个GIF演示的一样)。

那么,这里发生了什么?为什么跟踪器会跟丢?

请记住,没有完美的目标跟踪器 – 并且,这种目标跟踪算法还不要求你在输入图像的每一帧上运行更消耗算力的对象检测器。

相反,dlib的关联跟踪器结合了(1)关于前一帧中对象边界框位置的先验信息:(2)从当前帧获得的数据以推断对象的新位置。

一定会有算法丢失对象的时候。

为了解决这种情况,我建议偶尔运行更费算力的对象探测器,以验证物体是否仍在那里,(2)用更新的(理想情况下是正确的)边界框坐标重新播种目标跟踪。

推荐:https://www.pyimagesearch.com/2018/08/13/opencv-people-counter/

那么多目标跟踪呢?

毫无疑问,我知道会有人希望将此方法应用于多目标跟踪而不是单个目标跟踪。是否可以使用dlib的关联跟踪器跟踪多个对象?

答案是肯定的!

我将在后续讨论多目标跟踪,敬请期待。

总结

在本文中,我们讨论了dlib的目标跟踪算法。与质心跟踪不同,dlib的目标跟踪算法可以利用从输入RGB图像获得的信息更新自身,即算法不需要为输入视频流中的每个帧计算一组边界框。

我们发现,dlib的关联跟踪算法很稳定,能够实时运行。

然而,最大的缺点是:如果视角发生了很大的变化,或者被跟踪的对象被遮挡关联跟踪器可能变得“混乱”,并丢失我们希望跟踪的对象。这种情况下,我们可以重新运行我们的(耗算力大)对象检测器来重新确定我们跟踪对象的位置。

下载源码:www.pyimagesearch.com/2018/10/22/object-tracking-with-dlib/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ATYUN订阅号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用dlib进行目标跟踪
  • 什么是关联跟踪器?
  • 项目结构
  • 实现我们的dlib目标跟踪器
  • 实时运行dlib的目标跟踪器
  • 缺点和可能的改进
  • 那么多目标跟踪呢?
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档