☰
基于MATLAB的疲劳检测系统完整设计与实现
2026/9/27 20:28:59 网站建设 项目流程

简介:本资源是一套完整的基于MATLAB实现的疲劳检测系统,专为本科生毕业设计与课程设计打造,面向图像处理、生物特征识别及人机交互方向的学习者,解决驾驶员或操作人员实时状态监测的实际问题。压缩包共48个文件,含31张测试/结果图像(jpg)、8个核心MATLAB函数脚本(m)、5张界面与原理图(png)、2个预训练数据集(mat)、1份说明文档(doc)及1个GUI布局文件(fig),总大小5.08MB,覆盖图像预处理、人脸定位、人眼精确定位、灰度积分投影分析及疲劳判据计算等完整流程。已有251人学习下载,项目代码全程手打、注释详尽,GUI界面美观且交互逻辑清晰,附带运行效果图与多帧实测样本,可直接部署运行,无需额外配置,适合作为高分毕设方案或期末大作业参考。 每年到了毕设季和课设季,总能看到一堆人在问“疲劳检测系统怎么做”“MATLAB GUI怎么搭”,然后评论区又是清一色的“同求资料”。我做这类图像处理项目有些年头了,从最早的肤色分割、模板匹配一路做到深度学习,说实话,基于MATLAB的疲劳检测系统确实是课程设计和毕业设计里最适合选题的方向之一——工作量清晰、视觉效果好、答辩容易讲,而且踩坑点基本都是可以预见的。

这篇文章我就把这套系统的完整设计思路、核心算法、GUI搭建流程和我在实际开发中踩过的坑一次性讲透。不管你是准备开题、正在写代码,还是答辩前紧急修复bug,这篇内容都能帮你省下大量试错时间。文章里的代码块都是可以直接拿过去改的,不是PPT级别的那种伪代码。

1. 项目整体设计与方案选型

1.1 疲劳检测要解决的真实问题

先说个常识:疲劳检测系统的核心价值不在“检测”,而在“及时提醒”。无论是驾驶员疲劳监测、网课注意力分析,还是生产线操作员状态监测,用户真正需要的是在闭眼持续超过安全阈值、打哈欠频率异常升高的时候,系统能立刻发出报警,把人从危险边缘拉回来。

所以这套系统的功能拆解非常明确:实时采集面部画面,定位人脸位置,截取眼睛和嘴巴区域,再通过特征计算判断眼睛是睁是闭、嘴巴是否张大,最后根据连续帧的状态变化推断疲劳等级并触发提醒。整个链路听起来不复杂,但每个环节都有不少细节,尤其是光照变化和姿态偏移这两个变量,会把很多“看起来没问题”的算法打得满地找牙。

1.2 为什么选MATLAB而不是Python

这个问题我每年都会被问至少十次。对于一个需要交付GUI界面的毕设项目,MATLAB的优势其实非常明显:首先,Image Processing Toolbox和Computer Vision Toolbox把人脸检测、形态学处理、图像增强这些底层操作都封装好了,你不需要像OpenCV那样手写太多底层逻辑;其次,MATLAB的GUI开发环境(GUIDE和App Designer)是拖拽式的,控件布局、回调函数生成都是半自动的,非常容易出效果;再者,MATLAB对矩阵运算的原生支持让图像数据操作非常痛快,调试的时候选中变量就能看到图像长什么样,这种直观性是Python+OpenCV比不了的。

当然,Python生态的深度学习方案精度更高,但对于大多数课设和毕设场景,评委更看重的是“你是否理解整个系统的工作原理”,而不是“你的模型mAP有没有刷到0.98”。MATLAB这种把重心放在算法逻辑和系统架构上的环境,其实更符合评审预期。

1.3 系统模块划分与工作流程

我把整个系统的架构拆成四个模块,这也是答辩时最容易讲清楚的结构:

  • 图像采集模块:负责从摄像头读取视频帧,用webcam接口即可,不需要额外装驱动。
  • 人脸与人眼检测模块:用Viola-Jones级联检测器定位人脸区域,再在人脸区域内提取眼睛和嘴巴的ROI。
  • 疲劳特征计算模块:计算眼睛开合度、PERCLOS(单位时间内眼睛闭合时间占比)和打哈欠频率。
  • 提醒与界面反馈模块:通过GUI显示实时画面、状态参数和报警信号。

