当前位置: 首页 > ops >正文

智能疲劳驾驶检测系统算法设计与研究

1. 系统概述

疲劳驾驶检测系统是一个基于计算机视觉和机器学习技术的应用程序,旨在实时监测驾驶员的疲劳状态,通过分析面部特征和头部姿态来判断驾驶员是否处于疲劳状态,并在检测到疲劳迹象时发出警告。本系统采用 PyQt5 构建图形用户界面,结合 OpenCV 进行图像处理,使用特征脸 (Eigenfaces) 算法进行驾驶员识别,并通过多种疲劳指标计算实现疲劳状态检测。

1.1 系统功能

  • 实时视频捕获与显示
  • 驾驶员面部特征检测与关键点定位
  • 驾驶员身份识别
  • 头部姿态估计(俯仰角、偏航角、滚转角)
  • 眼睛状态检测(眨眼频率)
  • 嘴巴状态检测(哈欠频率)
  • 头部运动检测(点头频率)
  • 疲劳状态综合评估与警告
  • 驾驶时间记录与超时警告

1.2 技术架构

系统采用模块化设计,主要由以下几个部分组成:

  • 图形用户界面 (GUI):基于 PyQt5 构建,提供用户交互界面
  • 视频处理线程:独立线程处理视频流,避免 UI 卡顿
  • 面部特征检测模块:使用 dlib 库进行人脸检测和关键点定位
  • 头部姿态估计模块:计算驾驶员头部的三维姿态
  • 疲劳指标计算模块:计算 EAR、MAR 等疲劳相关指标
  • 驾驶员识别模块:使用特征脸算法进行驾驶员身份识别
  • 疲劳状态评估模块:综合各项指标评估疲劳状态

2. 技术细节与实现原理

2.1 开发环境与依赖库

系统基于 Python 开发,主要依赖以下库:

  • OpenCV:用于图像处理、视频捕获和基本计算机视觉操作
  • imutils:OpenCV 的实用工具包,提供图像缩放等功能
  • dlib:包含先进的机器学习算法,用于人脸检测和关键点定位
  • PyQt5:用于构建图形用户界面
  • numpy:用于数值计算
  • datetime:用于时间相关操作

2.2 系统核心类与模块

2.2.1 VideoThread 类 - 视频处理与疲劳检测核心

VideoThread 类是系统的核心处理单元,继承自 QThread,在独立线程中运行,负责视频捕获、图像处理和疲劳状态检测。

2.2.1.1 初始化参数与状态变量
def __init__(self):super().__init__()self.running = False  # 线程运行标志self.face_path = './face_path'  # 人脸数据存储路径self.path = 0  # 使用摄像头(0表示默认摄像头)# 疲劳检测阈值self.EAR_threshold = 0.18  # 眼睛纵横比阈值self.MAR_threshold = 0.65  # 嘴巴纵横比阈值self.pitch_threshold = 8.0  # 点头检测阈值self.Driving_Time_Threshold = 1800  # 驾驶时间阈值(30分钟)# 时间相关变量self.driving_time = 0  # 累计驾驶时间self.starttime = datetime.datetime.now()  # 驾驶开始时间# 疲劳指标计数器self.blink_counter = 0  # 眨眼计数(临时)self.blinks = 0  # 眨眼计数(累计)self.yawn_counter = 0  # 哈欠计数(临时)self.yawns = 0  # 哈欠计数(累计)self.nod_counter = 0  # 点头计数(临时)self.nods = 0  # 点头计数(累计)self.P80_sum_time1 = []  # PERCOLS计算时间累计1self.P80_sum_time2 = []  # PERCOLS计算时间累计2self.f = 0  # PERCOLS值(疲劳程度指标)# 状态标志self.alarm_flag = '正常'  # 警报状态self.last_params = []  # 上次识别的驾驶员参数self.eyes_closed = False  # 眼睛闭合状态self.mouth_open = False  # 嘴巴张开状态self.head_nodding = False  # 头部点头状态# 时间记录self.blink_start_time = None  # 眨眼开始时间self.yawn_start_time = None  # 哈欠开始时间self.nod_start_time = None  # 点头开始时间self.P80_start_time1 = None  # PERCOLS时间记录1self.P80_start_time2 = None  # PERCOLS时间记录2# 图表数据self.EAR_history = []  # EAR历史数据self.MAR_history = []  # MAR历史数据self.max_history_length = 60  # 历史数据最大长度# 3D姿态绘制参数self.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]]# 加载EAR和MAR标准值self.everybody_EAR_mean, self.everybody_EAR_min, _ = get_everybody_EARandMAR_standard(self.face_path)

