基于Opencv+Mediapipe的头部姿态检测

系列文章目录

第一章 :基于深度学习的学生课堂专注度检测系统设计与实现之头部转向检测。



前言

在学生课堂专注度检测项目中,可以通过学生头部姿态来确定学生注意力方向,从而判断其是否专注于课堂。


一、头部姿态检测基本概念

1.1 目标------三个角

头部姿态检测的目标是计算出头部相对于摄像头的旋转角度,即 Pitch(俯仰角)、Yaw(偏航角)、Roll(翻滚角)。

Pitch(俯仰角):头部向上或向下的角度(点头/仰头)。
Yaw(偏航角):头部向左或向右的角度(摇头)。
Roll(翻滚角):头部的侧倾角(歪头)。

1.2 摄像机的内参矩阵(Camera Matrix)

① 作用
摄像机的内参矩阵用于描述摄像机的光学特性,它告诉我们:

摄像机的焦距(focal length),决定了物体的缩放比例;
图像中心(principal point),描述图像坐标系的中心位置;
像素比例(pixel scaling),影响 3D 物体在 2D 平面的投影。
在 OpenCV 中,内参矩阵是一个 3×3 的矩阵,形式如下:
在这里插入图片描述
其中:
• fx,fy是焦距(通常设为图像宽度的 1 倍);
• cx,cy是主点(Principal Point),通常设为图像中心,即 (w/2, h/2);
• (0,0,1) 是齐次坐标的一部分,不影响计算。

在代码中,我们定义的摄像机矩阵如下:

focal_length = 1 * w  # 设定焦距为图像宽度的 1 倍
cam_matrix = np.array([[focal_length, 0, w / 2],
                       [0, focal_length, h / 2],
                       [0, 0, 1]], dtype=np.float64)

② 为什么需要它?
摄像机拍摄的 2D 图像与真实 3D 物体的尺寸不一样,我们需要知道摄像机的焦距和成像比例,才能准确计算物体的 3D 旋转角度。因此,内参矩阵用于校正投影的尺度,让 solvePnP 计算出真实的旋转信息,摄像头内参矩阵其实就是告诉PnP函数摄像头的投影规则。(solvePnP后面叙述,先有个概念)

1.3 3D 参考点(Real-world 3D Points)

① 作用
3D 参考点是物体在现实世界中的固定坐标点,它们用于告诉 solvePnP 头部的实际结构。对于人脸,我们需要选择不会随表情变化的关键点,比如:额头中心点(1号点)、鼻尖(9号点)、左眼角(57号点)、右眼角(130号点)、左嘴角(287号点)、右嘴角(359号点)。
这些点的 3D 坐标通常用毫米(mm)表示,假设面部的参考大小是 200 mm:

face_coordination_in_real_world = np.array([
    [285, 528, 200],  # 额头中心
    [285, 371, 152],  # 鼻尖
    [197, 574, 128],  # 左嘴角
    [173, 425, 108],  # 左眼角
    [360, 574, 128],  # 右嘴角
    [391, 425, 108]   # 右眼角
], dtype=np.float64)

② 为什么需要它?
如果我们想知道人脸在 3D 空间中的旋转角度,我们需要有已知的 3D 坐标作为参考这些参考点是根据前人不断实验而得到的经验值,也可以利用一些其他3D模型的值(复杂)。例如,鼻尖总是比眼睛更靠前,我们可以通过这些相对位置计算头部的旋转角度。

1.4 2D 投影点(Image 2D Points)

① 作用
2D 投影点是摄像机在图像上检测到的 6 个关键点的坐标。它们是3D 参考点在 2D 图像上的投影,我们用 Mediapipe 计算这些点:

face_coordination_in_image = []
for face_landmarks in results.multi_face_landmarks:
    for idx, lm in enumerate(face_landmarks.landmark):
        if idx in [1, 9, 57, 130, 287, 359]:  # 选取 6 个关键点
            x, y = int(lm.x * w), int(lm.y * h)  # 归一化到图像尺寸
            face_coordination_in_image.append([x, y])
face_coordination_in_image = np.array(face_coordination_in_image, dtype=np.float64)

② 为什么需要它?
这些 2D 投影点是摄像机拍摄到的图像坐标,我们必须用它们来求解 3D 旋转矩阵。OpenCV 的 solvePnP 方法就是用3D 参考点和2D 投影点来计算摄像机的旋转向量。