这四部分各司其职,代码层面也基本可以独立调试。我的习惯是先做后台算法验证,再套GUI壳子,不然一边调参一边跟界面控件打交道,很容易乱套。

2. 核心算法细节拆解

2.1 人脸检测:级联分类器是性价比之王

在MATLAB里做人脸检测,最省事的就是用vision.CascadeObjectDetector,它封装了基于Haar特征的AdaBoost级联分类器,加载的是OpenCV训练好的模型文件。实际调用只需要三行代码:

faceDetector = vision.CascadeObjectDetector('FrontalFaceCART'); img = imread('test.jpg'); bbox = step(faceDetector, img);

注意这个检测器对光照和角度相当敏感,正脸、光线均匀的条件下检测率最高。如果检测不到人脸,先别急着调参数,看看画面里是不是有背光或者侧脸。如果确实需要提升鲁棒性,可以用"FrontalFaceLBP"模型,基于LBP特征的检测器在计算速度上更快,但小目标召回率略低。

2.2 眼睛区域定位:提取ROI的关键操作

检测到人脸框之后,不能直接拿整张图去算开合度,需要先从人脸框里切出眼睛区域。这里有一个关键知识:人脸检测返回的bbox是一个四元素向量[ x, y, width, height ],其中x和y是框的左上角坐标。根据脸部解剖比例,眼睛大致位于人脸框从上往下30%到55%、水平方向15%到85%的区域内。所以裁剪代码可以这样写:

faceImg = imcrop(frame, bbox(i,:)); [faceH, faceW, ~] = size(faceImg); % 左眼区域:人脸左半区中上部 leftEyeRect = [round(0.05*faceW), round(0.3*faceH), round(0.4*faceW), round(0.25*faceH)]; leftEye = imcrop(faceImg, leftEyeRect); % 右眼区域:人脸右半区中上部 rightEyeRect = [round(0.55*faceW), round(0.3*faceH), round(0.4*faceW), round(0.25*faceH)]; rightEye = imcrop(faceImg, rightEyeRect);

这个比例系数是经验值,实际的裁剪范围可以根据自己的脸微调。一个常见的问题是眉毛被划进眼睛区域,导致后续二值化时出现干扰。对策是把纵向裁剪范围稍微往下移,比如从0.32倍高度开始,同时把区域高度限制在0.22倍以内。

2.3 眼睛开合度计算:从二值化到黑点比例

在MATLAB环境里做眼睛状态判断,最接地气的办法是“暗像素比例法”。原理非常简单:睁眼时,眼睛区域里眼白面积大,瞳孔黑色区域占比相对较小;闭眼时,整个眼眶区域被皮肤和睫毛覆盖,暗像素比例会明显升高。实际操作分三步:灰度化、增强、二值化。

eyeGray = rgb2gray(leftEye); eyeGray = imadjust(eyeGray); % 增强对比度 bw = imbinarize(eyeGray, 0.5); % 阈值分割 bw = imcomplement(bw); % 黑色区域变为白色,方便统计 blackRatio = sum(bw(:)) / numel(bw); % 暗像素占比

然后设定一个开合度阈值,比如黑点比例超过0.35就判定为闭眼。这个阈值需要在实际光照下标定,因为光线暗的时候整个区域都会变暗,黑点比例整体偏高。我的做法是在代码里加一个可调参数,预设值为0.35,双击gui上的控件就能实时改,省去反复改源码的麻烦。

2.4 PERCLOS算法:疲劳判定的国际通用标准

如果只看单帧的眼睛状态,很容易误报,因为正常人每分钟眨眼15到20次,单次眨眼时间只有100到200毫秒。所以需要引入PERCLOS指标,它的完整定义是:在一定时间内,眼睛闭合程度超过某一阈值(通常为80%)的帧数占总帧数的百分比。有研究表明,当PERCLOS值超过0.4时,人的反应能力已经明显下降,这是最早用于驾驶员疲劳监测的生理指标之一。

实现PERCLOS需要维护一个滑动窗口,我一般取10秒内的帧数据:

