前言

本实验将借鉴陈佳林的《智能硬件与机器视觉基于树莓派、Python 和OpenCV》这本书使用OpenCV和Python实现简单场景下人脸追踪:我们将利用OpenCV完成简易的人脸目标追踪,我们实现了目标追踪功能,每一个识别到的目标都有一个单独的编号,那么就可以利用这个性能搭建一个计数器。比如,如果被要求识别的目标是人脸,我们就可以搭建一个计算人数的计数器。

本实验配套代码CSDN地址为:
https://download.csdn.net/download/qq_50492541/74147603

本实验配套代码百度网盘地址为:
https://pan.baidu.com/s/1PBdNvfpgp9ihcg49LMezrQ
提取码:p3dm


一、环境准备

1.所需设备及安装的软件包

所需设备:树莓派4B,树莓派3代广角摄像头500W像素

安装的软件包:OpenCV 3.3或更高版本,NumPy,SciPy,imutils。在安装之前请先确认你的电脑上是否安装了Python3,同时,这些软件包的安装过程,请参照网上教程或陈佳林的《智能硬件与机器视觉基于树莓派、Python 和OpenCV》这本书第三章,这里不再赘述。

2.文件目录结构

要实现一个人脸追踪器,所需要的代码文件的目录结构如下:
在这里插入图片描述

  • centroidtracker.py:定义了CentroidTracker类,是 实现形心追踪算法的基础。
  • object_tracker.py:创建CentroidTracker的对象, 并实现人脸追踪。
  • deploy.prototxt和 res10_300x300_ssd_iter_140000.caffemodel:OpenCV的 深度学习人脸检测器,将为形心追踪算法提供人脸识 别的功能。当然,这里也可以换成其他人脸识别器。

二、核心原理和效果简介

1.步骤介绍

识别视频中的人脸是OpenCV的一个经典应用,本实验我们将利用OpenCV完成简易的人脸目标追踪,实现以下几个步骤:

  • 第1步:识别出视频中的某些特定目标,比如用绘制目标外界矩形的方法来表示。
  • 第2步:为识别出的目标标上单独的编号。
  • 第3步:目标在图像中移动时,它们的编号不发生变化。也就是说,如果找到一个1号目标,无论它在画面中怎样运动,只要不离开视野,我们应该认为它始终就是1号目标,1号目标也始终是它,而不是为它重新编号或者将其他目标编为1号。

更进一步,如果我们实现了上述的目标追踪功能,每一个识别到的目标都有一个单独的编号,那么就可以利用这个性能搭建一个计数器。比如,如果被要求识别的目标是人脸,我们就可以搭建一个计算人数的计数器。以上这些对于很多机器视觉与图像处理算法的要求都很高,会需要充足的空间和性能来运行更加强悍的深度学习算法。但是在本实验中我们直接利用Python和OpenCV实现一种简易的、便于理解的目标追踪算法——形心追踪算法。

2.定义CentroidTracker类

在centroidtracker.py中,定义了一个CentroidTracker类,这个类包含了注册、注销等功 能,用于实现形心追踪算法。
#-*- coding: UTF-8 -*-
# 调用所需库
from scipy.spatial import distance as dist
from collections import OrderedDict
import numpy as np

