简介基于YOLOv5的人员跌倒检测训练包面向深度学习开发者、计算机视觉学习者及老年人监护应用相关从业者能帮助读者完成跌倒识别模型从训练到测试的完整流程。压缩包共331个文件、约207.91MB涵盖96个jpg图像样本、53个py训练脚本、56个yaml配置、42个txt标注、7个pt预训练权重并附有mp4测试视频、ipynb示例和sh辅助脚本图像、配置、代码、权重分层清晰便于直接实践。已有309人学习浏览。除源码与数据集外还提供Dockerfile等环境配置可直接复现训练环境训练过程涉及SGD、Adam等优化器与交叉熵损失通过调整学习率、批大小等参数可深入理解YOLOv5单阶段检测原理并结合mAP、召回率等指标评估效果。虽然原始数据量有限但可借助数据增强扩充样本进一步提升实际场景中的检测可靠性。1. 跌倒检测为什么值得用yolov5自己训练一段监控视频里的高成本瞬间做安防和养老监护的同行应该都有同感跌倒这个动作在视频里往往只占几帧人从站立到倒地可能只有0.5秒到1秒传统帧差法和背景建模根本追不上这个速度等检测到“画面有大面积变化”的时候人已经躺在地上了。而基于yolov5训练人员跌倒模型本质上就是把“跌倒”当成一个目标检测任务来做——用边界框把“person”和“fall”两类目标框出来交给后端业务逻辑判断是否触发告警。这个方案最大的优势在于yolov5的推理速度和生态成熟度一个在GPU上能跑到50 FPS以上的模型放在养老院走廊、工地高危区域这类场景里足够在倒地后几秒内触发通知。这篇笔记我会从数据集准备开始把环境配置、超参数调整、训练日志解读、常见翻车现场一直到导出部署完整走一遍适合手里有监控视频素材、想自己从零训一个跌倒检测模型的工程师参考。2. 准备跌倒数据集从零标注到格式转换的完整链路2.1 跌倒检测到底需要什么样的数据先说结论跌倒检测模型的效果70%由数据集决定20%由超参决定10%才是网络结构的事。很多人上来就套用COCO预训练权重拿几百张网图开训结果测试时框乱跳、误检一堆根本原因不是模型不行而是数据里没有覆盖真实场景的“跌倒”形态。跌倒数据要覆盖三个维度。第一是视角监控摄像头通常是斜上方俯视这和普通行人检测数据集里的水平视角差别很大俯视视角下的人体比例、遮挡关系完全不同。第二是动作形态跌倒不只是“躺在地上”还有从椅子上滑落、走路绊倒、从床上滚落这些动作的中间态长得完全不一样。第三是光照和遮挡走廊里的逆光、轮椅遮挡、被子盖住半身这些情况都需要样本。如果你没有现成数据常见做法是两条路并行一是用公开的跌倒检测数据集比如UR Fall Detection Dataset这类学术数据集二是自己从监控视频里抽帧标注。学术数据集的问题在于场景单一基本是在实验室里拍的直接拿来做迁移学习效果还行但落到真实场景必须混入自己的数据。我一般建议自建数据和公开数据按7:3的比例混合训练自建数据为主。2.2 用labelImg手工标注类别设定与bbox规范标注工具我习惯用labelImg虽然是老工具但胜在稳定、不需要配置环境、开箱即用。安装很简单# labelImg依赖pyqt5建议用pip直接装 pip install labelImg # 启动后选择Open Dir打开图片文件夹选择Change Save Dir设置标注输出目录 labelImg标注时只设两个类别person和fall。这里有个关键原则person类要标注所有“人”的边界框包括站着的、走着的、坐着的——因为模型需要学会区分“人”和“跌倒的人”。如果你只标跌倒的框模型会把所有直立的人都当成背景推理时fall类全漏检。标注格式是Pascal VOC的XML包含物体类别和左上角、右下角坐标。一个典型的标注框长这样object namefall/name bndbox xmin112/xmin ymin245/ymin xmax310/xmax ymax468/ymax /bndbox /object注意几个边界如果人只有半身出画面标注露出部分即可不要把框强拉到画面边缘如果两个人叠在一起要标两个框哪怕一个框被另一个完全挡住——因为模型要用这些框学习“被遮挡的人”长什么样画面模糊到看不清人形轮廓的帧直接删掉不要标。标注一个小时的视频大概要花三到四个小时这个时间省不得。2.3 转成yolov5需要的目录结构与labels格式yolov5不直接吃VOC格式它需要的是每个图片对应一个同名的txt标签文件每行一个目标格式是类别id x_center y_center width height全部坐标归一化到0到1之间。目录结构必须是dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/转换脚本可以复用以下代码它在遍历XML时把坐标做一次归一化同时处理越界修正import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_names): 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.iter(object): cls obj.find(name).text if cls not in class_names: continue cls_id class_names.index(cls) xmin float(obj.find(bndbox/xmin).text) ymin float(obj.find(bndbox/ymin).text) xmax float(obj.find(bndbox/xmax).text) ymax float(obj.find(bndbox/ymax).text) # 归一化到[0,1] x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 越界裁剪防止标注框超出画面边缘导致训练报错 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) w max(0, min(1, w)) h max(0, min(1, h)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines)) class_names [person, fall] xml_dir path/to/xmls out_dir path/to/labels/train os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir, class_names)这段脚本的边界修正是个血泪经验很多标注工具在图片边缘的框会给出超出图像的坐标如果不裁剪训练时yolov5会报AssertionError: label shape之类的错或者loss直接变nan。归一化之后的坐标如果大于1说明标注框本身就画出了边界裁剪成1是兜底方案但最好还是回到标注阶段修掉这些框。2.4 数据增强与类别不均衡少样本下的常见做法跌倒数据集的痛点永远是“跌倒样本太少”。一个小时的监控视频里真实的跌倒可能就一两次抽帧后能用的跌倒帧可能只有几百张而person类的样本随便就有几千张。这种类别不均衡会让模型倾向把目标都预测成person——毕竟这个类的先验概率大loss下降更快。解决思路有三个层面。第一是离线增强对跌倒样本做随机旋转±15度、左右翻转、随机亮度对比度调整把几百张扩到一千张以上。第二是在yolov5训练时开启在线增强--augment参数和hsv_h、hsv_s、hsv_v、degrees这些超参就是干这个的我一般会把degrees从默认的0.0调到10.0让模型对倾斜视角更鲁棒。第三是调整损失权重在数据yaml里给fall类设置更高的class_weights——不过yolov5官方仓库对class_weights的支持不直接实际操作时我会在loss.py里把fall类的obj loss权重乘个1.2效果更可控。还有一个容易被忽略的点跌倒过程帧比跌倒结果帧更重要。如果数据集里全是“人躺在地上”的终态模型学不到“从站立到倒地”的中间过渡姿态在视频流里就会漏检。标注时不要把视频抽帧成均匀间隔而是把跌倒发生前后各10帧全部保留中间态密集标注。这个细节决定了模型在真实视频流里是“人倒了才报警”还是“人刚要倒就报警”前者是马后炮后者才是真告警。3. 环境配置与源码准备torch版本、目录结构与第一次前向推理3.1 conda环境与CUDA匹配先跑通再训练yolov5环境配置的坑多到能单独写一本“踩坑实录”但核心矛盾就一个PyTorch版本和CUDA版本必须匹配。很多人在这一步翻车——装好了依赖import torch时报错或者训练时提示CUDA不可用然后开始玄学调环境变量最后发现是torch版本和显卡驱动对不上。我的建议是直接用conda建一个干净环境Python版本指定3.8或3.9不要用最新的3.11——yolov5的requirements里有些依赖对Python版本敏感比如pycocotools在3.11下编译会报错。手动做初建这一步最直白conda create -n yolo python3.9 -y conda activate yolo # 先装PyTorch核心库再装yolov5的其余依赖 # CUDA 11.8对应torch 1.13.1CUDA 12.1对应torch 2.1.0 conda install pytorch1.13.1 torchvision0.14.1 pytorch-cuda11.8 -c pytorch -c nvidia -y cd yolov5-master pip install -r requirements.txt装完后先别急着训练先在Python里确认CUDA真的可用import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果cuda.is_available()返回False不要调代码回到驱动层面查nvidia-smi看驱动版本是否支持你的CUDA版本。这里有个实际经验Ubuntu上很多人用默认的nvidia-driver-470这个驱动只支持CUDA 11.4以下装torch 2.x必挂。升到nvidia-driver-525以上并重启基本能解决。3.2 yolov5源码目录结构训练时真正会动到的文件源码包解压后不要一上来就到处翻训练人员跌倒模型真正会动到的文件只有这几类yolov5-master/ ├── train.py # 训练入口 ├── detect.py # 推理入口 ├── data/ # 数据配置文件存放处 │ └── fall.yaml # 自定义数据集配置 ├── models/ # 模型结构定义 │ └── yolov5s.yaml # 小模型结构改nc参数 ├── utils/ # 工具函数一般不用动 └── runs/ # 训练输出权重、日志、曲线models/yolov5s.yaml里的nc参数默认是80COCO类别数改成2。data/fall.yaml里写数据集路径和类别名。其他文件在训练跌倒模型时都不需要动——包括utils/loss.py在内前几轮训练先用默认设置跑通再谈定制。这里要纠正一个常见误用很多人把yolov5s.yaml里的nc改了但忘了改data/fall.yaml里的names或者反过来。两个文件必须同时改否则训练时类别索引对不上loss会异常。3.3 第一次前向推理验证用预训练权重确认环境环境搭建完成后不要直接训练——先跑一次推理确认代码链路完整。用官方预训练权重对一张测试图片做检测这步能过滤掉90%的环境问题# 下载yolov5s预训练权重后对测试图片执行检测 python detect.py --weights yolov5s.pt --source data/images/bus.jpg --conf-thres 0.5正常情况下runs/detect/exp下会生成带边界框的标注图。如果这一步报错优先排查requirements.txt有没有装全尤其是opencv-python——conda装OpenCV有时会装成opencv-headlessyolov5需要GUI版本的cv2.imshow。确认推理正常后再用同样的环境跑训练就能把“环境问题”和“训练问题”隔离开。这个习惯能让你后续排查时间省一半。4. 训练跌倒模型超参数设定与训练过程解读4.1 修改数据yaml和模型yaml类别数、路径、anchors训练前要改两个配置文件。先看data/fall.yaml# 数据集的根目录建议写绝对路径避免相对路径在不同机器上失效 path: /home/user/fall_dataset train: images/train val: images/val # 类别列表顺序和标注txt里的id一一对应 nc: 2 names: [person, fall]这里有个容易踩的坑path字段和train/val字段是拼接关系。yolov5的逻辑是读取path然后在后面拼接train和val的路径。如果你把path写成/home/user/fall_dataset/images下面又写train: train最终路径会变成/home/user/fall_dataset/images/train——多了一层目录训练时直接报AssertionError: train: 图片路径不存在。用绝对路径时把path指到数据集根目录train和val从根目录开始写。再看models/yolov5s.yaml模型结构配置里只需要改nc# parameters nc: 2 # number of classes depth_multiple: 0.33 width_multiple: 0.50depth_multiple和width_multiple控制模型缩放比例保持默认即可。yolov5在训练时默认开启autoanchor会自动根据数据集重新计算anchors所以这里不需要手动改anchors参数——如果你用了自定义anchors反而容易出问题。4.2 训练命令与关键超参数epoch、batch、img-size训练命令我一般这样写python train.py \ --data data/fall.yaml \ --weights yolov5s.pt \ --epochs 150 \ --batch-size 16 \ --img 640 \ --device 0 \ --workers 4 \ --cache逐项说明几个关键超参的选择逻辑--img 640yolov5的输入分辨率。跌倒检测的目标在画面里占比不大用640能保留更多小目标细节。如果你的监控画质是1080P可以试试--img 960或--img 1280但显存占用会陡增。我实测过640和960的mAP差距在3到5个点但训练时间差了一倍先用640跑通后续再提分辨率。--batch-size 16这个数值取决于显存12G显存跑yolov5s可以开到16跑yolov5m建议8。不要为了显存去降--img--img对模型效果的影响大于batch size。--workers 4数据加载线程数。设太高会卡死——Windows上workers超过2就可能出问题Linux可以开到8。--cache把图片预加载到内存极大减少训练时的磁盘I/O等待。数据集在10G以内时强烈建议加这个参数。4.3 训练日志怎么读loss曲线、mAP和混淆矩阵训练完成后runs/train/exp目录下会生成results.csv和results.png这是判断模型训练是否正常的核心依据。我每次都会盯三个指标第一是box loss这个值反映预测框和真实框的坐标误差。正常曲线应该在前50个epoch快速下降之后进入平台期。如果box loss在150个epoch后还在持续下降说明还没收敛可以加epoch如果box loss前20个epoch就开始反弹大概率是学习率太大或数据集标签有问题。第二是mAP0.5这是目标检测的标配指标。跌倒检测场景下mAP0.5达到0.85以上基本可以上线测试。如果你的模型只有0.5左右先不要调参——去检查数据集看看是不是标注框不准确或者fall类样本太少。第三是混淆矩阵confusion_matrix.png里能看到person和fall互相误检的比例。如果fall被误检成person的比例很高说明模型把“躺在地上的人”和“站立的人”区分度不够——去补数据特别是“跌倒后挣扎坐起”这类中间状态的数据。训练日志里还有一个容易被忽略的F1_curve.png它展示不同置信度阈值下的F1分数指导你后续部署时选conf-thres参数——选F1峰值对应的阈值而不是默认的0.25。顺带一提yolov5训练时的hyp.yaml超参数我可以从“不要动”开始——尤其是lr0和lrf默认值0.01和0.2是官方在COCO上验证过的多个项目经验下来自定义数据集上乱调学习率基本没有正面效果。真出问题优先改epoch和图像分辨率这俩见效最快。5. 训练中的翻车现场5个高频踩坑与排查记录5.1 现象训练时loss不降或变nan这是最常见的训练崩溃场景。loss从一开始就冲上两位数或者在某一步直接变成nan。原因分两类。一类是数据集的标签有非法值——坐标出现负数、宽度高度为0、类别id超出nc范围。另一类是学习率过大尤其是用了迁移学习权重但数据集很小的时候模型参数在初期剧烈震荡loss就会发散。解决先用脚本扫描所有txt标签把包含nan、inf、负数和大于1的坐标的文件找出来。另一个办法是降学习率——在train.py里加--hyp hyp.scratch-low.yaml这个文件里lr0: 0.001比默认低一个数量级收敛会慢但稳定得多。python train.py --data data/fall.yaml --weights yolov5s.pt --hyp hyp.scratch-low.yaml --epochs 1505.2 现象模型训练完推理时框乱跳一帧框人、一帧框墙典型的过拟合和样本单一问题。模型没有学会“人”这个概念的泛化特征而是记住了训练集里几张图的环境纹理——比如把走廊尽头的消防栓当成人。解决思路是回数据集补样本加入不同角度、不同光照、不同背景的跌倒样本同时开启--augment增强。这一步没有捷径模型乱跳的根源就是数据多样性不足调整网络结构或loss都救不回来。另外检一下验证集和训练集是否重叠——很多人用随机抽帧切分数据时同一段视频的相邻帧被同时分进train和val导致验证集loss虚低模型实际泛化很差。5.3 现象显存不足CUDA out of memory这个报错在训练时太常见了。原因很直接--batch-size太大或者--img分辨率太高。缓解的手段是按顺序排查先把batch-size减半再把--workers降到2最后把--cache去掉——--cache会把图片缓存到内存而不是显存但它会和batch一起占用资源。如果还要继续缩就换更小的模型结构yolov5n比yolov5s的参数量少一半显存占用直接降。代价是mAP下降1到2个点但对跌倒检测这种类别数只有2的场景yolov5n精度足够用了。5.4 现象训练时报错AssertionError: label shape或找不到标签文件这个错误基本是标签目录结构和图片目录不一致导致的。yolov5要求images/train和labels/train必须一一对应——每张jpg必须有同名的txt哪怕这个txt内容是空的表示该图没有目标。不要忽略空txt如果某张图完全没有标注但训练代码找不到它的txt标签会直接报错中断。解决写一段脚本遍历images/train下所有图片为缺失的标签创建空txt文件这段“补洞”代码很绕但能救急import os img_dir dataset/images/train label_dir dataset/labels/train for img in os.listdir(img_dir): txt_name os.path.splitext(img)[0] .txt txt_path os.path.join(label_dir, txt_name) if not os.path.exists(txt_path): open(txt_path, w).close() # 创建空标签文件 print(fcreated empty label: {txt_name})5.5 现象精测中误检率高把床、椅子、地上的杂物框成fall这是所有做跌倒检测的人最头疼的问题——模型训练正常loss收敛了mAP也好看但一到真实场景就草木皆兵。原因不在网络结构在于数据集里没有“难负样本”。模型没见过“和跌倒长得很像的物体”比如地上堆的被子、深色地毯上的阴影、低角度的沙发。解决方向是专门收集一个“hard negative”集从真实监控中截取那些容易导致误检的画面——空床、空走廊、有人蹲下但没倒地的画面全部标注成person或者不标注直接作为背景加入。yolov5训练时会把这些困难背景压到低置信度大幅降低误检。这一步几乎决定了跌倒检测能不能真正上线——很多项目死在误检太多导致告警被关掉而不是漏检。6. 把训练好的模型用起来导出ONNX与部署验证6.1 导出ONNX把PyTorch模型转为通用格式训练完成后runs/train/exp/weights/下会有best.pt和last.pt——前者是验证集mAP最高的权重后者是最后一个epoch的权重。部署时永远用best.pt不要用last.pt哪怕两者只差一点点——这是节省血泪教训的固定习惯。导出ONNX的命令python export.py --weights runs/train/exp/weights/best.pt --img 640 --batch 1 --simplify--simplify参数会让ONNX模型用onnx-simplifier做计算图优化去掉冗余节点推理速度至少提升10%。导出后的ONNX文件可以用onnxruntime直接在CPU上推理如果你要部署到树莓派5或者嵌入式设备这一步基本是必经路径。6.2 视频流检测的两种方式ONNX Runtime与OpenCV部署推理有两种常见选择一是继续用PyTorch加载.pt文件好处是能用yolov5自带的detect.py但依赖环境重二是用ONNX Runtime加载.onnx环境轻量且CPU也能跑。对于跌倒检测这种需要长时间后台轮询的场景我倾向后者。一段核心的ONNX推理代码可以这样组织import cv2 import numpy as np import onnxruntime as ort # 加载ONNX模型启用CPU推理 sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name sess.get_inputs()[0].name def letterbox(img, new_shape(640, 640), color(114, 114, 114)): # 保持宽高比缩放并填充灰色边框和yolov5预处理保持一致 shape img.shape[:2] r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) img_resized cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) dw, dh new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] top, bottom dh // 2, dh - dh // 2 left, right dw // 2, dw - dw // 2 return cv2.copyMakeBorder(img_resized, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor) # 读视频帧预处理后送入模型 cap cv2.VideoCapture(test_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break img letterbox(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) img img.astype(np.float32) / 255.0 # 归一化到[0,1] img np.transpose(img, (2, 0, 1))[None] # HWC - CHW并增加batch维 outputs sess.run(None, {input_name: img})[0] # outputs shape: [1, 25200, 6]后处理解析框、置信度和类别 # 后续按yolov5的decode逻辑解析即可这段代码的关键点是letterbox预处理——训练时的数据增强默认会做letterbox缩放推理时也必须做同样的操作否则模型输入尺寸不匹配检出的框会出现系统性偏移。outputs的解析需要对照你导出时的模型输出去写第一次调试建议打印outputs.shape确认维度。6.3 判断跌倒的工程技巧不止是画一个框模型输出的边界框本身不等于“跌倒告警”——真实场景里人蹲下系鞋带、躺在地上休息框都是“fall”或者“person”的低置信度结果如果直接触发告警你的手机一晚上会被打爆。工程上需要加两道过滤逻辑第一道是框形态过滤。跌倒后的人体边界框宽高比bbox width / bbox height通常大于1.2而站立姿态的宽高比在0.3到0.6之间——可以用这个比例筛掉大部分“蹲下捡东西”的误检。第二道是持续时间过滤。在连续N帧比如30帧约1秒都检测到fall类目标后再触发告警。这个“冷静期”能把那种“人恰好摔倒又立刻站起来”的情况滤掉——真正跌倒的人不会1秒内站起来。最后分享一个我自己的习惯模型训练完不要急着上生产环境先拿一段至少30分钟的现场监控视频离线跑一遍把误检和漏检的帧全部截图按场景归类再回到数据集补样本。做到这个程度模型才算真正能交付使用——毕竟是做人的安全监护不能只靠mAP数字说话。希望这篇笔记能帮你少走几步弯路祝顺利训出可用的跌倒检测模型。本文还有配套的精品资源点击获取