简介:这是一份基于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 python=3.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预训练权重,下载失败就手动把权重文件放到当前目录,再用model=yolov8n.pt指定。
2.3 用预训练权重先跑通一次推理,确认整条链路没坑
环境搭好后,别急着训练,先用官方预训练模型跑一次推理,验证图像读取、模型加载、输出保存这些环节都正常:
# 用一张自己拍的或手机里的照片先试试,source 指向图片路径 yolo predict model=yolov8n.pt source=sample.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张以上,总共800~1000张图片,足够训练出一个可用的模型。
3.2 从视频中抽帧:用ffmpeg按需抽帧而不是逐帧标注
自己录制跌倒视频是收集样本最快的方式。让朋友或同学在不同场景下模拟跌倒,每段视频录10~20秒,然后用ffmpeg隔几帧抽一张:
# 每 2 秒抽 1 帧,输出为 4 位数字编号的 jpg 图片 ffmpeg -i fall_scene_01.mp4 -vf fps=0.5 frames/01_%04d.jpg抽帧频率要控制好。fps=0.5表示每秒抽半帧,也就是每2秒一张,20秒视频出10张。为什么不逐帧抽?因为视频中相邻帧几乎完全一样,标注出来全是重复样本,模型学到的是位置偏差而不是姿态多样性。抽帧太稀疏也不行,跌倒动作往往只有1~3秒,抽得太稀可能漏掉关键的“倒地瞬间”。我一般用fps=0.5到fps=1之间,保证同一段动作能抽出3~5张不同姿态的图。
抽完帧后人工挑一遍:模糊的、动作变形的、人太小的全部删除。这一步花费的时间直接决定训练集质量,值得认真做。
3.3 labelme标注:矩形框还是多边形?跌倒场景的标注要点
标注工具选择很多,labelme是配合yolov8最顺手的,因为它的JSON格式容易转成YOLO所需的txt:
# 安装并启动 labelme pip install labelme labelme打开labelme后,左侧选择“Open Dir”打开frames目录,然后逐个文件标注。这里有一个关键选择:用矩形框还是多边形。跌倒检测建议直接用矩形框(Create Rectangle),因为跌倒检测的目标是稳定输出一个边界框,不需要像素级分割。矩形框要“贴住人主体”,不要图省事把周边背景框进去。
标注跌倒样本时,人的姿态往往是伸展开的,手脚可能分开较大。很多人下意识的做法是只框躯干,结果模型学到的是“躯干横过来就是跌倒”,当人蹲下或弯腰时误检率飙升。正确的做法是:把整个人的主体包括头部、躯干、四肢末端都包进框里,但不要为了兜住脚后跟而框进大片地面。框的上边缘顶到头顶,下边缘收到脚底,左右收到最外侧手臂或腿的边缘。每张图的框要控制在1~3个,不要漏标画面里站着的人。
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', encoding='utf-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_ok=True) 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_ok=True) os.makedirs(lab_dir, exist_ok=True) 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 \ data=fall.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=640 \ patience=20 \ optimizer=AdamW \ lr0=0.001 \ save_period=10对照表解释这组参数为什么这么设:
| 参数 | 建议值 | 含义与选择理由 |
|---|---|---|
| model | yolov8n.pt | 用nano版预训练权重初始化,参数最少,CPU能跑,训练速度最快 |
| epochs | 100 | 小数据集100轮足够完成收敛,再多容易过拟合 |
| batch | 16 | CPU训练时受内存限制,16是一个平衡值;GPU显存不够就降到8 |
| imgsz | 640 | 标准分辨率,精度和速度的折中;CPU训练可降到512 |
| patience | 20 | 连续20轮验证集指标没提升就早停,能省大量时间 |
| optimizer | AdamW | 中小数据集上比SGD收敛更稳,不太需要手动调学习率策略 |
| lr0 | 0.001 | 初始学习率,过大loss波动剧烈,过小前几十轮几乎不下降 |
| save_period | 10 | 每10轮保存一次checkpoint,中途断了不用从头训 |
如果你是第一次跑,建议先把epochs设成10,imgsz设成512,batch设成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 \ data=fall.yaml \ model=runs/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没有矩阵加速单元,imgsz=640、batch=16时单轮就要几分钟到十几分钟。很多人误以为小batch能提速,实际上batch太小反而导致每步开销占比更高。
解决:先用yolov8n + imgsz=512 + epochs=10跑通全流程,确认能收敛后,再考虑上完整参数。如果要出正式结果,直接用带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 \ model=runs/detect/train/weights/best.pt \ source=fall_det/test/images \ conf=0.35 \ save=Trueconf=0.35是我在跌倒检测场景里的经验值。之前用过0.5,漏检率高;降到0.25又容易在监控画面上产生大量误报。0.35到0.4之间是安全线和召回率的折中区间。如果这里发现置信度普遍偏低,不要急着调conf,回头检查训练集和验证集是否来自不同场景分布。
6.2 导出ONNX并验证精度一致性
可视化确认没问题后,把best.pt导出成ONNX格式,方便脱离yolov8环境部署:
yolo export \ model=best.pt \ format=onnx \ imgsz=640 \ opset=12导出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。
最后说一个我从跌倒检测项目里沉淀下来的习惯:拿到任何检测项目,第一件事不是跑模型,而是把数据集的场景分布和标注质量完整过一遍——多少人、多少场景、多少光照条件、框贴不贴边。这个习惯帮我少翻了很多次车。希望帮到你。
本文还有配套的精品资源,点击获取