☰
基于YOLOv5的异常行为检测实践:从环境搭建到部署避坑指南
2026/9/28 1:09:08 网站建设 项目流程

简介:这是一份面向本科计算机专业毕业设计的基于YOLOv5的异常行为检测完整实战资料,适合毕业设计参考、课程设计和技能学习。它围绕目标检测模型从环境配置、数据处理到训练推理的全流程展开,帮助读者快速上手并形成可复现的项目方案;文档详细,适合初学者按步骤推进,也适合有基础者快速复用。压缩包共212个文件,约2.81MB,核心以105个yaml配置文件和45个Python脚本为主,配合测试图片、Docker部署文件、Jupyter教程等内容,构成完整的项目代码框架。目前已有637人学习下载。资料是作者整理的毕业设计真实成果,内含模型配置、数据集标注组织、训练与推理脚本及常见排错思路,并附有测试样例图片,可验证模型效果;既能作为毕业设计完整参考,也可作为课程设计或YOLOv5工程化入门的学习素材。

1. 基于YOLOv5的异常行为检测:一份能直接跑通的毕业设计工程包

异常行为检测是目标检测在安防、校园监控、养老院场景里最常见的毕业设计选题,但真正动手才知道坑比想象中多——环境装到一半就劝退,数据集标注到崩溃,训练出来的模型不是漏检就是误检。这个资源包我拆完的感受是:它把从零到一的过程收敛成了一整套可以直接跑的工程,Dockerfile、教程、测试图片、配置项全部齐备,不是那种只丢几个py文件的半成品。里面带tutorial.ipynb,跟着走一遍就能把模型训练和推理串起来,bus.jpg和Mosaic示例图用来验证预处理和标签格式对不对,省掉排查数据管线的时间。适合三类人:本科毕设选了这个题目的、想系统学YOLOv5源码的、工作中要在监控项目里叠加行为识别能力的。接下来按环境、数据、训练、避坑、部署五条线把要点和踩坑记录都讲清楚。

2. 环境搭建与项目结构:conda和Docker两条路都要会

2.1 为什么选YOLOv5而不是更新版本的模型

YOLOv5虽然名字里带v5,但到现在依然是工程落地最稳的选择。YOLOv8、YOLOv9的检测精度确实更高,但社区沉淀的教程、issue讨论、部署方案都远不如v5丰富。做毕业设计,答辩老师大概率也最熟悉YOLOv5的内部结构,你讲backbone、neck、head的改动时,对方能从源码层面和你对话,换成YOLOv8那个C2f模块,反而增加沟通成本。

另一个务实的理由是改造成本。YOLOv5的模型结构把backbone、neck、head拆分得很清楚,自定义检测头或者改PANet都容易下手。这个资源包里的文件结构我看了一下,就是标准的YOLOv5官方布局:Dockerfile负责容器化环境,setup.cfg管理包的metadata,tutorial.ipynb把训练到推理的流程做成可执行文档。拿到手你不需要重新组织代码,直接在这个骨架上做二次开发。从工程角度讲,YOLOv5的issue几乎你能踩的坑都有人踩过,搜个报错马上能找到解决方案,这一点在答辩前一周救过不少人的命。

2.2 conda方式安装:CPU机也能跑,GPU机要核对版本

资源包里虽然带了Dockerfile,但日常开发调试我更习惯先用conda把环境拉起来,改代码迭代的速度比容器里快。YOLOv5的依赖项不算多,核心就是torch、torchvision、opencv、numpy、pandas、matplotlib、seaborn,外加一个pyyaml读配置。setup.cfg里列的就是这些基础依赖,不需要额外装奇怪的东西。

conda create -n yolo5 python=3.9 -y conda activate yolo5 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt

第一行创建Python 3.9的独立环境,命名为yolo5。Python版本不要选3.11以上,有些旧版本依赖包编译会出问题。第二行激活环境。第三行指定CUDA 11.8对应的PyTorch版本,如果你的显卡驱动只支持CUDA 12.x,把路径换成cu121或cu124。最后一行安装requirements.txt里的包,这个文件在YOLOv5仓库根目录下,资源包里也应该有,没有的话手动装上面列的那几个也行。

