基于计算机视觉与深度学习的驾驶员疲劳状态检测系统设计与实现
一、 研究背景与系统目标
疲劳驾驶是导致交通事故的核心诱因之一。在驾驶过程中,驾驶员的精神状态会通过面部表情和头部动作直观地反映出来,例如频繁打哈欠、眨眼频率异常、眼睛闭合时间过长以及无意识的点头等。随着计算机视觉和人工智能技术的成熟,利用车载摄像头实时捕捉驾驶员面部特征,并通过算法分析其疲劳程度,已成为主动安全预警系统的重要技术路线。本系统旨在通过多维度的面部特征分析,实时评估驾驶员的注意力状态,并在检测到疲劳迹象时及时触发安全预警。
二、 疲劳驾驶检测技术路线分析
目前主流的疲劳驾驶检测方法主要分为三类:基于车辆行驶状态的方法、基于生理信号的方法以及基于驾驶员面部特征的方法。基于车辆状态的方法通过分析方向盘转角、车道偏离等数据来判断,但容易受路况干扰;基于生理信号的方法(如脑电波、心率)精度高,但设备侵入性强,不适合日常驾驶场景。相比之下,基于面部特征的视觉检测方法具有非接触、成本低、实时性强的优势。系统通过提取人脸关键点,计算眼睛纵横比(EAR)、嘴巴纵横比(MAR)以及头部三维姿态,从而综合判定疲劳状态。
三、 数据集构建与预处理
为了训练和验证基于深度学习的疲劳分类模型,构建了一个包含多场景的驾驶疲劳人脸数据集。该数据集涵盖了三种精神状态:清醒、轻度疲劳和重度疲劳。样本总数为4800张图像,其中重度疲劳1622张,轻度疲劳1506张,清醒状态1618张。数据来源包括网络公开数据集抓取与车载模拟环境下的视频抽帧,确保了光照、角度和人物多样性,为后续模型训练提供了可靠的数据支撑。
四、 基于头部姿态估计的打瞌睡检测
4.1 姿态估计算法原理
头部姿态估计是判断驾驶员是否打瞌睡(点头)的关键。算法首先通过2D人脸关键点检测获取面部特征点,然后将其与标准的3D人脸模型进行匹配。利用OpenCV中的 solvePnP 函数,结合相机内参和畸变系数,求解出3D空间点到2D图像平面的旋转矩阵和平移向量。最后,通过罗德里格斯公式和投影矩阵分解,计算出头部在三个维度上的欧拉角(Pitch俯仰角、Yaw偏航角、Roll翻滚角)。
4.2 头部姿态估计核心代码
以下代码重构了姿态估计模块,采用面向对象的设计模式,封装了相机参数和3D模型点,提高了代码的可维护性。
import cv2
import dlib
import numpy as np
from imutils import face_utils
class HeadPoseEstimator:
def __init__(self, landmark_model_path):
self.face_detector = dlib.get_frontal_face_detector()
self.landmark_predictor = dlib.shape_predictor(landmark_model_path)
# 定义标准3D人脸模型关键点
self.model_3d_points = np.array([
(6.825897, 6.760612, 4.402142), # 左眉外侧
(1.330353, 7.122144, 6.903745), # 左眉内侧
(-1.330353, 7.122144, 6.903745), # 右眉内侧
(-6.825897, 6.760612, 4.402142), # 右眉外侧
(5.311432, 5.485328, 3.987654), # 左眼外侧
(1.789930, 5.393625, 4.413414), # 左眼内侧
(-1.789930, 5.393625, 4.413414), # 右眼内侧
(-5.311432, 5.485328, 3.987654), # 右眼外侧
(2.005628, 1.409845, 6.165652), # 鼻翼左侧
(-2.005628, 1.409845, 6.165652), # 鼻翼右侧
(2.774015, -2.080775, 5.048531), # 嘴角左侧
(-2.774015, -2.080775, 5.048531), # 嘴角右侧
(0.000000, -3.116408, 6.097667), # 下唇中央
(0.000000, -7.415691, 4.070434) # 下巴尖端
], dtype=np.float32)
# 配置相机内参矩阵与畸变系数
self.focal_length = 653.08
self.cx, self.cy = 319.5, 239.5
self.camera_matrix = np.array([
[self.focal_length, 0, self.cx],
[0, self.focal_length, self.cy],
[0, 0, 1]
], dtype=np.float32)
self.dist_coeffs = np.array([0.0708, 0.0691, 0.0, 0.0, -1.307], dtype=np.float32)
def calculate_euler_angles(self, landmarks_np):
# 提取与3D模型对应的14个2D关键点
indices = [17, 21, 22, 26, 36, 39, 42, 45, 31, 35, 48, 54, 57, 8]
image_2d_points = np.array([landmarks_np[i] for i in indices], dtype=np.float32)
# 求解旋转向量和平移向量
_, rot_vec, trans_vec = cv2.solvePnP(
self.model_3d_points, image_2d_points, self.camera_matrix, self.dist_coeffs
)
# 将旋转向量转换为旋转矩阵
rot_mat, _ = cv2.Rodrigues(rot_vec)
proj_mat = cv2.hconcat([rot_mat, trans_vec])
# 分解投影矩阵获取欧拉角
_, _, _, _, _, _, euler_angles = cv2.decomposeProjectionMatrix(proj_mat)
return euler_angles
def run_pose_estimation():
estimator = HeadPoseEstimator('shape_predictor_68_face_landmarks.dat')
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret: break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = estimator.face_detector(gray, 0)
for face in faces:
landmarks = estimator.landmark_predictor(gray, face)
coords = face_utils.shape_to_np(landmarks)
angles = estimator.calculate_euler_angles(coords)
pitch, yaw, roll = angles[0][0], angles[1][0], angles[2][0]
cv2.putText(frame, f"Pitch: {pitch:.2f}", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.putText(frame, f"Yaw: {yaw:.2f}", (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.putText(frame, f"Roll: {roll:.2f}", (20, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.imshow("Head Pose Estimation", frame)
if cv2.waitKey(1) & 0xFF == ord('q'): break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
run_pose_estimation()
4.3 打瞌睡状态判定逻辑
在获取到实时的欧拉角后,系统重点监控Pitch(俯仰角)和Roll(翻滚角)。驾驶员在打瞌睡时,头部通常会呈现明显的下垂或倾斜,而Yaw(偏航角)变化较小。系统设定角度阈值为20°,在一个10秒的滑动时间窗口内,如果Pitch或Roll的绝对值超过20°的帧数占比超过30%,则判定驾驶员处于打瞌睡状态,并触发警报。
4.4 多特征融合疲劳检测代码
为了实现全面的疲劳监控,系统将眼睛闭合(EAR)、打哈欠(MAR)与头部姿态(Pitch)结合。以下代码展示了融合检测逻辑的封装实现。
import cv2
import dlib
import numpy as np
from scipy.spatial import distance as dist
from imutils import face_utils
import imutils
def compute_eye_ear(eye_pts):
v1 = dist.euclidean(eye_pts[1], eye_pts[5])
v2 = dist.euclidean(eye_pts[2], eye_pts[4])
h = dist.euclidean(eye_pts[0], eye_pts[3])
return (v1 + v2) / (2.0 * h)
def compute_mouth_mar(mouth_pts):
v1 = np.linalg.norm(mouth_pts[2] - mouth_pts[9])
v2 = np.linalg.norm(mouth_pts[4] - mouth_pts[7])
h = np.linalg.norm(mouth_pts[0] - mouth_pts[6])
return (v1 + v2) / (2.0 * h)
class FatigueMonitor:
def __init__(self, predictor_path):
self.detector = dlib.get_frontal_face_detector()
self.predictor = dlib.shape_predictor(predictor_path)
self.EAR_THRESH = 0.22
self.EAR_CONSEC = 3
self.MAR_THRESH = 0.50
self.MAR_CONSEC = 3
self.PITCH_THRESH = 20.0
self.eye_close_frames = 0
self.yawn_frames = 0
self.nod_frames = 0
(self.l_s, self.l_e) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
(self.r_s, self.r_e) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
(self.m_s, self.m_e) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"]
def analyze_frame(self, frame, pitch_angle):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
rects = self.detector(gray, 0)
for rect in rects:
shape = self.predictor(gray, rect)
coords = face_utils.shape_to_np(shape)
left_eye = coords[self.l_s:self.l_e]
right_eye = coords[self.r_s:self.r_e]
mouth = coords[self.m_s:self.m_e]
ear = (compute_eye_ear(left_eye) + compute_eye_ear(right_eye)) / 2.0
mar = compute_mouth_mar(mouth)
# 眼睛闭合检测
if ear < self.EAR_THRESH:
self.eye_close_frames += 1
else:
if self.eye_close_frames >= self.EAR_CONSEC:
cv2.putText(frame, "DROWSINESS!", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
self.eye_close_frames = 0
# 打哈欠检测
if mar > self.MAR_THRESH:
self.yawn_frames += 1
cv2.putText(frame, "YAWNING!", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
else:
self.yawn_frames = 0
# 点头检测
if abs(pitch_angle) > self.PITCH_THRESH:
self.nod_frames += 1
else:
self.nod_frames = 0
if self.nod_frames >= self.MAR_CONSEC:
cv2.putText(frame, "NODDING OFF!", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
return frame
def main_loop():
monitor = FatigueMonitor('shape_predictor_68_face_landmarks.dat')
cap = cv2.VideoCapture(0)
# 此处假设已通过HeadPoseEstimator获取实时的pitch_angle
mock_pitch_angle = 0.0
while True:
ret, frame = cap.read()
if not ret: break
frame = imutils.resize(frame, width=720)
frame = monitor.analyze_frame(frame, mock_pitch_angle)
cv2.imshow("Fatigue Detection System", frame)
if cv2.waitKey(1) & 0xFF == ord('q'): break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main_loop()
五、 基于CNN与SVM的疲劳状态分类模型
5.1 网络架构设计
为了进一步提升对复杂光照和遮挡环境下的疲劳识别精度,系统引入了深度学习模型。采用卷积神经网络(CNN)作为特征提取器,结合支持向量机(SVM)作为分类器。网络结构包含两层卷积层和两层池化层用于提取面部空间特征,随后通过两层全连接层进行特征降维,最终将高维特征向量输入SVM进行三分类(清醒、轻度疲劳、重度疲劳)。这种串联结构既利用了CNN强大的特征表达能力,又发挥了SVM在小样本和高维空间中的分类优势。
5.2 模型训练流程
模型训练采用小批量梯度下降法(Mini-batch Gradient Descent),每个批次包含20张图像。训练流程如下:首先随机初始化网络权重与偏置,设定5x5的卷积核;然后将预处理后的人脸图像批次输入网络进行前向传播;计算输出结果与真实标签的交叉熵损失;通过反向传播算法更新CNN层的权重参数;当CNN特征提取器收敛后,冻结CNN层,将提取到的特征向量输入SVM进行最终的分类边界训练。
5.3 实验结果分析
在构建的4800张图像数据集上进行训练与测试。实验记录了每个Epoch的准确率与损失率变化。结果表明,随着迭代次数的增加,训练集和验证集的准确率稳步上升,损失率逐渐下降并趋于收敛。最终模型在测试集上的综合识别准确率达到了较高水平,能够有效区分不同程度的疲劳状态,验证了CNN+SVM架构在疲劳驾驶检测任务中的有效性。