
简介这是一份基于Python的人脸识别与专注度检测完整源码面向AI入门开发者与需要做考勤或课堂专注分析的工程人员围绕人脸考勤打卡和注意力状态判断两个核心场景设计。包内共161个文件约437.34MB主要包含22个py源码、93个png图片素材、5个dat与3个pt模型权重、xml配置文件以及可直接运行的exe程序覆盖人脸检测、特征提取、比对、关键点定位到GUI展示的完整链路。已有662人学习下载。借助dlib、face_recognition、OpenCV等库源码实现了基于眼睛开合度、眨眼频率与面部表情的专注度判别并配套考勤记录与通知触发逻辑人脸比对包含特征向量提取与相似度计算考勤结果可对接数据库存储适合二次开发。适合希望从实际项目入手系统掌握人脸识别应用开发、模型调用和Python工程化写法的人群。1. 为什么“python人脸识别与专注度检测源码”值得自己做一套我接过一个辅导机构的活儿他们要统计录播课里每个学生每分钟的走神次数。买来的人脸识别门禁机只能回答“谁来了”答不了“他有没有在看屏幕”教学软件自带专注度检测又没法事后批量跑一段MP4。最后绕回Python自己搭才算把这个问题落地。所谓“python人脸识别与专注度检测源码”简单说就是一条流水线人脸检测把人脸从画面里框出来关键点定位找到眼睛和嘴巴的位置专注度检测再把“闭眼、哈欠、低头”这些状态换算成可解释的分数。这篇文章给想跑通这套源码的人看包括做网课助教系统的、做自习室摄像头分析的以及拿它当毕业设计起步的。你不需要先懂深度学习但要愿意跟着一行行把参数调明白。2. 人脸检测与关键点先把每帧的人脸和68点跑稳定专注度检测里能用到的指标几乎都来自眼睛和嘴巴周围的关键点所以整个工程的第一个门槛不是“识别出这是谁”而是“每帧能不能稳定拿到人脸框和关键点”。这里有个常见误区一看到“人脸识别”就想到ArcFace那种身份特征比对。ArcFace解决的是“谁是谁”专注度检测要回答的是“眼睛有没有闭、头有没有低”两条链路完全不同。跑通这套源码实际需要的只有两件事一个能框出人脸位置的检测器一个能输出眼部、嘴部坐标的关键点模型。2.1 人脸检测的选型参数用Haar Cascade跑通什么时候换YOLO常见做法是用OpenCV内置的Haar Cascade做第一版。它的原理是Haar-like特征加AdaBoost级联分类器滑动窗口扫过图像每一级弱分类器快速排除明显不是脸的区域越到后面越精确。在我的机器上单人固定机位的画面Haar Cascade单帧耗时在5到15毫秒CPU就能扛住这对一整套专注度检测工程来说非常友好。有些场景下漏检太多再换MTCNN或YOLO v8。像“学生专注度检测yolo v8”这类方案里YOLO的价值是一次检测把头部、眼睛、嘴巴都框出来多人前后遮挡时比Haar稳。但它是目标检测框架依赖更重第一版没必要直接上。先看一段最小检测代码import cv2 cascade_path haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(cascade_path) cap cv2.VideoCapture(lesson.mp4) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors4, minSize(60, 60) ) for (x, y, w, h) in faces: cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2)逻辑说明每帧转灰度后调用detectMultiScale返回的是若干矩形框。灰度化会丢掉颜色信息但检测器训练时用的就是灰度特征这一步能减少一半计算量。画框只是让你直观看到检测结果真正落到源码里这个矩形框下一步要交给关键点模型使用。参数说明scaleFactor是每轮检测对图像缩小的比例1.1意味着每次只缩小10%窗口扫得多、召回率高、速度慢调到1.3会快不少但小脸容易漏。minNeighbors控制一个区域要被几个相邻窗口都判定为脸才算数默认3误检多就往4或5调调太高会把远处的脸丢掉。minSize设成60x60低于这个尺寸的候选框直接忽略能拦住不少背景噪点。这三个参数是这套源码里第一批要动手试的值别用默认值直接跑真实场景。什么时候换YOLO我一般按漏检率判断截几百帧带标注的现场截图如果Haar在人脸正对镜头时漏检超过15%就值得上YOLO。网上能翻到的免费源码大全很多但搬运来的工程普遍头重脚轻检测器换得勤评分逻辑却写得稀烂。真正落地前先用OpenCV自带的Haar把整条链路打通比盲目换模型重要。2.2 Dlib 68点关键点模型文件、最小跑通代码与帧率取舍检测器给的是矩形框专注度指标需要的是“眼睛在哪、嘴巴在哪”这一步靠Dlib的68点关键点模型。它用的是ERT级联回归多棵回归树逐级预测每一级都在修正上一级的残差最终输出68个2D坐标。下标约定要背下来36到41是右眼42到47是左眼48到67是嘴部30号点大致是鼻尖。后面算EAR和头部姿态全是拿这些下标取值。跑通的最小代码长这样import dlib import cv2 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) rects detector(gray, 0) # 0表示不放大图像 for rect in rects: shape predictor(gray, rect) for i in range(68): pt shape.part(i) cv2.circle(frame, (pt.x, pt.y), 1, (0, 0, 255), -1)逻辑说明dlib自己的检测器返回rectspredictor吃进去灰度图和一个人脸框吐出来的是shape对象。shape.part(i)取第i个关键点x和y就是像素坐标。注意这里用的是dlib的检测器不是前面的Haar两者可以互换实际工程里我常把Haar检测的rect转成dlib.rectangle再传给predictor效果差不多但dlib自带检测器在侧脸上更稳一些。参数说明detector的第二个参数是upsample_num_times0表示不放大原图速度最快设成1会对图像做2倍上采样小脸更容易被找到代价是每帧多花几十毫秒。我一般设0因为专注度检测的场景里人脸占比通常不小。模型文件建议固定在models目录下路径写死或写到配置文件里别让它在命令行参数里飘。已知的关键点模型文件是公开的如果加载时报尺寸错误多半是32位和64位版本混用了重下对应版本就好。帧率取舍方面CPU机器上Haar加Dlib关键点往往只能跑到8到10帧。我的处理是隔帧检测关键点第0帧检测第1帧沿用上一帧的坐标第2帧再检测。眼睛和嘴巴在相邻帧之间的位移很小沿用一帧不会让指标失真但能把速度提上去不少。这个“间隔检测”机制要写进源码后面评分才不会因为帧率忽高忽低而抖动。3. 从关键点算出专注度EAR、MAR与头部姿态有了68点接下来把这些坐标变成有业务含义的状态。我在工程里保留三个指标EAR判断闭眼MAR判断打哈欠头部姿态判断低头或偏头。三个指标各自独立计算最后落到同一个“当前帧状态”结构体里。先说明一点这些指标不是给深度学习模型用的特征它们本身就是带物理含义的几何量所以调参时能直接对应到人的实际动作不会是个黑匣子。3.1 EAR眼睛纵横比为什么这个指标能判断闭眼与眨眼EAR的全称是Eye Aspect Ratio中文常叫眼睛纵横比。它用眼睛周围6个关键点算一个比值EAR (|p2-p6| |p3-p5|) / (2 * |p1-p4|)p1到p6分别是外眼角、上眼睑两侧、下眼睑两侧、内眼角。睁眼时EAR大概在0.25到0.35闭眼时掉到0.1以下。好处是这是距离的比值对画面里人脸的大小不敏感人坐近坐远数值基本稳定所以不用一帧帧去标定像素距离。import numpy as np def eye_aspect_ratio(eye_pts): # eye_pts长度6顺序对应dlib的36-41或42-47 a np.linalg.norm(eye_pts[1] - eye_pts[5]) b np.linalg.norm(eye_pts[2] - eye_pts[4]) c np.linalg.norm(eye_pts[0] - eye_pts[3]) return (a b) / (2.0 * c)逻辑说明函数输入6个点按公式算出比值。左右眼分别调用一次取左右眼的平均值或较小值来判定。我一般取较小值因为单眼半闭的状态也是不专注平均会把它稀释掉。参数说明EAR阈值通常落在0.2到0.25之间但第一次跑别直接拍一个值。我习惯的做法是让被测者睁眼坐20秒算出EAR均值作为基线再取基线的0.65到0.7作为阈值。这个“个人基线校准”能解决大量个体差异问题戴眼镜和单眼皮的人基线差很多全局阈值会误杀。至于眨眼和闭眼的区别靠连续帧条件区分眨眼持续100到200毫秒闭眼持续时间更长。25帧率下连续3帧低于阈值才算一次闭眼眨眼会被过滤掉。帧率变了这个帧数要同步调整后面会专门说这个坑。3.2 哈欠与低头用MAR和solvePnP补全专注状态嘴巴状态用MAR和EAR同一个思路只是选点换成嘴部。dlib的48到67号点里取上嘴唇内轮廓和下嘴唇内轮廓的关键点算纵横比。MAR大于0.6并持续一段时间基本可以判断为打哈欠。有人会问怎么区分说话和大笑实际做的时候让哈欠的持续时间阈值长一点比如连续5帧说话时嘴巴开合是快速的达不到这个持续条件。头部姿态麻烦一些需要知道头在三维空间里转了多大角度。常见做法是PnP求解把68点里的6个关键点映射到一张标准3D人脸模型坐标上再由solvePnP算出旋转矩阵最终提取出pitch和yaw。pitch表示点头低头yaw表示左右转头。object_pts np.float32([ [0.0, 0.0, 0.0], # 鼻尖 30 [0.0, -3.3, -12.5], # 下巴 8 [-4.5, 4.5, -12.5], # 左眼外角 36 [4.5, 4.5, -12.5], # 右眼外角 45 [-3.0, -3.5, -12.5], # 左嘴角 48 [3.0, -3.5, -12.5] # 右嘴角 54 ]) img_pts np.float32([ shape_pt(30), shape_pt(8), shape_pt(36), shape_pt(45), shape_pt(48), shape_pt(54) ]) _, rvec, tvec cv2.solvePnP(object_pts, img_pts, camera_matrix, dist_coeffs) rmat, _ cv2.Rodrigues(rvec)逻辑说明object_pts是标准3D模型坐标img_pts是从当前帧68点里取出的对应2D点。solvePnP解出旋转向量rvec和平移向量tvec再用Rodrigues把旋转向量转成旋转矩阵。后面的欧拉角提取就是从旋转矩阵里拆出来的OpenCV里不同教程用的坐标系定义不完全一致所以正负号必须实测确认。参数说明camera_matrix是相机内参包含焦距和主点坐标。严格做法是用棋盘格标定偷懒做法是用图像宽度近似fx和fy主点取图像中心精度对专注度检测够用。dist_coeffs畸变系数可以给全零日常摄像头画面中央区域畸变很小。最坑的是角度符号同样的旋转矩阵有的实现里把pitch为负解释成低头有的解释成抬头。我的习惯是代码写完对着摄像头点一下头、偏一下头看输出正负对不对不对就乘负号。低头事件的判定阈值一般设在pitch绝对值大于15度持续若干帧后触发这个值建议按摄像头安装高度调俯拍和正拍差别很大。4. 专注度评分逻辑与源码模块结构从逐帧指标到分钟级分数逐帧的EAR和pitch对业务没用你要给别人看的是一分钟里走了几次神、专注度打多少分。所以源码里要有一个专门的评分模块把帧级指标聚合成窗口级事件再由窗口级事件加权成分数。这一章解决两件事评分逻辑怎么设计源码工程怎么拆分能让后面扩展不掉链子。4.1 事件计数与滑动窗口把帧级状态变成可用的评分我在工程里的做法是先把连续帧状态翻译成离散事件。每当前一帧不在闭眼、当前帧闭眼且闭眼持续条件满足就记一次“闭眼事件”哈欠事件同理。低头事件则是统计一个窗口内低头帧占的比例低于阈值就说明这半分钟姿势基本正常。评分按滑动窗口计算窗口长度10秒步进5秒。窗口内统计闭眼次数、哈欠次数、低头时长比例。一个可用的示例评分公式score 100 - 8 * eye_count - 5 * yawn_count - 30 * pitch_ratio参数说明8和5是闭眼、哈欠的事件惩罚权重pitch_ratio是低头帧数占窗口总帧数的比例。这个公式不是普适的权重取决于你对“什么行为最影响专注”的判断。我一般先拿一段人工标注过的视频做回归看哪些事件和标注的走神重合度高再调权重大小。score下限截到0上限到100。注意事件只在满足条件那一刻计数一次不能在窗口评分时再去翻原始EAR重新判断否则同一个3秒的闭眼会被两个窗口重复计两次。之所以要用滑动窗口而不是整分钟硬切是因为学生可能在第59秒闭眼、第61秒睁眼硬切会把一次闭眼拆成两个窗口各记一半数据很难看。窗口步进小于窗口长度就是为了让相邻窗口有重叠事件归属相对稳定。4.2 源码模块划分与主循环按照这个结构组装不会散整套源码我建议拆成五个模块不要把所有逻辑写在一个脚本里。capture负责读摄像头或视频文件face负责检测和关键点attention负责算EAR、MAR、头部姿态并产生事件scorer负责窗口评分recorder负责把结果写到CSV或SQLite。模块之间不要互相调内部函数统一走一个事件结构体。主循环的简化代码config { ear_threshold: 0.22, ear_min_frames: 3, window_frames: 600, # 10秒窗口按帧数算 weights: {eye: 8, yawn: 5, pitch: 30}, } frame_queue deque(maxlenconfig[window_frames]) while True: ok, frame cap.read() if not ok: break keypoints detect_keypoints(frame, interval2) event attention_engine(keypoints, config) frame_queue.append(event) if len(frame_queue) config[window_frames]: score calc_score(frame_queue, config) recorder.write(timestamp, event, score)逻辑说明frame_queue是固定长度的双端队列满了就自动挤出最旧的一帧天然实现滑动窗口。detect_keypoints内部做了隔帧检测interval2表示有两帧沿用旧坐标。attention_engine返回当前帧的事件状态包括是否正在闭眼、是否触发了一次新事件。calc_score在窗口满了之后运行。参数说明window_frames我用帧数而不是秒数因为deque不知道帧率。30FPS下600帧正好10秒换了一个15FPS的摄像头600帧就是40秒评分窗口完全不对。所以配置里要有一个fps字段窗口长度写成10秒初始化时换算成帧数。weights三个值决定三类事件对分数的贡献建议第一次跑先全部设低看数据再调别上来就把低头惩罚设成50头稍微一低分数就崩了。5. 实测避坑漏检、误判与参数玄学的五个问题跑这套源码真正花时间的是调参和修边界问题下面五条是我在类似工程里遇到过的典型问题。每一条都按“现象、原因、解决”来写你看自己的日志对号入座就行。5.1 侧脸切换时人脸框闪烁与关键点跳动现象被测者头转向侧面时人脸框一路闪烁关键点在正确位置和乱飘之间切换EAR值跟着剧烈跳动。原因Haar和dlib的检测头对大幅度侧脸都很脆弱偏转超过30度后召回率明显下降导致一帧有框、一帧没框。关键点模型在接到的框本身不准时输出的坐标自然不会是平滑的。解决做一个“最近有效框”缓存。维持一个变量记录最后一次检测到的人脸框后续帧检测不到时沿用旧框并让它带一点小范围平移衰减连续丢失超过15帧再彻底丢脸。这个机制能消除大部分闪烁。同时把低头事件的角度阈值放宽到20度以上避免侧脸被误判成低头。侧脸问题没法完全消除但把“丢帧”的影响降到业务不可见就够了。5.2 戴眼镜的人被一直判定为闭眼现象被测者眼睛睁着EAR却长期低于阈值整个视频里闭眼事件刷屏。原因镜片反光和镜框阴影会干扰眼睑关键点的定位上眼睑点被吸到镜框边缘让上下距离被压缩。全局阈值0.22对这种个体直接失效。解决把阈值改成“开机十秒基线校准”。启动后在屏幕上提示被测者自然睁眼采集十秒内EAR的最大值作为睁眼基线闭眼判定阈值取基线乘0.65。这段校准窗口要写进配置并允许手动输入阈值覆盖。戴眼镜场景下还可以在预处理时做一次直方图均衡化能减轻镜框阴影的影响。这是血泪经验不校准的话误报率高到没法跟客户解释。5.3 低帧率摄像头把眨眼当成闭眼现象运行环境从30FPS的USB摄像头换到15FPS的IPC流之后眨眼频繁被计入闭眼事件。原因判定闭眼用的是“连续N帧EAR低于阈值”的帧数条件眨眼持续约150毫秒30FPS下约4帧15FPS下只有2帧帧率变了但N没变眨眼就满足闭眼条件了。解决把帧数条件改成时间条件。闭眼判定的持续时间阈值取120毫秒判定时换算成帧数min_frames max(2, int(fps * 0.12))。这样30FPS下是3帧15FPS下是2帧眨眼在两个帧率下都不会被误判为闭眼。同理哈欠的持续条件也要做成时间制。5.4 dlib模型加载失败却静默运行现象程序跑起来不报错但人脸框能画出来关键点全是零或者画出来所有点都挤在画面左上角。原因shape_predictor_68_face_landmarks.dat路径写错了predictor没构造成功异常在初始化阶段被吞掉或者根本没被检查。解决加载模型后立刻断言shape.num_parts 68打印模型文件名。工程目录里固定一个models文件夹模型文件路径只在配置里出现一次不要到处写相对路径。这类问题本质上是个黑匣子把加载成功的日志输出出来几秒钟就能定位藏起来的代价是后面所有关键点指标全部不可信。5.5 光线突变让整条专注度曲线失真现象有人走过摄像头前或者突然拉窗帘这段时间的评分直接掉到底人走开后恢复正常。原因自动曝光在适应新亮度时会有几百毫秒到一秒的调整期期间图像过亮或过暗检测器大量丢帧。丢帧期间没有闭眼事件代码也没有惩罚按理说分数不该掉但很多人把“没人脸”直接计成了低分或者关键点乱跳触发了一堆闭眼事件。解决计算每帧灰度均值与上一帧的差值超过15时认为光线突变直接跳过当前帧不计入窗口也不产生任何事件。另外不要把“没检测到人脸”直接当成专注度0正确做法是记录一个face_lost状态让评分逻辑把它当作中性帧。专注度曲线里出现一段没有数据的间隙总比一段错误的低分好。6. 进阶把专注度时序落盘并用重叠标注视频验证参数源码跑通后最值得做的一件事就是把评分结果和原始画面对应起来否则你根本无法判断阈值是否合理。我一般会把每一帧的EAR值、头部姿态角度、事件类型用putText画到视频帧上同时把窗口评分写进CSV每行一条时间戳、闭眼次数、哈欠次数、低头比例、最终分数。CSV比SQLite更适合这种一次跑一批视频的离线场景excel打开就能看趋势。验证方法很简单把标注了状态和分数的视频导出一份MP4找一段包含典型行为的片段人工数一下真实闭眼次数和低头次数再和CSV里的统计对一遍。误差超过20%就回头调阈值或基线校准逻辑。阈值是标定出来的不是靠感觉调的这个认知能省掉大量“参数玄学”的折腾。我自己早期把EAR阈值硬编码成0.25戴眼镜的测试者一上来误报率高到不可用后来改成开机十秒基线校准才压住问题。做这套东西最值钱的不是那个模型文件而是你愿意把每一次误判现场当作调参样本并把修正手段写进配置。希望帮到你。本文还有配套的精品资源点击获取