逻辑说明:YU在YOLOv5的官方代码里仍在使用,所以PyTorch的匹配版本要在2.0到2.2之间,太新或太旧都可能出现算子不支持的情况。参数说明:--index-url指定了PyTorch官方预编译包的下载源,CPU用户把这段去掉直接pip install torch就行。装完后验证一下:

python -c "import torch; print(torch.cuda.is_available())"

输出True说明GPU可用,False就回到CPU模式跑,速度慢点但能出结果。这一步很多人忽略,训练到一半报CUDA out of memory才发现装的CPU版,浪费半天时间。我一般装机后的第一件事就是跑这条验证命令,顺手再nvidia-smi看一眼驱动支持的CUDA版本,确保和PyTorch编译版本匹配。

2.3 Docker方式:答辩演示不翻车的关键

资源包里带了Dockerfile,这对毕设演示来说是个大杀器。答辩现场用自己笔记本跑环境,最怕的是前一天还好好的,第二天开机驱动失效或者conda环境被不小心动过。Docker把整个环境连同依赖一起打包,在哪个机器上跑行为都一样。

# 基于CUDA 11.8基础镜像构建,带cuDNN加速 FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu20.04 ENV DEBIAN_FRONTEND=noninteractive # 安装Python 3.9和系统依赖 RUN apt-get update && apt-get install -y python3.9 python3.9-dev python3-pip wget git libgl1 libglib2.0-0 # 复制项目代码到容器 WORKDIR /app COPY . /app # 安装Python依赖 RUN pip install --no-cache-dir -r requirements.txt # 暴露Jupyter端口 EXPOSE 8888 CMD ["jupyter", "notebook", "--ip=0.0.0.0", "--port=8888", "--allow-root"]

这个Dockerfile的做法是先把系统依赖装好,注意libgl1和libglib2.0-0这两个包,OpenCV在容器里跑必须要它们,缺了会报libGL.so.1找不到的错误。然后复制项目代码、安装Python依赖、暴露Jupyter端口。构建命令和启动命令如下:

docker build -t yolo5-anomaly . docker run -it --gpus all -p 8888:8888 -v /home/user/datasets:/app/datasets yolo5-anomaly

--gpus all参数把宿主机GPU透传给容器,这一步依赖NVIDIA Container Toolkit,没装的话在宿主机执行sudo apt-get install nvidia-container-toolkit后重启Docker。-v把宿主机的数据集目录挂载进容器,这样容器销毁后训练结果还在。启动后浏览器访问8888端口就能打开tutorial.ipynb,跟着教程一步步跑。

小提示:如果Docker里跑训练显存不够,多半是基础镜像使用的CUDA版本和驱动不匹配。先nvidia-smi看驱动支持的CUDA版本,再选对应的镜像tag,这步省不掉。

3. 数据准备与标注:异常行为检测的核心工作量

3.1 行为类别怎么定:从场景反推标签体系

异常行为检测的目标是识别出不符合场景常规的行为模式,在毕设里最常见的三个类别是打架斗殴、跌倒、翻越围栏。但这里有个区别需要讲清楚:YOLOv5是单帧检测模型,它看到的是一张静态图片,而行为和动作天然是时序概念。这个矛盾决定了你的标注策略——要么把动作拆成关键姿态帧,比如跌倒就取倒地瞬间的帧作为正样本,要么用多帧投票的方式做后处理,连续5帧里有3帧检测到跌倒才触发告警。

资源里的bus.jpg和Mosaic示例图恰好帮你理解这个场景。bus.jpg是典型的人群密集场景,异常行为很少发生在空地上,所以数据集里要包含大量拥挤场景的负样本。Mosaic_without_label和Mosaic_with_label两张图则是训练数据增强的直观展示,四张图拼接后通过放缩减小了目标尺寸,让模型不得不学习更鲁棒的特征。我在这类项目里通常把类别数控制在4个以内,样本数最少的类别也要有800张以上,低于这个数模型很难收敛出稳定特征。