class CentroidTracker():
   def __init__(self, maxDisappeared=50):
      # 初始化下一个新出现人脸的ID
      # 初始化两个有序字典 
      # objects用来储存ID和形心坐标
      # disappeared用来储存ID和对应人脸已连续消失的帧数
      self.nextObjectID = 0
      self.objects = OrderedDict()
      self.disappeared = OrderedDict()

      # 设置最大连续消失帧数
      self.maxDisappeared = maxDisappeared

   def register(self, centroid):
      # 分别在两个字典中注册新人脸 并更新下一个新出现人脸的ID
      self.objects[self.nextObjectID] = centroid
      self.disappeared[self.nextObjectID] = 0
      self.nextObjectID += 1

   def deregister(self, objectID):
      # 分别在两个字典中注销已经消失的人脸
      del self.objects[objectID]
      del self.disappeared[objectID]

   def update(self, rects):
      # 检查输入的人脸外接矩形列表是否为空
      if len(rects) == 0:
         # 对每一个注册人脸 标记一次消失
         for objectID in self.disappeared.keys():
            self.disappeared[objectID] += 1

            # 当连续消失帧数超过最大值时 注销人脸
            if self.disappeared[objectID] > self.maxDisappeared:
               self.deregister(objectID)

         # 因为没有识别到人脸 本次更新结束
         return self.objects

      # 对于当前帧 初始化外接矩形形心的存储矩阵
      inputCentroids = np.zeros((len(rects), 2), dtype="int")

      # 对于每一个矩形执行操作
      for (i, (startX, startY, endX, endY)) in enumerate(rects):
         # 计算形心
         cX = int((startX + endX) / 2.0)
         cY = int((startY + endY) / 2.0)
         inputCentroids[i] = (cX, cY)

      # 如果当前追踪列表为空 则说明这些矩形都是新人脸 需要注册
      if len(self.objects) == 0:
         for i in range(0, len(inputCentroids)):
            self.register(inputCentroids[i])

      # 否则需要和旧人脸进行匹配
      else:
         # 从字典中获取ID和对应形心坐标
         objectIDs = list(self.objects.keys())
         objectCentroids = list(self.objects.values())

         # 计算全部已有人脸的形心与新图像中人脸的形心的距离 用于匹配
         # 每一行代表一个旧人脸形心与所有新人脸形心的距离
         # 元素的行代表旧人脸ID 列代表新人脸形心
         D = dist.cdist(np.array(objectCentroids), inputCentroids)

         # 接下来的两步用于匹配新物体和旧物体
         # min(axis=1)获得由每一行中的最小值组成的向量 代表了距离每一个旧人脸最近的新人脸的距离
         # argsort()将这些最小值进行排序 获得的是由相应索引值组成的向量
         # rows里存储的是旧人脸的ID 排列顺序是按照‘最小距离’从小到大排序
         rows = D.min(axis=1).argsort()

         # argmin(axis=1)获得每一行中的最小值的索引值,代表了距离每一个旧人脸最近的新人脸的ID
         # cols里存储的是新人脸的ID 按照rows排列
         cols = D.argmin(axis=1)[rows]

         # 为了防止重复更新、注册、注销 我们设置两个集合存储已经更新过的row和col
         usedRows = set()
         usedCols = set()

         # 对于所有(row,col)的组合执行操作
         for (row, col) in zip(rows, cols):
            # row和col二者之一被检验过 就跳过 
            if row in usedRows or col in usedCols:
               continue

            # 更新已经匹配的人脸 
            objectID = objectIDs[row]
            self.objects[objectID] = inputCentroids[col]
            self.disappeared[objectID] = 0

            # 更新以后将索引放入已更新的集合
            usedRows.add(row)
            usedCols.add(col)

         # 计算未参与更新的row和col
         unusedRows = set(range(0, D.shape[0])).difference(usedRows)
         unusedCols = set(range(0, D.shape[1])).difference(usedCols)

         # 如果距离矩阵行数大于列数 说明旧人脸数大于新人脸数
         # 有一些人脸在这帧图像中消失了
         if D.shape[0] >= D.shape[1]:
            # 对于没有用到的旧人脸索引
            for row in unusedRows:
               # 获取它的ID 连续消失帧数+1
               objectID = objectIDs[row]
               self.disappeared[objectID] += 1

               # 检查连续消失帧数是否大于最大允许值 若是则注销
               if self.disappeared[objectID] > self.maxDisappeared:
                  self.deregister(objectID)

         # 如果距离矩阵列数大于行数 说明新人脸数大于旧人脸数
         # 有一些新人脸出现在了视频中 需要进行注册 
         else:
            for col in unusedCols:
               self.register(inputCentroids[col])

      # 返回追踪列表
      return self.objects

3.人脸追踪的实现

