简介:这份源码包面向Python开发者与计算机视觉初学者,提供一套可运行的人脸识别与专注度检测完整方案,可用于人脸考勤打卡、课堂或办公场景的注意力分析等实践。包内共161个文件,以93个png与12个jpg图像样本、22个py脚本、9个xml配置、5个dat模型文件及3个pt权重为主,另含少量exe可执行程序与csv数据表,压缩包约437.34MB,涵盖从人脸检测、关键点定位到特征比对的全流程代码与模型资源。已有662人学习下载,适合希望动手复现人脸识别项目、理解dlib与OpenCV配合方式的读者参考。源码涉及人脸检测、68点关键点、专注度判断、视频流处理、人脸比对与考勤记录、GUI界面及数据库存储等模块,可帮助读者掌握特征向量计算、相似度度量与实时分析思路,并借助现成模型文件快速搭建自己的检测环境。
1. 从一张摄像头截图说起:这套 Python 人脸识别与专注度检测源码到底能跑出什么
很多人第一次接触「专注度检测」,脑子里浮现的是脑电波头环或者眼动仪,动辄几千上万。但如果你手里只有一台普通 USB 摄像头,其实也能做出一个可用的版本——这正是这套 Python 人脸识别与专注度检测源码要解决的问题。它把「人脸检测 + 关键点定位 + 专注度打分」串成一条完整链路,输入是摄像头画面或一段视频,输出是每帧的人脸框、眼睛/头部姿态,以及一个 0 到 1 之间的专注度分数。
这套源码适合三类人:一是做课程设计或毕设的学生,需要一套能跑通、能改参数的完整流程;二是想入门计算机视觉的 Python 开发者,拿它当第一个「有业务含义」的练手项目;三是做在线教育、驾驶监控、工位状态统计的从业者,想先验证算法可行性再决定要不要上硬件。它不依赖专用设备,核心依赖是 OpenCV、dlib 或 MediaPipe 这类常见库,装好 Python 环境就能跑。
需要先说明边界:这套源码做的是「基于视觉的专注度估计」,不是医学级注意力测量。它判断的是「你有没有看屏幕、头有没有偏、眼睛有没有长时间闭合」,而不是你大脑里在想什么。理解这一点,后面调参和看结果时才不会跑偏。
2. 拆开看算法链路:人脸检测、关键点与专注度打分怎么串起来
2.1 为什么选 dlib + OpenCV 这条经典组合
这套源码的技术栈并不花哨,主干是 OpenCV 负责视频读取和图像预处理,dlib 负责人脸检测和 68 点关键点定位。选这条组合的理由很实际:dlib 的 68 点模型(shape_predictor_68_face_landmarks.dat)是公开且稳定的,关键点编号有明确语义,比如 36–41 是左眼、42–47 是右眼、30 是鼻尖、8 是下巴。做专注度判断时,你需要的正是这些语义明确的点。
相比之下,MediaPipe 更快、更轻,但它的关键点编号体系和 dlib 不同,网上大量现成的专注度计算代码是按 dlib 的 68 点写的。如果你拿到的源码里出现shape[36]、shape[45]这种下标,基本可以确定是 dlib 体系。常见做法是先用 dlib 跑通逻辑,等要上嵌入式或追求帧率时,再考虑换 MediaPipe 或轻量模型。
提示:dlib 的 68 点模型文件需要单独下载,源码包里通常会附带,如果没有,要确认版本匹配,否则
shape_predictor初始化会直接报错。
2.2 从关键点算出三个核心指标
专注度不是一个直接测量值,而是由几个可观测指标加权得到的。这套源码里通常会出现三个量:
第一个是眼睛纵横比 EAR(Eye Aspect Ratio)。原理是:眼睛睁开时,上下眼睑关键点的垂直距离较大;闭眼时这个距离变小。用公式表示,对左眼取 36–41 六个点,EAR = (|p38-p42| + |p39-p41|) / (2 * |p37-p40|)。当 EAR 低于某个阈值并持续若干帧,就判定为闭眼或眨眼。
第二个是头部姿态。用鼻尖、下巴、左右眼角这几个点,结合 solvePnP 可以估算头部的俯仰、偏航、滚转三个角度。头低得太厉害,通常意味着在看手机或走神。
第三个是视线方向或面部朝向的粗略估计。有些实现会用瞳孔位置,有些直接用头部偏航角代替。
下面是一段典型的 EAR 计算代码,你可以对照自己手里的源码看结构是否一致:
import numpy as np def eye_aspect_ratio(eye_points): # eye_points 是 6 个 (x, y) 坐标,顺序为左角、上左、上右、右角、下右、下左 # 垂直距离:上眼睑两点与下眼睑两点的距离 vertical_1 = np.linalg.norm(eye_points[1] - eye_points[5]) vertical_2 = np.linalg.norm(eye_points[2] - eye_points[4]) # 水平距离:左右眼角距离 horizontal = np.linalg.norm(eye_points[0] - eye_points[3]) # 加 1e-6 防止除零 ear = (vertical_1 + vertical_2) / (2.0 * horizontal + 1e-6) return ear逻辑说明:eye_points是从 dlib 68 点里切出来的 6 个点,顺序必须和公式对应,顺序错了 EAR 会完全失真。参数说明:分母加1e-6是防止人脸侧转时水平距离趋近于零导致除零崩溃,这是血泪经验,不加的话某些帧会直接抛异常。
2.3 专注度分数的加权与平滑
拿到 EAR、头部角度之后,源码一般会做归一化和加权。常见做法是:EAR 低于阈值扣分,头部偏航超过 ±20 度扣分,俯仰超过 ±15 度扣分,最后映射到 0–1。但直接逐帧算出来的分数会剧烈跳动,所以必须做滑动窗口平滑,比如取最近 30 帧的均值。
from collections import deque class FocusScorer: def __init__(self, window=30, ear_thresh=0.21): self.window = window self.ear_thresh = ear_thresh self.history = deque(maxlen=window) def score(self, ear, yaw, pitch): # 眼睛分数:EAR 高于阈值给满分,低于阈值线性衰减 eye_score = min(1.0, ear / self.ear_thresh) # 头部分数:偏航和俯仰偏离越大扣分越多 head_score = max(0.0, 1.0 - (abs(yaw) / 45.0 + abs(pitch) / 30.0)) # 加权,眼睛权重更高 raw = 0.6 * eye_score + 0.4 * head_score self.history.append(raw) return sum(self.history) / len(self.history)逻辑说明:deque(maxlen=window)自动维护固定长度队列,不用手动裁剪。参数说明:ear_thresh默认 0.21 是经验值,但不同人眼型差异大,戴眼镜的人 EAR 普遍偏低,这个值要按实际画面调。yaw和pitch的单位是度,除以 45 和 30 是把角度归一化到 0–1 区间。
3. 把源码跑起来:环境配置、模型加载与实时检测
3.1 Python 环境与依赖安装的稳妥顺序
这套源码对 Python 版本不算挑剔,3.8 到 3.11 都能跑,但 dlib 的安装是第一个坎。Windows 上直接pip install dlib经常因为缺少 CMake 和 Visual Studio 编译环境而失败。稳妥顺序是:先装 CMake,再装 dlib,最后装 OpenCV。
# 建议在虚拟环境里操作,避免污染全局 python -m venv focus_env # Windows 激活 focus_env\Scripts\activate # macOS / Linux 激活 source focus_env/bin/activate # 先升级 pip,老版本 pip 解析依赖容易出错 pip install --upgrade pip # 安装 CMake,dlib 编译需要 pip install cmake # 安装 dlib,如果这里卡住,见 3.2 的替代方案 pip install dlib # 安装 OpenCV 和数值计算库 pip install opencv-python numpy逻辑说明:把 cmake 单独拎出来先装,是因为 dlib 的 setup 脚本会在编译阶段调用它,混在一起装时 pip 的构建隔离有时会让 cmake 不可见。参数说明:opencv-python是包含 GUI 功能的版本,如果你在无桌面的服务器上跑,换成opencv-python-headless可以省掉一堆图形库依赖。
3.2 dlib 装不上时的两条退路
如果pip install dlib反复失败,不要死磕。第一条退路是用 conda:conda install -c conda-forge dlib,conda 有预编译包,省去本地编译。第二条退路是改用 MediaPipe,它的安装几乎不会失败,代价是关键点编号要重新映射。
# MediaPipe 替代方案的关键点映射示例 import mediapipe as mp mp_face_mesh = mp.solutions.face_mesh # MediaPipe 左眼上下点索引,和 dlib 不同 LEFT_EYE_TOP = 159 LEFT_EYE_BOTTOM = 145 LEFT_EYE_LEFT = 33 LEFT_EYE_RIGHT = 133 def ear_mediapipe(landmarks, w, h): top = np.array([landmarks[LEFT_EYE_TOP].x * w, landmarks[LEFT_EYE_TOP].y * h]) bottom = np.array([landmarks[LEFT_EYE_BOTTOM].x * w, landmarks[LEFT_EYE_BOTTOM].y * h]) left = np.array([landmarks[LEFT_EYE_LEFT].x * w, landmarks[LEFT_EYE_LEFT].y * h]) right = np.array([landmarks[LEFT_EYE_RIGHT].x * w, landmarks[LEFT_EYE_RIGHT].y * h]) return np.linalg.norm(top - bottom) / (np.linalg.norm(left - right) + 1e-6)逻辑说明:MediaPipe 返回的是归一化坐标,要乘以画面宽高还原成像素坐标。参数说明:索引 159、145、33、133 是 MediaPipe Face Mesh 里左眼对应的点,右眼要换成另一组索引,具体查官方拓扑图。这套映射一旦写错,EAR 会算出离谱的值,表现为分数一直为 0 或一直为 1。
3.3 主循环:读帧、检测、打分、显示
把前面几块拼起来,主循环结构大致如下。这段代码是整套源码的心脏,理解它之后改任何参数都有方向。
import cv2 import dlib detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") scorer = FocusScorer(window=30, ear_thresh=0.21) cap = cv2.VideoCapture(0) # 0 表示默认摄像头 while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector(gray, 0) for face in faces: shape = predictor(gray, face) # 转成 numpy 方便计算 points = np.array([[p.x, p.y] for p in shape.parts()]) left_eye = points[36:42] right_eye = points[42:48] ear = (eye_aspect_ratio(left_eye) + eye_aspect_ratio(right_eye)) / 2.0 # 头部姿态这里省略 solvePnP 细节,返回 yaw 和 pitch yaw, pitch = estimate_head_pose(points, frame.shape) focus = scorer.score(ear, yaw, pitch) cv2.putText(frame, f"Focus: {focus:.2f}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow("Focus Detection", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:detector(gray, 0)的第二个参数是上采样次数,0 表示不放大,速度快但小脸可能漏检,改成 1 能提升召回率但帧率下降。参数说明:cv2.waitKey(1)里的 1 是毫秒,值越小循环越快,但太小在某些系统上窗口不响应,一般 1 到 30 之间。ord('q')是退出键,调试时很实用。
4. 避坑与排查:跑这套源码最容易翻车的五个地方
4.1 摄像头打开成功但画面全黑
现象:cap.read()返回 True,但frame全是零,imshow出来一片黑。原因:摄像头被其他程序占用,或者索引不对。有些笔记本内置摄像头是 0,外接 USB 摄像头是 1,但顺序不固定。解决:把VideoCapture(0)依次改成 1、2 试,或者用cap.isOpened()先判断。另外,某些系统上首次调用摄像头有权限弹窗,没点允许就会拿到黑帧。
4.2 关键点模型加载报错
现象:运行到dlib.shape_predictor(...)时抛RuntimeError: Unable to open shape_predictor_68_face_landmarks.dat。原因:模型文件路径不对,或者文件根本没下载。解决:确认文件在当前工作目录,或者写绝对路径。注意这个模型文件有几十 MB,如果源码包里的是几 KB 的文件,那是下载失败的占位文件,要重新获取。
4.3 专注度分数一直为 0 或一直为 1
现象:不管怎么动,分数不动。原因:EAR 阈值和实际画面不匹配,或者关键点索引取错。解决:先把 EAR 实时打印出来,正常睁眼时应该在 0.25 到 0.35 之间,闭眼时降到 0.15 以下。如果打印出来是 0.01 或 5.0 这种离谱值,就是索引错了。戴眼镜的人 EAR 整体偏低,阈值要从 0.21 往下调到 0.18 左右。
4.4 帧率低到个位数
现象:画面卡顿,分数更新很慢。原因:dlib 的 HOG 检测器在每帧全图检测,分辨率越高越慢。解决:把画面缩到 640×480 再检测,或者改成每 3 帧检测一次人脸,中间帧复用上一次的人脸框。这是常见做法,能显著提升流畅度,代价是人脸快速移动时框会滞后。
4.5 多人画面下分数串台
现象:画面里有两个人,分数在两个人之间乱跳。原因:源码默认只处理faces列表里的第一个或最后一个,没有按人绑定状态。解决:给每个人脸分配一个 ID(可以用简单的 IOU 跟踪),每个 ID 维护独立的FocusScorer实例。如果只是单人场景,可以在检测到多张脸时直接跳过或只取面积最大的那张。
5. 进阶玩法:把专注度分数变成可用的数据
跑通实时显示只是第一步,真正有价值的是把分数沉淀成数据。我一般会加一个 CSV 记录模块,每秒钟写一行,包含时间戳、人脸数、平均专注度。这样一段 45 分钟的网课下来,就能画出专注度曲线,看出哪个时间段走神最多。
import csv import time class FocusLogger: def __init__(self, path="focus_log.csv"): self.file = open(path, "w", newline="") self.writer = csv.writer(self.file) self.writer.writerow(["timestamp", "face_count", "avg_focus"]) self.last_write = 0 def log(self, face_count, avg_focus): now = time.time() # 每秒最多写一次,避免文件爆炸 if now - self.last_write >= 1.0: self.writer.writerow([f"{now:.2f}", face_count, f"{avg_focus:.3f}"]) self.file.flush() self.last_write = now逻辑说明:flush()保证程序被强制中断时数据不丢,这是后悔药级别的细节。参数说明:1.0是写入间隔秒数,做长时统计够用,要做精细分析可以降到 0.2。
另一个进阶方向是阈值自适应。固定阈值在不同光照、不同人之间表现差异很大,可以在程序启动时先采集 5 秒「正常注视」的 EAR 均值,以此为基准动态设定阈值,比如取基准的 70%。这样换个人、换个房间,不用手动改代码。
验证方法上,建议做一次对照实验:让同一个人分别「正常看屏幕」和「低头看手机」各 30 秒,看两种状态下平均分是否有明显区分。如果区分度不到 0.2,说明权重或阈值需要调。我自己的习惯是,每次改完参数都强制走一遍这个对照,不然很容易被某一帧的偶然高分骗过去。希望这套源码和上面的调参思路,能帮你少走几个弯路。
本文还有配套的精品资源,点击获取