标注环节我推荐用LabelImg,界面简单且直接输出YOLO格式。每个类别有一个索引编号,编号从0开始,打架斗殴是0、跌倒是1、翻越是2、闯入禁区是3。这个顺序在训练配置文件和标注工具里必须严格一致,否则模型学到的和你想表达的类别对不上。第一次做标注容易漏掉密集人群里的小目标,我在标注时会强制要求自己把图片放大到200%逐块扫一遍,虽然慢但样本质量直接决定模型上限。

3.2 数据目录组织和标签格式校验

YOLOv5训练时要求数据目录结构固定,images放原图,labels放标签文件,图片和标签同名。数据集还要按train和val划分,两个集合不能有重叠。下面的命令创建标准目录结构并完成初步划分:

mkdir -p dataset/images/train dataset/images/val mkdir -p dataset/labels/train dataset/labels/val # 按8:2比例随机划分图片 ls dataset/all_images | sort -R | head -n 800 > train_list.txt ls dataset/all_images | sort -R | tail -n 200 > val_list.txt while read img; do mv dataset/all_images/$img dataset/images/train/ mv dataset/all_labels/${img%.jpg}.txt dataset/labels/train/ done < train_list.txt

先创建训练集和验证集的图片目录、标签目录。然后用sort -R打乱图片列表,head -n 800取前800张作为训练集,tail -n 200取后200张作为验证集。最后循环把对应图片和标签分别移动到目标目录。这里有个关键点:图片和标签文件的匹配靠的是同名不同后缀,比如frame_001.jpg对应frame_001.txt。移动时用${img%.jpg}.txt把后缀替换掉,这行逻辑如果写粗心很容易造成标签缺失。

目录结构搭好后,还要校验一下标签内容是否合法。YOLO格式的标签每行是五个数字:类别索引加四个归一化坐标。第一个数必须是整数,后四个数必须在0到1之间。下面这段Python脚本能快速排查标签问题:

import os label_dir = 'dataset/labels/train' for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname)) as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) != 5: print(f'{fname}:{i} 列数不对, got {len(parts)}') continue cls = int(parts[0]) x, y, w, h = map(float, parts[1:]) if x < 0 or x > 1 or y < 0 or y > 1: print(f'{fname}:{i} 中心点越界, x={x}, y={y}') if w <= 0 or h <= 0: print(f'{fname}:{i} 宽高异常, w={w}, h={h}')

这个脚本遍历标签目录下每个txt文件,对每行做三项检查:是否是五个字段、中心点x和y是否在[0,1]范围内、宽高是否为正数。逻辑说明:YOLO格式的坐标是相对图片尺寸归一化后的值,如果中心点越界或宽高为负,训练时损失计算会出现NaN或者目标框飞出图片边界的情形。这些肉眼看不出来的脏数据,会在训练到一半的时候以loss爆炸的形式突然冒出来,提前查一遍等于给自己省一次重训的折腾。

3.3 Mosaic增强原理与验证

资源包里的mosaic_with_label.jpg和mosaic_without_label.jpg两张图是非常重要的教学参考。Mosaic是YOLOv5的核心数据增强策略之一,把四张训练图片随机缩放裁剪后拼接成一张新图,每张子图保留自己的标注框,这就是mosaic_with_label的含义。不带标签的那张图是没勾选Label边界框时的可视化效果,用来对比标注框在拼接后的变化。

Mosaic能显著提升模型对遮挡和小目标的检测能力,因为拼接操作模拟了一个画面里同时出现多个不同尺度的目标。但Mosaic不是一直开启就好。训练后期如果mosaic_prob还保持1.0,模型会在拼接图的分布上过拟合,导致真实场景里表现下降。YOLOv5在训练最后10个epoch会自动关闭Mosaic,这个设计是官方内置的,你在日志里会看到Closing mosaic的提示,属于正常现象。

还有一类翻车案例是自定义数据加载器时没实现Mosaic或误改了概率参数,导致训练日志正常但精度始终上不去。建议用资源里这两张图做个快速自检:把自己的训练数据跑一次数据加载器,把输出的batch保存成图片,对比Mosaic开启前后的差异。如果拼接图里标注框位置明显错位,就要检查标签格式或随机拼接的逻辑了。验证脚本很短:

