简介面向智慧课堂与深度学习应用开发人群资源为基于PyQt5与深度学习的线下课堂学生专注度分析系统完整工程适用于毕业设计、课程设计及入门进阶实践。压缩包共218个文件约17.02MB包含88个Python脚本、66个编译后的pyc文件以及Qt界面ui文件、图标图片、模型与文档等覆盖界面开发、模型推理、数据处理与项目说明等模块结构清晰便于按需查阅。已有150人学习下载适合计算机相关专业学生或开发者直接运行体验与二次开发。资源附有设计文档、模型下载与视频源配置示例代码功能验证稳定可快速启动并支撑专注度检测、课堂行为分析等场景既可作为毕设项目演示底座也能在此框架上扩展新功能使用价值较高。1. 为什么课堂专注度分析值得自己搭一遍线下课堂里老师最缺的不是讲课能力而是对课堂状态的实时感知。一个班四五十人靠肉眼巡场只能看到点头和低头看不清谁在走神、谁在趴桌、谁在频繁转头。这套基于 PyQt5 和深度学习的专注度分析系统用普通摄像头就能完成人脸检测、头部姿态估计和专注度评分输出每个学生的注意力曲线和统计报表。它最实用的地方不是替代老师而是把“凭感觉判断课堂氛围”变成一组可量化、可回放的数据。项目完整包含 Python 源码、设计文档、训练好的模型文件和一个 GPU NMS 加速模块适合做毕业设计、课程设计也适合想入门 PyQt5 桌面应用 深度学习推理部署的人。我从工程结构、专注度判据、部署排错到二次开发四个角度拆一遍把每一步的选型逻辑和踩过的坑都讲清楚。2. 工程骨架与数据源接入PyQt5 界面层的正确打开方式2.1 拿到压缩包后先看清目录结构解压后不要急着运行先把目录整理清楚。项目根目录下几个关键文件直接决定了系统能做什么video_sources.csv是数据源配置文件demo.gif是效果演示webcam.ico、scan.ico、qr-code-scan.ico是界面图标资源gpu_nms.hpp和nms_kernel.cu是 GPU 非极大值抑制的加速模块。如果文件散落在一层目录里建议按下面的结构重新组织smart-classroom/ ├── main.py # 程序入口启动 PyQt5 主窗口 ├── ui/ # Qt Designer 生成的界面文件与对应逻辑 ├── models/ # 模型权重、检测器配置 ├── utils/ │ ├── video_loader.py # 读取 video_sources.csv解析视频/摄像头来源 │ └── face_analyzer.py # 人脸检测 头部姿态估计 专注度评分 ├── gpu_nms/ # GPU NMS 加速模块 │ ├── gpu_nms.hpp │ └── nms_kernel.cu └── data/ # 运行日志、统计结果输出目录提示项目路径和项目名不要出现中文或空格PyQt5 加载资源文件和 CUDA 编译时对路径中的非 ASCII 字符非常敏感。解压后先重命名为英文路径再运行。main.py的入口逻辑通常是创建QApplication、初始化主窗口、连接摄像头或视频源、启动检测线程。核心线程模型是界面线程负责绘制和交互检测线程负责跑深度学习推理两个线程通过信号槽通信避免界面卡死。2.2 video_sources.csv 的接入逻辑多路数据源怎么驱动video_sources.csv是系统的数据源开关项目用它可以无缝切换摄像头、本地视频文件和图片序列这在答辩演示时非常有用。我拿到的版本字段结构大致如下type,source,label,enabled camera,0,教室前摄像头,1 video,./data/class1.mp4,课堂录像回放,1 video,./data/class2.mp4,第二节课录像,0 image,./data/snapshot.jpg,点名抓拍,1常见做法是用 Python 内置的csv模块读取这个文件然后根据type字段创建不同的数据源对象。对应代码如下import csv import cv2 def load_video_sources(csv_path: str): sources [] with open(csv_path, r, encodingutf-8-sig) as f: for row in csv.DictReader(f): if row[enabled] ! 1: continue src_type row[type].strip() src_val row[source].strip() if src_type camera: cap cv2.VideoCapture(int(src_val)) elif src_type in (video, image): cap cv2.VideoCapture(src_val) else: continue if not cap.isOpened(): # 失败时给出明确提示避免运行时黑屏 print(f[warn] source load failed: {src_val}) continue sources.append({ cap: cap, label: row[label].strip(), }) return sources逻辑说明type为camera时source是摄像头索引号0表示笔记本内置摄像头外接 USB 摄像头通常为1或2type为video时source是视频文件相对路径image类型则交给cv2.VideoCapture按单帧处理。enabled字段用于快速切换参与分析的数据源答辩时可以只保留需要演示的那一路。参数调整建议读取 CSV 时务必用utf-8-sig因为很多用户用 Excel 编辑过 CSV 后会加入 BOM 头直接utf-8解析会导致第一列字段名变成\ufefftype匹配不到数据。如果检测线程读取帧的速率高于摄像头帧率建议在循环里加cv2.waitKey(1)或按时间戳抽帧否则 CPU 会被空转耗尽。2.3 图标资源与界面初始化webcam.ico、scan.ico、qr-code-scan.ico分别对应摄像头画面、扫描检测和二维码入口的按钮图标。PyQt5 中正确加载资源的方式是使用QIcon类的绝对路径或资源文件。这里有一个新手高频坑直接写相对路径时如果工作目录不是项目根目录图标会加载失败。稳妥做法如下import os import sys from PyQt5.QtGui import QIcon def app_icon(name: str) - QIcon: base os.path.dirname(os.path.abspath(__file__)) path os.path.join(base, f{name}.ico) if os.path.exists(path): return QIcon(path) return QIcon() # 兜底空图标避免界面异常 if __name__ __main__: app.setWindowIcon(app_icon(webcam)) main_window.setWindowIcon(app_icon(scan))逻辑说明os.path.abspath(__file__)拿到当前文件所在目录再拼接图标名这样无论从哪个目录启动脚本都能正确定位。QIcon()空对象兜底是为了防止启动时因资源缺失抛异常。图标加载失败不会导致程序崩溃但窗口左上角会出现空白图标答辩观感不好建议提前确认。3. 专注度评分核心从人脸检测到头部姿态的量化逻辑3.1 为什么选“人脸 头部姿态”作为专注度判据课堂场景的视觉分析业界主流的切入点有三种视线估计、面部表情分类、头部姿态与身体朝向。视线估计需要高分辨率摄像头和精确标定普通教室摄像头很难满足表情分类受口罩、遮挡影响大且标注成本高头部姿态估计对摄像头角度不敏感且能直接反映“低头、转头、趴桌”等典型分心动作是课堂场景下性价比最高的方案。系统的检测流程分三层先做人脸检测再提取面部关键点最后用关键点计算头部欧拉角。欧拉角的物理含义是头部绕三个轴的旋转角度——yaw是水平转头角度pitch是低头抬头角度roll是左右歪头角度。课堂专注度的核心判据就是pitch 角持续下压视为低头yaw 角长时间偏离中心视为侧向分心。3.2 关键点检测与姿态解算的完整实现项目中模型推理部分基于深度学习框架完成核心逻辑类似下面的流程。以 OpenCV dlib 风格的关键点检测为例姿态解算的完整代码如下import cv2 import numpy as np def estimate_head_pose(landmarks, img_size): # 2D 人脸关键点dlib 68点模型中的关键索引 image_points np.array([ landmarks[30], # 鼻尖 landmarks[8], # 下巴 landmarks[36], # 左眼外角 landmarks[45], # 右眼外角 landmarks[48], # 嘴左角 landmarks[54], # 嘴右角 ], dtypenp.float64) # 3D 人脸标准模型点单位mm 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) # 嘴右角 ], dtypenp.float64) focal_length img_size[1] center (img_size[1] / 2, img_size[0] / 2) camera_matrix np.array([ [focal_length, 0, center[0]], [0, focal_length, center[1]], [0, 0, 1]], dtypenp.float64) dist_coeffs np.zeros((4, 1)) success, rotation_vector, translation_vector cv2.solvePnP( model_points, image_points, camera_matrix, dist_coeffs) if not success: return None rotation_matrix, _ cv2.Rodrigues(rotation_vector) # 从旋转矩阵提取欧拉角sy 用于判断万向锁边界 sy np.sqrt(rotation_matrix[0, 0] ** 2 rotation_matrix[1, 0] ** 2) if sy 1e-6: pitch np.arctan2(-rotation_matrix[2, 1], rotation_matrix[1, 1]) yaw np.arctan2(-rotation_matrix[1, 2], rotation_matrix[0, 0]) roll 0 else: pitch np.arctan2(rotation_matrix[2, 1], rotation_matrix[2, 2]) yaw np.arctan2(-rotation_matrix[2, 0], sy) roll np.arctan2(rotation_matrix[1, 0], rotation_matrix[0, 0]) return { yaw: np.degrees(yaw), pitch: np.degrees(pitch), roll: np.degrees(roll) }逻辑说明solvePnP负责求解 2D 关键点到 3D 标准模型的外参矩阵输出的旋转向量经过Rodrigues变换后得到旋转矩阵再反解出欧拉角。第 40 行附近的sy判断是标准的万向锁处理当俯仰角接近正负 90 度时 yaw 和 roll 退化不处理会出现角度跳变。参数调整建议focal_length取图像宽度适用于视场角约 60 度的普通摄像头如果用的是广角摄像头这个值需要按实际焦距换算否则角度误差会达到 10 度以上直接影响分心判定阈值。表格专注度判定阈值推荐行为特征参考角度范围建议判定阈值说明低头pitch -15°-20°阈值过小容易把正常看课本视为分心侧头abs(yaw) 20°25°结合持续时间过滤瞬时转头趴桌pitch -45° 且稳定 3 秒-45°属于严重分心权重加倍抬头听课pitch 在 -10° ~ 15°基准区间按时间加权累积3.3 专注度评分与状态平滑单帧的姿态角不能直接作为专注度结论因为学生会正常地看黑板、看书、短暂低头记笔记。常见做法是加入滑动窗口和状态机把连续帧归约为状态片段再对状态片段做加权评分。参考实现如下class FocusScorer: def __init__(self, window_size30, fps15): self.window_size window_size # 滑动窗口帧数 self.fps fps # 视频帧率 self.history [] # 保存最近 window_size 帧的状态 def feed_frame(self, is_focused: bool): self.history.append(1 if is_focused else 0) if len(self.history) self.window_size: self.history.pop(0) def get_score(self) - float: if len(self.history) self.window_size: return None # 数据不足先不评分避免冷启动偏差 focused_count sum(self.history) return (focused_count / len(self.history)) * 100逻辑说明:每个学生维护一个 30 帧的滑动窗口窗口内专注帧的占比即为当前专注度。fps15表示滑动窗口覆盖 2 秒内的行为2 秒内的瞬时低头不会导致分数骤降超过 2 秒的持续分心才会体现到分数上。参数调整建议窗口越长分数越平滑但对分心行为的响应越迟钝。课堂场景建议 30~60 帧实时演示想要更灵敏可以压到 15~20 帧。另外可以根据时间对分数做加权例如课堂前 5 分钟和临下课的 5 分钟学生分心概率高这两个时间段的权重可以调低 20%避免个别状态主导整体评分。4. 模型部署与运行排错PyQt5 环境下的常见坑4.1 模型文件下载与摆放位置压缩包内模型文件需要按固定目录摆放。常见做法是创建一个models目录把检测模型、关键点模型和分类模型分别存放。如果从网盘单独下载了模型文件解压后先和源码里的目录结构对照一遍。标准结构如下models/ ├── face_detector/ # 人脸检测模型 │ ├── det1.onnx │ ├── det2.onnx │ └── det3.onnx ├── landmark/ # 人脸关键点模型 │ └── landmark_model.onnx └── config.yaml # 模型路径、阈值、评分权重配置在config.yaml里集中管理参数而不是写死在代码中对跑通和二次开发都更友好detector: model_dir: ./models/face_detector conf_threshold: 0.7 # 低于此置信度的人脸框丢弃 nms_threshold: 0.5 # 非极大值抑制 IoU 阈值 landmark: model_path: ./models/landmark/landmark_model.onnx points_num: 68 # 关键点数量 scorer: pitch_threshold: -20 # 低头角度阈值 yaw_threshold: 25 # 转头角度阈值 window_size: 30 # 滑动窗口帧数逻辑说明conf_threshold控制人脸检测的灵敏度太高会漏检侧脸太低会出现大量误检框nms_threshold控制重叠框的合并力度。教室场景下学生人脸较小建议conf_threshold在 0.5~0.7 之间。4.2 PyQt5 环境安装与依赖冲突处理项目基于 PyQt5 和深度学习推理框架依赖安装顺序有讲究。先创建干净虚拟环境再按顺序安装避免 OpenCV 和 PyQt5 的 Qt 库互相覆盖python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install --upgrade pip pip install pyqt55.15.10 pip install opencv-python4.8.1.78 pip install numpy pandas pip install onnxruntime-gpu # 有 NVIDIA GPU 时安装否则装 onnxruntime注意PyQt5 5.15 系列在 Python 3.10 及以上版本有较稳定的 wheel 包但不要用pip install pyqt5直接装最新版5.15.11 之后官方社区维护节奏变化出现过与部分 OpenCV 版本冲突导致QImage格式转换异常的问题。CPU 环境就用onnxruntimeGPU 环境才用onnxruntime-gpu二者不能同时安装。4.3 OpenGL 导致 PyQt5 界面无显示的问题这个坑在 PyQt5 桌面应用里非常高频尤其是 Windows 远程桌面、虚拟机和无独显环境下。症状是程序能启动、控制台无报错、进程在运行但窗口不显示或显示为纯黑。原因是 PyQt5 的窗口渲染依赖 OpenGL 上下文而远程桌面和虚拟机默认 OpenGL 支持不完整。针对这个场景两种解法实测有效。第一种禁用 OpenGL 硬件加速强制走软件渲染import os os.environ[QT_OPENGL] software # 强制 Qt 使用软件 OpenGL 渲染 os.environ[QT_QUICK_BACKEND] software from PyQt5.QtWidgets import QApplication第二种在远程桌面场景下配置 Qt 图形接口import os os.environ[QT_QPA_PLATFORM] offscreen逻辑说明QT_OPENGLsoftware让 Qt 不调用系统 GPU 驱动改用自己的软件实现兼容性最好但渲染性能下降offscreen是无窗口渲染模式适合拿不到显示环境时做后台推理测试不适合实际演示。提示虚拟机里跑时如果这两种方式仍然黑屏先检查pip list里有没有pyqt5-plugins和pyqt5-qt5两个包某些精简版 PyQt5 安装会缺少平台插件。执行pip install pyqt5-plugins可修复 70% 以上的平台插件缺失问题。另一种黑屏场景是视频画面在QLabel中不刷新。Qlabel 显示视频帧的常见错误是直接在 GUI 线程里cap.read()后调用setPixmap导致界面事件循环被阻塞。正确写法是用QTimer定时拉帧或把cap.read()放到子线程中发出信号。帧率建议用QTimer控制在 15~20 fps 而不是全速读取CPU 占用会降低一半以上。5. GPU NMS 加速与检测结果实用化5.1 nms_kernel.cu 在项目里的角色多人课堂场景下一帧画面可能同时出现 40~60 张人脸人脸检测模型会输出大量重叠候选框。非极大值抑制NMS用于从重叠框中选出最优解但 Python 版本的 NMS 循环在几十个框时还能应付超过 100 个框就开始拖慢帧率。项目里的nms_kernel.cu就是用 CUDA 并行计算 IoU 和抑制逻辑把 NMS 从 CPU 循环迁移到 GPU 线程块上执行。// nms_kernel.cu 核心结构示意每个线程块处理一个候选框与全部框的 IoU __global__ void nms_kernel(const float* boxes, int box_count, float threshold) { int idx blockIdx.x * blockDim.x threadIdx.x; if (idx box_count) return; float x1 boxes[idx * 4 0]; float y1 boxes[idx * 4 1]; float x2 boxes[idx * 4 2]; float y2 boxes[idx * 4 3]; for (int j 0; j box_count; j) { float ix1 fmaxf(x1, boxes[j * 4 0]); float iy1 fmaxf(y1, boxes[j * 4 1]); float ix2 fminf(x2, boxes[j * 4 2]); float iy2 fminf(y2, boxes[j * 4 3]); float iw fmaxf(0.0f, ix2 - ix1); float ih fmaxf(0.0f, iy2 - iy1); float inter iw * ih; // 后续在此处按置信度和 IoU 阈值标记抑制 } }逻辑说明CUDA 版 NMS 把每个候选框映射到一个线程所有 IoU 计算并行执行比起 Python 的嵌套 for 循环是数量级的提升。这个模块在 CPU 推理时不会被调用只有当检测器跑在 GPU 上且人脸框数量较大时收益才明显。纯 CPU 环境强行编译 CUDA 代码会直接报错需要加编译开关。5.2 检测结果落库与课堂报告生成专注度分析不能只停留在实时画面上还要产出可检索的记录。项目里可用 SQLite 保存每节课每个学生的专注度时间序列CREATE TABLE IF NOT EXISTS focus_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, -- 学生编号或人脸 ID class_id TEXT NOT NULL, -- 课程编号 timestamp INTEGER NOT NULL, -- Unix 时间戳单位秒 focus_score REAL NOT NULL, -- 该时刻专注度 0~100 attention_state TEXT NOT NULL -- focused / head_down / turn_away );写入逻辑使用参数化查询防注入批量插入用executemany而非逐条execute30 人课堂一节课产生约 10 万条记录批量插入能把写入时间从分钟级压到秒级import sqlite3 def save_batch(records): conn sqlite3.connect(classroom.db) sql (INSERT INTO focus_records (student_id, class_id, timestamp, focus_score, attention_state) VALUES (?, ?, ?, ?, ?)) conn.executemany(sql, records) conn.commit() conn.close()逻辑说明student_id是系统为每个检测到的人脸分配的临时 ID基于人脸特征向量聚类生成attention_state用文本标签而非数字便于直接生成报表时读取。这个表结构做好索引后可以支撑一学期全部课程的回放分析。5.3 从源码到可演示系统的进阶改造方向这套项目完成基础功能后至少有三个值得继续深入的改造方向。第一个方向是多摄像头级联。教室前摄像头拍全景侧面摄像头做补充检测结果通过时间戳对齐后合并到同一评分时段。此时需要处理的是各摄像头的人脸 ID 映射常见做法是用人脸特征向量做跨摄像头的 re-ID项目里的scan.ico图标对应的就是这条链路。第二个方向是专注度热力图可视化。把每个学生的专注度分数映射到座位表——先固定摄像头视角下的座位坐标然后按时间片生成热力图叠底到课堂画面上。PyQt5 的QPixmap支持直接绘制半透明色块不需要引入额外图表库。第三个方向是姿态角时钟频率分析。人在专注状态下头部姿势变化是有节奏的——低头看课本、抬头看黑板、低头做笔记这个节奏大约每 30~60 秒循环一次。在头部姿态序列上做 FFT 频谱分析如果频率成分集中在极低频段说明学生长时间处于静止需要区分是深度专注还是瞌睡如果频谱杂乱则可能是频繁东张西望。改造实现时预设 3 秒的角速度阈值区分“正常笔记动作”和“持续性低头”再把两类动作以不同颜色叠加显示在班级总览面板上能让系统从“课后看报表”升级为“课中可干预”。本文还有配套的精品资源点击获取
