简介:一套基于YOLO的安全监控系统设计代码包,面向计算机视觉、深度学习方向的毕业设计、课程设计与期末大作业场景,提供完整可运行的目标检测监控方案。项目采用YOLO算法,将目标检测任务转化为单阶段回归问题,实现实时视频流中的物体识别与分类,并针对监控需求优化模型准确度、降低虚警率,同时包含消息通知模块,可在检测到潜在危险时通过Kakao等渠道发送警告。资源共15个文件,压缩包仅28KB,以7个Python脚本为核心,涵盖实时流检测、危险检测、监控日志、消息发送等功能,另含3个JSON配置、3个Markdown说明文档及环境变量示例与.gitignore,便于快速部署与二次开发。目前已有46人学习下载,适合具备一定深度学习基础、需要参考完整工程结构与代码实现的开发者使用。从中可以获取项目架构设计思路、模型优化调参方法、数据准备与训练流程,以及使用说明和安装指南,是一份紧凑但信息量充足的实战参考。
1. 监控毕设选型与现实:基于YOLO的安全监控系统到底算不算一个好题目
做电子、计算机方向的毕业设计,只要和图像识别沾边,基于YOLO的安全监控系统几乎是每年都会出现的经典选题。好做,因为它技术路线固定、资料多、能演示;难拿高分,因为绝大多数人只交了「一个能跑通的目标检测模型」,而不是「一套完整的监控系统」。答辩时老师通常只问两个问题:你检测哪些类别?误报和漏报怎么处理?答不上来,mAP再高也白搭。
这套资源做的就是这件事:把 YOLO 从「模型」补成「系统」,覆盖环境配置、VOC 数据集转 YOLO 格式、类别裁剪、训练自己的检测器、视频与摄像头推理、误检抑制、边缘部署这几个完整环节。适合三类人:拿它当毕设主干想省搭建时间的、课程设计期末大作业需要现场演示交付的、想搞懂 YOLO 训练全流程但不想从零啃源码的。按第 2 章到第 5 章的顺序把链路跑通,你手里就是一份能讲清原理、能现场演示、能说清坑在哪里的毕设核心素材。
2. 系统架构与 YOLO 选型:七个核心模块加两类损失函数,是怎么凑成一个完整「系统」的
2.1 一个能答辩的系统,至少包含哪七个模块
很多人把毕设做成「一个训练好的模型 + 几张 test 图片」,这在答辩现场很容易翻车。监考老师想看到的是一个「输入视频流、输出告警记录」的闭环,而不是单张图片的检测演示。我在拆解这类项目时,一般会按下面七个模块去理解一套完整的监控系统:
| 模块 | 职责 | 说明 |
|---|---|---|
| 视频采集与帧抽取 | 读取摄像头 RTSP 流或视频文件,逐帧送入检测器 | OpenCV 的 VideoCapture 就能做,关键是丢帧策略 |
| 目标检测 | 对单帧图像输出类别、置信度、边界框 | 核心是 YOLO 模型推理 |
| 区域入侵判断 | 判断目标框是否进入预设 ROI 区域 | 用多边形包含关系或中心点坐标判断 |
| 告警去抖 | 连续 N 帧命中才触发告警,避免单帧误报 | 监控场景必备,否则飞鸟、光影变化都能触发 |
| 日志与截图 | 记录告警时间、截图、目标类别 | 毕设演示时最有说服力的部分是这里 |
| 模型管理 | 加载 best.pt / onnx,支持切换模型 | 方便你在答辩现场对比不同模型的差异 |
| 配置中心 | 管理 ROI、置信度阈值、检测类别白名单 | 阈值写死在代码里是新手最常见的毛病 |
这套资源里的代码和设计文档,基本上也是按这个思路组织的。换句话说,你拿到的不是「一个 predict 脚本」,而是一套可以按模块拆开讲给别人听的工程结构。答辩时你按「视频流入 → 检测 → ROI 判断 → 去抖 → 告警截图」这条链路讲,逻辑就顺了。
还有个容易被忽略的点:日志和截图模块虽然简单,但它决定了你能不能现场演示「这个系统真的在监控」。我见过不少同学训练完模型,却不知道往文件里写一条告警记录,最后只能反复跑同一张图。这是典型的「模型思维」而不是「系统思维」。
2.2 为什么选 YOLO 做主干:单阶段推理管够实时,损失函数要看懂
监控场景的硬指标是实时性。25 帧或 30 帧的视频流,单帧处理时间必须压到几十毫秒内,否则视频就会卡顿甚至丢帧。基于深度学习的目标检测里,两阶段检测器(Faster R-CNN 这类)精度可观,但单帧推理时间通常在 100ms 以上;YOLO 这类单阶段检测器把分类和回归放在同一个网络里一次完成,在同等硬件条件下推理速度快一个量级。这是监控项目大多选 YOLO 而不是 Faster R-CNN 的最直接理由。
从算法演进角度看,YOLOv5、YOLOv8 这类较新的实现已经转向 anchor-free,配合解耦分类头,收敛速度比早期版本更顺;损失函数由两部分构成:边界框回归损失(常见实现里是 CIoU)和分类损失(BCE)。监控场景调参时重点盯的是边界框回归损失,它直接决定检测框与目标贴合程度。如果框偏大或偏小,区域入侵判断就会出错——明明人没进 ROI,框的边缘先碰到了,告警就提前触发。
有个很容易被误解的点:很多人认为 YOLO 是「黑匣子」,训练完直接拿结果就行。但实际上,你对损失函数理解得越细,排错时越有方向。比如训练到后期发现 box_loss 一直在降、cls_loss 却震荡,说明分类样本不均衡,需要回查数据集,而不是盲目加 epoch。
2.3 检测类别裁剪与区域入侵:把通用 COCO 模型改成安防可用子集
直接拿 COCO 80 类的预训练权重做监控,不是不能用,但会带来两个问题:一是类别冗余,检测出「香蕉」「杯子」对安防毫无意义,还占算力;二是误报率升高,类别越多,类间混淆越严重。通用做法是裁剪类别,只保留安防相关的:person、bicycle、car、motorcycle、bus、truck、dog、cat 这几类。自定数据集时改 data.yaml 的 nc 和 names 即可:
# data.yaml 示例,路径换成你自己的 path: ./datasets/monitor train: train/images val: val/images nc: 3 names: ['person', 'car', 'dog']注意 nc 必须和 names 数量一致,且标签文件里的类别索引不能超过 nc-1。这个参数改错了,训练时大概率报索引越界,或者 mAP 恒为 0。第 4 章会单独展开这个坑。
区域入侵判断是监控系统区别于「纯检测」的关键。我一般用 ROI 多边形的点在多边形内算法(ray casting)判断目标框中心坐标是否落在区域内。阈值参数方面,置信度默认 0.25 偏保守,监控实景我通常调到 0.4 左右;NMS 的 IoU 阈值默认 0.7 在行人密集场景可以降到 0.5。这些参数直接写进配置中心,不要在代码里硬编码。
3. 环境搭建与数据准备:版本对照、预训练模型下载与 VOC 转 YOLO 脚本
3.1 环境配置:CUDA、PyTorch 与依赖链一次装对
YOLO 系项目的环境坑,九成出在版本错配上。核心规则是:先确认显卡驱动支持的 CUDA 版本,再选择对应编译的 PyTorch,最后安装 ultralytics 依赖。新手最容易犯的错是直接 pip install torch 装到 CPU 版,训练时才发现用不了 GPU。检查驱动的命令是 nvidia-smi,记住:这里显示的 CUDA 版本是你的驱动最高支持的版本,不要求 PyTorch 的 CUDA 版本正好等于它,但要求 PyTorch 的版本 ≤ 驱动版本。
# 创建独立环境,避免搞乱系统 Python conda create -n yolo_monitor python=3.10 -y conda activate yolo_monitor # CUDA 11.8 对应安装命令,镜像源替换了默认源 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python装完后一定做一次全链路自检,确认 PyTorch 真的能看到 GPU:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())" python -c "import torch; print(torch.cuda.get_device_name(0))"第一行输出里有 cuda 且第二行输出你的显卡型号,才算环境合格。如果第一行返回 False,说明装成了 CPU 版,卸载重装带 cu118/cu121 后缀的版本,不要继续往下走。> 提示:torch.cuda.is_available()返回 True 只代表能用 CUDA,不代表训练一定顺利。显存不足、cuDNN 版本异常都会在训练中途报错,后文避坑章节会展开。
3.2 预训练模型下载与校验:先跑通再谈训练
训练自有数据集前,我一般会先下载官方预训练权重做一次推理验证。这样能提前暴露环境问题,避免把「环境坏了」和「数据错了」混在一起排查。YOLO 预训练权重从官方 GitHub Release 或国内镜像站都能找到,文件后缀是 .pt。下载后别急着训练,先做两件事:确认文件没损坏,以及在单张图上跑通推理。
# 快速验证预训练权重可用性 from ultralytics import YOLO model = YOLO("yolov8s.pt") # 换成你下载的权重路径 result = model.predict("test.jpg", conf=0.4, imgsz=640) print(result[0].boxes.cls) # 打印检测到的类别索引 print(result[0].boxes.conf) # 打印置信度这段代码的用意是验证「模型加载 → 前向推理 → 结果解析」这条链路是通的。如果这里就报错,问题基本集中在 torch 和 ultralytics 版本兼容性上。建议先用 yolov8n.pt 这种最小的权重做验证,环境没问题了再换大权重训练。监控场景选型上,yolov8n 适合树莓派这类边缘设备,yolov8s 是毕设性价比选择,mAP 和速度平衡比较好。注意:预训练模型下载属于常规开源操作,走官方渠道或学校镜像即可,不要用来源不明的第三方打包权重,那种文件里被塞了恶意代码的话你根本察觉不到。
3.3 自己的数据集:VOC 标签转 YOLO 格式的转换脚本
多数公开数据集或课程配套数据是 VOC 格式(XML 标签),而 YOLO 训练需要 txt 格式的归一化坐标。这一步卡住过很多人,实际上一个脚本就能解决。VOC 的 XML 里记录的是绝对坐标,YOLO 需要的是相对于图像宽高的中心点坐标和宽高比例。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: continue # 跳过不在类别表里的目标 box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) # 过滤无效框:坐标越界或宽高为 0 直接丢弃 if x2 <= x1 or y2 <= y1: continue x_center = (x1 + x2) / 2 / img_w y_center = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: base = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base + ".txt"), "w") as f: f.write("\n".join(lines)) # 类别到索引的映射必须和 data.yaml 里的 names 顺序一致 class_map = {"person": 0, "car": 1, "dog": 2} for xml_file in os.listdir("xmls"): if xml_file.endswith(".xml"): voc_to_yolo(os.path.join("xmls", xml_file), "labels", class_map)脚本里有三个地方容易踩坑:一是 cls_map 的顺序必须和 data.yaml 一致,否则训练出来的模型类别全错位;二是忘记处理坐标超出图像边界的情况,转换后会出现大于 1 的归一化坐标,训练时直接报错;三是类别名字大小写不一致(比如 Person 和 person),会被 class_map 静默过滤掉,导致某些图片没有标签文件。转换完成后,统计一下每个类别的标签数量,如果某个类别只有几十个框,训练结果大概率类别不平衡。数据集目录结构统一按下面这样组织:
datasets/monitor/ ├── train/ │ ├── images/ # 训练图片 │ └── labels/ # 对应的 txt 标签 └── val/ ├── images/ └── labels/4. 避坑实录:五个让监控检测训练翻车的高频问题与解决步骤
这些坑是我在多个 YOLO 监控项目里反复见过的血泪经验,每一个都对应「现象 → 原因 → 解决」三步排查路径。训练前先把这五条过一遍,能省下大把挠头时间。
4.1 训练到一半 loss 变成 NaN:BN 层崩溃
现象:前几个 epoch loss 正常下降,某一步之后 box_loss 和 cls_loss 突然变成 nan,之后指标一直无法恢复。
原因:最常见是初始学习率过大,配合混合精度训练(AMP)导致梯度溢出。数据增强里 Mosaic 拼接如果存在大量空白标签图,也会放大这个问题。YOLO 默认的 lr0 在 0.01 左右,但如果你的数据集很小或类别很少,0.01 就是偏高的。
解决:把 lr0 降到 0.001,或者在配置里直接关闭 AMP。如果数据增强开到最大,先把 Mosaic 关闭再训一轮,确认 loss 能正常下降后再逐步开回来。出现 NaN 之后不要想着「再跑几个 epoch 自己恢复」,不会的,直接改参数重启。
4.2 CUDA OOM 报错:不是换张显卡就能解决的事
现象:训练脚本跑完第一个 epoch 甚至刚加载数据就报 CUDA out of memory,有时还把显卡驱动卡死。
原因:显存占用和 batch size、imgsz、Mosaic 增强同时相关。很多人 batch 设置成 16,imgsz 拉到 1280,却用着一张 8GB 显存的卡。另一个原因是没有关闭数据加载器的工作线程占用,导致 CPU 和 GPU 之间的内存通道被塞满。
解决:先按 batch=8、imgsz=640 训练,能跑通再逐步调大。设置 workers=4 或更低,pin_memory=True。如果你用的是 4GB 显存的笔记本显卡,imgsz 降到 512,或者直接用 yolov8n。注意在配置里加一句 cache=False,避免 ultralytics 尝试把整份数据集缓存进显存。有个小技巧:先用一张小图测试前向推理,确认模型本身能跑,再开训练。
4.3 类别索引越界与 mAP 恒为 0:数据标签和模型头对不上
现象:训练能启动,loss 也有值,但验证集 mAP 永远是 0,或者某个 epoch 报 IndexError: index 3 is out of bounds for axis 0 with size 3。
原因:data.yaml 里 nc 设了 3,但标签文件里出现了类别索引 3 甚至更大的值。多半是类别映射表没对齐,比如 VOC 转换脚本里 class_map 的顺序和 data.yaml 的 names 顺序不一致。还有一种情况:背景图没有标签文件,但被 Mosaic 拼进了有目标的图,导致空标签参与训练。
解决:写一个标签扫描脚本,读取所有 txt,统计类别索引的最大值和分布。如果最大值 ≥ nc,立即修正类别映射。另一个根治办法是训练前跑一遍数据集校验:统计有标签的图片数量和无标签图片数量,无标签图占比过高时从训练集里剔除,避免模型学到「空白区域也正常」的错觉。
# 扫描标签目录,检查类别索引是否越界 import os label_dir = "datasets/monitor/train/labels" max_cls = 0 empty = 0 for f in os.listdir(label_dir): path = os.path.join(label_dir, f) with open(path) as fp: lines = fp.readlines() if not lines: empty += 1 continue for line in lines: cls = int(line.split()[0]) max_cls = max(max_cls, cls) print("最大类别索引:", max_cls, " 空标签文件数:", empty) # 如果 max_cls >= data.yaml 里的 nc,标签有问题,需要修数据4.4 混淆矩阵总合不唯一:多标签与评价指标的错位
现象:训练结束后打印混淆矩阵,发现按行求和不是 1,或者按列求和也对不上。很多人以为是自己算错了。
原因:混淆矩阵的归一化逻辑是按行或按列单独进行的,如果你的数据里存在一张图同属多个类别(比如一张图里同时有人和车,两个标签都标注了),某些预测会被重复计数。另一个来源是模型输出了多个相近的框,NMS 没有完全去重,导致同一目标被算进多个类别。
解决:训练数据里尽量保持单标签策略——同一个目标只标注一个主类别。如果场景里人和车重叠严重,接受混淆矩阵的行和不为 1 是正常的,答辩时主动解释这一点比被老师问住要好。还可以用 predict 模式输出的 conf 和 cls 分布,画一张预测类别分布图,和标签分布对比,能快速发现某一类被系统性误判成另一类的问题。
4.5 远距离目标漏检与误检过高:监控场景的阈值调优
现象:训练完模型,视频测试时发现远处的人检测不到;反过来,某些场景下树影、飞鸟反复触发告警。
原因:漏检通常是因为输入分辨率太低。640×640 的输入下,远处行人只占几十个像素,特征图下采样后几乎不可见。误检则是置信度阈值取默认值 0.25 太低,监控画面里干扰物多,系统把「像人但不是人」的区域也框出来了。
解决:两个方向分开处理。漏检优先提高 imgsz 到 1280,同时确认显存能撑住;撑不住就用滑动窗口裁剪推理。误检则调高 conf 到 0.45 以上,并把告警去抖逻辑加上:连续 3 帧以上在同一区域命中才触发告警。这套组合拳下来,误报率能显著下降。监控实景里「置信度阈值」比「模型结构」对体验的影响更大,建议做一组对比实验记录不同阈值下的误报数据,答辩有素材可讲。
5. 训练验收与边缘部署:用 best.pt 导出 ONNX,把监控系统落到实机
5.1 训到哪一步才算合格:看损失,也看 PR 曲线
训练命令本身不复杂,关键是会读输出。推荐命令行训练后用 TensorBoard 或 ultralytics 自带的 results.csv 看曲线,重点盯 box_loss 和 mAP50-95 两个指标。只降 loss 不代表模型好,必须同时看验证集的 mAP。
yolo detect train data=data.yaml model=yolov8s.pt epochs=80 imgsz=640 batch=16 device=0 project=runs name=monitorepochs 不是越大越好,监控场景 80 到 120 轮一般够用。超过 150 轮如果不加早停,很容易过拟合。训练结束后用 runs/monitor/weights/best.pt,而不是 last.pt——这个选择很多人会忽略,last 是最后一轮的权重,过拟合风险高;best 是验证集 mAP 最高的那轮。
5.2 把模型接到视频流:推理与告警截图逻辑
训练完的模型最终要接到视频流上才算完成闭环。代码逻辑按「逐帧检测 → ROI 判断 → 帧间去抖 → 告警截图」四步走:
from ultralytics import YOLO model = YOLO("runs/monitor/weights/best.pt") def is_in_roi(cx, cy, roi): # roi 是多边形顶点列表,返回点是否在多边形内 inside = False px, py = roi[-1] for x, y in roi: if (y > cy) != (py > cy) and cx < (x - px) * (cy - y) / ((py - y) or 1) + px: inside = not inside px, py = x, y return inside cap = cv2.VideoCapture("monitor.mp4") # 换成 RTSP 地址 frame_count = 0 hit_count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break result = model.predict(frame, conf=0.45, imgsz=640)[0] for box in result.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 if is_in_roi(cx, cy, roi_points): hit_count += 1 else: hit_count = 0 if hit_count >= 3: # 连续3帧命中才触发告警 cv2.imwrite(f"alerts/{frame_count}.jpg", frame) hit_count = 0 frame_count += 1关键参数是 conf=0.45 和连续命中帧数 3,两者共同决定误报率。这份代码里的 ROI 判断函数是简化的点判断,你可以换成 OpenCV 的 pointPolygonTest 更省事。RTSP 源注意加?tcp参数避免 UDP 丢包。
5.3 落地与扩展:ONNX 导出与火灾监控方向
毕设如果做到这里还有余力,强烈建议把模型导出成 ONNX,再走一次推理验证。导出命令很简单:yolo export model=best.pt format=onnx opset=12,opset 别太高,部分边缘设备推理引擎对高版本 opset 支持不全。导出后写几行代码用 onnxruntime 跑一遍同样的图,确认输出和 PyTorch 推理一致。想上树莓派或 RK3588 这类设备,接着转 RKNN 或 TensorRT,这是目前边缘部署的主流路线。
如果想给毕设加点亮点,把检测类别从人、车换成火焰、烟雾,配上红外数据集,就是一套火灾监控系统。这类数据大多是单通道红外图,训练时把输入改成灰度三通道复制即可。我后来再碰监控类项目,都会要求自己先把 RO I 区域和置信度阈值写进配置,再开始训练数据。这个习惯是从第一次现场演示翻车后养成的——当时系统把墙上的影子当成人,连着触发了十几次告警,全场都在看笑话。希望帮到你。
本文还有配套的精品资源,点击获取