import cv2 from yolov5.models.experimental import attempt_load # 读取本地图片,验证数据加载器输出 img = cv2.imread('mosaic_with_label.jpg') print('Image shape:', img.shape)

4. 训练自己的数据集:超参数与调优实践

4.1 模型选型与配置文件修改

YOLOv5提供了n/s/m/l/x五个不同尺度的模型,从轻量到高精度。异常行为检测的场景通常在固定摄像头下,目标尺寸中等偏大,不需要追求极致精度而牺牲速度。我一般推荐从yolov5s开始调,跑通整个流程后再视精度需求升级到m。没必要一上来就练l或x,训练时间成倍增加不说,小数据集上还可能过拟合。

模型配置文件是yolov5s.yaml,训练前必须改两个地方:nc改成你的类别数,然后确认depth_multiple和width_multiple保持默认。对异常行为检测来说,类别数一般不超过4,所以模型的head部分和COCO的80类差别很大,改动后模型参数的运算量明显下降。改完后的yaml文件类似这样:

# yolov5s_anomaly.yaml nc: 4 # 类别数:打架、跌倒、翻越、闯入 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [10,13, 16,30, 33,23] - [30,61, 62,45, 59,119] - [116,90, 156,198, 373,326]

改动集中在第一行nc,anchors保留官方值即可。anchors是根据COCO数据集聚类得到的,如果你的目标尺寸分布差异特别大,比如全是小目标,可以通过python utils/autoanchor.py重新聚类。但异常行为的检测目标通常是完整的人体或半身,尺寸分布接近COCO的行人类别,用默认anchor就够了,强行重算还可能在答辩时被追问合理性。

训练数据配置在data.yaml里,这个文件指定训练集和验证集路径、类别数、类别名。路径建议写绝对路径,写相对路径容易因为工作目录切换导致FileNotFoundError。类名顺序要和标注时的编号一致,我从0开始数。

# anomaly_data.yaml train: /home/user/dataset/images/train val: /home/user/dataset/images/val nc: 4 names: ['fight', 'fall', 'climb', 'intrusion']

train和val指向图片目录,YOLOv5会自动找到对应的labels目录。names的排列顺序决定了模型输出的类别含义,在后续推理和可视化里要反复用到,建议现在就用全小写单词命名,别用缩写。推理时显示类别名,答辩演示数据集的类别名清晰会让整个项目显得更专业。

4.2 训练命令与超参数调节

训练入口是train.py,最小可用参数集合是这样的:

python train.py \ --weights yolov5s.pt \ --data anomaly_data.yaml \ --hyp hyp.scratch-low.yaml \ --epochs 100 \ --batch-size 16 \ --img 640 \ --device 0 \ --name anomaly_exp1

逐行说下参数含义。--weights指定预训练权重,从官方仓库下载的yolov5s.pt是在COCO上训好的,迁移到你的数据集上能省大量训练时间。--data就是刚才写的数据集配置文件。--hyp指定超参数文件,hyp.scratch-low.yaml是官方给的低配置超参,显存小的机器用它更稳妥。--epochs是训练轮数,100轮在大约1000张训练图上差不多能收敛,数据量更少可以减到60轮。--batch-size受显存制约,16GB显存跑yolov5s配16的batch没问题,显存不够就降到8或4。--img是训练输入尺寸,640是精度和速度的平衡点。--device 0指定第0块GPU,CPU训练改成cpu。--name是实验名,输出会保存在runs/train/anomaly_exp1目录下。

训练过程中重点关注两个信息:loss曲线和mAP指标。YOLOv5在训练日志里会每100轮输出一次验证集的mAP50和mAP50-95,前者表示预测框和真实框的IoU超过0.5时算作正确的精度,后者是对IoU阈值从0.5到0.95取平均,更严格。如果看到mAP50在最后10轮还在明显上升,说明还没收敛完,需要增加epochs;如果mAP50已经很高而mAP50-95很低,说明预测框的定位精度不够,可以调整NMS的iou阈值或者换大模型。