windowSize = 150; % 假设15fps,10秒共150帧 eyeStateBuf = zeros(1, windowSize); % 每一帧计算出闭眼状态后,更新缓冲区 eyeStateBuf = [eyeStateBuf(2:end), isClosed]; perclos = sum(eyeStateBuf) / windowSize;

滑窗的好处是内存占用固定,实时性好,而且能在眨眼频繁的场景下平滑掉单帧的跳变。窗口大小可以根据帧率动态调整,核心思路是保证窗口覆盖的物理时间在8到12秒之间。窗口太短容易误报,窗口太长则预警迟钝,这个平衡要试几次才能找到手感。

2.5 打哈欠检测:嘴巴区域的宽高比

打完哈欠之后的几秒钟其实比闭眼更危险,因为大脑供氧量下降,注意力会断崖式下跌。哈欠检测我沿用眼睛区域类似的思路,先定位嘴巴区域,再做二值化,不过嘴部的关键特征用宽高比更靠谱。

嘴部区域大致位于人脸框下半部分60%到90%的高度区间,水平方向30%到70%。提取后用肤色分割或者灰度二值化分离嘴部,再用regionprops计算最大连通区域的外接矩形,宽高比 = 矩形宽度 / 矩形高度。正常闭嘴时宽高比大约为2到4,打哈欠张大嘴时这个比值会急剧下降,接近1甚至小于1。

mouthGray = rgb2gray(mouth); bwMouth = imbinarize(mouthGray, 0.6); bwMouth = imopen(bwMouth, strel('disk', 3)); % 去除细小噪声 stats = regionprops(bwMouth, 'BoundingBox', 'Area'); if ~isempty(stats) [~, idx] = max([stats.Area]); bb = stats(idx).BoundingBox; mouthWHRatio = bb(3) / max(bb(4), 0.001); end

嘴巴的判别阈值通常设置在1.2左右,低于该值并持续两秒以上,判定为一次哈欠。注意哈欠需要“持续”判断,偶尔一次张嘴说话不算,所以要加计数器做持续帧数验证。

3. GUI界面设计与交互逻辑

3.1 工具选型:GUIDE还是App Designer

MATLAB的GUI开发经历过两个主要阶段,GUIDE是传统方案,App Designer是R2016a及以后版本推的新框架。GUIDE生成.fig和.m文件对,代码结构比较直白,适合新手改;App Designer则更现代,支持的面板布局、标签控件更丰富,回调函数封装也更规范,但它需要以类的方式编写,很多初学者第一次接触会不适应。

如果用的是R2020a之前的版本,直接上GUIDE没毛病,网上的老项目代码也基本都是这套;如果用的是新版MATLAB,建议直接用App Designer,因为新版里GUIDE已经标记为不推荐,未来版本会彻底移除。App Designer的UIFigure、坐标区(UIAxes)、按钮、标签等控件足够实现我们需要的所有功能,而且自动生成的代码本身就带有严格的属性规范,风格上适合直接作为毕设代码展示。

3.2 界面布局与功能分区

我的界面布局原则就是三个词:直观、清晰、演示友好。主界面左侧是一个大坐标区,用来显示实时摄像头画面,右上侧是四个参数指示控件:人脸是否检测到、左眼状态、右眼状态、整体疲劳等级。右下侧是一排操作按钮:打开摄像头、开始检测、停止检测、退出系统。底部放一个滑动条或者输入框,用于调节疲劳判定阈值。

状态展示这块,我用了一个偷懒但效果很好的方式:在坐标区内用text函数叠加文字,或者在坐标区旁边放一个可编程的LED指示灯图标。闭眼时指示灯变红,睁眼时变绿。这种视觉反馈在答辩现场非常加分,因为评委一眼就能看到系统在正常工作。

3.3 定时器驱动的实时刷新机制

很多新手做GUI摄像头程序时,喜欢在按钮回调函数里写while循环,边采集边处理边更新图像,结果点了一个按钮整个界面就卡死了。这个问题的根源在于:while循环会独占MATLAB的主线程,UI事件队列得不到处理,界面自然就“假死”了。

