百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

构建机器人指南:9 面部跟踪摄像头

zhezhongyun 2025-01-05 21:23 48 浏览

本章涵盖

  • 使用 OpenCV 库检测图像中的人脸
  • 测量和优化人脸检测性能
  • 在实时视频中进行人脸检测
  • 使用伺服电机制作面部跟踪相机

本章将首先展示如何使用 OpenCV 库在图像中检测人脸。然后,我们将扩展此功能以在实时视频流中检测人脸,并测量和优化我们的人脸检测过程。一旦我们建立了快速的人脸检测机制,我们将创建一个应用程序,在实时视频流中执行人脸检测。本章的最后部分包括创建一个可以检测人脸运动并通过电机将摄像头移动到检测到的人脸方向的应用程序。人脸检测是一项要求高的计算机视觉活动,使用机器学习来检测人脸。

机器学习在人工智能领域中扮演着重要角色,并且在机器人技术中有许多应用。在本章中,我们将创建一个机器人,该机器人使用电机根据从相机接收到的图像输入数据中看到的面孔来移动其相机。这是一种强大的技术,可以扩展到许多能够根据环境中检测到的事件自动反应和采取行动的机器人。许多自主机器人系统是通过获取传感器输入而创建的,它们利用机器学习来决定机器人应该采取什么行动以实现其目标。这些应用范围从食品配送到建筑,机器人执行复杂的任务,例如砌砖。

9.1 硬件堆栈

图 9.1 显示了硬件堆栈,本章中使用的具体组件已被突出显示。机器人将使用伺服电机将附加的摄像头移动到检测到的面孔方向。根据检测到的面孔是在摄像头的左侧还是右侧,伺服电机将朝面孔的方向移动电机。本章的初始应用将专注于使用摄像头硬件进行面部检测,随后将相关的伺服运动添加到机器人功能中。

9.2 软件栈

本章中使用的特定软件的详细信息在图 9.2 中描述。我们通过创建 detect_face 应用程序开始本章,该应用程序将使用 OpenCV 库在单个图像上执行人脸检测。然后,我们使用 measure_face 脚本以及 statistics 和 time 模块来测量我们的人脸检测过程的性能。一旦我们应用了一些性能增强,我们将创建 face 库,该库可以执行快速人脸检测,从而使 live_face 应用程序成为可能。 live_face 应用程序在实时视频流中执行人脸检测。本章以 follow 应用程序结束,该应用程序移动伺服电机以跟随人脸运动。我们将使用 Linux 视频子系统和摄像头硬件进行人脸检测。 crickit 库将用于控制伺服电机。

图 9.2 软件栈:将使用 OpenCV 库进行人脸检测。

9.3 在图像中检测人脸

第一步是在单张图像上执行人脸检测。我们需要创建一个满足以下要求的 Python 应用程序:

  • 应使用 OpenCV 计算机视觉库来检测图像中人脸的位置。
  • 该应用程序应能够在检测到的人脸周围绘制一个矩形,并在其中心放置一个标记。
  • 面部中心的 x,y 坐标应被计算并返回。

计算面部中心的最终要求在本章后面将非常有帮助,因为我们将用它来决定伺服电机的移动方向。

9.3.1 探索人脸检测

