简介:本资源面向计算机视觉入门与进阶开发者、安防场景算法实践者,提供一套完整的YOLOv8行人跌倒检测方案,解决从数据准备到界面部署的全流程问题。包内包含基于一千多张行人跌倒图像训练好的检测权重,目标类别为fall,并附PR曲线、loss曲线等训练过程记录,便于评估模型表现。资源共约2000个文件,以txt标签、jpg样本图、py源码、pyc缓存、yaml配置为主,另有pt权重、ui界面文件、mp4演示视频及pdf说明文档,压缩包约134.81MB,目录按数据集、代码、界面等模块划分清晰。PyQt界面支持图片、视频与摄像头三种检测方式,数据集同时提供txt和xml两种标注格式,分别存放于独立文件夹,方便直接用于训练或格式转换。目前已有744人学习下载,适合希望快速复现跌倒检测项目、理解YOLOv8训练与推理流程、并借助可视化界面完成演示的读者参考。
1. 从一次养老院走廊的误报说起:YOLOv8行人跌倒检测到底在做什么
去年帮一个做智慧养老的朋友调系统,他们用普通摄像头做跌倒报警,结果老人蹲下系鞋带、弯腰捡东西,后台一天弹几十条告警,护工直接把通知关了。问题不在摄像头,在于只用了「人体框高宽比突变」这种规则判断,没有真正识别「跌倒」这个动作的时空特征。后来换成 YOLOv8 做人体检测,再叠加姿态和时序逻辑,误报率才压下来。这套「YOLOv8行人跌倒检测+训练好的模型+pyqt界面+数据集」的组合,本质是把检测、判定、可视化三件事串成一条能落地的链路:YOLOv8 负责在每一帧里稳定框出人,跌倒判定负责区分「躺下」和「摔倒」,PyQt 界面负责让非技术护工也能看懂和操作。它适合两类人:一类是想拿现成模型快速搭演示或毕设的开发者,另一类是想把跌倒检测接进自己监控系统的工程人员。下面我按「数据怎么准备、模型怎么训、界面怎么接、坑在哪」的顺序,把这条链路拆开讲清楚。
2. 数据集与标注:跌倒检测的成败八成在数据这一层
2.1 跌倒检测数据集和通用行人数据集差在哪
很多人第一反应是拿 COCO 或 VOC 里的人物框直接训,结果模型只学会「框人」,根本不知道什么叫跌倒。跌倒检测的数据集必须包含三类关键样本:正常站立行走、蹲坐弯腰等易混淆动作、真实跌倒(含躺地、侧摔、后仰)。常见做法是自建,用固定摄像头在走廊、卧室、卫生间门口各录一段,再抽帧标注。公开数据集里,像 UR Fall Detection、Le2i 这类偏学术,场景和国内养老院差异大,直接拿来训往往过拟合。我一般建议:公开数据集做预训练底子,自建 2000~5000 张做微调,比例大概 1:2。
标注用 LabelImg 或 Labelme 都行,YOLOv8 要的是 YOLO 格式的 txt。每张图一个同名 txt,每行类别 x_center y_center w h,坐标全部归一化到 0~1。类别建议只设两类:person和fall。别把「蹲下」单独设一类,否则类别不平衡会更严重,蹲下交给后处理逻辑去过滤。
2.2 把标注数据整理成 YOLOv8 能吃的目录结构
YOLOv8 对目录结构有固定要求,整理错了训练直接报找不到标签。标准结构如下:
fall_dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 对应训练标签 txt │ └── val/ # 对应验证标签 txt └── data.yaml # 数据集配置文件data.yaml内容:
path: /home/user/fall_dataset # 数据集根目录,绝对路径最稳 train: images/train val: images/val nc: 2 # 类别数 names: ['person', 'fall'] # 类别名,顺序必须和标注时一致这里有个血泪经验:names的顺序必须和标注时类别 id 对应,标fall时如果 id 写成 1,names 里fall就得排第二。顺序错位模型会把跌倒当人,训练 loss 看着降,实际全错。整理完跑一句校验,确认图片和标签数量一致:
import os img_dir = 'fall_dataset/images/train' lbl_dir = 'fall_dataset/labels/train' imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir)} lbls = {os.path.splitext(f)[0] for f in os.listdir(lbl_dir)} print('无标签的图片:', imgs - lbls) # 应为空 print('无图片的标签:', lbls - imgs) # 应为空参数说明:imgs - lbls是漏标的图片,lbls - imgs是删图后残留的标签,两者都必须为空才能开训。这一步花两分钟,能省后面几小时的排查。
2.3 数据增强里哪些能用哪些会帮倒忙
YOLOv8 默认开启 mosaic、HSV 抖动、随机翻转。跌倒检测里,水平翻转可以用,垂直翻转千万别开——人倒过来在物理上不成立,模型会学到错误特征。mosaic 四图拼接对小目标有帮助,但跌倒样本本身占画面比例大,mosaic 反而可能把两个跌倒拼成一张,造成标签混乱。我一般把mosaic关掉或调低到 0.3,fliplr保持 0.5,scale控制在 0.5 以内,避免人体被缩得太小。这些在训练命令里通过参数覆盖即可,不用改源码。
3. 训练 YOLOv8 跌倒检测模型:参数怎么设、曲线怎么看
3.1 环境配置与最小训练命令
环境用 Python 3.9~3.10,PyTorch 按显卡选版本。CPU 也能跑,只是慢,ubuntu20.04上装 CPU 版做验证没问题,正式训还是建议有张 8G 以上显存的卡。装 ultralytics:
pip install ultralytics==8.0.200 yolo checks # 检查环境和依赖最小训练命令:
yolo detect train \ data=fall_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/fall \ name=exp1参数说明:model=yolov8n.pt是官方预训练权重,n 最轻量,跌倒检测场景够用,显存紧张就选它;imgsz=640是输入尺寸,摄像头分辨率高可以提到 960,但显存翻倍;batch=16按显存调,8G 卡跑 640 一般能到 16;lr0=0.01初始学习率,微调自建小数据集可以降到 0.001;patience=20是早停,20 轮没提升就停,省时间。训练完权重在runs/fall/exp1/weights/best.pt。
3.2 损失曲线和 mAP 曲线到底该看哪条
训练完runs/fall/exp1/下会有results.png,包含 box_loss、cls_loss、mAP50、mAP50-95。看曲线有三个判断点:box_loss 和 cls_loss 应平稳下降,如果 cls_loss 震荡大,多半是学习率偏高或类别不平衡;mAP50 到 0.85 以上基本可用,跌倒检测因为样本少,能到 0.8 就不错;如果训练集 mAP 高、验证集低,是过拟合,加数据或加dropout。想自己画曲线,读results.csv:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/fall/exp1/results.csv') df.columns = df.columns.str.strip() # 列名常带空格,必须清理 plt.plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP50') plt.plot(df['epoch'], df['train/box_loss'], label='box_loss') plt.legend(); plt.xlabel('epoch'); plt.savefig('curve.png')参数说明:columns.str.strip()这行别省,ultralytics 导出的 csv 列名前后有空格,不清理会 KeyError,这是很多人画不出曲线的直接原因。
3.3 推理验证:先确认模型真的能分「跌倒」和「蹲下」
训完别急着接界面,先用命令行验证几张图:
yolo detect predict \ model=runs/fall/exp1/weights/best.pt \ source=test_imgs/ \ conf=0.4 \ save=Trueconf=0.4是置信度阈值,跌倒检测宁可稍低也别漏报,但太低会误报,0.4 是常见起点。重点看两类图:蹲下弯腰的有没有被误判成 fall,真实躺地的有没有被漏掉。如果蹲下误报多,说明数据里负样本不够,回去补;如果躺地漏报,看是不是跌倒样本姿态太单一,补侧摔、后仰样本。这一步是接界面前最后一道关,别跳过。
4. PyQt 界面接入:把模型变成护工能用的工具
4.1 界面最小功能集与线程模型
PyQt 界面别一上来做花哨的,最小可用集就四块:视频显示区、开始/停止按钮、报警日志区、置信度滑块。核心难点是推理不能跑在主线程,否则界面卡死。常见做法是 QThread 开子线程跑 YOLOv8 推理,通过信号把结果发回主线程刷新。下面是一个可复用的推理线程骨架:
from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO import cv2 class DetectThread(QThread): frame_signal = pyqtSignal(object) # 发带框的帧 alarm_signal = pyqtSignal(str) # 发报警文本 def __init__(self, model_path, source=0, conf=0.4): super().__init__() self.model = YOLO(model_path) self.source = source self.conf = conf self.running = True def run(self): cap = cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame = cap.read() if not ret: break results = self.model(frame, conf=self.conf, verbose=False) for r in results: for box in r.boxes: cls_id = int(box.cls[0]) name = self.model.names[cls_id] if name == 'fall': self.alarm_signal.emit('检测到跌倒,请立即查看') annotated = results[0].plot() # 画框 self.frame_signal.emit(annotated) cap.release() def stop(self): self.running = False self.wait()逻辑说明:frame_signal传 numpy 图像给主线程显示,alarm_signal传报警字符串。results[0].plot()是 ultralytics 自带的画框方法,省得自己写。参数conf从界面滑块读,source=0是本地摄像头,也可以传视频路径。注意verbose=False关掉每帧日志,否则控制台刷屏拖慢速度。
4.2 主窗口怎么把信号接起来
主窗口负责显示和交互,关键是把子线程信号连到槽函数:
from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget from PyQt5.QtGui import QImage, QPixmap import cv2 class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label = QLabel('视频区') self.btn = QPushButton('开始检测') self.btn.clicked.connect(self.toggle) layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn) container = QWidget(); container.setLayout(layout) self.setCentralWidget(container) self.thread = None def toggle(self): if self.thread is None: self.thread = DetectThread('runs/fall/exp1/weights/best.pt', conf=0.4) self.thread.frame_signal.connect(self.show_frame) self.thread.alarm_signal.connect(self.show_alarm) self.thread.start() self.btn.setText('停止') else: self.thread.stop() self.thread = None self.btn.setText('开始检测') def show_frame(self, frame): rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, _ = rgb.shape img = QImage(rgb.data, w, h, w * 3, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(img).scaled(self.label.size())) def show_alarm(self, text): print('报警:', text) # 实际可换成日志区追加参数说明:QImage构造里的w * 3是每行字节数,RGB 三通道,写错会花屏。scaled保持比例显示。show_alarm里换成往 QTextEdit 追加文本即可。这套骨架跑通后,再加报警声音、日志落盘、多路摄像头都不难。
4.3 界面和模型解耦:换模型不用改界面
把模型路径、置信度、类别名抽成配置,界面只读配置。这样以后换yolov8s.pt或换自训权重,只改配置文件。常见做法是写个config.yaml,界面启动时读。别把路径硬编码在代码里,否则部署到另一台机器全是错。
5. 避坑与排查:跌倒检测落地时最容易翻车的五件事
5.1 现象:模型把蹲下、弯腰全报成跌倒
原因:训练集里负样本(蹲、坐、弯腰)太少,模型只学到「人体框变矮变宽」就等于跌倒。解决:补 500~1000 张易混淆动作图,标成person,重新微调;同时在推理后加逻辑,连续 N 帧判定为 fall 才报警,单帧不报。
5.2 现象:训练 loss 正常下降,但验证 mAP 一直是 0
原因:data.yaml里names顺序和标注类别 id 错位,或者val路径写错导致验证集为空。解决:跑 2.2 的校验脚本确认图片标签配对,再打印data.yaml核对 names 顺序,路径用绝对路径。
5.3 现象:PyQt 界面点开始后卡死无响应
原因:推理跑在主线程,或者子线程里直接操作了界面控件。解决:推理必须放 QThread,界面刷新只能通过信号槽在主线程做,子线程里禁止直接调setPixmap。
5.4 现象:CPU 上跑推理只有 2~3 帧,界面像幻灯片
原因:yolov8n在 CPU 上 640 输入也就这个水平。解决:降imgsz到 416,或换yolov8n的 OpenVINO/ONNX 导出加速;实在要实时,得上 GPU 或边缘加速棒。别指望 CPU 跑 1080p 实时。
5.5 现象:换一台机器部署,报找不到模型或类别错乱
原因:模型路径硬编码、names从模型里读但和界面写死的类别对不上。解决:模型路径走配置,类别名统一从model.names读,界面不写死。部署前在目标机器跑一遍yolo detect predict确认模型能加载。
6. 让跌倒检测更稳的一个技巧:用连续帧投票压掉单帧误报
单帧检测再准也架不住光照突变、遮挡、运动模糊。我一般会在 PyQt 线程里加一个滑动窗口投票:维护最近 10 帧的判定结果,fall 出现超过 6 帧才触发报警,同时要求人体框中心在窗口内持续下移。这样蹲下这种短暂动作会被自然过滤,真实跌倒因为持续躺地会稳定命中。实现上用一个collections.deque(maxlen=10)存每帧是否 fall,报警前统计比例:
from collections import deque class FallVoter: def __init__(self, window=10, threshold=0.6): self.buf = deque(maxlen=window) self.threshold = threshold def update(self, is_fall): self.buf.append(1 if is_fall else 0) if len(self.buf) < self.buf.maxlen: return False return sum(self.buf) / len(self.buf) >= self.threshold参数说明:window=10对应约 0.3~0.5 秒(按帧率),threshold=0.6表示 10 帧里 6 帧判定跌倒才报警。窗口太长会延迟报警,太短压不住误报,10 和 0.6 是我在 25fps 摄像头下试出来的平衡点。验证方法很简单:录一段「蹲下捡东西再站起」和一段「真实侧摔」,分别跑投票逻辑,看误报和漏报。我自己的习惯是每换一个场景,先跑这两段回归,再上线。这套投票逻辑不复杂,但它是把 demo 变成能用的系统之间最关键的一步。希望帮到你。
本文还有配套的精品资源,点击获取