YOLOv5课堂违纪检测系统:从目标检测训练到PyQt界面落地
简介基于YOLOv5的学生课堂违纪检测系统课程设计资源包面向计算机视觉方向的学习者与项目开发者。资源聚焦课堂场景下学生睡觉、玩手机等违纪行为的自动识别任务提供从数据准备、模型训练到部署上线的完整方案无论是初学者理解模型原理还是进阶者复用工程代码都能从中获得有效支撑。压缩包共收录220个文件体积仅1.19MB主体为103个Python脚本并配有YAML配置、Shell脚本、Dockerfile、Jupyter Notebook和Markdown文档分别用于模型参数定义、环境自动化配置、容器化运行、实验演示与项目说明目录结构清晰便于按需查阅。该资源已有1939人学习在同类型课堂行为分析课设中具有较高参考价值。通过压缩包可获取可复现的项目源码、训练数据与配置文件深入理解YOLOv5在课堂场景下的数据标注格式、超参数调节、训练评估与结果可视化配合Docker部署脚本和开发文档可快速搭建环境并迁移到其他违纪行为或教学互动分析任务中是可直接改造落地的工程参考。1. 这门课设到底在做什么从检测到判定先看清两级方案一个人坐在教室里低头玩手机、趴桌睡觉摄像头把画面传回来yolov5 检测系统先把它“框”出来再由规则逻辑判断这是在听课还是在违纪——这就是基于 yolov5 的学生课堂违纪检测系统最朴素的工作流程。它把目标检测模型的“能看见”和业务规则的“能理解”拆成了两级模型只负责认人和认物体违纪与否交给时间序列和区域规则去裁决。课程设计版尤其吃透这个分工你不需要做一个完美的行为识别模型而是要用一份完整代码 数据跑通“视频进来→结果出去→界面展示记录落库”的闭环答辩时每一步都能讲清原理。这套方案的价值在于数据可用、代码可改、演示不翻车适合有 python 和一点深度学习基础的学生在 3 天内从零跑通。2. 训练一个能认人的 yolo 检测器数据集组织、训练命令与模型选型2.1 先想清楚检测目标只有“人”还是“人手机”课堂违纪检测的多数场景只需要识别一个类别人。手机、书本、水杯这些东西在画面里太小标注成本高检测器学起来也容易过拟合。常见做法是只标 person 一类后续判定违纪靠“人的姿态和位置 基于时间的规则”去推导。比如低头、趴桌、长时间停留在某个小区域不动这些都建立在 person 框和关键点之上不需要单独一个 phone 类别——这既省标注时间也让模型的泛化能力更稳因为手机的各种形态横屏、竖屏、被手挡住一半是训练数据很难收敛完整覆盖的。课程设计建议你干脆用 coco128 里自带的 person 类先跑通流程再换上自己录制的教室监控片段。如果你只有连续视频没有标注文件不要急着用 labelimg 一张张画框先用 1 秒钟采 1 帧的方式抽帧筛掉重复度高的画面因为教室里同学姿态高度相似重复标注只会让你多花一倍时间对精度提升却几乎没有帮助。2.2 按 yolo 格式重组自己的课堂数据数据目录按 yolov5 的标准 layout 组织train 与 val 的比例 9:1 足够用test 可以省掉val 直接观察 loss 曲线和 PR 曲线。每张 jpg 对应一个同名 txt行格式是class x_center y_center width height四点坐标要归一化到 0~1宽高用像素值除以图像宽高class 从 0 开始编号。写一个转换脚本一次性把 IAT 格式或随手记的坐标转过来import os def convert(size, box): 将 VOC 的 xyxy 坐标转换为 yolo 的 xywh 归一化坐标 dw 1.0 / size[0] dh 1.0 / size[1] x (box[0] box[1]) / 2.0 y (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return (x * dw, y * dh, w * dw, h * dh) img_w, img_h 1280, 720 box [100, 300, 50, 680] # x1, x2, y1, y2 yolo_box convert((img_w, img_h), box) print( .join([0] [f{v:.6f} for v in yolo_box]))这段代码最关键的是convert函数里坐标顺序先取 x 方向平均值计算中心点再算宽高千万不能把x2和y2当作右下角直接减x1得到的结果写反否则训练时 loss 能收敛但预测框全部偏移。实际转换后一定要随机抽 5 张图用 opencv 把归一化坐标乘回去画框叠加在原图上肉眼检查一遍这一步能拦住 80% 的标注事故。2.3 选哪个模型权重s 还是 m以及为什么课程设计的数据量往往只有几百到一千张从零训练一个 yolo 很容易过拟合正确的做法是加载官方预训练权重做迁移学习。yolov5s 是性价比最高的起点单卡 6G 显存就能训推理速度在教室场景能达到 40 FPS 以上精度对“数人头”这种粗粒度任务完全够用。只有在画面里学生极小、教室超过 60 人的情况下才建议升到 m 或 n 做平衡。网络结构不用改yolov5 的 CSPDarknet 骨架在这个场景下已经足够调参收益远大于改结构。训练前建立一个class_count.txt统计每个类别样本数你会发现 person 这类在教室里的近景和远景差异极大小目标占比高。yolov5 内部 anchor 是按 coco 数据分布的你的数据里人头占整图比例可能只有 2%这种情况需要手动关闭自动 anchor 优化--noautoanchor让模型先跑一轮再自己重新聚类 anchor。训练命令参数python train.py \ --data classroom.yaml \ --weights yolov5s.pt \ --imgsz 1280 \ --batch-size 16 \ --epochs 100 \ --device 0 \ --noautoanchorimgsz 1280是这里最值得记住的参数。课堂监控画面里人的像素面积小用默认的 640 训练会把小目标的信息严重压缩1280 的输入尺寸是数据量不足时最有效的提精度手段。显存不够就把 batch-size 降到 8或者改用--cache预加载数据——它会先把图片复制进内存训练速度提升 30% 以上唯一的坑是内存不足时直接爆掉。训练过程看两个指标val loss 是否持续下降以及results.png里 PR 曲线在 person 类上的 AP 是否超过 0.85。如果 50 轮后 AP 还在 0.7 徘徊优先检查数据标注和类别分布而不是继续加 epoch。3. 把“检测结果”变成“违纪记录”区域规则与目标追踪的取舍3.1 为什么不用动作识别模型数据、算力和可解释性三座山很多人在这个环节会掉头去研究 pose estimation 或 3D-CNN 动作识别我的建议是课程设计慎用。原因有三条。第一课堂违纪是长尾动作趴桌、转头、玩手机这些动作的视觉差异极小公开动作识别数据集里根本没有这类场景自己采集又要重新做视频标注工作量翻倍。第二动作识别模型推理要吃连续帧一张 1080p 视频用 3D-CNN 跑推理单帧耗时可能到 50ms 以上答辩现场稍有卡顿就很狼狈。第三也是最重要的判定结果必须能在界面上向老师解释清楚——为什么判定为违纪。yolo 输出“人在什么位置、置信度多少”规则判定输出“此人低头超过 10 秒”这种透明度是纯黑匣子动作模型给不了的。所以我一般会用两段式yolo 负责框人规则模块负责看这个人在框里的行为模式。检测模型每 1 秒输出一帧拿到每个人的 bounding box 和中心点后规则模块自己维护一个对应表格记录“这个人最近 10 帧的中心点变化、框高度变化、是否低于桌面高度阈值”。3.2 埋伏笔的第一版判断逻辑低头与趴桌的启发式用最少的数学量完成最像样的判断是我做这类系统时坚持的原则。对每个检测框计算三个特征头部中心点的 y 坐标变化低头会让 y 向下移动、框的宽高比变化趴桌会把头压扁w/h 会明显变大、检测框与画面底部桌面的距离。由于你没有做关键点检测这三个特征全部来自 yolo 的边框本身不用额外模型。以下这段代码是系统里最核心的判定循环。它不是一个完整项目只是一套思路的最小实现import time class ViolationTracker: def __init__(self, low_head_ratio0.75, duration_thresh8): self.status {} # track_id - 状态字典 self.low_head_ratio low_head_ratio self.duration_thresh duration_thresh def update(self, detections, frame_height): now time.time() for det in detections: track_id, x1, y1, x2, y2, conf det h y2 - y1 w x2 - x1 # 头部下沉程度头框高度占整个检测框高度的比例 head_down (y2 - y1) / frame_height aspect w / h # 低头头框底部接近画面底部且宽高比显著变大 is_low_head aspect self.low_head_ratio * 1.3 and y2 frame_height * 0.6 # 趴桌检测框高度骤减到正常的一半以下 is_prone h (self.status.get(track_id, {}).get(avg_h, h) * 0.5) if is_low_head or is_prone: if track_id not in self.status: self.status[track_id] {start: now, duration: 0} else: self.status[track_id][duration] now - self.status[track_id][start] if self.status[track_id][duration] self.duration_thresh: self.trigger_violation(track_id, 低头, conf) else: # 行为恢复清除状态避免误报累积 self.status.pop(track_id, None)这段代码把问题拆成了刻度frame_height用来做比例判断避免 720p 和 1080p 分辨率导致阈值失效duration_thresh是可以调的“宽容时间”太短容易把正常的伸懒腰误判成违纪8 秒是教室场景下比较平衡的值。最终判定不是单帧行为而是“持续行为”这才是课堂违纪检测的本质——玩手机和低头看书的动作本身在单帧里无法区分但持续时间完全不同。self.status.pop(track_id, None)这一步重要一个人恢复抬头后如果不清除记录第二次低头触发时 duration 会继承上一次的累计时间出现“坐下三分钟就报警”的假象。这套启发式规则有一个明显的边界画面变化剧烈、学生走出画面再回来时track_id可能切换导致avg_h被重算置信度也不稳定。解决的办法是加一个轻量级目标追踪不必引入 deepsort 这个重武器yolov5 自带的track()方法接 ByteTrack 就能满足。追踪模块把运动特征和检测框做 IoU 关联即使中间丢帧 1~2 秒也能保持同一个 track_id违规事件的连续性因此不会被切断。需要强调的一点是任何追踪器都不可能完美教室后排小人密集遮挡时track_id 频繁切换是常态此时宁可不报警也不要误报——把duration_thresh调大比调小更安全。训练和推理的过程中数据是最后的生命线。yolo 模型对训练数据的分布极其敏感教室场景的光照变化灯光开关、窗帘遮挡、摄像头角度教室左上角与正前方的检测难度差很多都要在数据采集阶段考虑进去。至少录两个时段上午自然光和傍晚灯光保证 val 集里有这两种光照的样本否则你在答辩现场演示时投影仪一开光照变了模型性能断崖下跌那个场面在课程设计答辩里几乎等于翻车。4. 完整代码闭环PyQt 界面、数据落盘、实时演示4.1 从推理脚本到可演示系统还差什么如果你只把模型跑通测了几张图片那份代码在评分标准里顶多算“模型复现”。课程设计的完整代码必须包括实时摄像头推理、检测结果可视化、违纪事件记录、历史记录可查询最好再加一个简单的数据导出。这些功能在代码量上占比超过 60%但又是评委最容易上手试的部分——他们会直接点界面上的按钮而不是看训练代码。技术选型上不要绕远路。推理可复用 detect.py 的思路界面套 PyQt5展示用 OpenCV 的imshow和 Qt 的QLabel都可以。关键点在视频处理线程必须单独开一个QThread把模型推理放进工作线程主线程只负责刷新QLabel。如果在线程里直接调pytorch的 GPU 推理界面会周期性卡顿评委一拖动窗口就会明显感受得到卡顿。4.2 界面和数据记录的核心代码别在 UI 线程里跑模型import threading import cv2 from PyQt5.QtCore import QThread, pyqtSignal class VideoThread(QThread): frame_ready pyqtSignal(object) violation_on pyqtSignal(dict) def __init__(self, model, source0): super().__init__() self.model model # 加载好的 YOLO 模型 self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running: ok, frame cap.read() if not ok: break results self.model(frame) # 推理发生在子线程 dets self.parse_results(results) self.frame_ready.emit(frame) # 主线程只负责显示 for det in dets: if det[is_violation]: self.violation_on.emit(det) # 信号把记录送到主线程 cap.release()frame_ready和violation_on这两个信号是本段代码的主心骨。frame_ready把原始帧送回主线程显示推理本身不阻塞 UIviolation_on只在检测到违纪时触发携带的字典里写清楚track_id、violation_type、时间戳这些信息就是一条记录。PyQt 的信号跨线程传递是线程安全的可以在子线程里直接 emit这一点对新手来说经常意识不到结果自己加锁做线程间通信代码又难读又容易死锁。用信号槽机制之后整个系统的并发模型变得非常简单子线程产数据主线程管展示、落库、响应用户操作。任何数据库选型能撑住这次课设。如果只是单机演示SQLite 足够但如果你要把本系统扩展成多机房的“智慧课堂”演示PostgreSQL 的 JSONB 字段可以很好地以result_json一列存取检测结果。数据记录的目的是事后可追溯——答辩时评委指出某一帧有误报你能当场查出那个时间点的检测框、置信度和判定依据这份从容比任何花哨的图表都有说服力。4.3 历史记录展示从 tablewidget 到 qtableview 的性能教训违纪记录会随着课堂时间逐渐累积几百条记录时 QTableWidget 还看不出毛病一旦视频流连续跑一节课上千条记录同时插入界面会肉眼可见地变卡。有人会在 QTableWidget 的insertRow里一条条插入可想而知那会卡到什么程度。这个热搜词我拆解下qt 表格大数据卡顿优化 tablewiget 到 qtableview 自定义model——这正是我在这个环节的踩坑史。QTableWidget 把数据存在每个 cell 的 item 里每次插入都触发刷新QTableView 配合自定义 QAbstractTableModel 只维护一个数据列表刷新由视图层按需完成。改造思路如下from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt class ViolationTableModel(QAbstractTableModel): def __init__(self): super().__init__() self.records [] # 每条记录是一个 dict def rowCount(self, parentQModelIndex()): return len(self.records) def columnCount(self, parentQModelIndex()): return 4 # 时间 / 位置 / 类型 / 置信度 def data(self, index, roleQt.DisplayRole): if role Qt.DisplayRole: r self.records[index.row()] keys [time, position, type, conf] return str(r[keys[index.column()]]) return None def add_record(self, rec): self.beginInsertRows(QModelIndex(), len(self.records), len(self.records)) self.records.append(rec) self.endInsertRows()beginInsertRows和endInsertRows是无脑替换insertRow的正确姿势数据量上千条后界面依旧流畅——你只要不在data()方法里做任何耗时计算包括把 float 转 string 都提前算好缓存。这个data()是最容易再踩一脚的地方每刷新一帧视图都会重新调用它如果这里塞了数据库查询卡顿程度会比之前更严重。我当时就是把格式化操作写进了data()结果 1000 条记录时滚动一次要卡两秒改掉后秒开。如今我已经习惯成自然凡是表格数据超过 3 位数就直接上 QTableView 自定义模型。4.4 报告导出尽量优雅一点课程设计的交付物里除了实时界面还要有阶段性的“课堂统计报告”。导出成 PDF 或 Excel 都行openpyxl 写 xlsx 是最安稳的路径不用装额外的 GUI 组件。导出的内容至少包含三部分检测总帧数、违纪事件列表、每类违纪的频次统计。为了答辩时数据好看建议在导出同时生成一张简单的柱状图用 matplotlib 画好嵌入 Excel这会让你的“系统完整性”在评语里高一个档次。from openpyxl import Workbook from openpyxl.chart import BarChart, Reference wb Workbook() ws wb.active ws.append([违纪类型, 次数]) ws.append([低头, 12]) ws.append([趴桌, 5]) chart BarChart() chart.add_data(Reference(ws, min_col2, min_row1, max_row4), titles_from_dataTrue) ws.add_chart(chart, E5) wb.save(违纪统计.xlsx)这段代码的切入点在于titles_from_dataTrue它把第一列“违纪类型”当成了图例来源图表才有意义。答辩时评委就是一个偶然想到细节的人他关注的往往不是一个图表多漂亮而是你导出流程是通畅的——按钮点下去文件真的生成并且数据是对的。课程设计里还有一类常见思路是把检测结果直接同步到浏览器端用 echarts 做数据可视化大屏。如果时间充裕把同一套 SQLite 数据通过 Flask 暴露一个轻量接口前端 echarts 拉取 JSON 渲染折线图这是完全可复制的加分项。但在有限的交付周期里先把 PyQt 闭环跑稳大屏页别在答辩前夜才动手否则演示时接口报错、图表空白这类事故会让你前面的努力白费。5. 避坑与常见问题行为判定翻车前先检查这 6 条5.1 漏检小目标导致“人突然消失”现象后排学生所在区域目标较小模型经常检测不到违纪判定时断时续track_id频繁丢失。原因训练时imgsz太小小目标特征被池化层消融或者数据集里小目标样本占比不足。解决把训练和推理的imgsz统一提到 1280并在数据集中补充至少 30% 的远景画面。推理时不要单独调大imgsz而训练用小尺寸否则检测框置信度会普遍偏低。实在显存不够的把模型换成 yolov5n宁可用更小的模型换 1280 的输入也不要大模型跑 640。5.2 低头判定被伸懒腰干扰现象学生只是伸了个懒腰或弯腰捡笔系统误判为低头违纪。原因单帧规则把“低头的形态”当成“低头的持续状态”忽视了恢复动作。解决把duration_thresh拉长至 8~10 秒并且在规则模块中增加“中间无恢复”条件连续 8 帧里如果有 2 帧以上回到正常姿态就重置计时。这个特性在代码里只需要维护一个正常帧计数非常值得做因为它直接决定系统的误报率。5.3 摄像头在教室角落导致俯视角度过斜方向不统一现象模型能检测到人但框的宽高比整体失真低头判定的aspect阈值全部无效。原因yolo 框学习的是目标的最小外接矩形俯视视角下人头框投影畸变静态比例阈值不具备视角自适应能力。解决不要用绝对宽高比判断改成它的一阶导数正常坐姿的人的检测框宽高比在时间轴上比较平稳低头动作会让宽高比发生一个明显的正脉冲。用这个变化量作为特征视角的影响会小很多。如果实在要换视角直接在数据里加入该教室角度的 100 张样本做微调比调阈值更有效。5.4 GPU 显存溢出训练爆显存现象训练到第 30 轮时CUDA out of memory核心 log 指向DetectionLoss。原因imgsz 1280配合大 batch 把显存吃满且没有做梯度累积。解决先把--batch-size降到 8再开启--cache。这两个参数都试过仍然爆显存就改用--device 0 --workers 4 --image-weights配合显存清理。一个止损技巧是训练前先用python train.py --help查看你当前版本是否支持--v5local之类的激活内存管理参数不同分支的 yolov5 在显存策略上差异不小。5.5 track_id 抖动导致重复报警现象一段违纪持续 10 秒系统报了 3 条记录时间戳只隔 2 秒。原因目标追踪的 IoU 关联在目标重叠时把 track_id 换成了新 id规则模块把它当成一个新目标处理。解决在ViolationTracker里增加一个“最近 3 秒内已报过同位置违纪”的冷却机制。用位置坐标的欧氏距离做暴风匹配距离小于 50 像素的直接归为同一个人不触发新警报。这个冷却功能代码量不大但效果非常显著看似小细节实则是系统的门面。5.6 数据标注对象层错误导致训出来的模型把投影也当成人现象教室里有投影仪幕布上的人像被误检成学生触发一堆假违纪。原因标注时确实框了屏幕里的人或者模型盲目学到“亮背景上的人形轮廓”这个特征。解决首先在标注阶段把投影仪、屏幕、宣传海报上的人形全部主观排除掉只在真实学生身上框。如果已训练完再发现这个问题的想在最短时间内做局部更新就把含投影仪的帧挑选出来增补标注对模型做 20 轮微调即可不必重新采集整个数据集。6. 把模型参数再收紧一个能在讲台上演示的高收益验证闭环最后分享一个我常在课设项目里用的收尾手法把训练和推理做成一键联动让评委在几分钟内看到模型行为的变化。具体做法是先跑一组 100 轮训练日志画一张 loss 曲线然后把superset里的三个关键超参数学习率lr0、置信度阈值conf_thres、IoU 阈值iou_thres单独暴露成一个 json 文件让 PyQt 界面在初始化时读取这个 json再传给 detect 模块。# 超参配置示例tuning.json { lr0: 0.01, conf_thres: 0.35, iou_thres: 0.45, imgsz: 1280 }推理时把 confidence 阈值调到 0.35 左右让模型多输出一些低置信度框教室监测这种场景中召回比精确更重要——你宁可让规则模块多看几个不确定的人也不能让后排低头玩手机的学生因为置信度低而被漏检。IoU 阈值保守一点设 0.45 可以避免密集人群中一个框压住两个人导致计数丢目标。这个 json 里的参数组合就是你的超参数调优方法论lr0 影响收敛速度conf 和 iou 决定行为判定入口的数据质量imgsz 控制小目标表现。讲台上演示时当着评委的面把 json 里的 conf_thres 从 0.35 改到 0.6界面上的检测框肉眼可见变少再用嘴解释“这是精确与召回的取舍”比空谈原理有说服力得多。我这套项目做完最大的心得是检测系统永远是 yolo 在打底但真正让它变成一个“课程设计”的是包裹在模型外面的工程逻辑。规则要简单可解释界面要流畅不卡顿数据要真实可追溯这三点做到了评分已经站稳第一梯队答辩翻车率大幅下降。另外一个习惯了坚持的小事是每次改完任何一个参数都把 json 复制一份带时间戳的备份哪怕回归到旧配置你也有后悔药可吃——我在这上面被自己坑过一次改了conf_thres忘了记录旧值演示时想还原却找不到原始参数只能重新跑一次推理找感觉白白浪费半小时。这种单一参数的坑看起来小关键时刻真会让人手忙脚乱。回到你自己手里这份“基于yolov5的学生课堂违纪检测系统”选题技术上没有不可逾越的深水区难点全在耐心组织数据、认真写记录逻辑、遵守少即是多的工程原则。希望这份拆解能帮你把完整代码和数据从“能跑”带到“能讲”也让评委看到的不只是一个模型而是一套有判断力、有追溯力、有演示说服力的完整系统。本文还有配套的精品资源点击获取