基于YOLOv8的跌倒检测实战:从数据集标注到模型训练与部署
简介这是一份基于YOLOv8的跌倒检测毕业设计完整资料面向计算机视觉方向学生可用于课程设计、算法学习或作品提交。压缩包共1437个文件包含1428张已标注的跌倒与非跌倒图像、6个Python训练/推理脚本、1个ONNX模型、1个PT权重文件和1个说明文档整体体积约78MB。跌倒检测广泛服务于老年人监护、公共安防等场景资源提供从数据加载、模型训练到结果评估的完整代码框架并附带可直接运行的权重便于快速复现和二次开发。已有93人学习下载适合希望掌握YOLO系列实战流程的入门及中级开发者。通过分析源码的数据增强、训练参数和推理逻辑可深入理解目标检测落地要点是兼顾工程实践与算法原理的毕业设计参考。1. 养老院走廊的摄像头记录了一个人从站立到倒地yolov8跌倒检测就是干这个的做跌倒检测这个方向的毕设或安防项目最常见的一套组合就是基于yolov8训练跌倒检测模型配一份标注好的数据集再加上能直接改的训练与推理源码。它的核心诉求很明确——用普通监控摄像头实时识别“人摔倒了”在没人喊救命的场景里自动触发告警。适合三类人计算机视觉方向的毕业生做毕设、养老机构或家庭看护的智能化方案选型、以及想快速上手yolov8训练全流程的从业者。别把它想成黑匣子它的技术栈不复杂yolov8负责目标检测数据集决定模型能不能分清“站着”和“摔倒”源码把这两件事串起来。这篇文章会按“为什么选yolov8、数据集怎么造、参数怎么设、坑在哪、怎么部署”的顺序完整讲一遍。2. ubuntu20.04 搭建 yolov8 环境CPU 版本与选型理由先跑通再谈训练2.1 从跌倒检测的任务特点看选型为什么是yolov8而不是yolov5或OpenPose跌倒检测本质上是一个单阶段目标检测问题给定一帧画面输出每个人的边界框和“是否跌倒”的置信度。它有几个明显特点直接影响了模型选型。第一实时性要求高。摄像头按帧推流检测要跟着画面走不能像离线分析那样一帧算上几秒。yolov8是单阶段检测器推理速度天然占优yolov8n在CPU上处理一帧640×640图像大约是几十毫秒到一百多毫秒在GPU或NPU上远低于单帧间隔。第二跌倒这个动作的视觉特征集中在“人的宽高比”和“姿势形态”上不需要像OpenPose那样把每个关节都标出来。姿态估计在遮挡严重的跌倒场景里很容易翻车——人被沙发挡一半、被桌子挡住手脚关节点缺失直接导致检测失败检测框受遮挡影响小很多只要人主体可见就能框住。第三yolov8是anchor-free设计相比yolov5不需要针对数据集手动聚类预设anchor尺寸对跌倒这种“横躺时宽高比剧烈变化”的目标反而更省心。还有一个现实原因值得说yolov8的生态太完整了。训练、验证、导出、部署都是同一个CLI命令官方权重初始化、数据增强、早停机制都内置对毕设而言这意味着从零跑通一个可用模型的成本被压得很低。如果你的需求是“快速出结果且要能落地上板”yolov8是目前综合性价比最高的选择如果非要追求极致精度再去看yolov11或RT-DETR也不迟。2.2 ubuntu20.04 搭建 yolov8 CPU 环境最小命令与跑通验证很多同学第一步就卡在环境上。这里给出一套在ubuntu20.04上搭建CPU版本环境的操作全程不需要考虑CUDA和显卡驱动适合先把流程跑通。# 1. 创建独立 conda 环境避免污染系统 Python conda create -n fall python3.10 -y conda activate fall # 2. 安装 ultralytics 包CPU 机器上会自动装好可用的 torch 版本 pip install ultralytics环境建好之后先做一个最小验证确认ultralytics能正常导入python -c from ultralytics import YOLO; print(ultralytics ok)这里有两个细节。第一Python版本建议3.10不要用3.12或更高的版本部分依赖包比如onnxruntime的某些旧版本在3.12上容易遇到编译问题。第二CPU环境下pip install ultralytics会拉取torch即使装的是带CUDA标记的wheel没有NVIDIA驱动时会自动退回到CPU执行不会报错只是训练和推理速度慢一个量级。首次运行yolo命令时会自动下载yolov8n.pt预训练权重下载失败就手动把权重文件放到当前目录再用modelyolov8n.pt指定。2.3 用预训练权重先跑通一次推理确认整条链路没坑环境搭好后别急着训练先用官方预训练模型跑一次推理验证图像读取、模型加载、输出保存这些环节都正常# 用一张自己拍的或手机里的照片先试试source 指向图片路径 yolo predict modelyolov8n.pt sourcesample.jpg预测结果会输出到runs/detect/predict目录下。第一次跑的时候会下载yolov8n.pt这张权重是在COCO数据集上训的本身能检出人但检不出“跌倒”这个类别——这是正常的。这一步的目的是验证环境可靠不是验证效果。CPU上跑一张图可能要多等几秒别急如果连这个都跑不通后面训练调试会更痛苦。确认推理正常后就可以开始准备自己的跌倒检测数据集了。3. 构建跌倒检测数据集从采集视频、labelme标注到YOLO格式转换3.1 老人跌倒检测的数据集怎么造场景覆盖比数量更重要很多人在数据集上栽跟头原因是“以为跌倒样本就是找一堆人躺在地上的图片”。实际上yolov8训练自己的数据集最关键的指标不是总量而是场景分布的多样性。同一个人在同一块地板上摔倒五十次和五个不同人在五种光照/角度下各摔倒十次后者训练出的模型泛化能力强得多。在构造数据集之前先想清楚部署场景摄像头装多高、俯视角度大概多少度、室内是亮光还是暗光。然后针对这些条件做场景规划。以养老院走廊和客厅为例至少要覆盖这几类不同摄像头俯角45度俯视、平视、斜上方30度不同光照白天自然光、夜晚开灯、昏暗环境不同遮挡程度身体被沙发扶手挡一半、被桌子挡腿不同姿态侧摔、仰摔、趴着倒地、慢慢滑倒类别设计上我一般建议做两个类别fall和standing。只标fall单类也能训练但把standing标出来当负样本模型能更好地学会区分“站着的人”和“倒地的人”。如果场景里常有坐姿比如老人坐在沙发上可以加第三类sitting但别贪多类别越多每个类别的有效样本数需求越大。起步阶段fall至少300张standing 400张以上总共8001000张图片足够训练出一个可用的模型。3.2 从视频中抽帧用ffmpeg按需抽帧而不是逐帧标注自己录制跌倒视频是收集样本最快的方式。让朋友或同学在不同场景下模拟跌倒每段视频录1020秒然后用ffmpeg隔几帧抽一张# 每 2 秒抽 1 帧输出为 4 位数字编号的 jpg 图片 ffmpeg -i fall_scene_01.mp4 -vf fps0.5 frames/01_%04d.jpg抽帧频率要控制好。fps0.5表示每秒抽半帧也就是每2秒一张20秒视频出10张。为什么不逐帧抽因为视频中相邻帧几乎完全一样标注出来全是重复样本模型学到的是位置偏差而不是姿态多样性。抽帧太稀疏也不行跌倒动作往往只有13秒抽得太稀可能漏掉关键的“倒地瞬间”。我一般用fps0.5到fps1之间保证同一段动作能抽出35张不同姿态的图。抽完帧后人工挑一遍模糊的、动作变形的、人太小的全部删除。这一步花费的时间直接决定训练集质量值得认真做。3.3 labelme标注矩形框还是多边形跌倒场景的标注要点标注工具选择很多labelme是配合yolov8最顺手的因为它的JSON格式容易转成YOLO所需的txt# 安装并启动 labelme pip install labelme labelme打开labelme后左侧选择“Open Dir”打开frames目录然后逐个文件标注。这里有一个关键选择用矩形框还是多边形。跌倒检测建议直接用矩形框Create Rectangle因为跌倒检测的目标是稳定输出一个边界框不需要像素级分割。矩形框要“贴住人主体”不要图省事把周边背景框进去。标注跌倒样本时人的姿态往往是伸展开的手脚可能分开较大。很多人下意识的做法是只框躯干结果模型学到的是“躯干横过来就是跌倒”当人蹲下或弯腰时误检率飙升。正确的做法是把整个人的主体包括头部、躯干、四肢末端都包进框里但不要为了兜住脚后跟而框进大片地面。框的上边缘顶到头顶下边缘收到脚底左右收到最外侧手臂或腿的边缘。每张图的框要控制在13个不要漏标画面里站着的人。3.4 处理数据集用于yolov8训练labelme JSON转YOLO格式的脚本与归一化细节labelme保存的是JSON文件里面记录的是像素坐标的顶点列表而yolov8训练需要的txt文件是类别ID加归一化后的中心点坐标和宽高。写一个脚本做转换这是整套源码里最重要的工具脚本之一import json import glob import os from PIL import Image # 类别映射顺序要和之后 data.yaml 里的 names 保持一致 label_map {fall: 0, standing: 1} def convert(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) # 用图片真实宽高做归一化不能拿标注时的显示尺寸凑合 img Image.open(os.path.join(os.path.dirname(json_path), data[imagePath])) w, h img.size name os.path.splitext(os.path.basename(data[imagePath]))[0] out_path os.path.join(out_dir, name .txt) rows [] for shape in data[shapes]: label shape[label] if label not in label_map: continue pts shape[points] # 兼容矩形框和多边形取所有顶点的最小/最大值得边界框 xs [p[0] for p in pts] ys [p[1] for p in pts] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) # YOLO 格式类别 中心点x 中心点y 框宽 框高全部归一化到 0~1 cx ((x1 x2) / 2) / w cy ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h rows.append(f{label_map[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w) as f: f.write(\n.join(rows)) os.makedirs(datasets/fall/labels, exist_okTrue) for j in glob.glob(labelme_out/*.json): convert(j, datasets/fall/labels)转换脚本有几个容易忽略的细节。第一坐标必须用图片原始像素尺寸归一化不能用labelme界面上显示的分辨率否则画框比例是错的。第二labelme的矩形工具在少数版本里points顺序不固定有时是左上右下有时是右下左上所以脚本里用min/max取边界而不是直接假设points[0]是左上角。第三归一化后的坐标要保留至少6位小数四舍五入太狠会导致框的尺寸和位置发生肉眼可见的偏移。第四如果一个标签不在label_map里比如误标了“person”脚本会跳过它不会报错中断——但你要确认这个类型确实是你想丢弃的。3.5 划分train/val/test每个子集的图片和标签必须配对转换完成后目录结构大致是images/放全部图片labels/放对应的txt文件。接下来按比例划分训练集、验证集、测试集。千万不要手动拖文件写个脚本按随机种子划分import os import random import shutil from glob import glob random.seed(42) # 固定随机种子保证每次运行结果一致 base datasets/fall imgs sorted(glob(os.path.join(base, images, *.jpg))) random.shuffle(imgs) n_train int(len(imgs) * 0.8) n_val int(len(imgs) * 0.15) splits { train: imgs[:n_train], val: imgs[n_train:n_train n_val], test: imgs[n_train n_val:] } for split, paths in splits.items(): img_dir os.path.join(base, split, images) lab_dir os.path.join(base, split, labels) os.makedirs(img_dir, exist_okTrue) os.makedirs(lab_dir, exist_okTrue) for img_path in paths: label_path img_path.replace(images, labels).replace(.jpg, .txt) if not os.path.exists(label_path): continue shutil.copy(img_path, img_dir) shutil.copy(label_path, lab_dir)这个脚本有一个隐形约束img_path中的“images”目录只能出现一次如果之前把目录命名为“images_original”之类replace会把路径替换错。建议一开始目录结构就按images/labels来命名。划分后的目录里每张jpg必须对应一个同名txt训练时yolov8会按这个映射关系去找标签文件少标签的图片会被跳过。所以划分完务必检查一下有没有图片缺失标签文件简单做法是统计两个目录的文件数是否一致。4. 训练跌倒检测模型参数怎么设、损失曲线怎么读、验证怎么跑4.1 训练自己的数据集第一步编写data.yaml训练前需要一个data.yaml文件告诉yolov8“数据在哪、类别是什么”。训练脚本、划分脚本、标注脚本都可以通用但data.yaml里的路径和类别名必须和你的数据集严格对应# 数据集根目录建议填绝对路径避免相对路径在不同终端下失效 path: /home/user/fall_det # 划分好的子集路径相对于 path 而言 train: train/images val: val/images test: test/images # 类别数量fall 和 standing 两类 nc: 2 # 类别名顺序必须和转换脚本里的 label_map 一致 names: 0: fall 1: standing一个常见错误是names写成[fall, standing]然后下标从0开始这本身没问题但如果转换脚本里fall映射为1、standing映射为0模型训练时就会完全不同。出现这种情况时训练日志里的类别名称和标注框的语义对不上但loss曲线看起来一切正常排查起来很费劲。所以data.yaml里的顺序必须和3.4节label_map的顺序完全一致。4.2 yolov8模型训练参数含义这批参数值我会这么设训练命令本身不长真正需要花心思的是参数的含义和选择。这是yolov8训练自己的数据集时最核心的命令yolo detect train \ datafall.yaml \ modelyolov8n.pt \ epochs100 \ batch16 \ imgsz640 \ patience20 \ optimizerAdamW \ lr00.001 \ save_period10对照表解释这组参数为什么这么设参数建议值含义与选择理由modelyolov8n.pt用nano版预训练权重初始化参数最少CPU能跑训练速度最快epochs100小数据集100轮足够完成收敛再多容易过拟合batch16CPU训练时受内存限制16是一个平衡值GPU显存不够就降到8imgsz640标准分辨率精度和速度的折中CPU训练可降到512patience20连续20轮验证集指标没提升就早停能省大量时间optimizerAdamW中小数据集上比SGD收敛更稳不太需要手动调学习率策略lr00.001初始学习率过大loss波动剧烈过小前几十轮几乎不下降save_period10每10轮保存一次checkpoint中途断了不用从头训如果你是第一次跑建议先把epochs设成10imgsz设成512batch设成8跑一个“冒烟训练”确认数据读取、模型加载、日志输出全都正常再启动完整训练。直接开100轮然后发现路径错了浪费的是几小时甚至十几个小时。训练过程中终端会输出每轮的box_loss、cls_loss、dfl_loss以及验证集的mAP指标训练结束后runs/detect/train/目录下会生成权重文件和训练曲线图。4.3 训练完先看损失函数曲线图怎么判断模型没跑偏训练结束后打开runs/detect/train/results.png这是yolov8自动生成的损失函数曲线图也是判断训练是否正常的第一手依据。先看train/box_loss和val/box_loss两条曲线不看上面的绝对值看趋势。正常情况train/box_loss前二三十轮明显下降然后趋于平缓val/box_loss紧随其后下降最后在一个较低位置稳定。这说明模型学到了检测框的回归规律。过拟合的典型信号是train/box_loss持续下降、val/box_loss却在后半程掉头上升两者形成“开口向上的剪刀差”。对策是减小epochs、增大数据增强或降低模型复杂度。loss曲线像锯齿一样大幅震荡说明学习率偏大或batch太小先降lr0到0.0005再试。前十几轮完全不降先别急着调参检查一下数据是不是大量图片没有标签、是不是img_path读到了全黑的损坏图片。还有一条很实用的经验当类别只有两类且样本数差距明显时cls_loss如果一直下不去多半是负样本类别数量不足模型没有足够的“站着的人”去学会把fall和standing分开。这时候调学习率没用回去补标注数据。4.4 验证与评估不看mAP就不知道模型能不能用训练完成后用验证集跑一次正式评估yolo detect val \ datafall.yaml \ modelruns/detect/train/weights/best.pt输出里最关键的指标是mAP50和mAP50-95。mAP50是IoU阈值0.5下的平均精度代表“框出来并且定位基本准确”的比例mAP50-95是把IoU阈值从0.5逐步提高到0.95再算平均要求更严格的框质量。对跌倒检测这种安全敏感场景mAP50建议至少在0.9以上mAP50-95在0.7以上才算“可靠”。如果mAP50已经很高但mAP50-95偏低说明模型框的位置有偏移框是大概对了但不精细需要回头检查标注框紧密度。除了看数字直接把best.pt在验证集上跑一遍可视化推理人眼筛一遍漏检和误检比任何指标都直观。这一步放在最后一章详细讲。5. 跌倒检测训练避坑5 个高频翻车现场与排查思路5.1 跌倒样本太少模型把“蹲下”当成“跌倒”现象训练完后测试人蹲下系鞋带、弯腰捡东西时被报成fall置信度还很高。原因fall类只有100来张样本模型没有见过足够的“非跌倒低姿态”负样本。蹲下和跌倒的边界框形态非常像——都是人的高度变矮、宽高比变大——模型只能靠猜。解决至少把fall补到300张同时给standing类增加蹲下、坐下、弯腰的样本。如果实在找不到数据把站立状态下蹲、弯腰的视频抽帧补进去比硬加数据增强更有效。判断依据是验证集里这类误检占了多大比例。5.2 标注框过大模型学了一堆背景特征现象mAP指标很好看但实际推理时框比人大一圈而且背景里有类似纹理时置信度虚高。原因标注时为了省事框没有收紧把地面、墙面、沙发边角都包进去了。yolov8回归的是边界框模型会把“背景花纹”学成目标特征推理时一旦画面里出现类似背景就开始误报。解决没有捷径只能回到labelme里把框修小。一个快速排查方法是统计labels目录下txt文件中框宽和框高归一化后的最大值如果超过0.95说明有框几乎撑满整张图基本就是画太松了。5.3 换了个摄像头角度精度骤降现象训练集用的是45度俯视视角部署到现场换成平视视角mAP呕掉十几个点。原因检测模型对视角非常敏感。45度俯视时跌倒的人框型偏“扁”平视时跌倒的人框型更接近“竖长条”宽高比分布完全不同。这是数据分布漂移问题调学习率没有用。解决最稳妥的做法是直接把部署场景里的真实画面抽帧并标注加入训练集。临时方案可以加ultralytics内置的随机旋转和透视增强参数但增强只是延缓问题没有根本解决。5.4 CPU上训练太慢一跑就是十几个小时现象用yolov8s开640分辨率训100轮CPU上跑了一整天才到第40轮毕设时间根本不够。原因CPU没有矩阵加速单元imgsz640、batch16时单轮就要几分钟到十几分钟。很多人误以为小batch能提速实际上batch太小反而导致每步开销占比更高。解决先用yolov8n imgsz512 epochs10跑通全流程确认能收敛后再考虑上完整参数。如果要出正式结果直接用带GPU的云平台跑训练本地CPU只做推理验证。这是性价比最高的搭配方式。5.5 损失曲线剧烈震荡训练中途loss突然变成nan现象训练日志里loss值忽高忽低某一步突然变成nan之后再也没恢复。权重文件检不出任何目标。原因学习率太大导致梯度爆炸或者数据里出现了损坏图片全黑、全白、分辨率异常。yolov8在读取异常图片时可能返回无效的梯度。解决先降低lr0到0.0005这是最常见的修复手段同时用脚本检查数据集中所有图片的尺寸是否正常、是否有0字节文件。遇到过不止一次问题不在参数而在硬盘里躺着几张截了一半的jpg。删除损坏图片重新训练即可。6. 模型验证与部署收尾从best.pt导出ONNX并走通RK3588部署路线6.1 部署前先用真实场景图做可视化验证训练结束后别急着导出模型先在没参与训练的图片上跑一遍推理确认检测框的位置和置信度符合预期# 在 val 或 test 目录上跑推理save 保存可视化结果conf 控制置信度阈值 yolo predict \ modelruns/detect/train/weights/best.pt \ sourcefall_det/test/images \ conf0.35 \ saveTrueconf0.35是我在跌倒检测场景里的经验值。之前用过0.5漏检率高降到0.25又容易在监控画面上产生大量误报。0.35到0.4之间是安全线和召回率的折中区间。如果这里发现置信度普遍偏低不要急着调conf回头检查训练集和验证集是否来自不同场景分布。6.2 导出ONNX并验证精度一致性可视化确认没问题后把best.pt导出成ONNX格式方便脱离yolov8环境部署yolo export \ modelbest.pt \ formatonnx \ imgsz640 \ opset12导出ONNX后精度通常会有微小的损失这是正常的浮点转换误差。需要验证的是这个误差没有影响到实际使用——用一个简单的onnxruntime脚本跑同一张图和yolov8的预测结果对比框位置import onnxruntime as ort import numpy as np from PIL import Image sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) img Image.open(test.jpg).resize((640, 640)) x np.array(img).astype(np.float32) / 255.0 x x.transpose(2, 0, 1)[None] # HWC 转 NCHW形状为 (1, 3, 640, 640) out sess.run(None, {sess.get_inputs()[0].name: x}) # out 的第一个元素形状是 (1, 8400, 4类别数)8400 是三个尺度特征图输出的锚点总数 # 后面还需要做置信度过滤和 NMS这一步已经超出模型本身属于后处理逻辑ONNX模型不含NMS输出的是8400个候选框的原始预测结果需要自己在后处理代码里实现置信度过滤和极大值抑制。这是导出部署时最容易忽略的一环很多人拿到ONNX后直接取输出发现一堆重叠框误以为导出失败了其实是后处理缺失。6.3 在RK3588等边缘设备上部署的路线建议项目如果要上板RK3588是目前常见的选择它的NPU能跑yolov8的小模型。流程并不复杂先把best.pt导出为ONNX再用rknn-toolkit2把ONNX转成RKNN格式最后在板端加载RKNN模型推理。转换时注意两个点一是imgsz要和训练时一致不要随意改分辨率否则精度会有可见下降二是int8量化会带来几个点的精度损失如果模型在验证集上已经是勉强过线建议先用fp16或混合精度部署等确认没问题后再尝试int8。最后说一个我从跌倒检测项目里沉淀下来的习惯拿到任何检测项目第一件事不是跑模型而是把数据集的场景分布和标注质量完整过一遍——多少人、多少场景、多少光照条件、框贴不贴边。这个习惯帮我少翻了很多次车。希望帮到你。本文还有配套的精品资源点击获取