这些参数和变量用于跟踪驾驶员状态、记录时间信息、存储疲劳指标,并设置各种检测阈值。系统会加载预先计算的 EAR (眼睛纵横比) 和 MAR (嘴巴纵横比) 标准值,用于个性化疲劳检测。

2.2.1.2 run 方法 - 核心处理逻辑

run 方法是线程的入口点,包含整个系统的核心处理逻辑:

  1. 视频捕获与预处理
  2. 人脸检测与关键点定位
  3. 驾驶员识别
  4. 头部姿态估计
  5. 眼睛和嘴巴状态分析
  6. 疲劳指标计算
  7. 疲劳状态评估
  8. 结果可视化
def run(self):self.cap = cv2.VideoCapture(self.path)while self.running:ret, im_rd = self.cap.read()if ret:im_rd = imutils.resize(im_rd, height=480, width=640)  # 调整图像大小original_img = im_rd.copy()# 灰度化处理img_gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY)# 人脸检测faces = detector(img_gray, 0)# 初始化信息字典info = {'alarm': self.alarm_flag,'driver_id': '未知','driving_time': 0,'pitch': 0,'yaw': 0,'roll': 0,'ear': 0,'mar': 0,'eyes_state': '睁开','nod_duration': 0,'yawn_duration': 0,'blinks': 0,'yawns': 0,'nods': 0,'percols': 0}# 处理检测到的人脸if len(faces) == 1:for k, d in enumerate(faces):try:# 提取人脸区域并调整大小roi_gray = img_gray[d.top():d.bottom(), d.left():d.right()]roi_gray = cv2.resize(roi_gray, (92, 112))# 驾驶员识别params = Eigen_Face_Model.predict(roi_gray)except:continue# 获取面部关键点shape = predictor(original_img, d)shape_array = face_utils.shape_to_np(shape)# 驾驶员变更检测if params[0] != self.last_params:self.driving_time = 0self.starttime = datetime.datetime.now()self.last_params = params[0]# 尝试获取驾驶员IDtry:info['driver_id'] = names[params[0]]except:info['driver_id'] = '未识别'# 头部姿态估计reprojectdst, _, pitch, roll, yaw = HPE.get_head_pose(shape_array)info['pitch'] = round(pitch, 2)info['yaw'] = round(yaw, 2)info['roll'] = round(roll, 2)# 提取面部特征点leftEye = shape_array[lStart:lEnd]rightEye = shape_array[rStart:rEnd]mouth = shape_array[mStart:mEnd]# 计算眼睛纵横比(EAR)leftEAR = ARE.eye_aspect_ratio(leftEye)rightEAR = ARE.eye_aspect_ratio(rightEye)EAR = (leftEAR + rightEAR) / 2.0info['ear'] = round(EAR, 2)# 眨眼检测current_time = datetime.datetime.now()if EAR < self.EAR_threshold and not self.eyes_closed:self.eyes_closed = Trueself.blink_start_time = current_timeself.blink_counter += 1elif EAR >= self.EAR_threshold and self.eyes_closed:self.eyes_closed = Falseif self.blink_start_time and (current_time - self.blink_start_time).total_seconds() < 0.5:self.blinks += 1self.blink_start_time = None# 计算嘴巴纵横比(MAR)MAR = ARE.mouth_aspect_ratio(mouth)info['mar'] = round(MAR, 2)# 哈欠检测if MAR > self.MAR_threshold and not self.mouth_open:self.mouth_open = Trueself.yawn_start_time = current_timeself.yawn_counter += 1elif MAR <= self.MAR_threshold and self.mouth_open:self.mouth_open = Falseif self.yawn_start_time:yawn_duration = (current_time - self.yawn_start_time).total_seconds()if yawn_duration >= 1.0:self.yawns += 1info['yawn_duration'] = int(yawn_duration)self.yawn_start_time = None# 点头检测if pitch > self.pitch_threshold and not self.head_nodding:self.head_nodding = Trueself.nod_start_time = current_timeself.nod_counter += 1elif pitch <= self.pitch_threshold and self.head_nodding:self.head_nodding = Falseif self.nod_start_time:nod_duration = (current_time - self.nod_start_time).total_seconds()if nod_duration >= 0.5:self.nods += 1info['nod_duration'] = int(nod_duration)self.nod_start_time = None# PERCOLS值计算(疲劳程度指标)if params[0] in range(len(self.everybody_EAR_mean)):T1 = self.everybody_EAR_min[params[0]] + 0.2 * (self.everybody_EAR_mean[params[0]] - self.everybody_EAR_min[params[0]])T2 = self.everybody_EAR_min[params[0]] + 0.8 * (self.everybody_EAR_mean[params[0]] - self.everybody_EAR_min[params[0]])# 计算眼睛闭合程度if EAR < T1 and abs(pitch) < 15 and abs(yaw) < 25 and abs(roll) < 15:if self.P80_start_time1 is None:self.P80_start_time1 = current_timeelif self.P80_start_time1 is not None:duration = (current_time - self.P80_start_time1).total_seconds()if duration > 0:self.P80_sum_time1.append(duration)self.P80_start_time1 = Noneif EAR < T2 and abs(pitch) < 15 and abs(yaw) < 25 and abs(roll) < 15:if self.P80_start_time2 is None:self.P80_start_time2 = current_timeelif self.P80_start_time2 is not None:duration = (current_time - self.P80_start_time2).total_seconds()if duration > 0:self.P80_sum_time2.append(duration)self.P80_start_time2 = None# 计算PERCOLS值sum_t1 = sum(self.P80_sum_time1)sum_t2 = sum(self.P80_sum_time2)if sum_t2 > 0:self.f = min(round(sum_t1 / sum_t2, 2), 1.0)else:self.f = 0# 每60秒重置PERCOLS计算if int(self.driving_time) % 60 == 0:self.P80_sum_time1 = []self.P80_sum_time2 = []self.f = 0# 设置眼睛状态info['eyes_state'] = '闭合' if EAR < self.EAR_threshold else '睁开'info['blinks'] = self.blinksinfo['yawns'] = self.yawnsinfo['nods'] = self.nodsinfo['percols'] = self.f# 疲劳状态判断if int(self.driving_time) % 60 == 0:if self.blinks >= 30:self.alarm_flag = '眨眼频率警告'elif self.yawns >= 5:self.alarm_flag = '哈欠频率警告'elif self.nods >= 3:self.alarm_flag = '点头频率警告'elif self.f > 0.6:self.alarm_flag = 'PERCOLS值警告'elif self.driving_time > self.Driving_Time_Threshold:self.alarm_flag = '长时间驾驶警告'else:self.alarm_flag = '正常'# 重置计数器self.blinks = 0self.yawns = 0self.nods = 0info['alarm'] = self.alarm_flaginfo['driving_time'] = int(self.driving_time)# 可视化处理结果for i in range(68):cv2.circle(im_rd, (shape.part(i).x, shape.part(i).y), 2, (0, 255, 0), -1, 8)for start, end in self.line_pairs:cv2.line(im_rd, (int(reprojectdst[start][0]), int(reprojectdst[start][1])),(int(reprojectdst[end][0]), int(reprojectdst[end][1])), (0, 0, 255))# 绘制眼睛和嘴巴轮廓leftEyeHull = cv2.convexHull(leftEye)rightEyeHull = cv2.convexHull(rightEye)cv2.drawContours(im_rd, [leftEyeHull], -1, (0, 255, 0), 1)cv2.drawContours(im_rd, [rightEyeHull], -1, (0, 255, 0), 1)mouthHull = cv2.convexHull(mouth)cv2.drawContours(im_rd, [mouthHull], -1, (0, 255, 0), 1)# 添加状态文字status_color = (0, 255, 0) if self.alarm_flag == '正常' else (0, 0, 255)cv2.putText(im_rd, f"状态: {self.alarm_flag}", (10, 50),cv2.FONT_HERSHEY_SIMPLEX, 0.7, status_color, 2)elif len(faces) == 0:cv2.putText(im_rd, "未检测到人脸", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)else:cv2.putText(im_rd, "检测到多个人脸", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)# 更新驾驶时间endtime = datetime.datetime.now()self.driving_time = (endtime - self.starttime).total_seconds()# 转换图像用于Qt显示rgb_image = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB)h, w, ch = rgb_image.shapebytes_per_line = ch * wqt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)self.change_pixmap_signal.emit(qt_image)# 发送信息更新UIself.update_info_signal.emit(info)else:# 视频结束或摄像头断开,重新打开self.reset_counters()self.cap = cv2.VideoCapture(self.path)
2.2.2 FatigueDetectionApp 类 - 图形用户界面

