当前位置:首页 > 技术 > 正文内容

基于计算机视觉与深度学习的驾驶员疲劳状态检测系统设计与实现

访客 技术 2026年7月10日 1

一、 研究背景与系统目标

疲劳驾驶是导致交通事故的核心诱因之一。在驾驶过程中,驾驶员的精神状态会通过面部表情和头部动作直观地反映出来,例如频繁打哈欠、眨眼频率异常、眼睛闭合时间过长以及无意识的点头等。随着计算机视觉和人工智能技术的成熟,利用车载摄像头实时捕捉驾驶员面部特征,并通过算法分析其疲劳程度,已成为主动安全预警系统的重要技术路线。本系统旨在通过多维度的面部特征分析,实时评估驾驶员的注意力状态,并在检测到疲劳迹象时及时触发安全预警。

二、 疲劳驾驶检测技术路线分析

目前主流的疲劳驾驶检测方法主要分为三类:基于车辆行驶状态的方法、基于生理信号的方法以及基于驾驶员面部特征的方法。基于车辆状态的方法通过分析方向盘转角、车道偏离等数据来判断,但容易受路况干扰;基于生理信号的方法(如脑电波、心率)精度高,但设备侵入性强,不适合日常驾驶场景。相比之下,基于面部特征的视觉检测方法具有非接触、成本低、实时性强的优势。系统通过提取人脸关键点,计算眼睛纵横比(EAR)、嘴巴纵横比(MAR)以及头部三维姿态,从而综合判定疲劳状态。

疲劳驾驶检测方法分类

三、 数据集构建与预处理

为了训练和验证基于深度学习的疲劳分类模型,构建了一个包含多场景的驾驶疲劳人脸数据集。该数据集涵盖了三种精神状态:清醒、轻度疲劳和重度疲劳。样本总数为4800张图像,其中重度疲劳1622张,轻度疲劳1506张,清醒状态1618张。数据来源包括网络公开数据集抓取与车载模拟环境下的视频抽帧,确保了光照、角度和人物多样性,为后续模型训练提供了可靠的数据支撑。

疲劳人脸数据集样本示例

四、 基于头部姿态估计的打瞌睡检测

4.1 姿态估计算法原理

头部姿态估计是判断驾驶员是否打瞌睡(点头)的关键。算法首先通过2D人脸关键点检测获取面部特征点,然后将其与标准的3D人脸模型进行匹配。利用OpenCV中的 solvePnP 函数,结合相机内参和畸变系数,求解出3D空间点到2D图像平面的旋转矩阵和平移向量。最后,通过罗德里格斯公式和投影矩阵分解,计算出头部在三个维度上的欧拉角(Pitch俯仰角、Yaw偏航角、Roll翻滚角)。

3D人脸模型与2D关键点映射

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在小样本和高维空间中的分类优势。

CNN与SVM串联网络结构

5.2 模型训练流程

模型训练采用小批量梯度下降法(Mini-batch Gradient Descent),每个批次包含20张图像。训练流程如下:首先随机初始化网络权重与偏置,设定5x5的卷积核;然后将预处理后的人脸图像批次输入网络进行前向传播;计算输出结果与真实标签的交叉熵损失;通过反向传播算法更新CNN层的权重参数;当CNN特征提取器收敛后,冻结CNN层,将提取到的特征向量输入SVM进行最终的分类边界训练。

网络训练与反向传播流程

5.3 实验结果分析

在构建的4800张图像数据集上进行训练与测试。实验记录了每个Epoch的准确率与损失率变化。结果表明,随着迭代次数的增加,训练集和验证集的准确率稳步上升,损失率逐渐下降并趋于收敛。最终模型在测试集上的综合识别准确率达到了较高水平,能够有效区分不同程度的疲劳状态,验证了CNN+SVM架构在疲劳驾驶检测任务中的有效性。

模型训练准确率与损失率曲线1 模型训练准确率与损失率曲线2

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。