
简介Python人脸识别与专注度检测源码是一套面向Python开发者与AI初学者的完整工程聚焦人脸考勤打卡与专注度实时分析两大场景覆盖人脸检测、关键点定位、特征比对、眨眼/表情识别及视频流处理等核心环节。资源共161个文件整体约437MB包含22个Python脚本、93张图片数据、5个模型文件如dlib人脸关键点模型与识别模型、9个XML配置以及可直接运行的exe程序目录结构清晰便于按模块学习和二次开发。已有662人学习下载。借助源码可系统掌握OpenCV、dlib、face_recognition等库的实战用法理解MTCNN或HOG关键点检测、CNN专注度分类、数据库考勤存储与GUI交互设计配套的模型和可执行文件也能帮助快速验证效果适合用于课程设计、毕业设计或企业考勤系统原型搭建。1. 人脸识别和专注度检测搅在一起远比想象中复杂一份标着“python人脸识别与专注度检测源码”的资源远不是把人脸框出来、打个名字那么简单。我拆这类项目时最在意的不是识别率数字而是三件事摄像头换了还能不能跑、模型文件缺不缺、所谓“专注度”到底是用什么规则算出来的。前两件事决定你能不能跑起来最后一件事决定输出结果你敢不敢当真。这套源码适合做网课状态分析、远程监考辅助、无人值守实验环境记录的人。它和普通的人脸识别Demo最大的区别是把“这个人是谁”和“这个人此刻是否在状态”放在同一条管道里算而这恰恰是最容易翻车的地方。2. 从检测框到专注度评分这套源码的技术栈与判定逻辑2.1 人脸检测的两级结构先框人再编码认人拿到这类源码第一步我会先看它用的是什么检测方案。人脸检测有很多条路OpenCV自带的Haar级联快但误检高Dlib的HOG在侧脸时很吃力OpenCV DNN或MediaPipe这类基于深度学习的方法在速度和精度之间更平衡。现在资源包里主流做法是两级串联第一级用轻量检测器把每一帧里的人脸框出来第二级把框内人脸裁切、对齐、缩放后送入特征编码模型生成一个128维或512维的向量再和库里预先注册的人脸向量做距离比对。# detector.py 的核心逻辑OpenCV DNN 方式的人脸检测 import cv2 import numpy as np class FaceDetector: def __init__(self, prototxt, caffemodel, conf_threshold0.7): self.net cv2.dnn.readNetFromCaffe(prototxt, caffemodel) self.conf_threshold conf_threshold def detect(self, frame): h, w frame.shape[:2] # 把帧缩放成300x300减均值后送入网络 blob cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0)) self.net.setInput(blob) detections self.net.forward() boxes [] for i in range(detections.shape[2]): conf detections[0, 0, i, 2] # 置信度 if conf self.conf_threshold: continue # 注意输出坐标是0~1的归一化比例 x1 int(detections[0, 0, i, 3] * w) y1 int(detections[0, 0, i, 4] * h) x2 int(detections[0, 0, i, 5] * w) y2 int(detections[0, 0, i, 6] * h) boxes.append((x1, y1, x2, y2, conf)) return boxesblobFromImage里的缩放尺寸和均值必须和模型训练时一致否则检测框会整体偏移或者漏检。conf_threshold默认0.7实际测试时如果现场遮挡多我会降到0.5但代价是误检变多。2.2 专注度判定不是靠表情而是靠三条几何线索专注度检测是这份资源的重头戏。我看到很多入门项目会用“笑脸识别”或“眨眼计数”来冒充专注度但那只是表情分类和注意力没有直接关系。正经做法是测三条几何线索眼睛开合程度、头部姿态角、视线方向。眼睛开合程度用EAR值计算闭眼时EAR明显下降累计一段时间内闭眼比例就能得到PERCLOS指标这是驾驶疲劳检测里验证过的有效参数。头部姿态角通过人脸关键点鼻尖、下巴、左右眼外角等和三维人脸模型做PnP求解得到pitch、yaw、roll三个角低头、侧头、歪头都能量化。视线方向需要更多关键点如果是基于MediaPipe Face Mesh还能把虹膜中心位置也算出来。# attention.py 中眼睛纵横比计算的典型写法 from scipy.spatial import distance as dist def eye_aspect_ratio(eye): # eye 是6个关键点坐标按顺序排列 # 计算两条纵向距离和一条横向距离 A dist.euclidean(eye[1], eye[5]) B dist.euclidean(eye[2], eye[4]) C dist.euclidean(eye[0], eye[3]) # EAR 在正常睁眼时约0.25~0.35闭眼时降到0.1以下 return (A B) / (2.0 * C)EAR的绝对值和人的脸型、摄像头距离都有关系不能拿一个固定阈值套所有人。这也是专注度检测最容易“失灵”的地方后面避坑章我会专门讲。2.3 检测、识别、状态判断之间的数据流整个系统的输入是视频流输出是标签加专注度分数中间数据流大致是抓帧 → 人脸检测 → 人脸对齐裁切 → 特征编码 → 库匹配 → 关键点提取 → EAR与姿态角计算 → 时间窗口聚合 → 输出分数。每一帧都做全流程会非常慢所以源码里通常会有采样间隔检测识别每5帧或10帧做一次专注度判定因为依赖连续帧会保持每帧都算或者用滑动窗口每30帧刷一次。原始帧 → 人脸检测器得到若干个 (x1, y1, x2, y2, conf) 框 → 人脸对齐根据关键点做仿射变换到112x112或160x160 → 编码模型得到 ndarray 形状 (1, 128) 的特征向量 → 匹配引擎与注册库里所有向量算余弦相似度或欧氏距离 → 状态评估对每个跟踪对象维护 EAR / 姿态角 的时间序列 → 聚合器每 N 帧输出一次 0~100 的专注度分数常见误区是让识别和专注度共用同一个窗口比如都每5帧跑一次。这样会让EAR曲线变得非常稀疏眨眼这种1帧内发生的动作很容易被漏掉导致PERCLOS算出来比实际闭眼时间小很多。我一般会把识别频率放到5帧一次专注度计算保持逐帧。3. 核心代码结构模块划分与主循环的串联方式3.1 目录结构与模块职责拿到源码包先看目录结构。有效的项目通常拆成四个独立模块检测模块、编码识别模块、专注度模块、结果输出模块。我最怕看到一个大文件把OpenCV、dlib、写日志全塞进去那种代码改一个参数要翻三屏。focus_tracker/ ├── main.py # 主入口组织摄像头与循环 ├── config.yaml # 模型路径、阈值、窗口参数集中管理 ├── requirements.txt # 依赖清单 ├── models/ │ ├── deploy.prototxt │ ├── res10_300x300_ssd_iter_140000.caffemodel │ ├── shape_predictor_68_face_landmarks.dat │ └── encoder_model.onnx ├── modules/ │ ├── detector.py # 人脸检测 │ ├── encoder.py # 特征提取 │ ├── matcher.py # 库匹配 │ ├── attention.py # EAR与头部姿态 │ └── recorder.py # 日志与输出 └── register.py # 注册新的人脸到特征库如果资源里没有config.yaml这种集中配置而是一堆散落的常量我会自己动手把阈值、路径、采样间隔抽到一个配置里。这个动作看起来不起眼但实际调参会省很多时间。3.2 主循环抓帧、检测、识别、状态判定四步串联主循环是整个程序的中枢。它的组织方式直接决定性能和排错难度。合理的流程是先做检测再做跟踪匹配再做状态判定最后统一绘制和输出。要注意的是检测、识别、状态判定不应该耦合在一个函数里否则你想单独关掉识别只测专注度时就得动主循环结构。# main.py 主循环的骨架写法 import cv2 from modules.detector import FaceDetector from modules.encoder import Encoder from modules.matcher import Matcher from modules.attention import AttentionMonitor cap cv2.VideoCapture(0) # 0 表示默认摄像头 detector FaceDetector(models/deploy.prototxt, models/res10_300x300_ssd_iter_140000.caffemodel) encoder Encoder(models/encoder_model.onnx) matcher Matcher(data/features.npy, names_pathdata/names.txt) monitor AttentionMonitor(window_size150) # 150帧约5秒 frame_idx 0 while cap.isOpened(): ret, frame cap.read() if not ret: break faces detector.detect(frame) for (x1, y1, x2, y2, conf) in faces: # 识别相对耗时做间隔采样 if frame_idx % 5 0: embedding encoder.encode(frame[y1:y2, x1:x2]) identity, score matcher.match(embedding) # 专注度逐帧分析 ear, angle monitor.analyze(frame[y1:y2, x1:x2]) focus monitor.update(ear, angle, identity) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{identity} focus{focus:.1f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) frame_idx 1 cv2.imshow(focus_tracker, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里frame_idx % 5是识别采样率我一般把它放在识别调用前面而不是整帧跳过。因为专注度分析需要每一帧的EAR值如果你整帧跳过了眨眼这种关键动作就丢了。monitor.update里的identity参数很关键它让专注度分数能按人分组而不是把所有检测到的人混在一起算。3.3 特征库、时间窗队列与身份跟踪的数据结构人脸识别的库匹配本质上是算向量距离。资源包里通常会有一个.npy或.pkl文件存已经注册的脸部特征对应一个names列表。匹配时用余弦相似度或欧氏距离设一个阈值决定接受还是拒绝。专注度模块内部维护一个滑动窗口队列典型实现是用deque(maxlenN)窗口长度决定PERCLOS统计的平滑程度。# attention.py 中滑动窗口队列的典型结构 from collections import deque import numpy as np class AttentionMonitor: def __init__(self, window_size150, ear_closed0.20): self.window_size window_size self.ear_closed_threshold ear_closed self.ear_history deque(maxlenwindow_size) self.pose_history deque(maxlenwindow_size) def update(self, ear, pitch, identity): self.ear_history.append((identity, ear)) self.pose_history.append((identity, pitch)) # 只统计当前身份对应的数据 ear_values [e for (ident, e) in self.ear_history if ident identity] if len(ear_values) 10: return 50.0 # 数据不足时给中值 closed_ratio sum(1 for e in ear_values if e self.ear_closed_threshold) / len(ear_values) focus_score max(0.0, 100.0 - closed_ratio * 300.0) return focus_score这里的乘数300.0是经验值含义是闭眼比例每增加1%分数扣3分。这个系数不同场景差别很大做题场景比看视频场景对低头和闭眼更敏感所以最好是参数化配置不要写死。4. 复现与调参实战环境准备、启动方式和四个关键参数4.1 环境准备依赖库与模型文件先把运行环境说清楚。这类源码最常用的依赖是OpenCV、NumPy、dlib、scikit-learn或SciPy。如果你是在Windows上跑dlib的安装容易踩坑需要先装CMake和Visual Studio Build Tools。我一般建议换conda装或者直接用dlib预编译的wheel包能省半小时。# 基于 conda 的安装方式Python 3.9 比较稳妥 conda create -n focus python3.9 -y conda activate focus pip install opencv-python numpy scipy scikit-learn conda install -c conda-forge dlib模型文件是另一个关键点。很多资源包只带代码不带模型因为模型动辄几十上百MB不好打包。拿到资源后先检查models目录人脸检测的caffemodel、关键点模型的dat文件、编码器的onnx或pb文件到底齐不齐。少了任何一个跑起来都会在import阶段报错或者运行到一半抛异常。缺模型的话可以去OpenCV官方仓库找res10检测模型dlib官网也提供了关键点模型的下载路径编码器可以找基于FaceNet或ArcFace结构的ONNX版本注意输入尺寸要和代码里的对齐参数一致。4.2 启动入口与配置方式参数显式传别靠改代码好的资源会有一个启动入口和配置文件参数集中管理。如果源码里是散落的常量我会先重构出config.yaml或argparse。为什么要这一步因为后面调参你会反复修改识别阈值、EAR阈值、窗口长度如果每次都要去代码里搜索替换很容易改漏。# config.yaml 典型配置项 camera_id: 0 frame_width: 1280 frame_height: 720 detector: prototxt: models/deploy.prototxt caffemodel: models/res10_300x300_ssd_iter_140000.caffemodel conf_threshold: 0.7 matcher: threshold: 0.45 use_cosine: true attention: window_size: 150 ear_closed_threshold: 0.20 pitch_low_threshold: -15 pitch_high_threshold: 15 focus_sample_rate: 1 recognition: sample_interval: 5启动命令就简单了python main.py --config config.yaml。main.py里用argparse把config路径传进去再内部解析yaml。这样换一台摄像头、换一个场景只需要改配置不用动代码。我强烈建议拿到任何源码先做这一步整理这是排错时的后悔药。4.3 四个影响结果的关键参数与调优方向第一个是检测置信度conf_threshold。这个值决定人脸框的严谨程度。场景光线好、人脸正对镜头时设0.7没问题如果是教室后排、距离远、有遮挡0.7会把很多真脸滤掉降到0.5~0.55才稳定。但降太低会把墙壁上的海报、水杯反光当成脸后面要靠候选框的宽高比和面积过滤兜底。第二个是EAR闭眼阈值ear_closed_threshold。这是专注度模块里最敏感的数值。我见过不少源码直接写死0.2但不同人的眼睛形状差异很大戴眼镜、画眼线、单眼皮都会让EAR基准值变化。我习惯先让程序采集10秒睁眼状态作为基线用基线均值减去0.08到0.1作为闭眼阈值。第三个是识别采样间隔sample_interval。这个参数决定多少帧做一次人脸特征匹配。间隔太小CPU占用高发热掉帧间隔太大人转头之后身份要等很久才刷新。中等性能的笔记本上5~10帧比较舒服。第四个是专注度窗口window_size。它决定PERCLOS统计的时间跨度。150帧按25fps算约6秒适合实时反馈如果你要统计一整节课的专注趋势窗口要拉到900帧以上但实时性会下降。核心矛盾在于窗口越长越平滑、越不敏感。我一般会分开两条输出一条短窗口用于实时告警一条长窗口用于课后分析。5. 避坑指南漏检、误判与掉帧的现场排查记录5.1 现象摄像头打不开或画面卡到只有几帧很多人拿到代码第一步就卡死VideoCapture(0)返回True但read出来是黑帧或者无限等待。原因多半是摄像头被其他软件占用了比如浏览器、会议软件、手机摄像头虚拟驱动也可能是因为OpenCV编译时没带FFmpeg后端导致MJPG格式解不出来。解决办法关掉所有占摄像头的程序把cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M,J,P,G))加在打开之后试试还是不行就换成VideoCapture(1)或特定设备ID。另外注意Windows下部分笔记本摄像头只支持640x480你强制设1280x720会直接黑屏。cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M, J, P, G)) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)5.2 现象光线一变人脸框开始乱飘甚至出现重影午后阳光、灯光频闪、屏幕反光都会让人脸检测出现抖动。这是我在课堂场景里踩得最多的坑。根因是检测模型对光照变化敏感置信度在阈值上下反复横跳导致同一张脸一帧有框一帧没框。解决思路是加鲁棒性处理而不是只调阈值。我会做三件事第一检测结果按面积过滤小于全画面2%的候选框直接丢第二用上一次有效框的位置作为先验当前帧在附近没有检测到就沿用上一帧的框连续几帧都检测不到才真正判为丢脸第三把置信度阈值降到0.5用面积过滤来兜底误检。5.3 现象人脸识别把A认成B换角度后又变成不认识这是人脸识别最常见的翻车方式。正脸时特征相似度很高侧脸30度以上就崩。根因是注册库里的底图大多只有一张正脸特征编码模型对姿态变化没有足够的泛化覆盖。解决方法是注册阶段多采集几个角度正面、左右各15度、抬头低头每个角度算一个向量同一人保存多个向量匹配时取最高分。还有一个细节匹配阈值不能拍脑袋定我在新环境里会先收集20~30张干扰样本非库内的人脸把相似度分布画出来再定阈值。这样至少能避免把“库外陌生人”误认成“库内熟人”。5.4 现象专注度分数徘徊在99或长期卡在40不动分数没有梯度变化说明专注度模块根本没有接收到有效信号。我排查的时候只看两个数据点EAR曲线是否在波动头部pitch角是否能跟踪到。根因通常是两个模块互相没有对齐。一种情况是EAR用的关键点和检测框不是同一套坐标系dlib的68点检测器要求输入的是灰度图且脸要占足够大你把小脸裁切直接丢进去关键点全在乱蹦。另一种情况是跟踪对象用检测框ID来关联而检测框每帧都在变ID不稳定时间窗口里混入了不同人的数据。解决先单独跑关键点可视化把68点画在帧上确认坐标确实贴在脸上再把attention的窗口数据单独dump出来看。我一般在模块之间加一个临时调试开关输出当前帧的EAR、pitch和归属身份。数据不对时一眼看到底。5.5 现象多人同框时分数串号人一走分数还挂在名字上多目标场景下最容易出现串号因为检测框是新的一帧重新算的不是跟踪器维护的。框与框之间没有稳定的ID两个人交叉走过之后身份就互换了。根因是很多源码只做了单帧检测没有做多目标跟踪所以身份无法跨帧关联。解决在检测器和编码器之间加一个轻量跟踪器最简单的办法是用IoU匹配上一帧和当前帧的框IoU超过0.5判定为同一个目标更稳的用卡尔曼滤波或ByteTrack。加上ID关联后专注度时间窗才能按人分开维护。注册时也要注意一个人注册多张底图时特征库里不能只存文件路径还要存对应的人名列表否则匹配结果会串。6. 把检测结果变成可用的数据日志导出、回调与交叉验证6.1 把专注度数据写成CSV按时间窗口聚合实时画面里的分数看一眼就没了真正有工程价值的是把它结构化记录下来。我会在recorder模块里做一个简单的CSV导出每5秒写一行包含时间戳、身份、平均分数、闭眼比例、低头比例。这个文件后期可以导入Excel或拿去画曲线用来对比不同时段的专注度变化。6.2 用回调函数把结果推给上级服务如果整个系统不是单机运行而是要给一个监控平台或教学平台供数据我会在main.py里留一个回调入口。回调函数收到事件字典包括身份、时间戳、专注度分数、异常状态然后由外部使用者决定怎么处理——写入数据库、发告警、还是存到消息队列。这样就把检测源码和业务系统解耦了。def on_focus_event(event: dict): # event 包含 timestamp, identity, focus_score, is_abnormal # 这里接数据库写入或者告警逻辑 pass monitor AttentionMonitor(window_size150) monitor.set_callback(on_focus_event)6.3 用头部姿态与专注度分数的相关性做验证拿到一段输出数据我建议做一次朴素验证来确认专注度判定没有变成黑匣子。把低头比例pitch小于阈值的帧占比和专注度分数画成时间序列对比你会发现两者高度负相关低头越多分数越低这是符合直觉的。如果数据在低头时分数依然很高说明姿态角计算方向反了或者坐标系标定有问题回去反转pitch正负号试试。从那以后我每次验收这类资源都会先做这步可视化不再看它给的现成指标。这套资源的价值不在于“跑出一个人脸框”而在于把抽象的“专注”变成了一条可记录、可统计、可交叉验证的数据流。你现在要做的就是把环境装好、模型配齐、参数按你的镜头重调一遍。希望帮到你。本文还有配套的精品资源点击获取