数字图像处理实验

编程入门 行业动态 更新时间:2024-10-21 23:30:10

数字<a href=https://www.elefans.com/category/jswz/34/1769353.html style=图像处理实验"/>

数字图像处理实验

参考资料

基于OpenCV的实时睡意检测系统
头部姿态检测理论

1、实验介绍

  疲劳在人体面部表情中表现出大致三个类型:打哈欠(嘴巴张大且相对较长时间保持这一状态)、眨眼(或眼睛微闭,此时眨眼次数增多,且眨眼速度变慢)、点头(瞌睡点头)。本实验从人脸朝向、位置、眼睛开合度、眨眼频率、嘴部开合比等数据入手,实现了实时睡意监测功能。

2、研究方法原理

2.1 人脸检测关键点检测

  Dlib 是一个功能强大的 C++ 库,提供了许多用于计算机视觉和机器学习的工具和算法。本文使用其人脸检测功能可以检测出人脸的关键点,如眼睛、鼻子、嘴巴等,这些关键点的检测可以用于人脸分析和表情识别,使用Dlib算法检测这些特征时,我们实际上得到了每个特征点的映射。该映射由68个点组成,颚点=0–16右眉点=17–21左眉点=22–26鼻点=27–35右眼点=36–41左眼点=42–47口角=48–60嘴唇分数= 61–67可识别以下特征:

人脸检测功能使用了以下图像处理技术

  1. 图像金字塔(Image Pyramid)
    为了在不同尺度下检测人脸,dlib 库会对输入图像构建图像金字塔。图像金字塔是通过多次缩放原始图像来生成一系列具有不同分辨率的图像。这样可以使得人脸在不同尺度下都能被检测到。
  2. HOG 特征提取
    Dlib 使用了 Histogram of Oriented Gradients(HOG)特征来表示图像中的人脸。HOG 特征是一种局部图像梯度方向的直方图统计特征,能够捕捉到人脸的纹理和形状信息。通过计算图像中不同位置的 HOG 特征向量来表示人脸。

2.2 眼部和嘴部检测

用眼睛地标和嘴部地标来确定人的眼睛长宽比(EAR)和嘴部长宽比(MAR)。

  1. 眼部检测
    左上:当眼睛睁开时,眼睛界标的可视化。右上:闭上眼睛时的眼睛地标。
    底部:绘制随时间变化的眼睛纵横比。
    E A R = ( ∣ ∣ P 2 − P 6 ∣ ∣ + ∣ ∣ P 3 − P 5 ∣ ∣ ) / ( 2 ∣ ∣ P 1 − P 4 ∣ ∣ ) EAR=(||P_2-P_6 ||+||P_3-P_5 ||)/(2||P_1-P_4 ||) EAR=(∣∣P2​−P6​∣∣+∣∣P3​−P5​∣∣)/(2∣∣P1​−P4​∣∣)
  2. 嘴部检测
    嘴部与眼部类似都是检测是否开合,取嘴部关键点计算点间欧式距离并使用与眼睛纵横比相同的公式计算出嘴部开合程度MAR。

2.3 头部检测

  头部姿态估计(Head Pose Estimation)是计算机视觉中的一个重要任务,旨在估计人脸的三维旋转角度,即头部的俯仰(Pitch)、偏航(Yaw)和翻滚(Roll)角度。驾驶员在打瞌睡时,显然头部会做类似于点头和倾斜的动作。而根据一般人的打瞌睡时表现出来的头部姿态,显然很少会在Yaw上有动作,而主要集中在Pitch和Roll的行为。

  1. 特征点检测:首先,通过人脸检测算法定位和提取出人脸区域,使用关键点检测算法,Dlib的形状预测器(shape predictor),检测人脸的关键点,如眼睛、鼻子、嘴巴等。
  2. 三维模型准备:根据人脸的关键点位置,构建一个三维人脸模型。这个模型可以是一个平均模型,也可以是基于样本的模型。
  3. 投影变换:将三维人脸模型映射到二维图像中的人脸区域。通过摄像机内参矩阵和畸变系数,以及关键点的三维坐标和图像坐标之间的对应关系,进行投影变换。
  4. 优化求解:优化估计的头部姿态参数,即俯仰、偏航和翻滚角度。根据旋转矩阵求解欧拉角。OpenCV提供了求解PnP问题的函数solvePnP()。

3. 程序功能说明

  程序分为3个模块,眼部眨眼检测、嘴部闭合检测和头部姿态检测。摄像头以30Hz的帧率采集头部数据,计算每一帧图片对应的眼部纵横比,嘴部开合比和头部欧拉角,当连续3帧图片中动作数值都低于设定阈值时判断为一次眨眼、打哈欠、或点头动作。

