简介:一套完整的基于YOLOv5的异常行为检测本科毕业设计项目,面向计算机专业学生及目标检测技术学习者。项目覆盖数据处理、模型搭建、训练推理与部署全流程,包含可运行代码和详细文档,既可直接复现课题,也便于在此基础上进行功能扩展。压缩包共212个文件,以105个YAML配置文件和45个Python脚本为核心,分别用于模型结构定义、训练参数配置和核心功能实现;20张JPG图片提供样例可视化,另有Jupyter教程、Dockerfile及shell脚本等辅助资料,整体体积仅2.81MB,轻量易用。目前已有637人学习下载。资料对YOLOv5工程组织方式进行了系统性整理,内含配置说明、目录结构梳理、运行环境要求与常见问题处理思路,可帮助读者快速搭建异常行为检测系统,节省从零搜索、调试时间,是毕业设计与课程实践的高性价比参考资料。
1. 基于YOLOv5的异常行为检测,究竟在“检”什么
一到毕业设计季,基于YOLOv5的异常行为检测就成了高频选题。很多同学拿到题目时的第一反应是:这不就是目标检测嘛,把“打架”“跌倒”“闯入”当成几个类别,训练一个YOLOv5模型就能交差。但真正动手后才发现,检测出“人”只是第一步,把“人”的行为归类为“异常”才是这个题目的核心难点,也是答辩时最容易暴露短板的地方。这篇笔记我会把从环境配置、数据集制作、模型训练到行为判定和边缘部署的完整路径拆开讲,适合正在做本科毕设、又希望模型真的能出效果而不是只跑通demo的读者。
2. 用conda把YOLOv5环境跑通:依赖安装、源码目录与第一次推理
2.1 conda创建环境与安装PyTorch:版本对应关系
YOLOv5对环境的要求不算苛刻,但PyTorch、CUDA和Python版本之间的对应关系一旦错位,后面会连环翻车。我一般建议用conda新建一个独立环境,别直接装在base里,否则后面装其他开源项目时容易互相踩。
conda create -n yolov5 python=3.8 -y conda activate yolov5这里选Python 3.8是保守做法,YOLOv5官方要求的Python版本是3.8及以上,3.8到3.10之间都稳。装PyTorch时要注意,先确认自己的机器有没有NVIDIA显卡,有显卡就装CUDA版,没有就装CPU版。CPU版在训练和推理时都会慢不少,但用于验证流程是够用的。
# 有NVIDIA显卡(CUDA 11.8对应的PyTorch 2.0.x) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 无显卡,纯CPU版 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu装完之后用下面这段代码验证CUDA是否可用,这一步别跳过。很多人在训练时才报“CUDA not available”,那时再回头排查环境就浪费时间了。
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"输出里如果torch.cuda.is_available()为True,说明GPU环境没问题。如果为False,先看显卡驱动是否正常,再看PyTorch装的版本是不是带CUDA的版本。CPU版输出False是正常的,后面训练就把设备参数固定写成CPU。
2.2 拉取源码、装依赖与权重准备
YOLOv5的官方源码仓库结构很清晰,根目录下就是detect.py、train.py、export.py这些入口脚本,models/目录放网络结构定义,utils/目录放损失函数、增强策略等工具,data/目录放数据集配置和类别名。先把源码clone下来,再安装依赖文件。
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txtrequirements.txt里锁定了matplotlib、numpy、opencv-python、pillow、pyyaml等常用库的版本下限。如果在国内网络环境下载慢,可以给pip换国内镜像源,命令是pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。
权重文件建议单独下载,YOLOv5官方提供了yolov5s.pt、yolov5m.pt、yolov5l.pt、yolov5x.pt这几种规格。做异常行为检测这类任务,起步用yolov5s就够了,模型小、训练快、后续部署也方便。运行时detect.py会尝试自动下载权重,也可以手动把yolov5s.pt放进源码根目录。
# 手动下载到本地后放根目录,或直接用下面的命令让它自动下载 python detect.py --weights yolov5s.pt --source data/images/bus.jpg第一次跑通这个命令,就说明环境配置成功了,这也是整个毕业设计里第一个里程碑。如果这一步没跑通,后面所有工作都白搭。
2.3 第一次跑detect.py:参数含义与输出
跑通示例图之后,就可以把source换成自己准备的视频了。基于YOLOv5的异常行为检测在毕设场景下,最终的输入大多是监控视频或摄像头实时画面,所以先用视频文件把流程走通,后面再接摄像头。
python detect.py \ --weights yolov5s.pt \ --source ./test_video.mp4 \ --conf-thres 0.5 \ --iou-thres 0.45 \ --classes 0 \ --device 0说明一下这几个关键参数:--weights指定权重路径;--source可以是图片、视频文件、目录或者摄像头ID;--conf-thres是置信度阈值,默认0.25,做行为检测时我习惯提到0.5以上,降低误报;--iou-thres是NMS的IOU阈值,用于合并重叠框;--classes指定只检测哪些类别,YOLOv5在COCO数据集上训练时类别0是person,行为检测场景下很多帧只需要关注person;--device是0表示用第一块GPU,用CPU就写cpu。
检测结果默认保存到runs/detect/exp/目录下,每次运行会自增生成exp、exp2、exp3。打开输出视频,如果能看到人的检测框而且框的位置基本贴合人体,说明整条链路已经通了,可以进入自己的数据集阶段。
2.4 推理后处理:置信度、NMS与类别过滤
很多人用detect.py跑出视频后就会忽略后处理,但答辩时评委非常爱问这一块。在detect.py的源码里,推理后的结果会经过一个关键流程:先过滤掉置信度低于--conf-thres的框,然后做NMS(非极大值抑制),把同一个目标上的多个重叠框合并成一个,最后按--classes参数过滤类别。
NMS的核心逻辑是:对所有检测框按置信度从高到低排序,保留最高分的框,然后删除和它IOU大于阈值的其他框,再在剩下的框里重复这个过程。--iou-thres设得越高,保留的框越多,容易多个框叠在一起;设得越低,框越干净,但也可能把靠得近的两个目标合并掉一个。
后处理在行为判定阶段还会再用到。比如我想统计每个人在视频里的轨迹,就不能只取detect.py的输出画面,而是要把检测框的坐标和置信度取出来做下一步分析,这就需要在detect.py里加些逻辑,或者直接把YOLOv5的模型加载进自己的脚本里。
import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt') results = model(frame) # 取出检测框、置信度、类别 boxes = results.xyxy[0].cpu().numpy() # x1, y1, x2, y2 for box in boxes: x1, y1, x2, y2, conf, cls = box if int(cls) == 0 and conf > 0.5: print(f'person: ({x1:.0f}, {y1:.0f}) -> ({x2:.0f}, {y2:.0f}), conf={conf:.2f}')results.xyxy[0]返回的数组里,每一行是[x1, y1, x2, y2, confidence, class_id],坐标是原图分辨率下的像素坐标。用这个接口拿到框的位置,后面做轨迹和行为判定就方便了。这也是把YOLOv5从“展示demo”变成“能做毕设功能”的关键一步。
3. 自建异常行为数据集:类别设计、YOLO格式标注与目录划分
3.1 异常行为类别怎么定才合理
标题里写的是“异常行为检测”,但“异常”是个很宽泛的词。毕设场景下必须先明确检测哪些行为,否则数据集没法做、答辩也没法自圆其说。常见的做法是从监控安防场景里挑3到5类高频、可辨别的行为,比如打架(fight)、跌倒(fall)、闯入禁区(intrusion)、抽烟(smoke)、破坏公物(vandalism)。
类别数量建议控制在3到5类,这是数据量和训练难度之间的一个折中。本科毕设的周期有限,类别太多每个类别的样本量会被稀释,模型容易漏检误检;类别太少又体现不出“异常行为”这个题目里“异常”的多样性。我遇到过一个同学,一口气定了8类行为,最后每类只有一百多张图,训练出来的模型在验证集上mAP只有0.3,答辩时被评委问得很难受。
另外要注意,异常行为检测通常不是把所有行为都算“异常”,而是把“正常行为”也作为背景或一个类目来理解。比如监控画面里正常行走的人、坐着办公的人,这些都属于无人报警的正常状态。所以数据集的构建思路是:正常行为画面打底,异常行为画面作为触发事件,模型在有异常的画面里要能检出对应类别。
3.2 用LabelImg标注并导出YOLO txt
数据标注是整个毕设里最耗时但回报率最高的一步。异常行为数据集没有现成的大型公开数据集能直接替代课堂场景,自己采集视频并抽帧标注是主流做法。标注工具我用的是LabelImg,轻量、安装简单、支持YOLO格式直接导出。
pip install labelimg labelimg打开LabelImg后,先在顶部菜单把标注格式切换成YOLO,然后打开存放抽帧图片的目录。每张图上用矩形框把行为对象框出来,选好类别名,保存后会在图片同目录下生成一个同名txt文件。
生成的txt文件内容格式是这样的,每行代表一个目标框:
# 类别id 中心点x(归一化) 中心点y(归一化) 宽(归一化) 高(归一化) 0 0.5132 0.4211 0.1832 0.2875 1 0.2201 0.3393 0.1045 0.2110第一列是类别索引,从0开始,和后面data.yaml里的names列表一一对应。第二列到第五列是归一化坐标,计算方式是x_center / 图片宽度、y_center / 图片高度、框宽度 / 图片宽度、框高度 / 图片高度,都是0到1之间的小数。这个格式是YOLOv5训练时直接读取的,自己写脚本批量生成的时候也务必按这个标准来。
3.3 目录结构与train/val划分脚本
标注完成后,数据集目录需要按YOLOv5约定组织。官方推荐的目录结构如下,images下放图片,labels下放对应的txt标注,train和val子目录分别放训练集和验证集。
datasets/ abnormal/ images/ train/ img_001.jpg img_002.jpg val/ img_101.jpg labels/ train/ img_001.txt img_002.txt val/ img_101.txt图片和标注文件必须同名同前缀,只是后缀从.jpg变.txt。写个小脚本把标注好的数据按8:2比例随机划分,并检查每个txt里是否有内容,空标注文件在训练时会导致异常。
import os import random import shutil image_dir = 'datasets/abnormal/images/all' label_dir = 'datasets/abnormal/labels/all' train_ratio = 0.8 images = [f for f in os.listdir(image_dir) if f.endswith('.jpg')] random.shuffle(images) split_idx = int(len(images) * train_ratio) train_images = images[:split_idx] val_images = images[split_idx:] for split, imgs in [('train', train_images), ('val', val_images)]: for img in imgs: shutil.copy(os.path.join(image_dir, img), f'datasets/abnormal/images/{split}/{img}') label_file = img.replace('.jpg', '.txt') if os.path.exists(os.path.join(label_dir, label_file)): shutil.copy(os.path.join(label_dir, label_file), f'datasets/abnormal/labels/{split}/{label_file}')划分脚本里要注意按8:2还是9:1划分,取决于你的样本总量。如果总数不到1000张,建议9:1,因为验证集太大训练集就薄了。随机划分前手动确认一下同一条视频的连续帧不要全落到训练集或验证集,否则颜色、场景高度重叠,验证结果会虚高。
3.4 标注质量检查:坐标归一化与类别索引
标注质量直接决定训练天与地的差别。我见过太多训练结果“loss收敛但检测框完全偏掉”的情况,查到最后全是标注数据的问题,常见的有这么几类:标注时用的PascalVOC格式,导出后坐标没归一化;类别索引从1开始,而YOLOv5要求从0开始;txt文件里混进了空行或空格;图片分辨率不统一导致归一化坐标换算错误。
写一个检测脚本,把所有txt文件的坐标范围检查一遍,同时统计每个类别的样本量,能在训练前就把问题干掉:
label_dir = 'datasets/abnormal/labels/train' for file in os.listdir(label_dir): path = os.path.join(label_dir, file) with open(path, 'r') as f: for line in f.readlines(): parts = line.strip().split() if len(parts) != 5: print(f'warning: {file} 行格式异常: {line}') continue cls, x, y, w, h = [float(p) if i > 0 else int(p) for i, p in enumerate(parts)] if x < 0 or x > 1 or y < 0 or y > 1 or w <= 0 or h <= 0: print(f'error: {file} 坐标越界: {line}')这种脚本不复杂,但能把标注阶段的小毛病一次性暴露出来。坐标越界的框说明标注时可能已经出框了,这类数据训练时会让模型学出“框跑到画面外”的坏习惯,务必重新看一遍原图修正。
4. YOLOv5训练自己的数据集:data配置、超参数调整与断点续训
4.1 写对data yaml:路径、类别与names顺序
数据准备好后,第一步是在YOLOv5源码的data/目录下建一个自己的配置文件,比如abnormal.yaml。这个文件告诉训练脚本:数据集在哪、有几类、类名是什么。
# abnormal.yaml path: ../datasets/abnormal train: images/train val: images/val nc: 3 names: 0: fight 1: fall 2: intrusionpath字段是数据集根目录的相对路径,相对于你执行训练命令的工作目录。如果训练命令是在yolov5源码根目录执行的,path就写成../datasets/abnormal(假设数据集放在和yolov5同级的位置)。train和val填相对于path的子路径。nc必须和names的列表长度一致,否则训练脚本直接报错。
这里有个极易踩的坑:names列表顺序必须和标注txt里的类别索引完全一致。比如标注文件里第二行类别是1代表fall,那names里索引1也必须写fall。一旦顺序乱了,训练时模型会把fight的框当成fall来学,推理结果自然全部错位。
4.2 train.py训练命令与关键参数
训练命令是整个毕业设计里出镜率最高的,参数也不复杂,但每个参数都值得认真理解。
python train.py \ --data data/abnormal.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --workers 4 \ --project runs/train \ --name abnormal_exp--weights填yolov5s.pt时,训练方式是基于COCO预训练权重做迁移学习,收敛快、效果比从零训练好很多,这是毕设最推荐的做法。--batch-size受显存限制,16G显存跑16没问题,显存小就降到8或4。--imgsz输入分辨率,默认640,如果数据集里目标小可以试768或960,但训练和推理时间会明显变长。--workers是数据加载线程数,Windows上建议设成0,否则偶尔会报DataLoader worker相关的错。
训练过程中终端会打印每轮的box_loss、obj_loss、cls_loss、mAP@0.5等指标。这里重点观察mAP@0.5在最后20轮是否还在上升,如果连续几十轮几乎不变,可以考虑提前停掉或者调低学习率再续训。
4.3 超参数文件怎么改:lr0、warmup与weight_decay
YOLOv5的超参数默认写在data/hyps/hyp.scratch-low.yaml里,训练时通过--hyp参数指定。对异常行为检测这种数据量不大的任务,默认参数往往偏激进,需要调。
python train.py \ --data data/abnormal.yaml \ --weights yolov5s.pt \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --batch-size 16打开这个yaml文件,核心关注这几个参数:
lr0: 0.01 # 初始学习率 lrf: 0.01 # 最终学习率 = lr0 * lrf warmup_epochs: 3.0 # 预热轮数 weight_decay: 0.0005如果自己的数据集很小(比如每类只有三四百张),lr0可以从0.01降到0.005,避免前期loss震荡。warmup_epochs保留默认,它让前几轮用很小的学习率热身在预训练权重上做平滑过渡。weight_decay权重衰减是防过拟合的,小数据集上可以适当提高到0.001。修改超参数后训一版,如果训练loss曲线前10轮就出现明显波动,大概率还是学习率偏高,继续降一半。
4.4 断点续训与训练日志排查
训练到一半断电、显存不够被系统杀掉,是常见得不能再常见的事。YOLOv5支持断点续训,只要训练没有显式终止,runs/train/exp*/weights/下会保存last.pt,用--resume就能从断点继续训练。
python train.py --resume runs/train/abnormal_exp/weights/last.pt续训时不用重新指定--data和--hyp,训练脚本会从上次的权重文件里读取训练配置。使用--resume后注意看终端打印的epoch起始值是不是接上了,如果从头开始说明权重路径有问题。
训练日志全部写在runs/train/abnormal_exp/下。results.csv里每一行是一个epoch的指标明细,可以直接用Excel或pandas打开;results.png是可视化曲线;confusion_matrix.png和PR_curve.png是最终评价模型要用到的图。如果发现cls_loss一直降不下去,先看混淆矩阵,通常是有两个类别经常被互相误判,说明这两个类的样本在画面里太相似,需要补充更有区分度的数据。
5. 5个必踩的坑:基于YOLOv5的异常行为检测翻车现场与排查记录
5.1 训练loss不降反升:学习率和数据问题
现象:训练前10轮loss掉得正常,后面突然上升,20轮后出现NaN,整个训练报废。
原因:最常见的是学习率过高,数据里存在误差过大的标签或者空标签文件,再有就是loss计算里混入异常值。
解决:先把lr0降一半,重新训练。如果还是NaN,检查数据集里所有txt文件是否有空文件、坐标为负数、类别索引超出nc范围的情况,把异常样本移出数据集。
5.2 检测框整体错位:归一化与坐标格式问题
现象:训练完在验证集上mAP很高,但部署到实际视频里,检测框位置明显偏在人体上方或旁边,或者框的宽高比例完全不对。
原因:标注阶段把YOLO格式和VOC格式混用了,VOC格式存的是x_min, y_min, x_max, y_max像素坐标,而YOLO格式要的是归一化的中心点和宽高。
解决:检查自己的标注工具是否切到了YOLO格式,再按3.4节的脚本统一校验所有txt文件的坐标范围和列数。少数文件有问题就直接重标,别想着后处理兜底。
5.3 类别名称互相串扰:names顺序不一致
现象:训练时一切正常,但跑detect.py时把打架的人标成了fall,或者把跌倒的人标成fight。
原因:训练用的abnormal.yaml里names顺序和标注时的类别索引不一致,或者推理时重新写了yaml,顺序又变了。
解决:训练前把标注txt里的类别id分布统计出来,对照names逐项确认。推理时锚定训练时的那个yaml文件,不要在部署阶段自作聪明改类别顺序。
5.4 视频检测掉帧严重:CPU推理与逐帧处理
现象:在普通笔记本上跑detect.py,视频检测速度只有每秒两三帧,画面一顿一顿。
原因:如果没有GPU,CPU模式处理每帧要几十到几百毫秒,再加上视频解码和结果画框写文件的IO开销,等于每帧都在排队。
解决:分三步走。一是把--imgsz降到416或320,检测速度提升明显但精度会略降;二是用--vid-stride 2每两帧处理一次,丢掉部分中间帧;三是写检测脚本时用批处理,把连续帧攒到一批再送模型推理,吞吐量比逐帧处理高得多。
5.5 模型在陌生场景漏检:场景迁移问题
现象:训练时用的全是白天的教室、走廊画面,答辩现场演示时换了光线较暗的楼道,模型漏检严重。
原因:训练数据基本是一个固定场景,模型把场景特征也学进去了,没有泛化到不同光线、不同角度。
解决:采集数据时尽量覆盖不同时段、不同光照和不同机位的画面。如果实在没条件,训练时开启YOLOv5自带的增强参数:hsv_h、hsv_s、hsv_v控制HSV色彩空间的随机扰动,适当调大可以让模型对光照变化更鲁棒,比如hsv_s: 0.7配合hsv_v: 0.5。
6. 进阶:把“检测框”变成“行为判定”,并压到树莓派5上
6.1 从单帧检测到行为判定:两类最简单的落地写法
模型训练好之后,只输出“有打架的人”还不够,异常行为检测要求的是事件级报警。我常用的两种判定方法都很轻量:单帧类别阈值和基于帧差的位置突变检测。
单帧阈值适合“打架”“闯入”这类单帧就能看出来的行为,逻辑是连续多帧检测到同一异常类别且置信度稳定在阈值以上才触发报警。帧差法适合“跌倒”“快速奔跑”这类依赖动作变化的行为,通过相邻帧同一个人的中心点位移速度来判断。
prev_center = None prev_frame_boxes = [] for frame in video_frames: results = model(frame) boxes = results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls = box if int(cls) == 1 and conf > 0.5: # fall # 计算人体中心点 center = ((x1 + x2) / 2, (y1 + y2) / 2) if prev_center is not None: speed = ((center[0] - prev_center[0]) ** 2 + (center[1] - prev_center[1]) ** 2) ** 0.5 # 相邻帧位移超过阈值,判定为快速动作变化 if speed > 80: trigger_alarm(frame, 'fall_detected') prev_center = center位移阈值80是个经验值,需要根据自己视频分辨率和帧率调整。视频分辨率越高、帧率越高,正常行走的帧间位移就越大,阈值也要相应放大。这里后续可以再接一个跟踪器,比如DeepSORT,用跟踪ID区分不同的人,避免多目标时轨迹错乱。
6.2 树莓派5上部署:导出、量化与实测
很多毕设会要求演示能在边缘设备上跑,树莓派5是个常见选择。部署前先把训练好的best.pt导出成更适合推理的格式,我一般导出成TorchScript或ONNX,再做INT8量化。
python export.py \ --weights runs/train/abnormal_exp/weights/best.pt \ --include torchscript onnx \ --int8 \ --device CPU--include可以同时导出多种格式,--int8启用INT8量化,能显著缩小模型体积并提升推理速度。量化后模型精度会掉一点,如果验证集上掉得太多,可以用--half半精度替代。实测中量化后的yolov5s在树莓派5上每帧推理大约几百毫秒,没法跑实时视频流,但做定时抓拍和离线视频分析完全够用。
毕设演示时最稳妥的方案是:树莓派5负责周期性抓帧,把图像送入模型推理,只把报警事件和截图存下来。整套逻辑跑通后,你会发现真正的门槛不是YOLOv5本身,而是数据质量、类别定义和异常判定的阈值调节。我自己做这个方向时的习惯是每个环节都保留日志和中间结果,训练前先记下标注分布,部署后留一段离线视频反复测阈值。这样才能在答辩时对评委的每一个追问都有数据支撑。希望这篇文章的做法能帮你在“基于YOLOv5的异常行为检测”这条路上少走弯路,把毕业设计真正做成一个能落地演示的完整系统。
本文还有配套的精品资源,点击获取