OpenCV 文档 ( https://docs.opencv.org/4.x/) 是一个优秀的资源,提供了关于常见主题(如人脸检测)的良好教程。在 Python 教程部分,它提到人脸检测由 objdetect 模块覆盖。具体来说,关于 objdetect 级联分类器的教程详细解释了理论和在 OpenCV 中的人脸检测应用。

OpenCV 使用基于 Haar 特征的级联分类器进行人脸检测。这种方法利用机器学习从大量正面和负面的人脸图像中训练级联函数。正面图像包含人脸,而负面图像则不包含人脸。一旦函数训练完成,我们就可以用它来检测我们提供的任何图像中的人脸。

预训练模型作为 OpenCV 库的一部分被提供,可以直接使用。这些模型是可以在 OpenCV 安装的数据目录中找到的 XML 文件。我们可以开始使用这些模型并在读取-评估-打印循环(REPL)中执行人脸检测。第一步是导入 cv2 包:

>>> import cv2

要定位 OpenCV 安装的路径,我们可以检查 __path__ 属性:

>>> cv2.__path__
['/home/robo/pyenv/lib/python3.9/site-packages/cv2']

__path__ 属性提供了 cv2 包的位置信息列表。列表中的第一个项目是我们感兴趣的。我们可以将其保存在 CV2_DIR 变量中以供进一步使用:

>>> CV2_DIR = cv2.__path__[0]
>>> CV2_DIR
'/home/robo/pyenv/lib/python3.9/site-packages/cv2'

现在我们可以计算用于人脸检测的模型 XML 文件的路径,并将其保存在一个名为 CLASSIFIER_PATH 的变量中:

>>> CLASSIFIER_PATH = f'{CV2_DIR}/data/haarcascade_frontalface_default.xml'

我们现在可以使用 CascadeClassifier 函数从模型文件创建分类器。创建后,我们将分类器保存在一个名为 face_classifier 的变量中:

>>> face_classifier = cv2.CascadeClassifier(CLASSIFIER_PATH)

这个分类器可以用来检测图像中的人脸。让我们试用一下这个分类器,开始检测人脸。用相机拍一张人脸的照片,并将图像保存为 photo.jpg ,与 REPL 会话保存在同一目录中。我们可以使用 imread 函数打开这个图像:

>>> frame = cv2.imread('photo.jpg')

如预期的那样,当我们检查图像的 shape 属性时,可以看到图像的分辨率为 640 x 480 像素,每个像素有三个颜色分量。图 9.3 显示了我们在此 REPL 会话中使用的图像:

>>> frame.shape
(480, 640, 3)


我们的分类器将检查图像不同区域的像素强度。为此,您希望图像以灰度图像而不是彩色图像的形式表示。我们可以通过调用 cvtColor 将彩色图像转换为灰度图像:

>>> gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

如果我们检查我们新的 gray 图像的 shape 属性,我们可以看到它不再为每个像素具有三个颜色组件。相反,它只有一个像素强度值,范围从 0 到 255 ,表示黑色的值为 0 ,然后是更高的灰色值,一直到白色的 255 。

>>> gray.shape
(480, 640)

我们在进行人脸检测之前对图像执行的第二个操作是直方图均衡化。此操作改善了图像的对比度,从而提高了我们人脸检测的准确性。我们将准备好的图像保存到一个名为 clean 的变量中。图 9.4 显示了应用直方图均衡化后结果图像的样子:

>>> clean = cv2.equalizeHist(gray)


我们现在可以在我们的分类器上调用 detectMultiScale 方法,该方法将在我们的图像上执行人脸检测,并将结果作为检测到的人脸列表返回:

>>> faces = face_classifier.detectMultiScale(clean)

当我们检查 faces 的长度时,可以看到在图像中成功检测到一个面

>>> len(faces)
1

检查 faces 显示,对于每个检测到的面孔,提供了一组与该检测到的面孔相关的值。每组值与一个匹配的矩形相关:

>>> faces
array([[215, 105, 268, 268]])

我们可以将第一个检测到的人脸的矩形值保存到表示矩形左上角坐标的变量 x, y 中,以及表示矩形宽度和高度的变量 w, h 中:

>>> x, y, w, h = faces[0]

我们可以看到匹配面左上角位于坐标 (215, 105):

>>> x, y
(215, 105)

我们现在有足够的知识来快速制作我们的第一个人脸检测应用程序。让我们将所学的内容整合到一个脚本中,以便在图像中检测人脸。

深入探讨:使用 OpenCV 进行机器学习

OpenCV 文档有一个全面的机器学习概述(https://docs.opencv.org/4.x/dc/dd6/ml_intro.xhtml),这是深入了解 OpenCV 中机器学习主题的一个很好的起点。

在机器学习的核心是使用训练数据构建和训练模型的算法,这些模型可以基于这些数据进行预测。一旦这些训练好的模型就位,我们可以向它们输入算法之前未见过的新数据,它们就可以基于这些数据进行预测。在本章中,我们使用了一个在一组人脸图像上训练的模型,以检测新图像中人脸的存在和位置。

另一个计算机视觉应用是对手写数字进行 OCR(光学字符识别)。OpenCV 项目提供了 5,000 个手写数字的样本,可以用作训练数据来训练我们的模型。可以使用 k 近邻算法来训练我们的模型,然后用它们来识别图像中的数字。在 OpenCV 文档的机器学习部分的 Python 教程中有一个很好的示例。

9.3.2 标记检测到的面孔

我们将创建一个脚本来对图像进行人脸检测,然后在匹配的人脸周围绘制一个矩形。我们还将计算匹配矩形的中心,并在中心点放置一个标记。一旦我们完成检测和形状绘制,我们将在我们的图形应用程序中显示最终图像。第一步是导入 cv2 库:

import cv2

蓝色的值保存在变量 BLUE 中, cv2 库的位置保存在 CV2_DIR 中。我们现在可以通过使用 CV2_DIR 来设置我们的 CLASSIFIER_PATH 。然后,我们的面部分类器被创建并保存在 face_classifier 中:

BLUE = (255, 0, 0)
CV2_DIR = cv2.__path__[0]
CLASSIFIER_PATH = f'{CV2_DIR}/data/haarcascade_frontalface_default.xml'
face_classifier = cv2.CascadeClassifier(CLASSIFIER_PATH)

prep_face 函数将通过将图像转换为灰度并应用直方图均衡化来准备面部检测的图像。然后返回准备好的图像:

def prep_face(frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    return cv2.equalizeHist(gray)

我们将定义 get_center 来计算矩形的中心坐标。我们可以用它来计算检测到的人脸的中心。该函数接收与矩形相关的标准值,然后返回中心点作为 x,y 坐标对:

def get_center(x, y, w, h):
    return int(x + (w / 2)), int(y + (h / 2))

detect_face 函数接收一张图像并返回匹配人脸的中心坐标。它首先调用 prep_face 来准备图像以进行人脸检测,然后调用 detectMultiScale 来检测图像中的人脸。如果找到人脸,我们将第一个匹配人脸的矩形值保存到变量 x, y, w, h 中。然后,我们计算人脸的中心并将该值保存到 center 中。 rectangle 函数用于在人脸周围绘制矩形, drawMarker 用于在脸部中心放置标记。最后,返回人脸中心的坐标:

def detect_face(frame):
    clean = prep_face(frame)
    faces = face_classifier.detectMultiScale(clean)
    if len(faces) > 0:
        x, y, w, h = faces[0]
        center = get_center(x, y, w, h)
        cv2.rectangle(frame, (x, y), (x + w, y + h), BLUE, 2)
        cv2.drawMarker(frame, center, BLUE)
        return center

main 函数将我们的面部图像加载到一个名为 frame 的变量中。然后,调用 detect_ face 进行面部检测,面部中心保存在 center 变量中。这些坐标被打印出来,并使用 imshow 显示面部图像。调用 waitKey 函数以在应用程序中按下键之前显示图像:

def main():
    frame = cv2.imread('photo.jpg')
    center = detect_face(frame)
    print('face center:', center)
    cv2.imshow('preview', frame)
    cv2.waitKey()

完整的脚本可以保存在树莓派上的 detect_face.py 中,然后执行。

清单 9.1 detect_face.py : 检测人脸并标记匹配的人脸

#!/usr/bin/env python3
import cv2
 
BLUE = (255, 0, 0)
CV2_DIR = cv2.__path__[0]
CLASSIFIER_PATH = f'{CV2_DIR}/data/haarcascade_frontalface_default.xml'
face_classifier = cv2.CascadeClassifier(CLASSIFIER_PATH)
 
def get_center(x, y, w, h):
    return int(x + (w / 2)), int(y + (h / 2))
 
def prep_face(frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    return cv2.equalizeHist(gray)
 
def detect_face(frame):
    clean = prep_face(frame)
    faces = face_classifier.detectMultiScale(clean)
    if len(faces) > 0:
        x, y, w, h = faces[0]
        center = get_center(x, y, w, h)
        cv2.rectangle(frame, (x, y), (x + w, y + h), BLUE, 2)
        cv2.drawMarker(frame, center, BLUE)
        return center
 
def main():
    frame = cv2.imread('photo.jpg')
    center = detect_face(frame)
    print('face center:', center)
    cv2.imshow('preview', frame)
    cv2.waitKey()
 
main()

当运行此脚本时,它将对 photo.jpg 图像执行人脸检测,并在检测到的人脸周围绘制匹配的矩形和标记。图 9.5 显示了应用程序在完成人脸检测并在匹配人脸周围绘制形状后将呈现的样子。


现在我们已经为图像中的人脸检测奠定了基础,我们可以开始进行实时视频流中的人脸检测这一激动人心的任务。

9.4 在实时视频中检测人脸

在实时视频中检测人脸的方法与在单张图像中检测人脸的方法类似。主要区别在于需要更高的性能要求,以足够快的速度进行人脸检测,以跟上实时视频流。我们需要创建一个满足以下要求的 Python 应用程序:

  • 人脸检测应在从摄像头视频流捕获的每一帧上进行。
  • 人脸检测应该足够快速,以跟上相机的帧率。
  • 应用中的实时视频流应显示任何检测到的面孔,并用匹配的矩形和标记进行标识。

首要任务是测量我们的面部检测性能,以查看其是否足够快速,以跟上我们从视频流中接收到的图像速率。

9.4.1 测量人脸检测性能

我们从上一章知道,我们的相机将以每秒 30 帧的速度捕捉图像。我们需要人脸检测过程的运行速度快于这个帧率,以便能够跟上视频流。我们将创建一个脚本来多次执行人脸检测,然后报告人脸检测所达到的平均帧率。

cv2 库被导入以执行人脸检测。 mean 函数被导入以计算平均帧率。 time 模块将用于测量人脸检测操作的执行时间:

import cv2
from statistics import mean
import time

人脸检测的功能和过程与 detect_face.py 脚本中使用的相同。我们将使用 get_detect_timing 函数来测量人脸检测的执行时间。该函数记录开始时间,然后调用 detect_face 函数。最后,它计算经过的时间(以秒为单位)并返回该值:

def get_detect_timing(frame):
    start = time.perf_counter()
    center = detect_face(frame)
    return time.perf_counter() - start

我们的 main 函数将像以前一样打开 photo.jpg 图像并将其保存在 frame 中。然后,我们首次调用 detect_face 并打印出匹配人脸中心的坐标。接下来,我们重复调用 get_detect_timing 以捕获 10 次执行时间的样本。我们取这个样本的平均值,并计算并报告实现的平均每秒帧数。在每次人脸检测期间,我们使用 frame.copy() 在每次人脸检测时提供帧的干净副本:

def main():
    frame = cv2.imread('photo.jpg')
    center = detect_face(frame.copy())
    print('face center:', center)
    stats = [get_detect_timing(frame.copy()) for i in range(10)]
    print('avg fps:', 1 / mean(stats))

完整的脚本可以保存在树莓派上的 measure_face.py 中,然后执行。

清单 9.2 measure_face.py : 测量人脸检测性能

#!/usr/bin/env python3
import cv2
from statistics import mean
import time
 
BLUE = (255, 0, 0)
CV2_DIR = cv2.__path__[0]
CLASSIFIER_PATH = f'{CV2_DIR}/data/haarcascade_frontalface_default.xml'
face_classifier = cv2.CascadeClassifier(CLASSIFIER_PATH)
 
def get_center(x, y, w, h):
    return int(x + (w / 2)), int(y + (h / 2))
 
def prep_face(frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    return cv2.equalizeHist(gray)
 
def detect_face(frame):
    clean = prep_face(frame)
    faces = face_classifier.detectMultiScale(clean)
    if len(faces) > 0:
        x, y, w, h = faces[0]
        center = get_center(x, y, w, h)
        cv2.rectangle(frame, (x, y), (x + w, y + h), BLUE, 2)
        cv2.drawMarker(frame, center, BLUE)
        return center
 
def get_detect_timing(frame):
    start = time.perf_counter()
    center = detect_face(frame)
    return time.perf_counter() - start
 
def main():
    frame = cv2.imread('photo.jpg')
    center = detect_face(frame.copy())
    print('face center:', center)
    stats = [get_detect_timing(frame.copy()) for i in range(10)]
    print('avg fps:', 1 / mean(stats))
 
main()

当运行此脚本时,它将对 photo.jpg 图像执行人脸检测。检测到的人脸中心的坐标将在终端中打印出来。然后,我们进行 10 次测量检测人脸所需时间的采样。根据这些样本的平均值,计算并报告帧率。我们可以看到,报告的帧率为 10.1 帧每秒,远低于我们所需的 30 帧每秒:

$ measure_face.py 
face center: (349, 239)
avg fps: 10.104761447758944

现在我们已经量化了人脸检测的性能,可以看到存在性能问题,我们可以着手改善人脸检测过程的性能,以便满足并希望超越每秒 30 帧的要求。

9.4.2 减少处理的像素数量

我们的面部检测操作不需要大图像就能准确检测面部。如果我们用较小的图像调用我们的面部分类器,它将处理更少的像素,并且返回结果的速度更快。因此,我们采取的策略是对已缩小到更小尺寸的图像进行面部检测,从而加快处理速度。

我们将把图像的大小调整为原始图像的 20%。通过实验我们可以发现,如果这个值显著小于 10%,会影响检测准确性。我们将看到将该值设置为 20%满足我们的性能需求,并且在安全范围内。

我们可以打开一个 REPL 会话并进行一些计算,以了解通过这种缩放我们减少了图像中像素数量的程度。我们的 20% 缩放相当于将图像的宽度和高度缩小 5 倍。我们可以通过以下计算轻松看到这一点:

>>> 1/5
0.2

捕获的图像宽度为 640 ,高度为 480 。我们可以通过以下计算来计算缩小图像的高度和宽度:

>>> 640/5
128.0
>>> 480/5
96.0

我们可以看到,调整大小后的图像宽度为 128 ,高度为 96 。我们现在可以计算原始图像和调整大小后图像的总像素数:

>>> 640*480
307200
>>> 128*96
12288

现在,我们可以将这两个像素计数相除,以找出我们减少了总像素数量的倍数:

>>> 307200/12288
25.0

我们将需要处理的总像素数量减少了 25 倍。这是一个数据处理的大幅减少,应该会显著提高处理速度。图 9.6 显示了当我们将两幅图像并排放置时,图像大小的显著差异。我们可以通过对宽度和高度的缩减因子进行平方来交叉验证这个数字:

>>> 5*5
25
25.0

如预期的那样,它产生了相同的 25 倍减小因子。

我们可以将较小的图像通过我们的面部检测脚本进行处理,以检查结果。图 9.7 显示图像明显像素化,但这对面部检测过程没有影响。

现在我们已经完成了初步计算,可以着手实现我们应用程序的新快速版本。

9.4.3 优化人脸检测性能

此实现将基于之前的实现,主要添加图像缩减步骤以获得性能提升。首先,我们将导入 cv2 库以执行人脸检测:

import cv2

缩放因子保存在 DETECT_SCALE 变量中:

DETECT_SCALE = 0.2

resize 函数接收图像和所需的缩放比例以调整图像大小并返回新的较小图像。图像的新宽度和高度是根据提供的 scale 计算的,并保存在 size 中。然后在图像上调用 cv2.resize 函数。OpenCV 文档 (https://docs.opencv.org/4.x) 关于 resize 函数提供了在缩小图像时使用 INTER_AREA 插值的指导,而在放大图像时使用 INTER_CUBIC 。我们正在缩小图像,因此我们使用 INTER_AREA :

def resize(img, scale):
    size = (int(img.shape[1] * scale), int(img.shape[0] * scale))
    return cv2.resize(img, size, interpolation=cv2.INTER_AREA)

detect_face 函数现在有了性能增强。在调用 prep_face 之后,会调用 resize 来创建一个更小的图像以进行人脸检测。然后,使用 small 调用 detectMultiScale 。当矩形值返回时,我们将它们除以 DETECT_SCALE ,以便可以重新映射到原始全分辨率图像。通过这种方式,我们可以在全尺寸原始图像上显示检测到的人脸细节,但通过在更小的图像上进行人脸检测来获得性能提升。其余代码保持不变:

def detect_face(frame):
    clean = prep_face(frame)
    small = resize(clean, DETECT_SCALE)
    faces = face_classifier.detectMultiScale(small)
    if len(faces) > 0:
        x, y, w, h = [int(i / DETECT_SCALE) for i in faces[0]]
        center = get_center(x, y, w, h)
        cv2.rectangle(frame, (x, y), (x + w, y + h), BLUE, 2)
        cv2.drawMarker(frame, center, BLUE)
        return center

该库可以保存为 face.py 在 Pi 上,以便被其他应用程序导入。

清单 9.3 face.py : 提供快速的人脸检测库

import cv2
 
BLUE = (255, 0, 0)
CV2_DIR = cv2.__path__[0]
CLASSIFIER_PATH = f'{CV2_DIR}/data/haarcascade_frontalface_default.xml'
face_classifier = cv2.CascadeClassifier(CLASSIFIER_PATH)
DETECT_SCALE = 0.2
 
def resize(img, scale):
    size = (int(img.shape[1] * scale), int(img.shape[0] * scale))
    return cv2.resize(img, size, interpolation=cv2.INTER_AREA)
 
def get_center(x, y, w, h):
    return int(x + (w / 2)), int(y + (h / 2))
 
def prep_face(frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    return cv2.equalizeHist(gray)
 
def detect_face(frame):
    clean = prep_face(frame)
    small = resize(clean, DETECT_SCALE)
    faces = face_classifier.detectMultiScale(small)
    if len(faces) > 0:
        x, y, w, h = [int(i / DETECT_SCALE) for i in faces[0]]
        center = get_center(x, y, w, h)
        cv2.rectangle(frame, (x, y), (x + w, y + h), BLUE, 2)
        cv2.drawMarker(frame, center, BLUE)
        return center

要查看这个库的实际应用,我们将创建一个新的脚本,该脚本将导入该库并多次调用人脸检测函数以测量其性能。我们首先导入 cv2 、 mean 和 time ,正如我们之前所做的那样,以打开图像、计算平均值和测量执行时间。然后,我们从新的 face 库中导入 detect_face 函数:

import cv2
from face import detect_face
from statistics import mean
import time

应用程序的其余部分具有与 measure _face.py 脚本中创建的相同功能,用于测量执行时间并报告达到的平均帧率。

完整的脚本可以保存在树莓派上的 fast_face.py 中,然后执行。

清单 9.4 fast_face.py : 快速人脸检测功能的性能报告

#!/usr/bin/env python3
import cv2
from face import detect_face
from statistics import mean
import time
 
def get_detect_timing(frame):
    start = time.perf_counter()
    center = detect_face(frame)
    return time.perf_counter() - start
 
def main():
    frame = cv2.imread('photo.jpg')
    center = detect_face(frame.copy())
    print('face center:', center)
    stats = [get_detect_timing(frame.copy()) for i in range(10)]
    print('avg fps:', 1 / mean(stats))
 
main()

当这个脚本运行时,它将调用我们新的更快的人脸检测实现。我们可以从结果中看到,已经取得了显著的性能提升,因为现在我们达到了每秒 75.6 帧的帧率。这为我们提供了超过七倍于之前人脸检测方法的性能提升:

$ fast_face.py 
face center: (347, 242)
avg fps: 75.63245789951259

这个帧率也远高于我们希望达到的每秒 30 帧的目标。我们现在可以继续使用这种改进的面部检测方法来处理实时视频流。

9.4.4 在实时视频中显示检测到的面孔

在以下脚本中,我们将从摄像头视频流中捕获图像进行人脸检测,然后在应用程序窗口中显示检测到的人脸。导入了 cv2 库以从摄像头视频流中捕获图像。导入了 detect_face 函数以执行人脸检测:

import cv2
from face import detect_face

如我们之前所做的,Esc 键的关键代码保存在 ESC_KEY 中。它将用于通过按 Esc 键退出图形应用程序:

ESC_KEY = 27

main 函数将视频捕获对象保存在变量 cap 中。然后我们检查捕获设备是否正确打开。我们进入一个事件循环,直到按下 Esc 或 Q 键为止。每次循环迭代中,我们从视频流中捕获一帧,并在捕获的图像上调用 detect_face 函数。然后我们调用 imshow 来显示带有任何检测到的面孔标记的捕获图像。当退出此循环时,我们通过调用 cap.release 函数释放视频捕获设备:

def main():
    cap = cv2.VideoCapture(0)
    assert cap.isOpened(), 'Cannot open camera'
    while cv2.waitKey(1) not in [ord('q'), ESC_KEY]:
        ret, frame = cap.read()
        assert ret, 'Cannot read frame from camera'
        detect_face(frame)
        cv2.imshow('preview', frame)
    cap.release()

完整的脚本可以保存在树莓派上的 live_face.py 中,然后执行。

清单 9.5 live_face.py : 在实时视频流中显示检测到的面孔

#!/usr/bin/env python3
import cv2
from face import detect_face
 
ESC_KEY = 27
 
def main():
    cap = cv2.VideoCapture(0)
    assert cap.isOpened(), 'Cannot open camera'
    while cv2.waitKey(1) not in [ord('q'), ESC_KEY]:
        ret, frame = cap.read()
        assert ret, 'Cannot read frame from camera'
        detect_face(frame)
        cv2.imshow('preview', frame)
    cap.release()
 
main()

当运行此脚本时,它将不断从视频流中捕获图像。每个图像都通过我们的面部检测功能。如果检测到面部,则在检测到的面部周围绘制一个矩形,并在其中心放置一个标记。带有面部检测的视频流将在应用程序窗口中显示,直到按下 Esc 键或 Q 键退出应用程序。

图 9.8 显示了相机安装在云台套件上的样子。两个伺服电机使相机能够朝不同方向移动。

在下一部分,我们将使用伺服电机将相机移动到检测到的面部方向。

9.5 创建一个面部跟随机器人

现在我们可以快速检测面部以处理实时视频,我们可以将我们的代码提升到一个新水平,让机器人对你面部的位置做出反应。机器人将移动摄像头以跟随你的面部。我们需要创建一个满足以下要求的 Python 应用程序:

  • 它应该能够识别面部是在画面的左侧、中间还是右侧被检测到。
  • 当在左侧或右侧检测到面部时,摄像头应朝向面部移动。
  • 在应用程序中,应显示一个带有检测到的面孔标记的实时视频流,以及显示三个区域(左、中和右)的网格。

显示应用中的三个区域将使应用更加互动,因为人们将能够知道他们的面部被检测到在哪个区域,以及相机将移动到哪里。

9.5.1 面部检测的区域划分

我们可以将机器人看到的内容分为三个区域或区域。当在中心区域检测到面孔时,我们不需要做任何事情,因为相机正对着这个人。如果在左侧区域检测到面孔,我们将移动伺服器,使相机将面孔放置在中心区域。如果在右侧区域检测到面孔,我们将再次移动伺服器,但方向相反。我们将只专注于使用伺服电机的平移运动来左右移动相机。图 9.9 显示了面部检测的三个区域。

现在,让我们进入一个 REPL 会话,看看如何将相机视图区域分成这三个区域。首先,我们将导入 cv2 用于在图像上绘制,以及 numpy 用于创建一个新的空白图像:

>>> import cv2
>>> import numpy as np

我们将把相机图像的宽度和高度保存到变量 IMG_WIDTH 和 IMG_HEIGHT 中。这将使我们的代码更易读:

>>> IMG_WIDTH = 640
>>> IMG_HEIGHT = 480

我们可以通过将 IMG_WIDTH 除以二来获得宽度的中心或中点:

>>> (IMG_WIDTH / 2)
320.0

现在,让我们将这个中心位置向左移动 50 像素,以获取左侧和中心区域之间的线的位置。我们将把这个值保存在一个名为 LEFT_X 的变量中:

>>> LEFT_X = int((IMG_WIDTH / 2) - 50)
>>> LEFT_X
270

通过从中心向右移动 50 像素,我们得到了中心和右侧区域之间的线的位置。我们将这个值保存在 RIGHT_X 中:

>>> RIGHT_X = int((IMG_WIDTH / 2) + 50)
>>> RIGHT_X
370

我们可以将绿色的值保存在一个名为 GREEN 的变量中:

>>> GREEN = (0, 255, 0)

接下来,让我们创建一个具有所需尺寸的空白彩色图像:

>>> img = np.zeros(shape=(480, 640, 3), dtype=np.uint8)

我们可以通过在中心区域周围画一个矩形来绘制一个显示三个区域的网格:

>>> cv2.rectangle(img, (LEFT_X, -1), (RIGHT_X, IMG_HEIGHT), GREEN)

最后一步是保存我们创建的内容,以便我们可以查看图像。我们将使用 imwrite 将图像保存为文件名 zones.jpg :

>>> cv2.imwrite('zones.jpg', img)

图 9.10 显示了绘制区域网格后图像的样子。中心区域设置得比左侧和右侧区域更窄。通过这种方式,当面部在画面中移动时,我们可以使相机对左右移动更加敏感。

9.5.2 移动电机以跟随面部

我们现在可以尝试编写脚本,以跟踪人脸在摄像头视野内不同区域的移动。我们可以在上一部分的实验基础上进行扩展。

我们导入 cv2 库以从相机捕获图像。 detect_face 函数被导入,并将执行我们之前看到的人脸检测。最后,我们使用 crickit 模块来控制附有相机的伺服电机:

import cv2
from face import detect_face
from adafruit_crickit import crickit

接下来,我们定义 ESC_KEY 和 GREEN 来存储 Esc 键的键码和绿色的值。图像的高度和宽度在 IMG_WIDTH 和 IMG_HEIGHT 中定义。然后,我们计算 LEFT_X 和 RIGHT_X 的值,以帮助跟踪面部被检测到的区域:

ESC_KEY = 27
GREEN = (0, 255, 0)
IMG_WIDTH = 640
IMG_HEIGHT = 480
LEFT_X = int((IMG_WIDTH / 2) - 50)
RIGHT_X = int((IMG_WIDTH / 2) + 50)

正如我们在第 8 章中所做的,我们创建一个名为 PAN 的变量来跟踪与执行平移运动的伺服电机相关的值。也就是说,我们保持对伺服电机对象的最小、最大和起始角度的引用。我们还将驱动范围设置保存在 range 中。与前一章一样,我们在每一步中将角度变化的值存储在 ANGLE_STEP 中。我们使用 MOVE 将左、中、右区域映射到其相关的伺服电机运动:

PAN = dict(servo=crickit.servo_1, min=30, max=110, start=70, range=142)
ANGLE_STEP = 2
MOVE = dict(L=ANGLE_STEP, C=0, R=-ANGLE_STEP)

get_zone 函数将根据 LEFT_X 和 RIGHT_X 的值返回检测到的面部区域:

def get_zone(face_x):
    if face_x <= LEFT_X:
        return 'L'
    elif face_x <= RIGHT_X:
        return 'C'
    else:
        return 'R'

init_motors 函数用于初始化伺服电机的起始位置和动作范围:

def init_motors():
    PAN['servo'].actuation_range = PAN['range']
    PAN['servo'].angle = PAN['start']

我们将使用 move_motor 函数根据检测到的面部位置移动伺服电机。我们首先通过调用 get_zone 计算区域。然后,我们查找角度变化并将其保存在 change 中。接下来,如果检测到变化并且新角度在我们的最小和最大角度范围内,我们将应用新角度:

def move_motor(face_x):
    zone = get_zone(face_x)
    change = MOVE[zone]
    if change and PAN['min'] <= PAN['servo'].angle + change <= PAN['max']:
        PAN['servo'].angle += change

当我们创建一个新的视频捕获对象时,我们调用 check_capture_device 来检查设备。我们检查设备是否成功打开,以及设备捕获的图像的宽度和高度是否与我们的 IMG_WIDTH 和 IMG_HEIGHT 值匹配:

def check_capture_device(cap):
    assert cap.isOpened(), 'Cannot open camera'
    assert cap.get(cv2.CAP_PROP_FRAME_WIDTH) == IMG_WIDTH, 'wrong width'
    assert cap.get(cv2.CAP_PROP_FRAME_HEIGHT) == IMG_HEIGHT, 'wrong height'

main 函数首先调用 init_motors 来初始化伺服电机。然后我们创建一个视频捕捉设备,并通过调用 check_capture_device 来检查它。接着我们进入一个事件循环,只有在按下 Esc 或 Q 键时才会退出。在每个循环中,我们从视频流中抓取一帧图像并将其保存在 frame 中。然后我们调用 detect_face 进行人脸检测,并返回检测到的人脸中心位置。如果检测到人脸,我们将调用 move_motor ,并传入检测到的人脸的 x 坐标。然后我们通过调用 cv2.rectangle 以相关尺寸在图像上绘制我们的区域网格。循环的最后一步是通过调用 imshow 在应用程序中显示最新的视频帧。当我们退出循环时,我们调用 cap.release 释放视频捕捉设备:

def main():
    init_motors()
    cap = cv2.VideoCapture(0)
    check_capture_device(cap)
    while cv2.waitKey(1) not in [ord('q'), ESC_KEY]:
        ret, frame = cap.read()
        assert ret, 'Cannot read frame from camera'
        center = detect_face(frame)
        if center:
            move_motor(center[0])
        cv2.rectangle(frame, (LEFT_X, -1), (RIGHT_X, IMG_HEIGHT), GREEN)
        cv2.imshow('preview', frame)
    cap.release()

完整的脚本可以保存在树莓派上的 follow.py 中,然后执行。

清单 9.6 follow.py : 移动相机以跟随检测到的面孔

#!/usr/bin/env python3
import cv2
from face import detect_face
from adafruit_crickit import crickit
 
ESC_KEY = 27
GREEN = (0, 255, 0)
IMG_WIDTH = 640
IMG_HEIGHT = 480
LEFT_X = int((IMG_WIDTH / 2) - 50)
RIGHT_X = int((IMG_WIDTH / 2) + 50)
PAN = dict(servo=crickit.servo_1, min=30, max=110, start=70, range=142)
ANGLE_STEP = 2
MOVE = dict(L=ANGLE_STEP, C=0, R=-ANGLE_STEP)
 
def get_zone(face_x):
    if face_x <= LEFT_X:
        return 'L'
    elif face_x <= RIGHT_X:
        return 'C'
    else:
        return 'R'
 
def move_motor(face_x):
    zone = get_zone(face_x)
    change = MOVE[zone]
    if change and PAN['min'] <= PAN['servo'].angle + change <= PAN['max']:
        PAN['servo'].angle += change
 
def init_motors():
    PAN['servo'].actuation_range = PAN['range']
    PAN['servo'].angle = PAN['start']
 
def check_capture_device(cap):
    assert cap.isOpened(), 'Cannot open camera'
    assert cap.get(cv2.CAP_PROP_FRAME_WIDTH) == IMG_WIDTH, 'wrong width'
    assert cap.get(cv2.CAP_PROP_FRAME_HEIGHT) == IMG_HEIGHT, 'wrong height'
 
def main():
    init_motors()
    cap = cv2.VideoCapture(0)
    check_capture_device(cap)
    while cv2.waitKey(1) not in [ord('q'), ESC_KEY]:
        ret, frame = cap.read()
        assert ret, 'Cannot read frame from camera'
        center = detect_face(frame)
        if center:
            move_motor(center[0])
        cv2.rectangle(frame, (LEFT_X, -1), (RIGHT_X, IMG_HEIGHT), GREEN)
        cv2.imshow('preview', frame)
    cap.release()
 
main()

当运行此脚本时,您可以查看摄像头并在实时摄像头画面中看到您的脸,周围有一个边框标记着检测到的脸。脸的中心也在实时图像中用十字准星标记。从这个标记,我们可以判断脸所在的区域。如果您将脸移动到中心区域之外,伺服电机将自动重新定位摄像头,将您的脸放回该区域。图 9.11 显示了一个已被检测并标记在左侧区域的脸,这使得伺服电机移动摄像头,将脸放回中心区域。

这个应用程序让我们有机会通过使用计算机视觉和人脸追踪将机器学习应用于我们的机器人项目。在接下来的章节中,我们将使用其他计算机视觉功能,例如二维码检测,帮助我们的机器人通过使用相机来进一步与其环境互动。

现实世界中的机器人:机器人视觉处理

机器人可以使用计算机视觉进行特征检测,以提取物体的视觉特征,如角落和边缘。通过这种特征检测,机器人可以检测和分类它们在环境中看到的物体。

这种对象交互的一个应用是创建能够在制造和物流中拾取和放置物体的机器人。通过计算机视觉,它们识别物体,抓取它,然后将其从一个位置移动到另一个位置。

检查机器人是另一个将计算机视觉和机器人技术结合起来的案例,旨在创建可以作为制造质量控制过程一部分的机器人,以对制造产品进行完全自动化的检查。

摘要

  • 需要一个快速的人脸检测机制来在实时视频流中执行人脸检测。
  • 伺服电机用于将附加的摄像头移动到检测到的人脸方向。
  • 基于 Haar 特征的级联分类器在 OpenCV 中用于进行人脸检测。
  • 直方图均衡化提高了图像的对比度,并有助于提高人脸检测的准确性。
  • 检测到的人脸周围将绘制一个匹配的矩形和标记。
  • 人脸检测必须能够处理至少每秒 30 帧的摄像头图像速率,以便实时人脸识别能够正常工作。
  • 调用较小图像的面部分类器可以使面部检测更快。

相关推荐

激光手术矫正视力对眼睛到底有没有伤害?

因为大家询问到很多关于“基质不能完全愈合”的问题,有必要在这里再详细解释一下。谢谢@珍惜年少时光提出的疑问:因为手头刚好在看组织学,其中提到:”角膜基质约占角膜的全厚度的90%,主要成分是胶原板层,...

OneCode核心概念解析——View(视图)

什么是视图?在前面的章节中介绍过,Page相关的概念,Page是用户交互的入口,具有Url唯一性。但Page还只是一个抽象的容器,而View则是一个具备了具体业务能力的特殊的Page,它可以是一个...

精品博文图文详解Xilinx ISE14.7 安装教程

在软件安装之前,得准备好软件安装包,可从Xilinx官网上下载:http://china.xilinx.com/support/download/index.html/content/xilinx/z...

卡片项目管理(Web)(卡片设计的流程)

简洁的HTML文档卡片管理,简单框架个人本地离线使用。将个人工具类的文档整理使用。优化方向:添加图片、瀑布式布局、颜色修改、毛玻璃效果等。<!DOCTYPEhtml><html...

GolangWeb框架Iris项目实战-JWT和中间件(Middleware)的使用EP07

前文再续,上一回我们完成了用户的登录逻辑,将之前用户管理模块中添加的用户账号进行账号和密码的校验,过程中使用图形验证码强制进行人机交互,防止账号的密码被暴力破解。本回我们需要为登录成功的用户生成Tok...

sitemap 网站地图是什么格式?有什么好处?

sitemap网站地图方便搜索引擎发现和爬取网页站点地图是一种xml文件,或者是txt,是将网站的所有网址列在这个文件中,为了方便搜索引擎发现并收录的。sitemap网站地图分两种:用于用户导...

如何在HarmonyOS NEXT中处理页面间的数据传递?

大家好,前两天的Mate70的发布,让人热血沸腾啊,不想错过,自学的小伙伴一起啊,今天分享的学习笔记是关于页面间数据伟递的问题,在HarmonyOSNEXT5.0中,页面间的数据传递可以有很多种...

从 Element UI 源码的构建流程来看前端 UI 库设计

作者:前端森林转发链接:https://mp.weixin.qq.com/s/ziDMLDJcvx07aM6xoEyWHQ引言由于业务需要,近期团队要搞一套自己的UI组件库,框架方面还是Vue。而业界...

jq+ajax+bootstrap改了一个动态分页的表格

最近在维护一个很古老的项目,里面是用jq的dataTable方法实现一个分页的表格,不过这些表格的分页是本地分页。现在想要的是点击分页去请求数据。经过多次的修改,以失败告终。分页的不准确,还会有这个错...

学习ES6- 入门Vue(大量源代码及笔记,带你起飞)

ES6学习网站:https://es6.ruanyifeng.com/箭头函数普通函数//普通函数this指向调用时所在的对象(可变)letfn=functionfn(a,b){...

青锋微服务架构之-Ant Design Pro 基本配置

青锋(msxy)-Gitee.com1、更换AntDesignPro的logo和名称需要修改文件所在位置:/config/defaultSetting.jsconstproSett...

大数据调度服务监控平台(大数据调度服务监控平台官网)

简介SmartKettle是针对上述企业的痛点,对kettle的使用做了一些包装、优化,使其在web端也能具备基础的kettle作业、转换的配置、调度、监控,能在很大一定程度上协助企业完成不同...

Flask博客实战 - 实现博客首页视图及样式

本套教程是一个Flask实战类教程,html/css/javascript等相关技术栈不会过多的去详细解释,那么就需要各位初学者尽可能的先去掌握这些基础知识,当然本套教程不需要你对其非常精通,但最起码...

Web自动化测试:模拟鼠标操作(ActionChains)

在日常的测试中,经常会遇到需要鼠标去操作的一些事情,比如说悬浮菜单、拖动验证码等,这一节我们来学习如何使用webdriver模拟鼠标的操作首页模拟鼠标的操作要首先引入ActionChains的包fro...

DCS F-16C 中文指南 16.9ILS仪表降落系统教程

10–ILS教程我们的ILS(仪表着陆进近)将到达Batumi巴统机场。ILS频率:110.30跑道航向:120磁航向/126真航向无线电塔频率:131.0001.设置雷达高度表开关打开(前)并...