基于YOLO的安全监控系统实战:目标检测、训练避坑与边缘部署
发布时间:2026/10/1 13:52:32来源:尧图网络
简介一套基于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 python3.10 -y conda activate yolo_monitor # CUDA 11.8 对应安装命令镜像源替换了默认源 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python装完后一定做一次全链路自检确认 PyTorch 真的能看到 GPUpython -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, conf0.4, imgsz640) 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 变成 NaNBN 层崩溃现象前几个 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 设置成 16imgsz 拉到 1280却用着一张 8GB 显存的卡。另一个原因是没有关闭数据加载器的工作线程占用导致 CPU 和 GPU 之间的内存通道被塞满。解决先按 batch8、imgsz640 训练能跑通再逐步调大。设置 workers4 或更低pin_memoryTrue。如果你用的是 4GB 显存的笔记本显卡imgsz 降到 512或者直接用 yolov8n。注意在配置里加一句 cacheFalse避免 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 datadata.yaml modelyolov8s.pt epochs80 imgsz640 batch16 device0 projectruns namemonitorepochs 不是越大越好监控场景 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, conf0.45, imgsz640)[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(falerts/{frame_count}.jpg, frame) hit_count 0 frame_count 1关键参数是 conf0.45 和连续命中帧数 3两者共同决定误报率。这份代码里的 ROI 判断函数是简化的点判断你可以换成 OpenCV 的 pointPolygonTest 更省事。RTSP 源注意加?tcp参数避免 UDP 丢包。5.3 落地与扩展ONNX 导出与火灾监控方向毕设如果做到这里还有余力强烈建议把模型导出成 ONNX再走一次推理验证。导出命令很简单yolo export modelbest.pt formatonnx opset12opset 别太高部分边缘设备推理引擎对高版本 opset 支持不全。导出后写几行代码用 onnxruntime 跑一遍同样的图确认输出和 PyTorch 推理一致。想上树莓派或 RK3588 这类设备接着转 RKNN 或 TensorRT这是目前边缘部署的主流路线。如果想给毕设加点亮点把检测类别从人、车换成火焰、烟雾配上红外数据集就是一套火灾监控系统。这类数据大多是单通道红外图训练时把输入改成灰度三通道复制即可。我后来再碰监控类项目都会要求自己先把 RO I 区域和置信度阈值写进配置再开始训练数据。这个习惯是从第一次现场演示翻车后养成的——当时系统把墙上的影子当成人连着触发了十几次告警全场都在看笑话。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网