
简介面向高校课程设计与目标检测实践场景这套基于YOLOv5的学生课堂违纪检测系统完整工程包可用于识别课堂睡觉、玩手机等行为覆盖数据集构建、模型训练、优化评估与实时检测展示流程。包内共220个文件压缩包仅1.19MB以103个Python脚本支撑训练与推理逻辑79个yaml文件保存模型和数据配置另有Dockerfile、Shell脚本、Jupyter Notebook示例等便于环境复现与分步调试。已有1939人学习下载适合作为课程设计参考或YOLOv5入门到实战的项目范本。解压后可对照代码厘清数据标注、训练调参、评估指标和可视化部署的关键链路还能基于现有结构替换或扩充违纪行为类别快速迁移到其他检测场景。1. 基于YOLOv5的学生课堂违纪检测系统课程设计要交的到底是什么很多人把基于yolov5的学生课堂违纪检测系统的课程设计当成一个调包训练任务真正做下来才发现标注数据、格式转换、超参数这三件事占了八成时间。这个项目本质上是一条目标检测落地流水线先定义违纪类别睡觉、玩手机、交头接耳、举手、站立再采集或复用课堂图片用 LabelImg 标注出边界框转成 YOLO 格式训练最后把权重接进一个带摄像头或视频输入的界面输出告警和统计。适合计算机视觉、Python 开发、软件工程方向的课程设计交付物通常是代码加数据集加训练好的 best.pt 模型加报告。按我的经验一次能跑通的关键不在模型而在数据和标签。2. 数据是地基课堂违纪数据集怎么采、怎么标、怎么转课程设计里最容易翻车的就是数据。YOLOv5 代码本身成熟训练命令一句话就能跑但数据集要是标注得乱、类别口径不一致后面调出来的模型在答辩现场根本不敢放真实视频。所以先把数据这一环做扎实。2.1 违纪类别怎么定五类方案与采集策略打开标注工具之前先和老师确认识别目标。课程设计里最常见的类别方案是五类sleep趴桌睡觉、phone低头玩手机、talk交头接耳、hand举手、stand站立。这五类有区分度动作特征明显模型容易学如果连“托腮发呆”都要识别样本会变得很碎标注和训练成本直线上升。采集策略上最常见的做法是录一段教室视频按 5 到 10 秒间隔抽帧筛掉重复和模糊的帧留下 800 到 1200 张图。现场不允许录像的话就从公开的课堂场景图片里凑再加旋转、翻转、亮度调整做数据增强。每个类别至少要有 300 个实例不然类别不平衡会让 mAP 很难看。标注标准一定要提前定死。我的习惯是给每个类别写一句判断规则sleep 要求脸趴在桌上或闭眼phone 是手机在手上且屏幕亮着talk 是两人侧脸相对、有明显交流嘴型hand 是手掌举过肩stand 是人站立且不在座位上。规则写进项目 README小组多人标注时才不会口径不一。2.2 标注实操LabelImg 的安装与边界框规则LabelImg 是课程设计里最顺手的标注工具我一般先用 conda 建一个干净环境装上 PyQt5 和 LabelImg命令行直接打开图形界面。pip install pyqt5 pip install labelImg labelimg打开界面后左侧 Open Dir 选图片目录Change Save Dir 选 xml 输出目录右下角把格式切成 PascalVOC。快捷键 W 画框、D 下一张、A 上一张。一张图里每个目标都要单独画框同一人换个角度再出现要重新画不要想着跨帧复制标注。框要紧贴可见区域遮挡超过 60% 的目标建议跳过不然会给模型注入大量噪声。我自己标注时的土办法是每标完 100 张按类别统计一遍框数量。如果发现某个类别异常多多半是误标了比如把低头写字标成了 sleep。这一步不用写代码直接在 xml 目录里用文件管理器搜索类别名就能看个大概但非常能救模型。2.3 VOC 转 YOLO 格式转换脚本与数据划分LabelImg 默认导出 PascalVOC xmlYOLOv5 训练需要的是 txt 格式每行是“类别编号 x_center y_center width height”坐标全部归一化到 0 到 1。下面这个转换脚本可以直接用我习惯从 xml 里读图片宽高而不是硬编码尺寸省去一档子错位问题。import os import xml.etree.ElementTree as ET from pathlib import Path CLASSES [sleep, phone, talk, hand, stand] def voc2yolo(xml_file, out_dir): tree ET.parse(xml_file) root tree.getroot() # 从 xml 的 size 节点读原图宽高避免外部传参记错尺寸 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) x_center ((x1 x2) / 2.0) / img_w y_center ((y1 y2) / 2.0) / img_h box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if lines: txt_path Path(out_dir) / (Path(xml_file).stem .txt) txt_path.write_text(\n.join(lines), encodingutf-8) annot_dir Path(annotations) out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in sorted(annot_dir.glob(*.xml)): voc2yolo(str(xml_file), out_dir)逻辑说明坐标归一化用的是该 xml 对应原图的宽高xml 里的 size 节点就是标注时的原始尺寸直接读比硬编码 640 可靠。归一化后坐标理应在 0 到 1 之间如果出现负数或大于 1说明标注框跨出了画面边缘这类样本先别删记下来统一排查。CLASSES 的顺序就是模型预测时的类别编号一旦开始训练就别改。要是中途增删类别必须把 labels 目录下的 txt 全部重新生成否则编号错位会让整个模型白训。接下来划分训练集和验证集我习惯先洗牌再按比例分而不是按文件名顺序切。从视频连续抽帧的图相邻帧高度相似不洗牌直接分验证集会混进大量训练集同源帧mAP 虚高得离谱。import random from pathlib import Path random.seed(42) imgs sorted(Path(images).glob(*.jpg)) random.shuffle(imgs) ratio 0.85 train_imgs imgs[:int(len(imgs) * ratio)] val_imgs imgs[int(len(imgs) * ratio):] def copy_to(img_path, subset): dst_img Path(dataset) / subset / images / img_path.name dst_txt Path(dataset) / subset / labels / (img_path.stem .txt) dst_img.parent.mkdir(parentsTrue, exist_okTrue) dst_txt.parent.mkdir(parentsTrue, exist_okTrue) dst_img.write_bytes(img_path.read_bytes()) src_txt Path(labels) / (img_path.stem .txt) if src_txt.exists(): dst_txt.write_text(src_txt.read_text(encodingutf-8)) for p in train_imgs: copy_to(p, train) for p in val_imgs: copy_to(p, val) print(ftrain: {len(train_imgs)} images, val: {len(val_imgs)} images)参数说明随机种子固定后重跑脚本划分结果不变方便复现。85% 训练、15% 验证是初始值如果总图数不到 600验证集压到 10%。验证集中不能出现和训练集同帧截断出的相邻帧否则验证指标没有参考价值。3. 用YOLOv5训练自己的课堂违纪检测模型最小可复现流程如何用yolov5训练模型官方 train.py 是最省事的入口。但很多人第一次跑就卡在环境上torch 和 CUDA 版本对不上会直接报错。先把环境理清再谈训练。3.1 环境搭建与版本对应关系YOLOv5 对版本比较敏感我的建议组合是 Python 3.8 加 PyTorch 1.13 加 CUDA 11.7这一组在课程设计场景里踩坑最少。CUDA 驱动版本决定了你能装哪个 torch先跑nvidia-smi看右上角 Driver Version 对应的 CUDA 版本再选安装命令。conda create -n yolo python3.8 -y conda activate yolo pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt参数说明cu117对应 CUDA 11.7如果显卡驱动只支持到 11.3就换cu113后缀。全套 CPU 训练不是不能跑但每轮可能要几十分钟课程设计时间耗不起不如租一张云 GPU训练完把权重拉回来再本地推理。3.2 修改 dataset.yaml 与模型 yaml跑通前的关键一步进入 YOLOv5 代码目录后在 data 目录下新建一个 classroom.yaml内容如下。# classroom.yaml path: ../dataset train: images/train val: images/val nc: 5 names: [sleep, phone, talk, hand, stand]注意 names 顺序必须和第 2 章 CLASSES 完全一致。训练脚本启动时会把 yaml 里的 nc 自动写进模型配置不需要手动改 yolov5s.yaml 的nc80这个动作很多人都不知道结果要么报维度错要么训练出来的模型类别数还是 80。3.3 训练命令与超参数调整先看懂网络结构再调参对着 yolov5 网络结构图看模型在 Backbone 末尾输出三个尺度的特征图分别负责检测大、中、小目标。课堂场景里摄像头通常架在教室后方sleep 这类框往往很小所以训练时的输入尺寸不要随便砍保持和预训练权重一致的 640 最稳。python train.py \ --data data/classroom.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --batch-size 16 \ --epochs 100 \ --img 640 \ --device 0 \ --name classroom_exp参数说明--weights yolov5s.pt是 COCO 预训练权重课堂场景的“人”和 COCO 的 person 强相关用预训练权重比从零训练收敛快很多。--batch-size 16在 6GB 显存够用8GB 卡可以上 32显存不够就把 batch 降到 8 或 4优先保证训练能跑完。--epochs 100对 1000 张图偏多通常到 60 轮就开始过拟合但首次训练建议跑满回看 results.csv 再决定最后用哪轮权重。yolov5超参数里占用率最高的是学习率和数据增强开关。课程设计阶段没必要一上来就调 anchors那是最后的玄学手段先保证数据集干净、类别均衡mAP 一般不会太差。超参数默认值课堂数据建议说明lr00.010.005 或 0.01学习率过大在小数据集上容易震荡batch-size168 到 16显存不够先降 batch不要急着降图片epochs10060 到 100看 results.csv 决定是否提前终止img640640推理和训练必须一致conf0.250.35课堂场景误报多调高阈值更稳3.4 训练过程指标解读训练正常启动后每轮输出一行表格包含 Box、Obj、Cls、DFL 四个 loss以及 Precision、Recall、mAP0.5、mAP0.5:0.95。这里不用全看懂盯两组就够mAP0.5 是答辩里最容易说清楚的数字衡量框和类别是否大体对上mAP0.5:0.95 更严格数值会明显低一截别拿它当目标。训练结束后在 runs/train/classroom_exp 里找 best.pt 和 last.pt。best.pt 按验证集 mAP 自动挑选默认用它做推理如果 last.pt 对应的 mAP 曲线还在上升说明 100 轮没训够可以继续训 50 轮再比较。4. 课程设计避坑指南五个让人翻车的高发问题与排查课程设计翻车点高度集中我把这些年见过最多的问题整理成现象、原因、解决三段式照着排查能省下大量时间。4.1 显存溢出CUDA out of memory现象训练没几个 epoch 就报 RuntimeError: CUDA out of memory有时前一个 epoch 正常下一个突然崩。原因某个 batch 的图片偏大或显存本来就紧张后台还挂着另一个占用显存的进程。解决先跑nvidia-smi看显存占用关掉残留的 python 进程和 TensorBoard把--batch-size降到 8 或 4--img降到 480--workers改为 0 避免数据加载线程挤占内存。同一个 GPU 上不要同时训练和推理这是最常见的隐性占用。4.2 标签编号错位预测和真实类别对不上现象训练不报错测试时 phone 总出现在 hand 的位置类别整体平移mAP 却还算正常。原因CLASSES 顺序和 classroom.yaml 的 names 顺序不一致或者改过类别列表后旧的 txt 没清理。解决写一个校验脚本遍历 labels 目录检查每个 txt 第一列最大类号是否小于 nc再随机挑几张图把 txt 坐标画回原图人眼对比。最省事的办法是直接复用 2.3 节转换脚本重跑一遍把所有 txt 重新生成。4.3 训练时 loss 直接变成 nan现象第一个 epoch 的 loss 就是 nan或训到中段突然变 nan。原因学习率太大、数据里混进损坏图片、txt 里出现负坐标或大于 1 的坐标这三个最常见。解决先检查数据集是否有空 txt 或 0 字节的 jpg打印一条样本的归一化坐标看是否越界再把 hyp.scratch-low.yaml 的 lr0 调小。我遇到过一次是某张 jpg 被标注工具存成 0 字节训练没报错但 loss 从此变 nan删掉该图就好。4.4 mAP 一直很低但 loss 正常下降现象训练很稳定loss 降到 0.02mAP0.5 却只有 0.5 左右。原因最常见是小目标类别样本少比如 sleep 的框又小又少模型很难学到特征其次是标注标准不一致同一个 talk 画法差异太大。解决先给少数类补数据手工裁剪局部做增强也行再把标注标准贴在项目文档里逐张复核。最后才去动 anchors不要一上来就调那是玄学。4.5 导出 ONNX 后推理结果对不上现象PyTorch 推理正常用 ONNX Runtime 或 OpenCV 推理同一张图结果差很多。原因导出时带了后处理或者推理时预处理没做 letterbox输入图被直接拉伸了。解决用官方export.py --weights best.pt --include onnx导出。自定义加载 ONNX 时预处理要按 letterbox 做保持宽高比缩放短边补 114 灰边输入尺寸和训练时的 640 一致。5. 把模型变成检测系统推理部署与PyQt5界面模型训练完课程设计才做了一半。要做成一个能演示的系统还得把权重接进摄像头或视频流用界面展示检测结果并输出统计信息。5.1 摄像头视频实时推理最小脚本先验证模型在真实输入上的表现我用 torch.hub 加载本地权重写一个最小推理脚本。import cv2 import torch model torch.hub.load(ultralytics/yolov5, custom, pathruns/train/classroom_exp/weights/best.pt, trust_repoTrue) model.conf 0.35 model.iou 0.45 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break results model(frame, size640) det results.xyxy[0].cpu().numpy() for x1, y1, x2, y2, conf, cls_id in det: label model.names[int(cls_id)] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(frame, f{label} {conf:.2f}, (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(classroom violation, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明results.xyxy[0] 是当前帧的检测结果每行是 x1、y1、x2、y2、置信度、类别编号。model.names 从 best.pt 里读出类别名列表不会和你的 labels 错位。参数说明model.conf 设 0.35 是比较保守的起点课堂远景小目标多太低会出现大量虚警跑一遍视频后按误报情况再上调到 0.4 或 0.5。size 必须是 640和训练时的输入保持一致。5.2 告警逻辑连续几帧再触发单帧检测直接当告警演示时会被误报烦死。常见做法是连续 N 帧检测到同一位置目标才触发一次告警给一个最小实现。FRAME_N 5 alarm_counter {} def should_alarm(cls_name, x1, y1, x2, y2): key f{cls_name}_{int(x1 // 20)}_{int(y1 // 20)} alarm_counter[key] alarm_counter.get(key, 0) 1 if alarm_counter[key] FRAME_N: alarm_counter[key] 0 return True return False在主循环里调用 should_alarm返回 True 才截图或弹窗告警。FRAME_N 按视频帧率调30fps 下 5 帧约 0.17 秒足够滤掉偶然误检。x1 // 20 是对坐标做粗分桶避免同一目标每帧坐标抖动导致计数被拆开边界上可能误判移动但课程设计场景足够用。5.3 PyQt5 界面把推理接进 GUI答辩时评委更愿意看到图形界面而不是命令行黑框。最简 PyQt5 方案是放一个 QLabel 显示画面一个按钮控制启动和停止。import sys import cv2 import torch from PyQt5.QtCore import QTimer from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget class ClassroomGUI(QWidget): def __init__(self, weight_path): super().__init__() self.model torch.hub.load(ultralytics/yolov5, custom, pathweight_path, trust_repoTrue) self.model.conf 0.35 self.cap cv2.VideoCapture(0) self.label QLabel(等待开始) self.btn QPushButton(开始/停止) self.btn.clicked.connect(self.toggle) layout QVBoxLayout(self) layout.addWidget(self.label) layout.addWidget(self.btn) self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.timer.setInterval(30) def toggle(self): if self.timer.isActive(): self.timer.stop() else: self.timer.start() def update_frame(self): ret, frame self.cap.read() if not ret: return results self.model(frame, size640) frame results.render()[0] rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, channel rgb.shape qimg QImage(rgb.data, w, h, channel * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) app QApplication(sys.argv) gui ClassroomGUI(runs/train/classroom_exp/weights/best.pt) gui.show() sys.exit(app.exec_())逻辑说明QTimer 每 30ms 拉一帧推理在主线程里跑显示会有轻微卡顿但课程设计演示足够。想更流畅就把推理搬进 QThread定时器里只读取结果。这个例子里 QImage 转换放在界面层主线还是检测功能完整界面只承担展示用途。5.4 统计报表答辩时需要的数据检测系统除了实时告警还要能输出课堂整体统计。常见做法是按类别累计检测帧数结束时导出 CSV。import csv from collections import Counter counter Counter() # 在每帧检测循环里累加 counter[label] 1 # 视频结束后写 CSV with open(violation_stats.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([类别, 出现帧数]) for k, v in counter.most_common(): writer.writerow([k, v])这个 CSV 可以贴进课程设计报告比干说“能检测”更有说服力。统计口径要写清楚按帧累计不是按人次累计同一人连续 100 帧玩手机会计成 100 帧答辩时先说明这一点避免被追问。6. 部署后的验证与答辩先跑视频再写报告训练完别急着写报告。找个没参与训练也没参与验证、最好画质差一点的教室视频跑一遍推理一帧帧看结果。重点看两类错误漏检是不是集中在一个姿态或一个位置误检是不是集中在某个背景区域。漏检多就回标注补样本误检多就把 conf 阈值往上调这两件事做完再谈调模型。答辩时最加分的不是模型性能多高而是你知道它弱在哪。把验证时最典型的漏检帧截图贴进报告写一句原因样本不足、遮挡严重、光线暗。评委看到你能自我评估通常不会死磕性能。交代码前对照三个检查项best.pt 是不是对应 classroom.yaml 的 nc5推理脚本里的模型路径是否正确导出的 ONNX 能不能用官方 export.py 复现。我自己当年交这个项目时搞混过 last.pt 和 best.pt权重交错演示效果差一倍。后来养成一个习惯项目目录里只留 best.ptlast.pt 放进 backup 目录推理脚本写死文件名。之后再没出过这个问题。这个方向在课程设计里属于路径短、坑相对少的选题数据和标注做扎实训练和部署按上面的步骤走拿到一个能现场演示的完整系统不难。希望帮到你。本文还有配套的精品资源点击获取