简介:这是一份基于YOLOv8的校园宿舍安全监测系统完整项目包,适合计算机视觉、人工智能方向的学生用于毕业设计或课程设计,也便于初学者对照学习完整落地流程。压缩包共8个文件,主要包含Python源码文件(训练、检测及可视化界面)、PyTorch模型权重文件(yolov8n.pt、best.pt等)、以及README等说明文件,整体约15.91MB。目前已有62人学习下载。从内容预览看,项目包含可视化界面、模型训练与视频检测脚本,能够输出核心指标曲线、混淆矩阵、F1曲线、精确率-召回率曲线、验证集预测结果和标签分布图,适合答辩展示或本地快速部署验证。作者在描述中明确代码已测试可用,并附有部署与使用说明,下载后按README操作即可跑通流程,适合作为项目起步参考或二次开发基础。
1. 宿舍安全监测系统:拿到项目包之后,先别急着点运行
宿舍安全监测的毕设题年年都有,但绝大多数翻车不在模型,而在环境、数据和界面这三样不太像技术的技术上。标题里这个项目给出的是一条完整链路:YOLOv8检测模型、可视化界面、完整数据集、部署教程,做法上确实比单独训一个模型更能撑起答辩——因为评审老师看的不只是精度数字,而是你有没有把数据、训练、部署、展示真正串起来。它适合两类人:一类是拿它做毕业设计或课程设计,想尽快跑通并理解每条命令在干什么;另一类是入门目标检测,想用一套现成方案快速摸清 YOLOv8 的工作方式。我的建议是把“简单部署”四个字理解成“最小可运行路径”,后面所有坑都是从这个最小路径长出来的。
2. 先搭 YOLOv8 环境再跑代码:CPU 版和 GPU 版的实际选择
标题里的部署教程,第一步永远是环境。环境问题在毕设里占比极高,而且多数报错信息跟环境无关——是环境之间互相干扰。常见做法是先建一个干净的 conda 环境,把 Python 版本锁定,再装对应平台的 PyTorch 和 ultralytics 包。后面命令默认在 Ubuntu 20.04 下执行,Windows 用户只需要把 conda activate 的写法换成自己系统的对应命令,路径分隔符注意一下即可。
2.1 Ubuntu 20.04 CPU 版环境:为什么先把它作为兜底
很多毕设小组的机器配置参差不齐,实验室电脑可能没有独立显卡。YOLOv8 对 CPU 推理是支持的,PyTorch 的 CPU 版也是所有安装路径里最不容易出问题的一条。我一般会先用 CPU 版把整条链路跑通,再考虑要不要换 GPU——因为 CPU 版能帮你排除掉显卡驱动、CUDA 版本、显存不足这三类问题,剩下的错误基本都是自己代码的问题。
conda create -n dorm_safety python=3.9 -y conda activate dorm_safety pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics这段命令做了三件事:用 conda 创建并激活一个名为 dorm_safety 的独立环境;从 PyTorch 官方 CPU 源安装 torch 和 torchvision,避免误装到带 CUDA 的版本;安装 ultralytics 包,它会一并拉取 opencv-python、pandas、matplotlib 等依赖。python=3.9 是 YOLOv8 支持范围内兼容性最均衡的版本,3.8 也可以,但不建议用 3.11 以上的新版本,某些依赖包还没跟上。
CPU 版能干什么、不能干什么,要心里有数。推理一张 640×640 的图大约需要几秒到十几秒,取决于 CPU 型号;训练一个小数据集也能跑,但时间按小时算。所以 CPU 版适合先跑通流程、验证数据和代码,真正大规模训练还是得找 GPU。
2.2 有 NVIDIA 显卡时的 GPU 环境:1660Ti 这类卡怎么配对
如果机器有 GTX 1660Ti 或者同级别显卡,训练速度会快很多,但 6GB 显存会在 batch size 和图像尺寸上卡住你。先确认驱动状态,再装 PyTorch,顺序别反。
nvidia-smi python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"nvidia-smi 用来查看驱动版本、显卡型号和剩余显存。显卡没被识别时,后面所有 torch.cuda.is_available() 都会返回 False,这是新手最常踩的坑。确认驱动正常后,在刚才的 conda 环境里装 CUDA 版 PyTorch:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121cu121 对应 CUDA 12.1。如果你的驱动比较老,装 cu118 版本更稳妥。装完后重新运行上面那行 python 检测命令,看到 True 和显卡型号,说明 GPU 可用。这里有个经验:1660Ti 跑 yolov8n 或 yolov8s 是舒服的,batch 控制在 8 以内、imgsz 用 640,显存刚好够;yolov8x 在这个卡上基本没法跑,强行跑只能把 batch 压到 1,训练速度还不如用 yolov8s,别在这上面浪费时间。
2.3 环境验证:用一张测试图跑通推理 demo
环境装完先别碰自己的数据集。用官方权重跑一张普通图片,确认推理链路通不通。这一步的意义在于把“环境问题”和“项目问题”切分开。
yolo predict model=yolov8n.pt source=test.jpgyolo 是 ultralytics 自带的命令行入口。model 指定权重文件,source 指定图片路径,第一次运行会自动下载 yolov8n.pt,大概 6MB。如果命令执行完,在输出目录里出现了带预测框的 test.jpg,说明 ultralytics、torch、opencv 这条链路已经通了,后面所有报错都可以排除环境因素,放心去做数据和训练。source 参数也可以传视频路径或摄像头设备号,比如 source=0 表示调用第一个摄像头,这个到界面联调时会用到。
3. 把宿舍监控截图变成可训练的数据集:标注、转换与目录
数据是标题里“完整数据集”之后你大概率要自己做的一件事。宿舍安全监测的项目包通常会附带一批截图和标注,但你毕设答辩时几乎一定会面临一个问题:老师要求检测的类别和你拿到的数据集类别不一致,或者你想在自己的宿舍场景里补充数据。这时候就得自己动手处理数据集。
3.1 用 labelme 标注宿舍场景:类别命名与多边形注意点
标注工具用 labelme 就够了,界面简单,导出的是 JSON 格式,适合 YOLO 系列。安装和启动命令:
pip install labelme labelme打开 labelme 后,加载宿舍走廊或寝室截图为图片,用多边形或多边形矩形工具框住目标。类别命名务必统一用英文小写,比如 person、smoke、fire、phone,不要用中文,也不要一会儿 Person 一会儿 person——YOLO 对类别名大小写敏感,转换脚本里按精确匹配过滤,大小写不一致会导致目标被静默丢掉。
标注时有几个细节会直接影响后面训练:尽量贴目标边缘,不要把整张床都框进去只为了框住床上的人;遮挡严重的相邻目标宁可分开标两个框,也不要合并成一个;宿舍场景里常见的远处小目标,即使模糊也建议标上,这会让模型在走廊长焦画面里更稳健。
3.2 labelme JSON 转 YOLO 格式:转换脚本与四个边界坑
labelme 保存的 JSON 记录的是多边形顶点坐标,YOLO 训练需要的是一行一个目标的归一化框坐标。转换脚本是绕不开的,常见做法是写一个 Python 脚本,把多边形外接矩形算出来再归一化。下面这个脚本能直接用:
import json import os def labelme_to_yolo(json_path, save_dir, class_names): # 读取 labelme 导出的 JSON 文件 with open(json_path, encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] file_name = os.path.splitext(os.path.basename(json_path))[0] out_path = os.path.join(save_dir, file_name + ".txt") with open(out_path, "w", encoding="utf-8") as out: for shape in data["shapes"]: label = shape["label"] if label not in class_names: continue # 过滤掉未定义类别的标注 points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 转成 YOLO 格式的归一化坐标 cx = (x_min + x_max) / 2 / img_w cy = (y_min + y_max) / 2 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h class_id = class_names.index(label) out.write(f"{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n") if __name__ == "__main__": class_names = ["person", "smoke", "fire"] os.makedirs("labels", exist_ok=True) for name in os.listdir("jsons"): if name.endswith(".json"): labelme_to_yolo(os.path.join("jsons", name), "labels", class_names)这段脚本的核心逻辑是逐条遍历 shapes 数组,取多边形所有顶点的横纵坐标算出外接矩形,再除以图片宽高做归一化。除以 img_w 和 img_h 这一步是 YOLO 格式的关键,漏掉的话训练出的框会全部偏移。转换完检查一下:每张图生成的 txt 行数应该等于标注目标数,如果行数少了,多半是类别名大小写没对齐。
转换过程中的边界坑,逐个说清楚。第一,labelme 手滑点多了一个点,导致外接矩形比实际目标大一圈,这种问题只能在标注阶段用 labelme 重新打开检查,代码层看不出来。第二,同一张图片对应多个 JSON 文件时,txt 会被互相覆盖,脚本按图片名输出就是为了规避这个问题,但多轮标注时要注意清理旧文件。第三,归一化后的宽高值可能很小,比如远处目标只有 0.01 级别,保留 6 位小数防止精度丢失,这也是脚本里写{w:.6f}的原因。第四,图片尺寸很大时,比如 1920×1080,归一化本身没问题,但训练阶段 imgsz 会缩到 640,小目标的框会变得更小,所以标注时不要把极小目标全标进去,太小反而干扰模型学习。
3.3 数据集目录结构:images 和 labels 分开放
YOLOv8 的标准目录结构是 images 和 labels 并列,各自内部再分 train 和 val。每张 jpg 对应一个同名 txt,txt 里每行是一个目标。目录建好了,需要一个 data.yaml 指向这些路径。
path: /home/user/dorm_dataset train: images/train val: images/val names: 0: person 1: smoke 2: firepath 建议写绝对路径,不写相对路径。因为训练脚本可能从不同目录启动,相对路径会跟着当前工作目录变化,训练时大概率报找不到图片。train 和 val 的值是相对于 path 的路径,names 的索引顺序必须和转换脚本里的 class_names 顺序一致,这个索引顺序就是后面模型预测输出的类别 id,错一位就全错。
3.4 训练集和验证集划分:按时间段而不是随机打散
宿舍监控场景有个特殊性:同一摄像头拍的连续帧高度相似,光照和角度几乎一样。如果随机划分训练集和验证集,验证集里很可能混入训练集的“孪生帧”,训练出来的模型看起来 mAP 很高,实际部署到真实摄像头就掉链子,这是典型的数据泄漏。常见做法是按时间切分:比如前 7 天的截图全部进训练集,后 3 天的进验证集,这样验证集里出现的画面在时间上和训练集没有重叠,评估结果更接近真实部署。如果你的数据集不是按时间采集的,至少也要按摄像头编号切分,保证同一个摄像头视角的画面不完全落在同一个集合里。
4. 训练一个真正能用的宿舍检测模型:选型、参数与损失曲线
数据和环境就绪后,训练阶段的核心就两件事:选对模型体积、调对训练参数。很多项目包自带的训练脚本参数是通用的,你要做的是理解每个参数在宿舍场景下的合理取值,而不是照抄。
4.1 yolov8n 到 yolov8x 怎么选:显存和速度决定边界
YOLOv8 有 n、s、m、l、x 五个体积等级,网络结构由 backbone、neck、head 三部分组成,ultralytics 会在加载模型时自动搭好,不需要手动构建。选择标准很简单:显存小的机器用 n,显存充足的机器优先考虑 s。1660Ti 的 6GB 显存跑 yolov8s 没问题,yolov8m 就比较勉强了。CPU 训练只建议用 yolov8n,因为 m 及以上体积在 CPU 上迭代一轮的时间会让人怀疑人生。
宿舍安全监测的目标类别少、场景固定,yolov8n 的精度完全够用。很多人的误区是觉得越大越好,实际上在几百张宿舍截图的规模下,yolov8x 和 yolov8n 的差距主要来自参数量,数据量不够时大模型反而更容易过拟合。我的做法是先训一个 yolov8n 做基线,跑通后再训一个 yolov8s 对比精度,如果提升不明显就直接用 n,训练时间短,界面部署时帧率也更高。
4.2 训练脚本:epoch、batch、imgsz、patience 逐个拆
训练命令用 ultralytics 的官方 CLI 就能完成,不需要自己写训练循环。
yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ batch=8 \ imgsz=640 \ patience=20 \ project=runs \ name=dorm_experimentepochs=100 对宿舍场景足够了。你的数据集如果只有几百张图,100 轮训练会在 30 到 50 轮左右收敛,后面轮次主要看验证集有没有继续提升。batch=8 是 1660Ti 显存下的安全值,CPU 训练改成 batch=2 或 1,因为 CPU 内存虽然够,但 batch 太大一次迭代的时间会成倍拉长。imgsz=640 是 YOLOv8 默认的输入分辨率,宿舍监控画面不需要更大,2048×1536 的原图会被自动缩放,分辨率再高只会拖慢速度,精度提升有限。patience=20 是最值得关注的参数,意思是连续 20 轮验证集指标没有提升就自动停止训练,这个机制能帮你省掉大量无效等待。
训练完成后输出在 runs/dorm_experiment/ 目录下,里面有 best.pt 和 last.pt 两个权重文件,best.pt 是验证集上表现最好的,部署时用这个。另外会有 results.png 和 results.csv,分别保存训练曲线和逐轮指标,下一步画损失曲线就用这个 CSV 文件。
4.3 画损失函数曲线图:从 results.csv 提取并看懂过拟合信号
results.png 自带的曲线图够用,但很多场景下你需要自己画——比如远程训练完把权重和日志拷回来,或者想同时对比两次实验的曲线。从 CSV 里提取数据画图是最灵活的方式:
import pandas as pd import matplotlib.pyplot as plt log = pd.read_csv("runs/dorm_experiment/results.csv") print(log.columns) # 先看列名,不同版本列名略有差异 plt.figure(figsize=(10, 5)) plt.plot(log["epoch"], log["train/box_loss"], label="train_box_loss") plt.plot(log["epoch"], log["val/box_loss"], label="val_box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png", dpi=150)results.csv 是训练过程中自动写入的,每一行是一个 epoch 的汇总,列名包含 train/box_loss、val/box_loss、metrics/mAP50(B) 等。列名在不同 ultralytics 版本里略有差异,所以第一句先打印列名确认。把训练损失和验证损失画在一起,核心目的是观察两条曲线的走势:训练损失持续下降而验证损失在第某个 epoch 后开始回升,说明过拟合了,解决办法是减少 epochs、增大数据增强或缩小模型体积。如果曲线整体是锯齿状乱跳,先检查 batch 是不是太小——CPU 上 batch=1 训练出来的损失曲线就是一条剧烈抖动线,不代表模型有问题,这是单个样本的梯度噪声,属于正常现象,别被吓到。
5. 宿舍安全监测系统落地避坑:四个反复出现的部署问题
下面是这类型项目里出现频率最高的几个坑,每一条都是现象、原因、解决三步说清。这些坑不是模型原理层面的,全是环境、数据、界面联调时会遇到的实际问题,也是我见过最多人卡住的地方。
5.1 import ultralytics 报错或版本漂移
现象:conda 环境里 import ultralytics 提示 ModuleNotFoundError,或者昨天还能跑的训练命令今天突然报参数错误。
原因:本机装了不止一个 Python 环境,pip 命令实际装到了 base 环境,而不是当前激活的 conda 环境;另一个常见原因是 ultralytics 被后续某个操作自动升级了,新版本改了默认行为或接口。
解决:先用 python -m pip 替代裸 pip,保证装进当前环境;再查看当前版本并锁定依赖。
pip freeze | grep ultralytics python -m pip install ultralytics==你查到的版本号python -m pip 的关键在于它会绑定当前激活的 Python 解释器,而不是系统默认的那个。锁版本号的意义在于 ultralytics 迭代很快,大版本之间的训练参数和验证指标列名都可能变化,锁定版本后你的训练脚本、界面代码的行为可复现,不会因为重装环境而翻车。
5.2 训练时报 CUDA out of memory
现象:训练刚开始一两步就报 CUDA out of memory,进程直接退出,命令行里能看到类似“CUDA error: out of memory”的信息。
原因:batch size 开太大,或者数据加载器的多线程把多张图同时读入显存。很多人以为显存占用只跟模型体积有关,实际上 batch=8 时一次迭代要同时放 8 张 640×640 的图,加上中间激活值和梯度,占用会暴涨。
解决:把 batch 调到 2 或 1 重试。如果还想提高训练效率,不要只调大 batch,而是先把 workers 参数设小。
yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=100 batch=2 imgsz=640 workers=0workers=0 表示数据加载不开子线程,虽然读取速度会慢一点,但显存占用最小。这一步能用来看清楚你的显存边界在哪:batch=1 能跑,batch=2 爆掉,说明边界是 2,后面就按这个值训练,别跟显存较劲。
5.3 mAP 很高但现场检测不出来
现象:训练时验证集的 mAP50 在 0.8 以上,结果界面一打开摄像头,画面里明明有人却一个框都画不出来,或者框的位置错得离谱。
原因:训练数据和部署场景不一致,常见的有三种。训练图是白天光线充足的走廊截图,摄像头画面是夜间昏暗的寝室;部署时界面把画面缩放过小再喂给模型,比如训练时 imgsz=640,界面却把帧 resize 到 320 再推理;还有一种是摄像头画面是竖屏的,训练图是横屏的,长宽比差异导致目标形变。
解决:先把推理端的 resize 尺寸设置成和训练一致,确认这一步后再考虑场景差异。具体在界面代码里,找到调用 model.predict 的地方,把 imgsz 参数改成 640。如果尺寸一致后还是检测不到,基本就是训练数据和现场画面的光照、角度差异太大,解决办法是往训练集里加入现场采集的帧,哪怕标注几十张,效果也会有明显改善。宿舍这个场景尤其要注意夜间弱光画面,这是最常见的翻车点,别只拿白天截图训。
5.4 可视化界面调用摄像头闪退
现象:界面程序一开摄像头就弹窗报错,然后整个进程直接结束,连界面都关不掉。
原因:PyQt5 这类界面框架中,常见的做法是把摄像头读取放在主线程里,而 OpenCV 的 VideoCapture.read() 是阻塞调用,会导致界面消息循环卡死,严重时直接崩溃。另一个高频原因是摄像头被其他程序占用,比如浏览器、手机投屏软件占着摄像头不释放。
解决:把摄像头读取从界面主线程里拆出去,单独开一个子线程循环读帧,主线程只负责把最新一帧绘制到界面上。同时打开摄像头前先确认没有被其他程序占用。
import threading import cv2 class CameraThread(threading.Thread): def __init__(self, src=0): super().__init__() self.cap = cv2.VideoCapture(src) self.frame = None self.running = True def run(self): while self.running: ret, frame = self.cap.read() if ret: self.frame = frame这段代码只做了两件事:子线程里循环读取摄像头帧,存到公有变量 self.frame;主线程的界面刷新逻辑只管从这个变量取最新帧并显示。这样即使单帧读取偶尔慢一点,界面也不会卡死,最多是画面有点掉帧。如果加了线程仍然闪退,就先单独运行cv2.VideoCapture(0)的测试脚本,确认摄像头本身没被占用,再回来查界面代码。
5.5 训练用的类别与界面里的类别编号对不上
现象:模型推理正常,检测框也画出来了,但标签名全是乱的——人身上标着 smoke,或者显示的名字和训练时的类别完全对不上。
原因:YOLO 的类别识别靠 class id 而不是名称。训练时 data.yaml 里定义的第 0 类是 person,界面代码里的类名列表却是按另一种顺序写的,比如界面默认 0=smoke、1=fire,两边一错位,所有框的标签全串了。
解决:把界面代码里的类名列表和训练用的 data.yaml 同步一遍。常见做法是在界面代码里直接读取 data.yaml 生成类名列表,而不是手写死。
import yaml with open("dataset/data.yaml", "r", encoding="utf-8") as f: data_cfg = yaml.safe_load(f) class_names = data_cfg["names"]这个做法比在界面代码里手写["smoke", "fire", "person"]稳妥得多,因为训练配置是唯一的权威来源,界面直接读它,两边永远不会对不上。如果项目包里的界面代码已经写死了类名,改这里就行,权重里的类别顺序不会变。
6. 把检测结果接进可视化界面:一个避免重复推理的绘制技巧
训练好模型后,毕设界面最常见的实现方式是用 PyQt5 搭窗口,OpenCV 负责摄像头取帧和画框。这里有一个很多人绕远路的点:为了显示检测结果,在每一帧里调用两次模型推理,一次为了拿框,一次为了画图,或者把 results 对象里的坐标又手动换算一遍,白费工作量。
一个可靠的技巧是直接用预测结果的 boxes 对象在原始帧上画框,不再做任何坐标换算:
from ultralytics import YOLO import cv2 model = YOLO("runs/dorm_experiment/weights/best.pt") while True: ret, frame = cap.read() if not ret: break results = model(frame, imgsz=640, conf=0.4) for box in results[0].boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() cls = int(box.cls[0]) conf = float(box.conf[0]) label = f"{model.names[cls]} {conf:.2f}" cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, label, (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("dorm safety monitor", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break这段代码里最值得注意的两点是:box.xyxy 已经是原图坐标,直接拿去画矩形即可,不要自己再除以缩放系数;model.names 来源于训练时的 data.yaml,和 box.cls 天然对应,所以标签不会串。conf=0.4 是置信度阈值,宿舍场景下 0.4 比较稳,漏检多就降到 0.25,误报多就提高到 0.5,这个数值可以直接留到界面里做成滑块,方便答辩时演示。
还有一个习惯必须养成:model 初始化放在循环外面,在界面创建之前只加载一次。如果写在循环里,每帧都会重新读取权重文件,界面直接卡成幻灯片。这一点在 PyQt5 的定时器刷新回调里尤其容易犯,模型加载是毫秒级到秒级的操作,绝对不能放在槽函数里。
我习惯在项目目录里单独放一个 predict_single.py,只跑一张测试图,用来在改界面代码之前确认推理和画框逻辑没坏。这个习惯救过我很多次——界面 bug 和模型 bug 混在一起时,先定位是哪一层的毛病,再动手改,能省下大半调试时间。希望帮到你。
本文还有配套的精品资源,点击获取