OpenCV实现多目标追踪

OpenCV实现多目标追踪

  1. 项目框架
  2. 各代码块分析
  3. 完整代码及资源下载连接

实验框架,使用的模块

实验框架如下

├── mobilenet_ssd

​ ├── MobileNetSSD_deploy.caffemodel

└── MobileNetSSD_deploy.prototxt

├── multi_object_tracking_fast.py

├── race.mp4

├── race_output_slow.avi

└── race_output_fast.avi

用到的的模块

​ python3, OpenCV, dlib, python多进程模块(multiprocessing)

代码块分析

引入必须的库

from imutils.video import FPS
import multiprocessing
import numpy as np
import argparse
import imutils
import dlib
import cv2

​ 通过使用python的Process类来生成一个新的进程(每个进程都独立于原始进程)

进程函数定义

​ python多处理是指Python将调用此函数,然后创建一个全新的解释器来执行其中的代码。因此,每个启动跟踪程序生成的进程将独立于其父进程。要与Python驱动程序脚本通信,我们需要利用管道或队列。这两种类型的对象都是线程/进程安全的,使用锁和信号量完成。

​ 实质上,我们正在建立一种简单的生产者/消费者关系:

​ 父进程将生成新帧并将它们添加到特定对象跟踪器的队列中。

​ 子进程将使用帧,应用对象跟踪,然后返回更新的边界框坐标。

​ 首先,我们将尝试从第21行的inputQueue中获取一个新帧。

​ 如果帧不为空,我们将获取帧,然后更新对象跟踪器,从而获得更新的边界框坐标(第24-34行)。

​ 最后,我们将标签和边界框加入outputQueue,以便父进程可以在脚本的主循环中利用它们(第38行)。

​ 回到父进程,我们将分析命令行参数:

def start_tracker(box, label, rgb, inputQueue, outputQueue):
	'''
	brief : 从边界框坐标构造一个dlib矩形对象,然后启动相关跟踪器
	:param box:		由检测器返回的边界框坐标
	:param label:	检测的对象标签
	:param rgb:		启动初始dlib对象跟踪器的RGB有序图像
	:param inputQueue:	输入队列
	:param outputQueue:	输出队列
	:return: null
	'''
	# t就是一个追踪器对象
	t = dlib.correlation_tracker()
	rect = dlib.rectangle(box[0], box[1], box[2], box[3])
	t.start_track(rgb, rect)

	# 这个函数将作为守护进程调用,所以不用担心join它
	while True:
		# 尝试从输入队列中获取下一帧
		rgb = inputQueue.get()

		# 如果队列里有一项,那么就处理它
		if rgb is not None:
			# 更新追踪器并且获取追踪对象的位置
			t.update(rgb)
			pos = t.get_position()

			# 分析对象的位置
			startX = int(pos.left())
			startY = int(pos.top())
			endX = int(pos.right())
			endY = int(pos.bottom())

			# 把对象标签和边界框加入到输出队列,坐标信息是传的元组
			outputQueue.put((label, (startX, startY, endX, endY)))

解析命令行参数

–prototxt:Caffe“deploy”prototxt文件的路径。

–model:prototxt附带的模型文件的路径。

–video:输入视频文件的路径。我们将在这段视频中使用dlib执行多目标跟踪。

–outpuy:输出视频文件的可选路径。如果未指定路径,则不会将视频输出到磁盘。我建议输出到.avi或.mp4文件。

–confidence:对象检测置信阈值为0.2的可选覆盖。该值表示从目标检测器中过滤弱检测的最小概率。

# 构造命令行参数并解析他们
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("-v", "--video", required=True,
	help="path to input video file")
ap.add_argument("-o", "--output", type=str,
	help="path to optional output video file")
ap.add_argument("-c", "--confidence", type=float, default=0.2,
	help="minimum probability to filter weak detections")
args = vars(ap.parse_args())

初始化输入输出队列

​ 这些队列将保存我们正在跟踪的对象。生成的每个进程都需要两个队列对象:

​ 一个读取输入帧

​ 第二个进行写结果

	# 初始化输入输出队列, 我们要追踪每一个物体
inputQueues = []
outputQueues = []

初始化其他内容

# 初始化Caffe网络已经训练好的检测的对象列表
CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat",
	"bottle", "bus", "car", "cat", "chair", "cow", "diningtable",
	"dog", "horse", "motorbike", "person", "pottedplant", "sheep",
	"sofa", "train", "tvmonitor"]

# 从磁盘加载序列化模型
print("[INFO] loading model...")
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])

# initialize the video stream and output video writer
# 初始化视频流和输出视频对象
print("[INFO] starting video stream...")
vs = cv2.VideoCapture(args["video"])
writer = None


# 启动FPS吞吐量计数器,计算视频的FPS值
fps = FPS().start()

开始读取视频流

# 遍历视频流的每一个帧
while True:
	# 获取帧
	(grabbed, frame) = vs.read()

	# 判断是否为最后一帧
	if frame is None:
		break

	# 重新调整帧大小,转变成RGB格式
	frame = imutils.resize(frame, width=600)
	rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)


	# 如果要有输出的视频,初始化writer进行写视频
	if args["output"] is not None and writer is None:
		fourcc = cv2.VideoWriter_fourcc(*"MJPG")
		writer = cv2.VideoWriter(args["output"], fourcc, 30,
			(frame.shape[1], frame.shape[0]), True)

处理没有输入队列的情况