超参数的调节要克制,我见过很多人在训练阶段陷入调参深渊。出现loss震荡时优先减小学习率,动lr0和lrf:

# hyp.scratch-low.yaml 中动态调整 lr0: 0.003 # 初始学习率,震荡时降到0.001 lrf: 0.12 # 最终学习率 = lr0 * lrf warmup_epochs: 2.0 # 预热轮数,帮助收敛稳定

lrf表示学习率衰减到初始值的比例,warmup_epochs让模型在头两个epoch用较小的学习率做预热,之后再按余弦退火衰减。这组参数是官方推荐的基础配置,在没有明显依据的情况下不要大改。改了超参数后训练日志里会有对比,你是基于实验结果做调整,这句话在答辩时比任何理论推演都有说服力。

4.3 训练结果验证:不只是看mAP

训练结束后,runs/train/anomaly_exp1目录下会生成weights/best.pt和weights/last.pt两个权重文件。best.pt是验证集表现最好的模型,last.pt是最后一批epoch的模型。部署和测试一律用best.pt,这个习惯要养成。目录下的confusion_matrix.png给你的是每个类别的误检和漏检情况,异常行为检测最怕的是跌倒被检测成打架,这种混淆在图中一眼可见,发现混淆后去检查是不是标注时把两种动作的边界帧归类错了。

results.png把六条曲线画在一张图上,包括box_loss、cls_loss、obj_loss和mAP曲线。判断模型是否健康的经验是:三行loss曲线都应该单调下降然后趋于平稳,如果有任何一条突然上翘再下降,属于局部波动不需要管;如果尾部持续上行,那就要检查是不是学习率没降到位或者数据里有异常标签。最后我习惯跑一次验证脚本,在验证集上得到可视化的检测结果,确认预测框的置信度数值落在合理区间:

python val.py \ --weights runs/train/anomaly_exp1/weights/best.pt \ --data anomaly_data.yaml \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45

--conf-thres 0.25意思是预测框的置信度超过0.25才保留,低于这个值的都视为背景;--iou-thres 0.45是NMS的IoU阈值,两个框重叠超过45%时只保留置信度更高的那个。这两个值会影响最终mAP数字,验证时用的阈值要和预测推理时保持一致,否则报出来的指标没有参考价值。输出里会有一个表列出每个类别的精度、召回率、mAP50,想上桌面演示前先确认每个类别的mAP50都在0.8以上。

5. 避坑:YOLOv5异常行为检测的五个常见问题

5.1 loss直接变成NaN,训练崩溃

训练到几十个epoch,突然看到loss: nan,然后模型权重导出后完全不能推理。

原因是学习率太高导致梯度爆掉,或者数据里有坐标异常的目标框,比如宽高为0的标签。还有一种是数据集里存在完全空白的图片,网络前向传播计算出NaN。

我一般先停止训练,检查标签里是否有非法值,用前面那个校验脚本扫一遍。确认没问题后,把learning率从默认值降到0.001再调小batch-size重训。还不行就检查图片本身,YOLOv5有缓存机制,把--cache参数去掉重新跑。

5.2 检测框每帧乱跳,置信度忽高忽低

单帧检测正常,但连续视频帧里同一个目标框的位置和类别频繁变化,跌倒检测到一半框消失又出现。

原因在于NMS的iou阈值设得太低而置信度阈值设得太高,相邻帧的检测结果不稳定。另外模型在小目标上的定位精度不够,目标轻微移动就让检测框大幅漂移。

把--iou-thres从0.45降到0.3,--conf-thres从0.25降到0.15,框会变多但稳定性改善。更根本的解法是在推理端做时序平滑:连续三帧同一个位置都检出同一类别才输出告警,单帧的偶发结果直接丢弃。

5.3 Docker里训练报CUDA error,宿主机GPU用不了

在容器内跑训练,nvidia-smi能看到GPU,但PyTorch报CUDA error: no kernel image is available。