FatigueDetectionApp 类继承自 QMainWindow,负责构建和管理图形用户界面,提供用户交互功能。

2.2.2.1 界面布局设计

界面采用左右分栏布局,左侧为视频显示区域,右侧为驾驶状态监测信息:

def __init__(self):super().__init__()self.setWindowTitle('疲劳驾驶检测系统')self.setMinimumSize(1200, 700)  # 增大窗口尺寸# 设置样式表,提高界面美观度和对比度self.setStyleSheet("""QMainWindow {background-color: #121212;}QLabel {color: #e0e0e0;font-size: 14px;}QPushButton {background-color: #3b82f6;color: white;border-radius: 5px;padding: 8px 15px;font-size: 14px;}QPushButton:hover {background-color: #2563eb;}QPushButton:pressed {background-color: #1d4ed8;}QGroupBox {border: 2px solid #3b82f6;border-radius: 8px;margin-top: 10px;padding-top: 15px;}QGroupBox::title {subcontrol-origin: margin;subcontrol-position: top center;padding: 0 10px;color: #3b82f6;font-weight: bold;font-size: 15px;}""")# 创建中央部件和主布局central_widget = QWidget()main_layout = QHBoxLayout(central_widget)main_layout.setSpacing(15)  # 增加间距# 左侧视频显示区域video_group = QGroupBox("实时视频")video_group.setFont(title_font)video_layout = QVBoxLayout()self.video_label = QLabel("等待视频输入...")self.video_label.setAlignment(Qt.AlignCenter)self.video_label.setMinimumSize(700, 500)self.video_label.setStyleSheet("border: 2px solid #3b82f6; border-radius: 5px;")video_layout.addWidget(self.video_label)video_group.setLayout(video_layout)# 右侧信息显示区域info_group = QGroupBox("驾驶状态监测")info_group.setFont(title_font)info_layout = QVBoxLayout()info_layout.setSpacing(10)  # 增加间距# 驾驶人信息driver_info_layout = QHBoxLayout()self.driver_id_label = QLabel("驾驶人: 未识别")self.driver_id_label.setFont(font)self.driving_time_label = QLabel("驾驶时间: 0 秒")self.driving_time_label.setFont(font)driver_info_layout.addWidget(self.driver_id_label)driver_info_layout.addWidget(self.driving_time_label)info_layout.addLayout(driver_info_layout)# 警告信息 - 增大尺寸和对比度self.alarm_label = QLabel("状态: 正常")self.alarm_label.setFont(alarm_font)self.alarm_label.setStyleSheet("color: #4caf50; font-size: 18px; font-weight: bold;")self.alarm_label.setAlignment(Qt.AlignCenter)self.alarm_label.setMinimumHeight(40)info_layout.addWidget(self.alarm_label)# 头部姿态pose_group = QGroupBox("头部姿态")pose_group.setFont(font)pose_layout = QHBoxLayout()self.pitch_label = QLabel("俯仰角(Pitch): 0.00°")self.pitch_label.setFont(font)self.yaw_label = QLabel("偏航角(Yaw): 0.00°")self.yaw_label.setFont(font)self.roll_label = QLabel("滚转角(Roll): 0.00°")self.roll_label.setFont(font)pose_layout.addWidget(self.pitch_label)pose_layout.addWidget(self.yaw_label)pose_layout.addWidget(self.roll_label)pose_group.setLayout(pose_layout)info_layout.addWidget(pose_group)# 面部特征face_group = QGroupBox("面部特征")face_group.setFont(font)face_layout = QHBoxLayout()self.ear_label = QLabel("眼睛纵横比(EAR): 0.00")self.ear_label.setFont(font)self.mar_label = QLabel("嘴巴纵横比(MAR): 0.00")self.mar_label.setFont(font)self.eyes_state_label = QLabel("眼睛状态: 睁开")self.eyes_state_label.setFont(font)face_layout.addWidget(self.ear_label)face_layout.addWidget(self.mar_label)face_layout.addWidget(self.eyes_state_label)face_group.setLayout(face_layout)info_layout.addWidget(face_group)# 疲劳指标fatigue_group = QGroupBox("疲劳指标")fatigue_group.setFont(font)fatigue_layout = QGridLayout()fatigue_layout.setSpacing(10)  # 增加间距# 使用更清晰的标签self.blinks_label = QLabel("0 次")self.yawns_label = QLabel("0 次")self.nods_label = QLabel("0 次")self.percols_label = QLabel("0.00")self.nod_duration_label = QLabel("0 秒")self.yawn_duration_label = QLabel("0 秒")# 设置标签字体labels = [self.blinks_label, self.yawns_label, self.nods_label,self.percols_label, self.nod_duration_label, self.yawn_duration_label]for label in labels:label.setFont(font)# 优化布局fatigue_layout.addWidget(QLabel("眨眼次数:", font=font), 0, 0)fatigue_layout.addWidget(self.blinks_label, 0, 1)fatigue_layout.addWidget(QLabel("哈欠次数:", font=font), 1, 0)fatigue_layout.addWidget(self.yawns_label, 1, 1)fatigue_layout.addWidget(QLabel("点头次数:", font=font), 2, 0)fatigue_layout.addWidget(self.nods_label, 2, 1)fatigue_layout.addWidget(QLabel("PERCOLS值:", font=font), 3, 0)fatigue_layout.addWidget(self.percols_label, 3, 1)fatigue_layout.addWidget(QLabel("点头持续时间:", font=font), 0, 2)fatigue_layout.addWidget(self.nod_duration_label, 0, 3)fatigue_layout.addWidget(QLabel("哈欠持续时间:", font=font), 1, 2)fatigue_layout.addWidget(self.yawn_duration_label, 1, 3)fatigue_group.setLayout(fatigue_layout)info_layout.addWidget(fatigue_group)# 控制按钮control_layout = QHBoxLayout()self.start_button = QPushButton("开始检测")self.start_button.setFont(font)self.start_button.setMinimumHeight(40)self.stop_button = QPushButton("停止检测")self.stop_button.setFont(font)self.stop_button.setMinimumHeight(40)self.stop_button.setEnabled(False)control_layout.addWidget(self.start_button)control_layout.addWidget(self.stop_button)info_layout.addLayout(control_layout)info_group.setLayout(info_layout)# 将视频和信息区域添加到主布局main_layout.addWidget(video_group)main_layout.addWidget(info_group)main_layout.setStretch(0, 3)  # 视频区域占3份main_layout.setStretch(1, 2)  # 信息区域占2份self.setCentralWidget(central_widget)