如果没有输入队列(第101行),那么我们知道需要在对象跟踪之前应用对象检测。

我们在第103-109行应用目标检测,然后在第112行对结果进行循环。我们获取我们的置信值并过滤掉115-119行上的弱检测。

如果我们的置信度满足由命令行参数建立的阈值,我们会考虑检测,但我们会进一步通过类标签过滤掉它。在本例中,我们只寻找“person”对象(第122-127行)。

假设我们找到了一个“人”,我们将创建队列并生成跟踪进程:

我们首先计算第131-133行的边界框坐标。

从这里我们创建了两个新队列,iq和oq(第137和138行),分别将它们附加到inputQueues和outputQueues(第139和140行)。

从那里我们产生一个新的开始追踪过程,通过边界框,标签,rgb图像,和iq+oq(第143-147行)。别忘了在这里阅读更多关于多处理的内容。

我们还绘制了被检测对象的边框矩形和类标签(第151-154行)。

否则,我们已经执行了对象检测,因此需要将每个dlib对象跟踪器应用于帧:

# 如果输入队列为空,那么说明我们还没有创建第一个对象追踪器
	if len(inputQueues) == 0:

		# 获取帧的尺寸,转化为blob(caffe模型检测的基本单元)
		(h, w) = frame.shape[:2]
		# 归一化 : 0.007843等于127.5分之一
		blob = cv2.dnn.blobFromImage(frame, 0.007843, (w, h), 127.5)

		# 将blob输入到网络中,获取到检测结果,返回的是4维矩阵
		'''
		[0 0 i 1]:置信度
		[0 0 i 2]:标签序号
		[0 0 i 3:7]:检测对象的xy坐标
		'''
		net.setInput(blob)
		detections = net.forward()

		# 遍历矩阵
		for i in np.arange(0, detections.shape[2]):
			# 提取相关置信度,即检测到对象的概率
			confidence = detections[0, 0, i, 2]

			# 通过设定的最小置信度,过滤掉弱检测目标
			if confidence > args["confidence"]:
				# 获取检测到的对象标签,与之前的类标签序号对应
				idx = int(detections[0, 0, i, 1])
				label = CLASSES[idx]

				# 这里我们主要检测人,也可以通过设置参数,这样就可以检测类标签里面的对象
				if CLASSES[idx] != "person":
					continue

				# 计算目标对象边界框的坐标
				box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
				(startX, startY, endX, endY) = box.astype("int")
				bb = (startX, startY, endX, endY)

				# 分别创建两个全新的输入和输出队列
				iq = multiprocessing.Queue()
				oq = multiprocessing.Queue()
				inputQueues.append(iq)
				outputQueues.append(oq)

				# 为新的对象跟踪程序生成守护进程
				p = multiprocessing.Process(
					target=start_tracker,
					args=(bb, label, rgb, iq, oq))
				p.daemon = True
				p.start()

				# 抓取相应的类标签进行检测,并绘制边界框,绿色,二维
				cv2.rectangle(frame, (startX, startY), (endX, endY),
					(0, 255, 0), 2)
				# 放上类名字
				cv2.putText(frame, label, (startX, startY - 15),
					cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 255, 0), 2)

输入队列不为空

在每个输入队列上循环,我们向它们添加rgb图像(第162和163行)。

然后在每个输出队列(第166行)上循环,从每个独立的对象跟踪器(第171行)获取边界框坐标。最后,我们在第175-178行画出边界框+相关的类标签。

		# 输出队列不为空,说明我们已经有检测结果,所以我们继续追踪多个对象
		else:
			# 在每个输入队列上循环并添加输入RGB帧,使我们能够更新在不同进程中运行的每个对象跟踪器
			for iq in inputQueues:
				iq.put(rgb)

			# 在每个输出队列上循环
			for oq in outputQueues:

				# 抓取到更新后的边界框
				# get方法是一个阻塞操作,因此因此这将暂停我们的执行,直到进程完成跟踪更新
				(label, (startX, startY, endX, endY)) = oq.get()

				# 从追踪器画出边界框
				cv2.rectangle(frame, (startX, startY), (endX, endY),
					(0, 255, 0), 2)
				cv2.putText(frame, label, (startX, startY - 15),
					cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 255, 0), 2)

完成最后的循环

如有必要,我们将帧写入输出视频,并将帧显示到屏幕(第181-185行)。

如果按下“q”键,我们“退出”,跳出循环(第186-190行)。

如果我们继续处理帧,fps计算器将在第193行更新,然后在while循环的开始处再次开始处理。

否则,我们将处理帧,并显示FPS吞吐量信息+释放指针并关闭窗口。

		# 判断是否需要输出视频
		if writer is not None:
			writer.write(frame)

		# 播放处理后的帧
		cv2.imshow("Frame", frame)
		key = cv2.waitKey(1) & 0xFF

		# 按'q'退出
		if key == ord("q"):
			break

		# 更新FPS计数器
		fps.update()

	# 停止计数器并且显示FPS值
	fps.stop()
	print("[INFO] elapsed time: {:.2f}".format(fps.elapsed()))
	print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))

	# 释放写指针
	if writer is not None:
		writer.release()

	# 收尾工作
	cv2.destroyAllWindows()
	vs.release()

完整代码及参考网站及库下载

参考网址:https://www.pyimagesearch.com/2018/10/29/multi-object-tracking-with-dlib/

代码模型视频下载地址:https://download.csdn.net/download/qq903952032/12516940

你可能感兴趣的:(计算机视觉,python)