这是因为Docker镜像里的CUDA版本和宿主机的NVIDIA驱动不兼容,镜像是CUDA 11.8的,但宿主机驱动只支持到CUDA 11.4,PyTorch的算子编译时用的版本高于驱动能支持的版本,于是运行时崩溃。

解决办法是换镜像tag,把基础镜像的版本降下来匹配驱动。执行nvidia-smi看右上角的CUDA Version,比这个版本低一档的镜像基本安全。如果项目已经构建了不想重来,也可以把PyTorch版本换成CPU版凑合跑,速度慢但结果等价。

5.4 数据集带中文路径,训练时读取不到图片

Windows下做标注时用了中文目录,Linux下训练报FileNotFoundError,但路径看起来完全正确。

原因是YOLOv5读路径时用的字符编码默认是utf-8,Windows中文环境可能是gbk,两种编码对同一个路径字符串的字节表示不同,Linux端解码就失败了。

一个是把数据集整个移到纯英文路径,一劳永逸;另一个是在代码里强制指定编码,用open(path, encoding='utf-8')替换默认的读取方式。我建议直接改路径,数据集命名规范用英文和数字,所有项目通用这个习惯。

5.5 Mosaic增强导致小目标漏检,验证集上精度低于训练集

训练时mAP50一直是0.9,但验证集只有0.7,看起来像过拟合,但减少epochs后反而更差。

Mosaic增强拼接了四张图,模型的输入分布和真实单图场景差异较大。训练时大量使用Mosaic的图片,模型学会了在拼接图的特征模式上做检测,遇到真实场景的单图反而不适应。

训练后期手动调低mosaic概率,在hyp配置里把mosaic: 1.0改成mosaic: 0.5,让模型在最后的epoch接触更多真实场景的样本分布。也可以多训练10个epoch,给模型更多时间去适应关闭Mosaic后的数据分布。

6. 模型导出与实测:把权重用起来

6.1 单图和视频实测

训练出的best.pt要走到推理这一步才算是完整闭环。detect.py是官方推理脚本,输入可以是图片、视频或摄像头流:

python detect.py \ --weights runs/train/anomaly_exp1/weights/best.pt \ --source test_video.mp4 \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --save-conf

--source指定输入文件,视频会逐帧处理后生成标注过的视频。--save-txt把检测结果保存为txt,每一行是类别索引加归一化坐标,方便后续做行为判定的后处理。--save-conf把置信度也写进txt。输出文件在runs/detect/exp目录下。先拿test_video.mp4跑一遍,观察检测框是否有剧烈抖动;再拿一张静图跑一下,确认类别名输出正确。

6.2 导出ONNX做轻量部署

YOLOv5的pt权重依赖PyTorch环境,部署到边缘设备或嵌入到其他系统时,更常见的是导出为ONNX格式。导出命令:

python export.py \ --weights runs/train/anomaly_exp1/weights/best.pt \ --include onnx \ --img 640 \ --batch 1

导出时会打印动态轴信息,输出一个best.onnx文件。之后用onnxruntime加载这个文件做推理,不再需要PyTorch环境,部署体积大幅缩小。导出后一定要验证输出的一致性和原pt模型是否相同,找一个测试图分别用两种方式跑,对比检测框坐标和置信度的差异。

6.3 一个教训:验证集和超参数必须认真对待

做这类型毕设那会儿,我赶时间跳过了超参数验证,用默认配置直接练完100个epoch,mAP50看着不错就把权重部署到演示视频里。结果现场演示拍到跌倒场景时模型没反应,换成测试图片又能检出来,台上答辩很被动。后来检查发现,演示视频的帧率是25fps,目标在画面里停留时间短,默认阈值0.25在快速运动场景下置信度不够,被过滤掉了。从那以后,我每次训练完都强制走一遍完整验证流程,在多个不同场景的视频上测试,确认每个类别的置信度分布,然后针对典型的误检漏检场景微调阈值。这一步不做完,绝不上线。这个习惯帮我避开了不少演示翻车的局面,希望也能帮到你省下临场补救的时间。

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

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

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

立即咨询