1.5 三者之间的联系------PnP问题(核心算法)

我们希望通过已知的 3D 参考点(面部固定特征点)和摄像头拍摄到的 2D 投影点,求解头部的旋转角度。这就是PnP 问题(Perspective-n-Point 问题),其核心公式是:
在这里插入图片描述
其中:
• (X,Y,Z)是 3D 参考点(世界坐标系);
• (u,v) 是 2D 投影点(图像坐标系);
• K是 摄像机内参矩阵;
• R是 旋转矩阵(描述头部的旋转);
• t是 平移向量(描述头部的位置);
• s是尺度因子(用于归一化)。
我们在代码中这样求解:

success, rotation_vec, transition_vec = cv2.solvePnP(
    face_coordination_in_real_world, face_coordination_in_image,
    cam_matrix, dist_matrix)

在求得的结果中:
rotation_vec 是旋转向量,表示头部的旋转;
transition_vec 是平移向量,表示头部的 3D 位置。

然后,我们用 cv2.Rodrigues(rotation_vec)旋转向量转换成 3×3 旋转矩阵,并进一步转换成欧拉角,从而得到:Pitch(俯仰角,低头/抬头)Yaw(偏航角,左右转头)Roll(翻滚角,歪头)。

二、详细实现过程与代码解析

2.1 导入必要的库

import math
import cv2
import mediapipe as mp
import numpy as np

math:用于数学运算,比如 atan2() 计算角度。
cv2(OpenCV):用于图像处理、特征点检测、矩阵运算等。
mediapipe:用于检测人脸特征点。
numpy:用于矩阵运算(solvePnP 需要)。

2.2 初始化 Mediapipe 人脸网格(FaceMesh)

mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(min_detection_confidence=0.5, min_tracking_confidence=0.5)

FaceMesh():Mediapipe 提供的 人脸网格检测模型,可以获取 468 个关键点。
min_detection_confidence=0.5:人脸检测的置信度。
min_tracking_confidence=0.5:关键点跟踪的置信度。

2.3 打开摄像头

cap = cv2.VideoCapture(0)

cv2.VideoCapture(0) 打开默认摄像头(0 代表默认摄像头)。
cap.read() 用于获取摄像头的实时帧。

2.4 定义计算旋转矩阵的欧拉角函数

def rotation_matrix_to_angles(rotation_matrix):
    x = math.atan2(rotation_matrix[2, 1], rotation_matrix[2, 2])
    y = math.atan2(-rotation_matrix[2, 0], math.sqrt(rotation_matrix[0, 0] ** 2 + rotation_matrix[1, 0] ** 2))
    z = math.atan2(rotation_matrix[1, 0], rotation_matrix[0, 0])
    return np.array([x, y, z]) * 180. / math.pi

rotation_matrix 是 旋转矩阵,它描述了头部的旋转信息。
atan2(y, x) 计算 欧拉角(Pitch, Yaw, Roll)。
结果转换成角度(乘 180 / π)。

2.5 设定 3D 头部关键点

face_coordination_in_real_world = np.array([
    [285, 528, 200],  # 额头中央
    [285, 371, 152],  # 鼻尖
    [197, 574, 128],  # 左嘴角
    [173, 425, 108],  # 左眼角
    [360, 574, 128],  # 右嘴角
    [391, 425, 108]   # 右眼角
], dtype=np.float64)

这些是 3D 参考点(单位:毫米),表示人脸在真实世界的 3D 坐标。
选取的点:额头中央、鼻尖、左嘴角、右嘴角、左眼角、右眼角
这些点在 solvePnP 计算时,能够较好地描述头部姿态。

2.6 处理摄像头帧

while cap.isOpened():
    success, image = cap.read()
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    results = face_mesh.process(image)
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

cap.read() 读取摄像头图像帧。
cv2.cvtColor() 颜色格式转换(Mediapipe 需要 RGB 格式,而 OpenCV 使用 BGR)。
face_mesh.process(image) 运行 Mediapipe 的人脸关键点检测

2.7 获取 2D 人脸特征点

h, w, _ = image.shape
face_coordination_in_image = []

if results.multi_face_landmarks:
    for face_landmarks in results.multi_face_landmarks:
        for idx, lm in enumerate(face_landmarks.landmark):
            if idx in [1, 9, 57, 130, 287, 359]:  # 选取关键点
                x, y = int(lm.x * w), int(lm.y * h)
                face_coordination_in_image.append([x, y])