正确的做法是使用timer对象驱动刷新任务。timer是MATLAB的异步定时器,可以在后台按固定时间间隔调用回调函数,不会阻塞主线程。

t = timer; t.Period = 0.1; % 每0.1秒刷新一次,约10fps t.TimerFcn = @(~, ~) updateFrame(app); t.ExecutionMode = 'fixedSpacing'; start(t);

用timer之后,界面不会卡顿,而且可以在updateFrame函数里写各种状态更新逻辑。要注意的是,updateFrame的执行时间不能超过Period,否则timer会累积任务,导致延迟越来越严重。我的做法是将视频处理分辨率降为640x480,把人脸检测的MergeThreshold参数调高一些,处理速度就能稳稳跑在10到15fps。

3.4 疲劳报警的联动实现

当PERCLOS超过阈值时,系统需要立刻给出声光提示。MATLAB里播放声音非常简单:

[audio, fs] = audioread('warning.wav'); sound(audio, fs);

为了避免警报声反复触发把人吓一跳,我加了一个防抖逻辑:只有在“正常→疲劳”这个状态跳变沿上才播报警音,并记录当前时间戳。如果已经处于疲劳状态,后续帧不再重复播放,直到系统恢复“正常”状态才允许再次报警。这样做体验会好很多,否则系统会像个复读机一样吵个不停。

4. 实操过程与核心代码实现

4.1 项目文件结构与准备

在动手写代码之前,先建好项目目录,这能让你在后期维护时少掉很多头发。我的目录结构长这样:

FatigueDetection/ ├── main.fig % GUI窗口文件(GUIDE) ├── main.m % 主程序入口 ├── detectFace.m % 人脸检测封装函数 ├── getEyeFeatures.m % 眼睛特征计算函数 ├── getMouthFeatures.m % 嘴巴特征计算函数 ├── computePERCLOS.m % PERCLOS计算函数 ├── warning.wav % 报警音频 └── test_images/ % 测试图片集

用App Designer的话没有.fig文件,整个项目收敛成一个.mlapp文件加若干自定义函数。好处是文件数量少,坏处是单个文件比较长,后期改动时文件里的代码区域要点半天。我个人的习惯是:逻辑简单的项目用GUIDE,控件数量多且回调分散的项目用App Designer。

4.2 摄像头初始化和人脸检测主循环

编码的第一步是初始化摄像头和检测器。用webcam函数前要确保装了Image Acquisition Toolbox,MATLAB会返回一个camera对象,然后通过camera.snapshot获取当前帧。如果电脑没有内置摄像头,用USB外接摄像头时注意选择正确的设备编号。

% 初始化摄像头,1号设备通常是内置摄像头 cam = webcam(1); cam.Resolution = '640x480'; % 初始化人脸检测器 faceDetector = vision.CascadeObjectDetector('FrontalFaceCART'); % 主循环,读取每帧图像并检测 while ishandle(app) frame = snapshot(cam); grayFrame = rgb2gray(frame); bbox = step(faceDetector, grayFrame); % 都检测到人脸才继续 if ~isempty(bbox) % 选择面积最大的人脸框,避免多人场景干扰 areas = bbox(:,3) .* bbox(:,4); [~, maxIdx] = max(areas); faceBbox = bbox(maxIdx, :); % 画框并显示 frame = insertShape(frame, 'Rectangle', faceBbox, 'Color', 'green', 'LineWidth', 3); % 转入特征提取 [isClosed, ratio] = getEyeFeatures(frame, faceBbox); end imshow(frame, 'Parent', app.videoAxes); drawnow limitrate; end

这段代码是整个系统的主循环骨架。注意drawnow limitrate是控制刷新频率的关键,它会在帧之间留出空隙,避免界面绘图队列堆积。

4.3 眼睛开合度函数实现

getEyeFeatures函数是这个项目的“心脏”。它负责从人脸框中裁剪眼睛区域,计算暗像素比例,然后判定睁闭眼状态。完整实现如下:

