☰
YOLOv5课堂违纪检测系统:从目标检测训练到PyQt界面落地
2026/10/11 17:12:25 网站建设 项目流程

简介:基于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 \ --noautoanchor

imgsz 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_ratio=0.75, duration_thresh=8): 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, source=0): 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把原始帧送回主线程显示,推理本身不阻塞 UI;violation_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, parent=QModelIndex()): return len(self.records) def columnCount(self, parent=QModelIndex()): return 4 # 时间 / 位置 / 类型 / 置信度 def data(self, index, role=Qt.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_col=2, min_row=1, max_row=4), titles_from_data=True) ws.add_chart(chart, "E5") wb.save("违纪统计.xlsx")

这段代码的切入点在于titles_from_data=True,它把第一列“违纪类型”当成了图例来源,图表才有意义。答辩时评委就是一个偶然想到细节的人,他关注的往往不是一个图表多漂亮,而是你导出流程是通畅的——按钮点下去文件真的生成,并且数据是对的。

课程设计里还有一类常见思路是把检测结果直接同步到浏览器端,用 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的学生课堂违纪检测系统”选题,技术上没有不可逾越的深水区,难点全在耐心组织数据、认真写记录逻辑、遵守少即是多的工程原则。希望这份拆解能帮你把完整代码和数据从“能跑”带到“能讲”,也让评委看到的不只是一个模型,而是一套有判断力、有追溯力、有演示说服力的完整系统。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询