界面设计考虑了用户体验,采用分组布局清晰展示各类信息,并使用颜色区分正常状态和警告状态,提高视觉对比度。

2.2.2.2 信号与槽机制

PyQt5 的信号与槽机制是界面与后台处理通信的关键:

# 连接信号和槽
self.start_button.clicked.connect(self.start_detection)
self.stop_button.clicked.connect(self.stop_detection)# 创建视频线程
self.video_thread = VideoThread()
self.video_thread.change_pixmap_signal.connect(self.update_video_frame)
self.video_thread.update_info_signal.connect(self.update_info_display)
  • 按钮点击信号连接到相应的槽函数,控制检测的开始和停止
  • 视频线程通过信号发送处理后的图像和状态信息,更新 UI 显示

2.3 疲劳检测算法原理

2.3.1 眼睛纵横比 (EAR) 与眨眼检测

眼睛纵横比 (Eye Aspect Ratio, EAR) 是衡量眼睛睁开程度的重要指标,计算公式如下:

其中P1到P6是眼睛周围 6 个关键点的坐标。当眼睛闭合时,EAR 值会显著减小,通常小于 0.2。系统通过监测 EAR 值低于阈值的持续时间来检测眨眼行为。

# 计算眼睛纵横比
def eye_aspect_ratio(eye):# 计算垂直距离A = dist.euclidean(eye[1], eye[5])B = dist.euclidean(eye[2], eye[4])# 计算水平距离C = dist.euclidean(eye[0], eye[3])# 计算EARear = (A + B) / (2.0 * C)return ear