image.shape 获取图像宽度(w)和高度(h)。
results.multi_face_landmarks 获取检测到的人脸关键点。
索引号解释:1(额头)、9(鼻尖)、57(左嘴角)、130(左眼角)、287、(右嘴角)、359(右眼角)
这些点与 face_coordination_in_real_world 对应,才能计算头部姿态。

2.8 计算头部姿态

focal_length = 1 * w
cam_matrix = np.array([[focal_length, 0, w / 2],
                       [0, focal_length, h / 2],
                       [0, 0, 1]])

dist_matrix = np.zeros((4, 1), dtype=np.float64)

相机矩阵(Camera Matrix):表示摄像头参数。
focal_length 设为 1 * w,假设焦距等于图像宽度。
dist_matrix 设为 0,假设无镜头畸变。

success, rotation_vec, transition_vec = cv2.solvePnP(
    face_coordination_in_real_world, face_coordination_in_image, cam_matrix, dist_matrix)

rotation_matrix, _ = cv2.Rodrigues(rotation_vec)
result = rotation_matrix_to_angles(rotation_matrix)

solvePnP() 计算 旋转向量 和 平移向量。
Rodrigues() 转换为 旋转矩阵,然后计算 欧拉角(Pitch, Yaw, Roll)。

2.9 显示角度信息