function [isClosed, darkRatio] = getEyeFeatures(frame, faceBbox) % 按比例裁剪眼睛ROI x = faceBbox(1); y = faceBbox(2); w = faceBbox(3); h = faceBbox(4); eyeRect = [x + 0.05*w, y + 0.32*h, 0.9*w, 0.22*h]; eyeRegion = imcrop(frame, eyeRect); % 转为灰度并增强对比度 eyeGray = rgb2gray(eyeRegion); eyeGray = imadjust(eyeGray); % 中值滤波去噪,保留边缘信息 eyeGray = medfilt2(eyeGray, [3 3]); % 自适应二值化,比固定阈值更抗光照变化 bw = imbinarize(eyeGray, 'adaptive', 'ForegroundPolarity', 'dark', 'Sensitivity', 0.6); % 形态学开运算去除小噪声 bw = imopen(bw, strel('disk', 2)); % 计算黑色像素占比 darkRatio = sum(bw(:)) / numel(bw); % 判定睁闭眼 isClosed = darkRatio > 0.35; end

这里用到了adaptive二值化,这是我在多个项目里实验后的推荐做法,比固定阈值更能适应光照变化。不过adaptive的Sensitivity参数要小心调,太大会把阴影都当成前景,太小又检测不到瞳孔。

4.4 参数标定与阈值设置

这个系统里最关键的三个阈值分别是:

  • 黑点比例阈值(默认0.35):用于判断单帧眼睛是否闭合。标定时,睁眼状态下的黑点比例通常在0.15到0.25之间,闭眼状态在0.45到0.65之间,0.35是中间值。
  • PERCLOS阈值(默认0.4):用于判断整体疲劳状态。闭眼帧占比超过40%就报警。
  • 哈欠宽高比阈值(默认1.2):用于判断嘴巴是否张大。

阈值标定的流程我建议写成Excel记录,针对五个人、不同光照条件各测一分钟,取中间值。这样答辩时如果老师问“阈值为什么定这些数字”,你可以拿出标定数据说话,这在评分上非常加分。

4.5 完整状态判定逻辑

真正的疲劳判定不能单纯看PERCLOS,我建议把眼睛闭眼比例和哈欠频率结合起来,做成加权评分:

score = 0; if perclos > 0.4 score = score + 60; end if yawnCount >= 3 % 10秒内打哈欠3次以上 score = score + 40; end isFatigue = score >= 60;

这个逻辑的好处是,单一眼部数据出现异常或嘴部数据异常,都不至于马上触发报警,两个指标互相验证,误判率更低。实际项目里,旁边有人说话、摇头、大笑等动作都会被识别为嘴巴张大,单靠嘴部数据很容易误报,加权策略能把这些问题压下来。

5. 常见问题与排查技巧实录

5.1 摄像头打开失败或黑屏

这个问题的概率在课设现场最高。常见原因有三个:一是没有安装Image Acquisition Toolbox,webcam函数直接报错;二是摄像头被其他软件占用,比如已经打开了微信视频,MATLAB拿不到设备句柄;三是设备编号不对,有的电脑内置摄像头在设备管理器里是0号,有的是1号。

排查顺序:先运行webcamlist查看可用摄像头列表,确认设备ID;然后检查工具箱状态,用ver命令查看工具箱列表;最后关掉所有可能占用摄像头的软件再试。如果是借用实验室电脑演示,最好提前一天把这些准备工作做完。

5.2 人脸检测漏检和误检

人脸检测器最怕三件事:侧脸、戴帽子遮挡额头、光线半明半暗。Viola-Jones级联分类器对正脸效果最好,偏转超过30度基本就丢失目标。网上有一些改进方案是同时加载多个方向的检测器,但对毕设来说性价比不高。

我的建议是,如果演示环境的光照条件不理想,在GUI界面上增加一个“补光提示”标签,提醒用户正对光源。这是最实用也最省事的“算法优化”。另外,可以在进入主循环前保存一张背景图,用帧差法检测画面里是否有运动目标,如果长时间没有人脸进入画面,给出提示而不是干等。

5.3 闭眼判定不准确

很多同学第一次跑通代码后会发现,明明睁着眼睛,系统却经常提示闭眼;或者闭眼了却不报警。这种问题绝大多数出在眼睛ROI裁剪不准确上。人脸检测框是矩形,如果你把眉毛也算进眼睛区域,眉毛本身是暗色,开运算后残留的大块暗色区域会导致黑点比例虚高。

