基于OpenCV的目标跟踪实现

本节内容是学习实现如何通过OpenCV实现质心跟踪,一个易于理解且高效的跟踪算法。

目标跟踪的过程:

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

此外,目标跟踪允许我们将唯一的ID应用于每个被跟踪的对象,从而使我们能够对视频中的唯一对象进行计数。目标跟踪对于建立人员计数器至关重要。

理想的目标跟踪算法将会:

  • 仅仅需要请求一次目标检测阶段
  • 将非常快-比运行实际的对象检测器本身快得多
  • 能够处理当被跟踪对象“消失”或移动到视频帧的边界之外
  • 抗遮挡能力强
  • 能够拾取帧之间丢失的目标

对于任何计算机视觉和图像处理算法来说这都是一个很高的要求,并且我们可以发挥多种技巧来帮助提高我们的目标检测器。

但是在我们构建一个鲁棒的方法之前,我们首先需要研究目标跟踪的基础。

目标跟踪算法被叫做质心跟踪算法是因为它依赖于以下两者的欧式距离:

  1. 已经存在的目标质心
  2. 视频后续帧中的新目标质心

我将实现一个包含质心追踪算法的Python类,并且创建一个脚本去导入视频并运行目标追踪器。

最后,将运行目标检测器并检查结果,同时注意算法的优缺点。

质心跟踪算法

质心跟踪算法是一个多步骤过程。下面回顾每一个跟踪步骤。

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

 

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

这些边界框可以由您想要的任何类型的对象检测器生成(颜色阈值+轮廓提取,Haar级联,HOG +线性SVM,SSD,Faster R-CNN等),前提是要针对帧中的每个帧进行计算该视频。

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

由于这些是提供给我们算法的边界框的第一组初始集合,因此我们将为其分配唯一的ID。

步骤2 计算新边界框和存在目标的欧式距离

 

对于视频流中的每个后续帧,我们应用计算对象质心的步骤1。但是,我们首先需要确定是否可以将新的对象质心与旧的对象质心相关联,而不是为每个检测到的对象分配新的唯一ID(这会破坏对象跟踪的目的)。为了完成此过程,我们计算了每对现有对象质心和输入对象质心之间的欧式距离。

从上图中你可以看到我们在图像中检测到三个物体。靠近的两对是两个存在的目标。

然后,我们计算每对原始质心和新质心之间的欧式距离。但是,我们如何使用这些点之间的欧式距离来实际匹配它们并将它们关联起来呢?

步骤3:更新存在目标的(x, y)坐标

 

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

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

上图中,你可以看到质心追踪算法如何选择将最小化欧式距离的质心关联起来。

但是左下角没有任何可以关联的点需要怎么办?

步骤4:注册新的目标

 

如果输入检测的数量多于被跟踪的现有对象,需要注册一个新目标。注册意味着需要添加一个新的对象到追踪列表中,通过:

  1. 给它分配一个新的对象ID
  2. 存储该对象的边界框坐标的质心

现在回到步骤2对视频流中的每一帧重复步骤。

上图演示了使用最小欧式距离来关联现有对象ID,然后注册一个新对象的过程。

步骤5:注销旧的目标

任何合理的对象跟踪算法都必须能够处理对象丢失,消失或离开视野时的情况。实际情况下,如何处理这些情况实际上取决于对象跟踪器的部署位置,但是对于此实现,我们将在旧对象无法与任何现有对象匹配(总共N个后续帧)时注销旧对象。

目标跟踪项目结构

# 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, maxDistance=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

		# store the maximum distance between centroids to associate
		# an object -- if the distance is larger than this maximum
		# distance we'll start to mark the object as "disappeared"
		self.maxDistance = maxDistance

	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 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

		# 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
				if row in usedRows or col in usedCols:
					continue

				# if the distance between centroids is greater than
				# the maximum distance, do not associate the two
				# centroids to the same object
				if D[row, col] > self.maxDistance:
					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

需要源码扫描下方二维码公众号内回复。

                                                                       基于OpenCV的目标跟踪实现_第1张图片

 

 

你可能感兴趣的:(OpenCV,Machine,Learning,机器学习)