Python 实现人脸追踪

1,998 阅读6分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。

1. 前言

人脸识别是计算机视觉的重要领域,而 OpenCV 是一个非常出色的计算机视觉框架,我们用 OpenCV 可以很容易的实现人脸的识别。同时我们也可以使用 OpenCV 读取视频,对视频逐帧进行人脸识别,这样就能达到人脸追踪的效果。

2. OpenCV 介绍

OpenCV 是一个非常出色的计算机视觉框架,里面提供了大量图像方面的操作。从基础的读取到修改参数再到高级的物体检测、边缘检测等,我们基础的 OpenCV 只提供了图像处理和一些图像方面的算法实现,要进行人脸识别我们需要安装两个模块:

pip install opencv-python
pip install opencv-contrib-python

其中 opencv-python 就是我们的基础库,而 opencv-contrib-python 是额外的库,里面就包含了人脸识别的函数。下面我们看看 OpenCV 的基础操作。

2.1 图像处理

我们实现人脸追踪是针对视频的,但是视频都是有一帧一帧图片组成,所以我们需要先对图像的操作有个简单的了解:

# 导入模块
import cv2
# 读取图片
im = cv2.imread('xscn.png')

# 输出图片的高宽和通道数
width = im.shape[1]		# 宽
height = im.shape[0]	# 高
num = im.shape[2]	# 通道数
# 修改图片大小
re_im = cv2.resize(im, (width//2, height//2))
# 将图片写入文件
cv2.imwrite('xscn_copy.png', re_im)

# 显示图片
cv2.imshow('im', re_im)
cv2.waitKey(0)
cv2.destroyAllWindows()

在我们导入的时候是导入cv2,读取图片的函数为imread,传入图片的路径,调用该函数后会返回一个ndarray对象。在返回的对象中,shape属性包含了宽高和通道数的信息。

在 OpenCV 中大多数操作都是直接使用模块中的函数操作ndarray对象,在显示图片时我们通常有三个步骤。首先是使用imshow显示图片,该函数传入窗口名和图片的ndarray对象。调用imshow函数图像只会显示一瞬间,我们需要调用waitKey让图片显示有一个时长,传入的参数为毫秒数,当传入 0 时表示无限等待。最后就是回收内存,因为 OpenCV 底层是 C++,所以我们需要回收窗口内存。

2.2 绘制形状

为了看起来更直观,我们需要将人脸绘制出来,我们来看看 opencv 中如何在图片上绘制形状。

import cv2
# 读取图片
im = cv2.imread('xscn_copy.png')
# 绘制图形
cv2.rectangle(im, (0, 0), (100, 100), (0, 255, 0), 2)
# 显示图片
cv2.imshow('im', im)
cv2.waitKey(0)
cv2.destroyAllWindows()

因为不是专门学习 OpenCV,所以这里只讲绘制矩形的函数rectangle。该函数通常传入五个参数,第一个为图片的 ndarray 对象,第二个为矩形左上角的坐标,第三个参数为右下角的坐标,第四个为颜色的元组,第五个则是线条的宽度。

2.3 读取视频

OpenCV 中提供了读取视频的类VideoCapture,我们可以使用该类读取视频,也可以使用该类读取设备的摄像头:

import cv2
# 读取指定视频
cap = cv2.VideoCapture('video.mp4')
# 读取帧,ret 表示是否有下一帧,frame 为当前帧的 ndarray 对象
ret, frame = cap.read()
while ret:
	# 将当前帧缩小后显示
    cv2.imshow('im', cv2.resize(frame, (frame.shape[1]//2, frame.shape[0]//2)))
    # 等待 10 毫秒
    cv2.waitKey(10)
    # 读取下一帧
    ret, frame = cap.read()
# 回收内存
cv2.destroyAllWindows()

在我们使用VideoCapture时,如果传入数字则表示读取第 n 个摄像头,通常设备只有 2 个摄像头,所以选取参数在 0 和 1,大家可以自己测试一下。

3. 人脸检测

在检测人脸之前我们需要获取人脸的特征文件,这个当然不需要我们自己创建,我们可以在 OpenCV 官网下载相应版本,界面如下:

在这里插入图片描述

下载后安装,然后在文件下 opencv\sources\data\haarcascades 找到对应的特征文件,通常我们使用 haarcascade_frontalface_default.xml 作为特征文件进行检测。如果有 GPU 加速可以使用 opencv\sources\data\haarcascades_cuda 下的特征文件。

下面我们就看看如何实现人脸检测:

import cv2

# 加载特征文件
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# 读取人脸图片
face_im = cv2.imread('xscn.png')
# 灰度转换
grey = cv2.cvtColor(face_im, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = face_detector.detectMultiScale(grey)
# 遍历人脸
for x, y, w, h in faces:
	# 在人脸区域绘制矩形
    cv2.rectangle(face_im, (x, y), (x+w, y+h), (0, 255, 0), 5)
    cv2.imshow('face', cv2.resize(face_im, (face_im.shape[1]//2, face_im.shape[0]//2)))
    cv2.waitKey(0)

cv2.destroyAllWindows()

我们将检测人脸的步骤大致分为如下:

  1. 加载特征文件
  2. 读取包含的人脸图片
  3. 灰度转换
  4. 检测人脸图片
  5. 遍历人脸

加载特征文件是通过 CascadeClassifier 类实现的。在读取人脸图像后我们进行灰度转换,这样是为了消除一些干扰信息。然后调用“检测者” face_detector 的 detectMultiScale 方法检测人脸,返回一个列表,列表中每个元素包含四个信息,人脸矩形左上角的 x、y 以及人脸的宽高。检测结果如下:

在这里插入图片描述

可以看到图中检测到了两个人脸,但是有一个是错误的。在准确率方面,OpenCV 的准确率也还算可以。

4. 人脸追踪

人脸追踪是建立在人脸检测之上的,在读取视频的时候对每一帧图像进行人脸检测就能实现人脸追踪的效果了。下面我们来看看具体的代码:

import cv2
# 读取视频
cap = cv2.VideoCapture('video.mp4')

# 加载特征文件
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

# 读取帧
ret, frame = cap.read()

# 循环读取
while ret:
    # 灰度转换
    grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 检测人脸
    faces = face_detector.detectMultiScale(grey)

	# 遍历人脸
    for x, y, w, h in faces:
    	# 绘制矩形
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 5)
        cv2.imshow('video.mp4', cv2.resize(frame, (frame.shape[1]//2, frame.shape[0]//2)))
        cv2.waitKey(10)
        break	# 只绘制一个人脸就到下一帧
    # 读取下一帧
    ret, frame = cap.read()
    
# 销毁窗口
cv2.destroyAllWindows()

效果如下图:

在这里插入图片描述

上面的代码就是读取视频和人脸检测的结合没有额外的内容。在遍历人脸的时候我添加了一个 break,这样是为了方便,这样的话只绘制一个人脸。当然我们可以将检测结果视频保存到本地,这需要用到我们的 VideoWriter 类了:

import cv2
cap = cv2.VideoCapture('video.mp4')

# 获取原视频的帧率
fps = cap.get(cv2.CAP_PROP_FPS)
# 获取画面大小
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
size = (width, height)

# 写入视频
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
writer = cv2.VideoWriter('result.mp4', fourcc, fps, size)

# 加载特征文件
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

ret, frame = cap.read()
while ret:
    # 灰度转换
    grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 检测人脸
    faces = face_detector.detectMultiScale(grey)
    for x, y, w, h in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 5)
        cv2.imshow('video.mp4', cv2.resize(frame, (frame.shape[1]//2, frame.shape[0]//2)))
        cv2.waitKey(10)
        break
	# 将当前帧写入视频
    writer.write(frame)
    ret, frame = cap.read()
# 释放
writer.release()
cv2.destroyAllWindows()

执行后我们可以在项目下看到 result.mp4 文件,就是我们的结果视频。我们先是用 VideoWriter_fourcc 类来设置视频的模式信息,然后创建 VideoWriter 对象进行视频的写入,我们将处理好的图像帧写入视频,最后调用 release 释放。如果我们不释放的话视频会出问题。

最后需要说一句,输出的视频是没有声音的,如果想添加声音需要使用 MoviePy 模块。感兴趣的读者可以去了解一下。