眨眼检测逻辑:当 EAR 低于阈值且眼睛之前处于睁开状态时,记录眨眼开始时间;当 EAR 高于阈值且眼睛之前处于闭合状态时,计算眨眼持续时间,若持续时间在合理范围内 (小于 0.5 秒),则计数一次眨眼。

2.3.2 嘴巴纵横比 (MAR) 与哈欠检测

嘴巴纵横比 (Mouth Aspect Ratio, MAR) 用于衡量嘴巴张开程度,计算公式如下:

其中P0到P7是嘴巴周围 8 个关键点的坐标。当嘴巴张开 (如打哈欠) 时,MAR 值会显著增大,通常大于 0.6。

# 计算嘴巴纵横比
def mouth_aspect_ratio(mouth):# 计算垂直距离A = dist.euclidean(mouth[13], mouth[19])B = dist.euclidean(mouth[14], mouth[18])C = dist.euclidean(mouth[15], mouth[17])# 计算水平距离D = dist.euclidean(mouth[0], mouth[6])# 计算MARmar = (A + B + C) / (2.0 * D)return mar

哈欠检测逻辑:当 MAR 高于阈值且嘴巴之前处于闭合状态时,记录哈欠开始时间;当 MAR 低于阈值且嘴巴之前处于张开状态时,计算哈欠持续时间,若持续时间超过 1 秒,则计数一次哈欠。