在object_tracker.py中,我们通过调用 CentroidTracker类和DNN人脸探测器来实现人脸追踪。当然,DNN只是人脸探测的一种方法,除此之外还有基于Haar特征的级联分类器、HOG+linear SVM、SSD、Faster R-CNN等机器学习和深度学习的方法,可以根据自己的掌握能力灵活使用。在这个文件中,我们需要做的工作包括如下几点。
  • 从视频图像流VideoStream中抓取图像。

  • 加载并实现OpenCV中的人脸检测器。

  • 实例化CentroidTracker类,实现基于形心追踪算法的人脸跟踪。

  • 在图像中绘制正在跟踪的人脸外接矩形并标号。

object_tracker.py的具体代码如下所示:

from objecttracker.centroidtracker import CentroidTracker
from imutils.video import VideoStream
from imutils.video import FPS
import numpy as np
import argparse
import imutils
import cv2
import time

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")
ap.add_argument("-v", "--video", type=str,
                help="path to optinal input video file")
args = vars(ap.parse_args())
ct = CentroidTracker()
(H, W) = (None, None)
print("[INFO] loading model...")
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
if not args.get("video", False):
    print("[INFO] starting video stream...")
    vs = VideoStream(src=0).start()
    time.sleep(1.0)
else:
    vs = cv2.VideoCapture(args["video"])
     
fps = FPS().start()

while True:
    frame = vs.read()
    frame = frame[1] if args.get("video", False) else frame
    if frame is None:
        break
    frame = imutils.resize(frame, width=400)
    if W is None or H is None:
        (H, W) = frame.shape[:2]
    blob = cv2.dnn.blobFromImage(frame, 1.0, (W, H),(104.0, 177.0, 123.0))
    net.setInput(blob)
    detections = net.forward()
    rects = []
    for i in range(0, detections.shape[2]):
        if detections[0, 0, i, 2] > args["confidence"]:
            box = detections[0, 0, i, 3:7] * np.array([W, H, W, H])
            rects.append(box.astype("int"))
            (startX, startY, endX, endY) = box.astype("int")
            cv2.rectangle(frame, (startX, startY), (endX, endY),(0, 255, 0), 2)
    objects = ct.update(rects)
    for (objectID, centroid) in objects.items():
        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)
    cv2.imshow("Frame", frame)
    key = cv2.waitKey(1) & 0xFF
    if key == ord("q"):
        break

fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
if not args.get("video", False):
    vs.stop()
else:
    vs.release()
cv2.destroyAllWindows()

4.测试人脸跟踪效果

1. 在代码文件路径下打开终端,执行命令
python object_tracker.py --prototxt deploy.prototxt --model res10_300x300_ssd_iter_140000.caffemodel --video video/test.mp4

注:test.mp4为你自己录的视频,再者注意各个文件路径。

2. 直接在object_tracker.py加载路径运行(直接调用摄像头参数测试)

ap = argparse.ArgumentParser()
ap.add_argument("-p", "--prototxt", default="deploy.prototxt",help="path to Caffe 'deploy' prototxt file")
ap.add_argument("-m", "--model", default="res10_300x300_ssd_iter_140000.caffemodel",help="path to Caffe pre-trained model")
ap.add_argument("-c", "--confidence", type=float, default=0.5,help="minimum probability to filter weak detections")
ap.add_argument("-v", "--video",type=str,help="path to optinal input video file")
args = vars(ap.parse_args())

注:注意各个文件路径。

三、模型缺陷与不足

对于我们的追踪算法,有以下两点明显的不足之处:
  1. 我们的目标追踪需要对每一帧图像做一次目标识别。对于速度较快的识别器,如基于Haar特征的级联分类器,这并没有什么问题,但是要注意,越快的算法一般来说识别质量越差。对于一些速度较慢的目标识别器,如HOG+Linear SVM或者深度学习的方法,尤其是当你需要在计算资源有限的设备上运行时,跟踪效果会因为识别器的高时间复杂度大打折扣。
  2. 形心追踪算法是建立在“所有目标在相邻帧的位移相对于各个目标之间的距离来说都是小量”的假设基础上的。我们追踪的目标都是三维世界中的目标,当它们在二维图像中重叠时,就很容易发生目标编号错乱的情况,这是因为我们在计算距离时使用的是欧氏距离而没有引入别的信息。对于一些很先进的算法,目标重叠对于跟踪效果的影响也是难以解决的。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