for i, info in enumerate(zip(('pitch', 'yaw', 'roll'), result)):
    k, v = info
    text = f'{k}: {int(v)}'
    cv2.putText(image, text, (20, i * 30 + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (200, 0, 200), 2)
cv2.imshow('Head Pose Angles', image)

cv2.putText() 在图像上绘制角度信息。
cv2.imshow() 显示最终结果。

三、总结

看到这了,实属创作不易,点赞收藏一下吧,要不然下次就仅VIP可见了(●ˇ∀ˇ●)!
开开玩笑,激励我一下就好,我还会持续创作免费作品的!

3.1 核心逻辑

这整个过程的核心逻辑是:

  1. 摄像头获取人脸图像 → 通过 Mediapipe 识别人脸,获得 2D 关键点
  2. 摄像机内参矩阵 → 由摄像头的分辨率、焦距等参数决定
  3. 3D 参考点 → 通过经验值或标准模型获取
  4. solvePnP() 计算位姿 → 输出 旋转向量(rvec) 和 平移向量(tvec)
  5. Rodrigues 公式 → 旋转向量转换为 旋转矩阵(R)
  6. 从旋转矩阵提取欧拉角 → 计算头部 Pitch、Yaw、Roll
    这个流程不仅适用于头部姿态检测,还可以扩展到 AR(增强现实)、疲劳驾驶检测等应用。通过这种方法,我们可以在普通摄像头下实现高精度的头部姿态估计,而不需要昂贵的 3D 传感器。

3.2 全过程代码

python版本不要超过3.10

import math

import cv2
import mediapipe as mp
import numpy as np

mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(min_detection_confidence=0.5,
                                  min_tracking_confidence=0.5)
cap = cv2.VideoCapture(0)


def rotation_matrix_to_angles(rotation_matrix):

    x = math.atan2(rotation_matrix[2, 1], rotation_matrix[2, 2])
    y = math.atan2(-rotation_matrix[2, 0], math.sqrt(rotation_matrix[0, 0] ** 2 +
                                                     rotation_matrix[1, 0] ** 2))
    z = math.atan2(rotation_matrix[1, 0], rotation_matrix[0, 0])
    return np.array([x, y, z]) * 180. / math.pi


while cap.isOpened():
    success, image = cap.read()


    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    results = face_mesh.process(image)

    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    face_coordination_in_real_world = np.array([
        [285, 528, 200],
        [285, 371, 152],
        [197, 574, 128],
        [173, 425, 108],
        [360, 574, 128],
        [391, 425, 108]
    ], dtype=np.float64)

    h, w, _ = image.shape
    face_coordination_in_image = []

    if results.multi_face_landmarks:
        for face_landmarks in results.multi_face_landmarks:
            for idx, lm in enumerate(face_landmarks.landmark):
                if idx in [1, 9, 57, 130, 287, 359]:
                    x, y = int(lm.x * w), int(lm.y * h)
                    face_coordination_in_image.append([x, y])

            face_coordination_in_image = np.array(face_coordination_in_image,
                                                  dtype=np.float64)

            focal_length = 1 * w
            cam_matrix = np.array([[focal_length, 0, w / 2],
                                   [0, focal_length, h / 2],
                                   [0, 0, 1]])

            dist_matrix = np.zeros((4, 1), dtype=np.float64)

            success, rotation_vec, transition_vec = cv2.solvePnP(
                face_coordination_in_real_world, face_coordination_in_image,
                cam_matrix, dist_matrix)

            rotation_matrix, jacobian = cv2.Rodrigues(rotation_vec)

            result = rotation_matrix_to_angles(rotation_matrix)
            for i, info in enumerate(zip(('pitch', 'yaw', 'roll'), result)):
                k, v = info
                text = f'{k}: {int(v)}'
                cv2.putText(image, text, (20, i*30 + 20),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (200, 0, 200), 2)


    cv2.imshow('Head Pose Angles', image)

    if cv2.waitKey(5) & 0xFF == 27:
        break

cap.release()


3.3 全过程叙述

头部姿态检测的核心目标是计算人脸在 3D 空间中的旋转角度,也就是俯仰角(Pitch)、偏航角(Yaw)和翻滚角(Roll),这些角度可以用于分析人的注意力方向,甚至用于人机交互、疲劳检测等应用。这个过程的关键在于PnP(Perspective-n-Point)问题,即在已知某些 3D 参考点和它们在 2D 图像中的投影位置的情况下,求解摄像头坐标系下的物体姿态(位置和角度)。我们通过 OpenCV 的 solvePnP 方法,结合 Mediapipe 提供的面部关键点信息,计算头部在 3D 空间中的旋转情况。为了实现这一点,我们首先需要导入一些必要的库,包括 math(用于数学运算)、cv2(OpenCV 负责图像处理、摄像头读取和计算头部姿态)、mediapipe(用于检测人脸关键点)以及 numpy(用于矩阵运算)。然后,我们使用 Mediapipe 的 FaceMesh 模块,它可以检测468 个面部关键点,比传统的 OpenCV 人脸检测或 Dlib 方案更加精确和高效。我们初始化 FaceMesh,并设置检测置信度和跟踪置信度,以确保人脸识别的稳定性和准确性。接着,我们使用 cv2.VideoCapture(0) 访问摄像头,并不断读取视频帧,每一帧都需要进行人脸关键点检测。由于 OpenCV 读取的图像默认是 BGR 格式,而 Mediapipe 需要 RGB 格式,因此我们在处理前需要进行颜色转换。检测到人脸后,我们会选取 6 个关键点(额头中央、鼻尖、左右嘴角、左右眼角),这些点在不同的人脸上位置基本固定,因此可以作为 3D 参考点。我们在代码中定义了这 6 个点的3D 坐标(单位是毫米),然后用 Mediapipe 计算它们在图像中的2D 坐标。这一步的核心思想是将已知的 3D 参考点与检测到的 2D 关键点一一对应,这样我们就有了足够的数据来求解头部的 3D 姿态。接下来,我们需要定义摄像机的内参矩阵(Camera Matrix),这个矩阵描述了摄像机的光学特性,包括焦距和主点位置。一般来说,我们可以假设摄像机的焦距等于图像宽度,并且光心位于图像中心。然后,我们调用 OpenCV 的 solvePnP 方法,它使用 3D 点和 2D 点的匹配关系,计算出旋转向量(rotation_vec)和平移向量(transition_vec)。旋转向量实际上是 Rodrigues 旋转公式表示的旋转信息,我们需要用 cv2.Rodrigues 将其转换为 3x3 旋转矩阵(Rotation Matrix),这样才能方便地转换成欧拉角。旋转矩阵是一个描述 3D 物体旋转状态的标准形式,但它并不是人类容易理解的角度表示。因此,我们需要将其转换为欧拉角,即 Pitch(绕 X 轴)、Yaw(绕 Y 轴)、Roll(绕 Z 轴),这些角度可以直观地告诉我们头部的方向。Pitch 代表低头或抬头,Yaw 代表左右转头,Roll 代表歪头。转换欧拉角的方法是通过 atan2() 计算角度,我们分别用 rotation_matrix 的不同元素计算这三个角度,并转换成角度制(度),以便直观显示。最后,我们使用 cv2.putText 在图像上实时显示 Pitch, Yaw, Roll 值,并用 cv2.imshow 展示带有检测信息的视频流。当检测到 ESC 键时,程序退出并释放摄像头资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sjsflyqy10

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值