2.3.3 头部姿态估计

头部姿态估计通过计算头部的三个欧拉角 (俯仰角 Pitch、偏航角 Yaw、滚转角 Roll) 来判断驾驶员的头部运动:

  • 俯仰角 (Pitch):头部上下转动
  • 偏航角 (Yaw):头部左右转动
  • 滚转角 (Roll):头部倾斜

系统使用 68 个人脸关键点,通过解决 PnP (Perspective-n-Point) 问题来估计头部姿态:

# 头部姿态估计
def get_head_pose(shape):# 3D模型点model_points = np.array([(0.0, 0.0, 0.0),               # 鼻尖(0.0, -330.0, -65.0),          # 下巴(-225.0, 170.0, -135.0),       # 左眼左角(225.0, 170.0, -135.0),        # 右眼右角(-150.0, -150.0, -125.0),      # 左嘴角(150.0, -150.0, -125.0)        # 右嘴角])# 2D图像点image_points = np.array([shape[30],                     # 鼻尖shape[8],                      # 下巴shape[36],                     # 左眼左角shape[45],                     # 右眼右角shape[48],                     # 左嘴角shape[54]                      # 右嘴角], dtype="double")# 相机内参focal_length = 1.0 * 640center = (320, 240)camera_matrix = np.array([[focal_length, 0, center[0]],[0, focal_length, center[1]],[0, 0, 1]], dtype="double")# 相机畸变参数dist_coeffs = np.zeros((4, 1))  # 求解PnP问题success, rotation_vector, translation_vector = cv2.solvePnP(model_points, image_points, camera_matrix, dist_coeffs,flags=cv2.SOLVEPNP_ITERATIVE)# 计算欧拉角rotation_matrix, _ = cv2.Rodrigues(rotation_vector)pose_mat = np.hstack((rotation_matrix, translation_vector))_, _, _, _, pitch, yaw, roll = cv2.decomposeProjectionMatrix(pose_mat)# 计算3D模型点的投影,用于可视化reprojectdst, _ = cv2.projectPoints(np.array([(0.0, 0.0, 1000.0),(0.0, 0.0, 0.0),(0.0, -330.0, -65.0),(-225.0, 170.0, -135.0),(225.0, 170.0, -135.0),(-150.0, -150.0, -125.0),(150.0, -150.0, -125.0)]),rotation_vector, translation_vector, camera_matrix, dist_coeffs)return reprojectdst, rotation_vector, pitch[0], yaw[0], roll[0]

点头检测逻辑:当俯仰角 (Pitch) 大于阈值且头部之前处于非点头状态时,记录点头开始时间;当俯仰角小于阈值且头部之前处于点头状态时,计算点头持续时间,若持续时间超过 0.5 秒,则计数一次点头。

2.3.4 PERCOLS 值 - 疲劳程度综合指标

PERCOLS 值 (Percentage of Eye Closure over a Long Period of Time) 是衡量驾驶员疲劳程度的综合指标,计算公式如下:

其中T1是眼睛闭合程度超过 20% 阈值的时间,T2是眼睛闭合程度超过 80% 阈值的时间。系统通过计算这两个时间段的比值来评估疲劳程度,值越大表示疲劳程度越高。