# 以眼部眨眼检测代码为例进行说明
# 判断眼睛是否闭合,判断前计算出当前的avg_eye_aspect_ratioif avg_eye_aspect_ratio < EYE_AR_THRESH: #眼部纵横比越小闭合程度越大COUNTER += 1else:# 如果连续3次都小于阈值,则表示进行了一次眨眼活动if COUNTER >= EYE_AR_CONSEC_FRAMES:  # 3帧TOTAL += 1cv2.putText(frame, "Drowsy", (10, 30), # 判断显示cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)# 重置眼帧计数器COUNTER = 0# 绘制眼睛区域并显示相关参数cv2.polylines(frame, [left_eye], True, (0, 255, 0), 1)cv2.polylines(frame, [right_eye], True, (0, 255, 0), 1)
cv2.putText(frame,"EAR:{:.2f}".format(avg_eye_aspect_ratio),(300,30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.putText(frame,"Blinks:{}".format(TOTAL),(450,30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)

注:头部姿态欧拉角计算时需要预置3个参考坐标,分别为世界坐标系的头部3D参考点,相机内参坐标系XYZ,图像中心坐标系UV。第一个坐标系可以参考相关文章进行设置,后两个参数在使用单目相机时影响不大,使用双目相机时需要通过实验获得具体参数。

4. 实验结果及分析

4.1 实验环境介绍

操作系统Windows 11
摄像头720p 30Hz
语言编辑器PyCharm 2023.1
编程语言Python 3.6
主要函数库OpenCV 3.2.0.7, Dlib 19.8.1

4.2 实验结果

  1. 运行效果图
    第一张图为程序运行时的效果图,图中会分别显示眼部、嘴部和头部的监测数据。
    第二张图片为程序运行结束后的EAR和MAR的统计图。
    第三张图片为实时输出的头部姿态的俯仰(Pitch)、偏航(Yaw)和翻滚(Roll)角度。


  2. 功能说明(涉及隐私不变展示功能–可以自己运行代码查看实际效果)
    红框:依次输出眼睛纵横比、嘴部开合比和头部坐标系XYZ值
    蓝框:眨眼次数,打哈欠次数和点头次数(头部偏下的动作都会被记为点头,判断范围与个人参数设置有关系)
    绿框:人脸检测区域的标识。

4.3 实验总结
  本实验提供了一个简单而有效的方法来监测睡意。通过实时检测眼睛、嘴巴和头部状态,我们可以及时识别出可能出现睡意的情况,这对于驾驶员状态监测、工作场景下的疲劳检测等具有实际应用的场景非常有价值。然而,本实验还有一些改进的空间和展望。可以进一步优化阈值的设定,通过更多的数据和实验来确定更适合的阈值,以适应不同个体和场景的差异。目前使用的人脸检测算法仅使用简单的SVM网络,识别的精度和效果并不算好,可以探索使用深度学习方法,如人脸关键点检测和姿态估计的深度学习模型,以提高检测的准确性和鲁棒性。总的来说,本实验为睡意监测提供了一个基础框架,展示了如何利用图像处理和计算机视觉技术来实现实时监测。通过进一步改进和探索,我们可以进一步提高睡意监测的准确性和可靠性。

完整代码

import cv2
import dlib
from scipy.spatial import distance
import numpy as np
from imutils import face_utils
import matplotlib.pyplot as plt
import math# 加载人脸检测器和关键点检测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')# 世界坐标系(UVW):填写3D参考点,该模型参考.cpp
object_pts = np.float32([[6.825897, 6.760612, 4.402142],  # 33左眉左上角[1.330353, 7.122144, 6.903745],  # 29左眉右角[-1.330353, 7.122144, 6.903745],  # 34右眉左角[-6.825897, 6.760612, 4.402142],  # 38右眉右上角[5.311432, 5.485328, 3.987654],  # 13左眼左上角[1.789930, 5.393625, 4.413414],  # 17左眼右上角[-1.789930, 5.393625, 4.413414],  # 25右眼左上角[-5.311432, 5.485328, 3.987654],  # 21右眼右上角[2.005628, 1.409845, 6.165652],  # 55鼻子左上角[-2.005628, 1.409845, 6.165652],  # 49鼻子右上角[2.774015, -2.080775, 5.048531],  # 43嘴左上角[-2.774015, -2.080775, 5.048531],  # 39嘴右上角[0.000000, -3.116408, 6.097667],  # 45嘴中央下角[0.000000, -7.415691, 4.070434]])  # 6下巴角# 相机坐标系(XYZ):添加相机内参
K = [6.5308391993466671e+002, 0.0, 3.1950000000000000e+002,0.0, 6.5308391993466671e+002, 2.3950000000000000e+002,0.0, 0.0, 1.0]  # 等价于矩阵[fx, 0, cx; 0, fy, cy; 0, 0, 1]
# 图像中心坐标系(uv):相机畸变参数[k1, k2, p1, p2, k3]
D = [7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000]# 像素坐标系(xy):填写凸轮的本征和畸变系数
cam_matrix = np.array(K).reshape(3, 3).astype(np.float32)
dist_coeffs = np.array(D).reshape(5, 1).astype(np.float32)# 重新投影3D点的世界坐标轴以验证结果姿势
reprojectsrc = np.float32([[10.0, 10.0, 10.0],[10.0, 10.0, -10.0],[10.0, -10.0, -10.0],[10.0, -10.0, 10.0],[-10.0, 10.0, 10.0],[-10.0, 10.0, -10.0],[-10.0, -10.0, -10.0],[-10.0, -10.0, 10.0]])
# 绘制正方体12轴
line_pairs = [[0, 1], [1, 2], [2, 3], [3, 0],[4, 5], [5, 6], [6, 7], [7, 4],[0, 4], [1, 5], [2, 6], [3, 7]]def get_head_pose(shape):  # 头部姿态估计# (像素坐标集合)填写2D参考点,注释遵循 17左眉左上角/21左眉右角/22右眉左上角/26右眉右上角/36左眼左上角/39左眼右上角/42右眼左上角/# 45右眼右上角/31鼻子左上角/35鼻子右上角/48左上角/54嘴右上角/57嘴中央下角/8下巴角image_pts = np.float32([shape[17], shape[21], shape[22], shape[26], shape[36],shape[39], shape[42], shape[45], shape[31], shape[35],shape[48], shape[54], shape[57], shape[8]])# solvePnP计算姿势——求解旋转和平移矩阵:# rotation_vec表示旋转矩阵,translation_vec表示平移矩阵,cam_matrix与K矩阵对应,dist_coeffs与D矩阵对应。_, rotation_vec, translation_vec = cv2.solvePnP(object_pts, image_pts, cam_matrix, dist_coeffs)# projectPoints重新投影误差:原2d点和重投影2d点的距离(输入3d点、相机内参、相机畸变、r、t,输出重投影2d点)reprojectdst, _ = cv2.projectPoints(reprojectsrc, rotation_vec, translation_vec, cam_matrix, dist_coeffs)reprojectdst = tuple(map(tuple, reprojectdst.reshape(8, 2)))  # 以8行2列显示# 计算欧拉角calc euler angle# 参考.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#decomposeprojectionmatrixrotation_mat, _ = cv2.Rodrigues(rotation_vec)  # 罗德里格斯公式(将旋转矩阵转换为旋转向量)pose_mat = cv2.hconcat((rotation_mat, translation_vec))  # 水平拼接,vconcat垂直拼接# decomposeProjectionMatrix将投影矩阵分解为旋转矩阵和相机矩阵_, _, _, _, _, _, euler_angle = cv2.decomposeProjectionMatrix(pose_mat)pitch, yaw, roll = [math.radians(_) for _ in euler_angle]pitch = math.degrees(math.asin(math.sin(pitch)))roll = -math.degrees(math.asin(math.sin(roll)))yaw = math.degrees(math.asin(math.sin(yaw)))print('pitch:{}, yaw:{}, roll:{}'.format(pitch, yaw, roll))return reprojectdst, euler_angle  # 投影误差,欧拉角
# 定义计算纵横比的函数
def eye_aspect_ratio(eye):# 计算眼睛的欧几里德距离a = distance.euclidean(eye[1], eye[5])b = distance.euclidean(eye[2], eye[4])# 计算眼睛的垂直欧几里德距离c = distance.euclidean(eye[0], eye[3])# 计算纵横比ear = (a + b) / (2.0 * c)return ear# 定义计算嘴巴开合程度的函数
def mouth_openness_ratio(mouth):# 计算嘴巴的欧几里德距离a = distance.euclidean(mouth[2], mouth[10])b = distance.euclidean(mouth[4], mouth[8])# 计算嘴巴的水平欧几里德距离c = distance.euclidean(mouth[0], mouth[6])# 计算嘴巴开合程度mor = (a + b) / (2.0 * c)return mor# 定义常数
# 眼睛长宽比
# 闪烁阈值
EYE_AR_THRESH = 0.2
EYE_AR_CONSEC_FRAMES = 3
# 打哈欠长宽比
# 闪烁阈值
MOUTH_OPEN_THRESH = 0.6
MOUTH_AR_CONSEC_FRAMES = 3
# 瞌睡点头
HAR_THRESH = 0.3
NOD_AR_CONSEC_FRAMES = 3# 初始化帧计数器和眨眼总数
COUNTER = 0
TOTAL = 0
# 初始化帧计数器和打哈欠总数
mCOUNTER = 0
mTOTAL = 0
# 初始化帧计数器和点头总数
hCOUNTER = 0
hTOTAL = 0
mouth_points = [48, 68]# 打开摄像头
cap = cv2.VideoCapture(0)
# 初始化记录列表
eye_ar_thresh_values = []
mouth_open_thresh_values = []while True:ret, frame = cap.read()if not ret:break# 调整图像大小,提高处理速度frame = cv2.resize(frame, (640, 480))gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 检测人脸faces = detector(gray)for face in faces:# 检测人脸的关键点landmarks = predictor(gray, face)landmarks = face_utils.shape_to_np(landmarks)# 定义左眼和右眼的索引left_eye = landmarks[36:42]right_eye = landmarks[42:48]# 计算左眼和右眼的纵横比left_eye_aspect_ratio = eye_aspect_ratio(left_eye)right_eye_aspect_ratio = eye_aspect_ratio(right_eye)# 计算双眼的平均纵横比avg_eye_aspect_ratio = (left_eye_aspect_ratio + right_eye_aspect_ratio) / 2.0# 将当前阈值记录到列表中eye_ar_thresh_values.append(avg_eye_aspect_ratio)# 判断眼睛是否闭合if avg_eye_aspect_ratio < EYE_AR_THRESH:COUNTER += 1else:# 如果连续3次都小于阈值,则表示进行了一次眨眼活动if COUNTER >= EYE_AR_CONSEC_FRAMES:  # 阈值:3TOTAL += 1cv2.putText(frame, "Drowsy", (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)# 重置眼帧计数器COUNTER = 0# 绘制眼睛区域cv2.polylines(frame, [left_eye], True, (0, 255, 0), 1)cv2.polylines(frame, [right_eye], True, (0, 255, 0), 1)cv2.putText(frame, "EAR: {:.2f}".format(avg_eye_aspect_ratio), (300, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.putText(frame, "Blinks: {}".format(TOTAL), (450, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)# 检测嘴巴区域mouth = landmarks[48:68]# 计算嘴巴的开合程度mouth_openness = mouth_openness_ratio(mouth)# 将当前阈值记录到列表中mouth_open_thresh_values.append(mouth_openness)# 判断嘴巴是否张开if mouth_openness > MOUTH_OPEN_THRESH:mCOUNTER += 1else:# 如果连续3次都小于阈值,则表示打了一次哈欠if mCOUNTER >= MOUTH_AR_CONSEC_FRAMES:  # 阈值:3mTOTAL += 1cv2.putText(frame, "Yawning", (10, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)mCOUNTER=0;# 绘制嘴巴区域mouth = landmarks[mouth_points[0]:mouth_points[1]]cv2.polylines(frame, [mouth], True, (0, 255, 0), 1)cv2.putText(frame, "MAR: {:.2f}".format(mouth_openness), (300, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)cv2.putText(frame, "Yawning: {}".format(mTOTAL), (450, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)#获取头部姿态reprojectdst, euler_angle = get_head_pose(landmarks)har = euler_angle[0, 0]  # 取pitch旋转角度if har > HAR_THRESH:  # 点头阈值0.3hCOUNTER += 1else:# 如果连续3次都小于阈值,则表示瞌睡点头一次if hCOUNTER >= NOD_AR_CONSEC_FRAMES:  # 阈值:3hTOTAL += 1# 重置点头帧计数器hCOUNTER = 0# 绘制正方体12轴for start, end in line_pairs:cv2.line(frame, reprojectdst[start], reprojectdst[end], (0, 0, 255))# 显示角度结果cv2.putText(frame, "X: " + "{:7.2f}".format(euler_angle[0, 0]), (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0, 255, 0), thickness=2)  # GREENcv2.putText(frame, "Y: " + "{:7.2f}".format(euler_angle[1, 0]), (150, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(255, 0, 0), thickness=2)  # BLUEcv2.putText(frame, "Z: " + "{:7.2f}".format(euler_angle[2, 0]), (300, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0, 0, 255), thickness=2)  # REDcv2.putText(frame, "Nod: {}".format(hTOTAL), (450, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)cv2.imshow("Sleepiness Detection", frame)if cv2.waitKey(1) & 0xFF == ord('q'):break# 释放摄像头并关闭窗口
cap.release()
cv2.destroyAllWindows()# 绘制阈值变化图表
plt.figure()
plt.plot(eye_ar_thresh_values, label='Eye AR Threshold')
plt.plot(mouth_open_thresh_values, label='Mouth Open Threshold')
plt.xlabel('Frames')
plt.ylabel('Threshold')
plt.title('Threshold Changes')
plt.legend()
plt.show()

更多推荐

数字图像处理实验

本文发布于:2024-02-10 21:57:57,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1677509.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:图像处理   数字

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!