还有一个隐蔽的问题:戴眼镜时,镜框边缘会产生大面积的暗色像素,睁眼时的比例可能比不戴眼镜闭眼时还高。解决思路是在裁剪到眼睛区域后,缩小到只保留眼睛中心区域,也就是把左右眼各自水平方向去掉外端5%和15%的镜框位置。这个方法不完美,但能显著减少误判。

5.4 GUI界面卡死或响应迟缓

卡死的原因前面说过,大概率是while循环霸占了主线程。还有一种情况是timer回调里面嵌套了耗时操作,比如每次刷新都重新初始化检测器,这会让Period形同虚设。解决办法是把检测器、摄像头等对象都放在初始化函数里创建一次,timer回调里只做“获取帧-处理-显示”这三件事,不要做任何new操作。

5.5 常见问题排查速查表

我整理了一份项目自检清单,基本覆盖了课设过程中最常遇到的十来个问题:

问题现象可能原因排查与解决
webcam函数报错未安装图像采集工具箱运行ver确认工具箱,安装Image Acquisition Toolbox
摄像头黑屏设备被占用或编号错误关闭占用软件,运行webcamlist核对ID
人脸检测不到侧脸、背光、距离太远正对光源,画面内人脸占比尽量大
闭眼状态误判ROI包含眉毛或眼镜框纵向裁剪下移,适当缩小ROI高度
系统运行卡顿主线程被while循环占用改用timer定时刷新,降低处理分辨率
PERCLOS频繁超阈值眨眼被统计为闭眼增加眨眼过滤,闭眼连续帧数至少2帧才算
报警音反复尖叫缺少状态去重加入状态跳变沿检测,只在正常转疲劳时播放
打哈欠误报严重说话和大笑被误判增加持续时间阈值,持续2秒以上才算哈欠
GUI退出后摄像头仍亮未释放摄像头对象在CloseRequestFcn中执行clear cam和stop(timer)
编译为exe后无法运行依赖工具箱未打包使用MATLAB Compiler时勾选全部依赖,目标机器装MCR

5.6 答辩演示的临场技巧

最后说点代码之外的经验。答辩现场最容易翻车的就是环境问题,我曾经见过一个同学在教室投影仪上演示,因为投影幕布反光,人脸检测器死活识别不了,场面一度非常尴尬。他的解决办法是换了个自带屏幕的笔记本侧面对着评委,但画面又太小。后来我总结出一个稳妥的演示流程:

  • 提前到答辩教室踩点,测试摄像头在不同座位位置的光照表现;
  • 准备一张清晰的正面照片作为本地图片测试模式,万一现场人脸检测不行,可以切到“图片识别”模式演示算法流程;
  • 把键盘快捷键做好,比如按P切换暂停,按R重置疲劳状态,演示时不用满屏幕找鼠标。

这套流程我用了好几年,基本没在答辩现场出过状况。

6. 代码之外的一点经验

做了这么多图像处理项目,我最大的感受是:疲劳检测系统的难点从来不在某个单独算法上,而是如何把采集、检测、状态分析、提醒反馈这些环节拼成一个稳定运行的闭环。MATLAB的封装程度让这个闭环的搭建门槛低了很多,但也因为封装,很多人出了问题不知道底层在干什么,只能盲目试参数。

如果你在调参过程中发现某个环境始终无法达到预期效果,不妨退一步,把摄像头画面截图存下来,在命令行里单独调试算法步骤。这比反复跑整个GUI要高效得多。另外,强烈建议在每个功能函数里加数据记录功能,把每帧的眼睛比例、PERCLOS值写入一个日志文件,这样答辩时可以把记录曲线展示出来,证明系统在持续稳定工作,也能事后复盘哪里阈值不合适。

这个项目后续可以扩展的方向也很多,比如用深度学习的关键点检测替换级联分类器,或者接入模拟驾驶器做更贴近真实场景的疲劳预警。但无论怎么扩展,底层这套“采集-检测-特征-判定-反馈”的框架都不会变,把它吃透了,你就掌握了所有实时状态监测类项目的骨架。

祝你的毕设答辩顺利,运行界面一次点亮。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询