# PERCOLS值计算
if params[0] in range(len(self.everybody_EAR_mean)):T1 = self.everybody_EAR_min[params[0]] + 0.2 * (self.everybody_EAR_mean[params[0]] - self.everybody_EAR_min[params[0]])T2 = self.everybody_EAR_min[params[0]] + 0.8 * (self.everybody_EAR_mean[params[0]] - self.everybody_EAR_min[params[0]])# 计算眼睛闭合程度if EAR < T1 and abs(pitch) < 15 and abs(yaw) < 25 and abs(roll) < 15:if self.P80_start_time1 is None:self.P80_start_time1 = current_timeelif self.P80_start_time1 is not None:duration = (current_time - self.P80_start_time1).total_seconds()if duration > 0:self.P80_sum_time1.append(duration)self.P80_start_time1 = Noneif EAR < T2 and abs(pitch) < 15 and abs(yaw) < 25 and abs(roll) < 15:if self.P80_start_time2 is None:self.P80_start_time2 = current_timeelif self.P80_start_time2 is not None:duration = (current_time - self.P80_start_time2).total_seconds()if duration > 0:self.P80_sum_time2.append(duration)self.P80_start_time2 = None# 计算PERCOLS值sum_t1 = sum(self.P80_sum_time1)sum_t2 = sum(self.P80_sum_time2)if sum_t2 > 0:self.f = min(round(sum_t1 / sum_t2, 2), 1.0)else:self.f = 0# 每60秒重置PERCOLS计算if int(self.driving_time) % 60 == 0:self.P80_sum_time1 = []self.P80_sum_time2 = []self.f = 0

2.4 驾驶员识别 - 特征脸 (Eigenfaces) 算法

系统使用特征脸算法进行驾驶员身份识别,这是一种基于主成分分析 (PCA) 的人脸识别方法:

  1. 将人脸图像转换为向量
  2. 使用 PCA 降维,提取主要特征 (特征脸)
  3. 将新的人脸图像投影到特征脸空间
  4. 通过计算与训练样本的距离进行识别
# 特征脸识别
from Eigen_Face_Recognizer import *# 预测驾驶员身份
params = Eigen_Face_Model.predict(roi_gray)
try:info['driver_id'] = names[params[0]]
except:info['driver_id'] = '未识别'

特征脸算法的优点是计算效率高,适合实时系统,但在表情变化大、光照条件变化时识别准确率会下降。系统会在驾驶员变更时重置驾驶时间计数。

2.5 多线程处理机制

系统采用多线程架构,将视频处理和 UI 更新分离到不同线程,避免 UI 卡顿:

  • VideoThread 线程负责视频捕获、图像处理和疲劳检测
  • 主线程负责 UI 界面显示和用户交互

通过 PyQt5 的信号与槽机制实现线程间通信,确保线程安全:

这种设计模式确保了系统的响应性和稳定性,即使视频处理较为耗时,UI 仍然可以保持流畅。

3. 系统工作流程

3.1 启动流程

  1. 应用程序初始化,创建 FatigueDetectionApp 实例
  2. 界面初始化,设置布局和控件
  3. 连接信号与槽
  4. 用户点击 "开始检测" 按钮
  5. 启动 VideoThread 线程,开始视频捕获和处理

3.2 处理流程

  1. 视频线程捕获一帧图像
  2. 图像预处理:调整大小、灰度化
  3. 人脸检测,获取人脸区域
  4. 驾驶员识别,获取驾驶员 ID
  5. 面部关键点定位,获取 68 个关键点坐标
  6. 头部姿态估计,计算俯仰角、偏航角、滚转角
  7. 计算 EAR 和 MAR,检测眨眼和哈欠
  8. 计算 PERCOLS 值,评估疲劳程度
  9. 综合各项指标,判断疲劳状态
  10. 可视化处理结果,绘制关键点和姿态
  11. 发送处理结果到 UI 线程更新显示

3.3 关闭流程

  1. 用户点击 "停止检测" 按钮或关闭窗口
  2. 停止视频线程,释放资源
  3. 退出应用程序

运行数据:

正在加载dlib模型: D:\Develop_Code\py_work\Fatigue-Driving-Detection-Based-on-Dlib-master\shape_predictor_5_face_landmarks.dat
检测到 OpenCV 版本: 4.0.0
OpenCV contrib 模块已正确安装
/*/*/*/*/*/*/* 特征脸识别器正在训练 /*/*/*/*/*/*/*
成功加载 158 张训练图像,共 2 个人
-*-*-*-*-*-*-* 特征脸识别器训练完成并保存至 ./eigen_face_model.yml -*-*-*-*-*-*-*
所有人睁眼ER平均值: [0.38413600756917976, 0.3819993541503343]
所有人闭眼EAR最小值: [0.2629860803947992, 0.292137046814521]
------------------- 开始执行主函数 -------------------
小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.3035419281974605 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.3069549345872935 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.2949592314589621 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.3027418010270625 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.2902889940735921 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.29684223252664194 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.30226910858134476 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.31337795439975213 T1: 0.2872160658296753 ;   t2: 0.35990602213430367 小于20%的时间:  0  s
小于80%的时间:  0  s
当前驾驶人的EAR:  0.31947443683898324 

4.项目说明: 

#项目文件说明***************************************************************************************************************
如何运行该项目
在运行项目之前, 应确保你有用于测试的视频文件. 本项目中提供了一个视频例程(driving.mp4)
必须执行: 首先运行 drivers_img_acquire.py 文件, 输入当前驾驶人的名字英文缩写,获取不同驾驶人的两类图像
获取的第一类图像为 摄像头全景图像, 默认存放于 './capture_path/{your name}'. 注意: 要删除capture_path文件夹下面的txt文件.
获取的第二类图像为 驾驶人人脸区域图像, 默认存放于 './face_path/{your name}'. 注意: 要删除face_path文件夹下面的txt文件.
其次运行main.py程序即可.***************************************************************************************************************
capture_path: 所有驾驶人的全景图像 (仅采集, 未使用)
face_path: 所有驾驶人的人脸区域图像, 用于身份识别的训练
test_video: 测试视频所存放的文件夹
aspect_ratio_estimation.py: 计算EAR 和 MAR的程序
dlib-19.7.0-cp36-cp36m-win_amd64.whl: dlib的安装文件 drivers_img_acquire.py: 获取驾驶人全景图像和人脸区域的程序
Eigen_Face_Recognizer.py: 特征脸识别器文件, 用特征脸识别不同驾驶人身份(效果并不好, 仅作为理论分析)
get_everybody_EARandMAR_standard.py: 得到每个驾驶人的EAR和MAR基准
haarcascade_eye.xml: 用于检测人眼睛位置的Haar级联分类器文件
haarcascade_frontalface_alt.xml: 用于检测人脸部位置的Harr级联分类器文件
head_posture_estimation.py: 头部姿态估计文件
main.py: 主函数, 用于处理拍摄好的视频图像

交流联系,主页或者点击这里 文章末尾----
下期再见

http://www.xdnf.cn/news/13833.html

相关文章:

  • 山东大学软件学院项目实训:基于大模型的模拟面试系统项目总结(八)
  • 微信小程序生成小程序码缓存删除
  • 程序是怎么跑起来的第三章
  • 产品成本分析怎么做?从0到1搭建全生命周期分析框架!
  • 基于 Transformer RoBERTa的情感分类任务实践总结之四——PGM、EMA
  • 操作系统导论 第42章 崩溃一致性:FSCK 和日志
  • TEXT2SQL-vanna多表关联的实验
  • 13.安卓逆向2-frida hook技术-HookJava构造方法
  • 动态规划优雅计算比特位数:从0到n的二进制中1的个数
  • FastJSON等工具序列化特殊字符时会加转义字符\
  • 深度学习-163-MCP技术之使用Cherry Studio调用本地自定义mcp-server
  • 门岗来访访客登记二维码制作,打印机打印粘贴轻松实现。
  • 107.添加附件上传取消附件的功能
  • 06_项目集成 Spring Actuator 并实现可视化页面
  • 基于 8.6 万蛋白质结构数据,融合量子力学计算的机器学习方法挖掘 69 个全新氮-氧-硫键
  • OrangePi 5 Max EMMC 系统烧录时下载成功,启动失败解决方案
  • 高开放性具身智能AIBOX平台—专为高校实验室与科研项目打造的边缘计算基座(让高校和科研院所聚焦核心算法)
  • 打卡第43天:Grad CAM与Hook函数
  • 【ffmpeg】windows端安装ffmpeg
  • ES集群的节点
  • 深度学习入门(4):resnet50
  • 今日行情明日机会——20250612
  • Python小酷库系列:Python中的JSON工具库(1)
  • 106.给AI回答添加点赞收藏功能
  • PCI总线概述
  • ubuntu22.04使用系统默认的中文输入法,打字只输入英文字符怎么操作才能打字中文
  • 模型合并(model merge)
  • 如何搭建独立站并使用Cloak斗篷技术
  • Intel J1900通讯管理机,支持8网8串,EFT过载保护
  • MTK APEX测光系统中各变量具体的计算方式探究