简介:本资源面向计算机视觉入门与进阶开发者、智能安防与养老监护方向的算法实践者,提供一套可直接运行的YOLOv8行人跌倒检测完整方案,目标类别为fall单类,解决从数据准备到界面部署的全流程问题。压缩包共约2000个文件、134.81MB,包含171个Python源码、56个yaml配置、1431个txt标签、205张jpg样本图,以及训练好的pt权重、PR曲线与loss曲线、PyQt界面文件、Dockerfile和说明文档,覆盖训练、推理与可视化各环节。资源附1000多张行人摔倒数据集,标签同时提供txt与xml两种格式,便于适配不同检测框架。PyQt界面支持图片检测、视频检测与摄像头实时调用,方便快速验证效果。目前已有744人学习下载,读者可据此复现训练流程、替换自有数据微调模型,并参考曲线与权重完成跌倒识别项目的落地与二次开发。
1. 从一次深夜误报到可复现的跌倒检测:这套资源到底能干什么
凌晨两点,养老院走廊的摄像头把一位老人蹲下系鞋带的动作识别成了跌倒,值班护士白跑一趟。这类误报在行人跌倒检测里太常见了,问题往往不在模型本身,而在数据标注、姿态特征和阈值策略没对齐。这套资源就是冲着这个场景来的:YOLOv8 行人跌倒检测,配训练好的权重、PyQt 可视化界面,还有一份可直接拿来重训的数据集。它解决的是从「有想法」到「能演示、能复现、能改」的完整链路,不是只丢一个模型文件让你自己猜怎么用。适合做毕设的学生、要快速搭原型的算法工程师,以及需要给客户演示跌倒告警的产品同学。你拿到手就能跑推理、看界面、换数据重训,省掉环境拼装和界面从零写的重复劳动。
2. 环境与依赖:把 YOLOv8 和 PyQt 装进同一个 Python 里
2.1 为什么优先用 conda 而不是裸 pip
YOLOv8 依赖 ultralytics,PyQt 依赖 Qt 运行时,两者对 Python 版本和底层库的敏感度不一样。裸 pip 装完经常出现 PyQt5 能跑但 torch 找不到 CUDA,或者反过来。我一般用 conda 建独立环境,把 Python 版本锁在 3.9 或 3.10,这两个版本对 ultralytics 和 PyQt5 的兼容性最稳。显卡驱动这块,如果你用的是 GTX 1660 Ti 这类 6G 显存的卡,跑推理完全够,训练时把 batch 压到 8 或 16 就行,别一上来就 32,显存爆了报错信息还特别绕。
conda create -n fall_detect python=3.10 -y conda activate fall_detect # 先装 torch,按你的 CUDA 版本选,没有 GPU 就用 cpu 版 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install PyQt5 opencv-python numpy这段命令的顺序有讲究:torch 必须先装,因为 ultralytics 安装时会检测 torch 是否已存在,如果先装 ultralytics,它可能拉一个 CPU 版 torch 进来,后面再换 GPU 版容易出冲突。--index-url指向 PyTorch 官方 wheel 源,cu118 对应 CUDA 11.8,你可以在nvidia-smi右上角看到自己的 CUDA 版本,按那个改。装完用下面三行验证,缺一个都别往下走。
import torch print(torch.__version__, torch.cuda.is_available()) from ultralytics import YOLO from PyQt5.QtWidgets import QApplication print("deps ok")torch.cuda.is_available()返回 False 不代表不能用,只是推理会走 CPU,速度慢但结果一致。如果你在 Ubuntu 20.04 上搭 CPU 版本,把 torch 安装命令换成pip install torch torchvision即可,别加 index-url。
2.2 目录结构决定你后面改代码顺不顺
拿到资源包先别急着运行,花两分钟看一眼目录。典型结构是weights/放训练好的.pt,datasets/放图片和标签,ui/放 PyQt 的.ui或.py,根目录一个main.py或detect.py作为入口。我习惯把权重和数据集分开放在项目外层,代码里用相对路径引用,这样换数据集时不用动代码。如果你发现标签文件是.txt且每行是class x_center y_center w h的归一化格式,说明是 YOLO 标准格式,可以直接训练;如果是.xml,那是 VOC 格式,得先转,转换脚本后面会讲。
注意:路径里不要出现中文和空格,PyQt 读文件时对中文路径的处理在不同系统上表现不一致,这是血泪经验,能避就避。
3. 数据集与标注:跌倒检测的类别设计和边界框怎么定
3.1 两类还是三类,取决于你的误报容忍度
跌倒检测常见做法是分两类:fall和person。但实际跑起来你会发现,蹲下、坐下、弯腰这些动作和跌倒的边界框高度重叠,模型很容易把蹲下判成跌倒。我的建议是加一个bending类,把蹲、坐、弯腰都归进去,让模型学到「非跌倒的低头动作」长什么样。这套资源的数据集如果已经标好,先看data.yaml里的names列表,是两类就按两类训,想加类就自己补标。类别数变了,模型头部的输出维度会变,训练时 ultralytics 会自动调整,不用手动改网络结构。
# data.yaml 示例 path: ./datasets/fall train: images/train val: images/val nc: 3 names: ['fall', 'person', 'bending']path是数据集根目录,train和val是相对路径,nc是类别数,names的顺序必须和标签文件里的 class id 对应,0 对应第一个名字。改完这个文件,训练命令里用data=data.yaml指过来就行。如果你只有两类,把nc改成 2,names删掉bending,同时检查标签里有没有 class id 为 2 的行,有的话要么删掉那些框,要么重新映射。
3.2 用 labelme 标完转 YOLO 格式的两个坑
很多人习惯用 labelme 标矩形框,导出的是 JSON,需要转成 YOLO 的 txt。转换脚本网上很多,但有两个坑:一是 labelme 的坐标是左上角和右下角的绝对像素值,YOLO 要的是归一化的中心点和宽高;二是图片如果有旋转信息(EXIF),labelme 读到的尺寸和实际渲染尺寸可能不一致,导致框偏移。转换时先统一用 PIL 读图拿width, height,再算归一化值。
import json, os from PIL import Image def labelme_to_yolo(json_path, out_dir, class_map): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img = Image.open(json_path.replace('.json', '.jpg')) w, h = img.size lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue cls_id = class_map[label] (x1, y1), (x2, y2) = shape['points'] cx = (x1 + x2) / 2 / w cy = (y1 + y2) / 2 / h bw = abs(x2 - x1) / w bh = abs(y2 - y1) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_name = os.path.basename(json_path).replace('.json', '.txt') with open(os.path.join(out_dir, out_name), 'w') as f: f.write('\n'.join(lines)) class_map = {'fall': 0, 'person': 1, 'bending': 2}class_map把标签名映射到数字 id,必须和data.yaml的names顺序一致。cx, cy是中心点归一化坐标,bw, bh是归一化宽高,保留 6 位小数足够。转换完抽查几张,用cv2.rectangle画出来看看框有没有偏,偏了多半是 EXIF 旋转没处理,加一句ImageOps.exif_transpose(img)再取尺寸。
3.3 训练集和验证集的划分别用随机
跌倒检测的数据往往来自连续视频帧,相邻帧几乎一样。如果你用随机划分,验证集里会出现和训练集同一秒的帧,指标虚高,实际部署就翻车。正确做法是按视频片段划分:同一个视频的帧要么全在训练集,要么全在验证集。资源包里如果已经分好,检查一下train和val的图片文件名有没有相同前缀,有的话说明划分可能有问题,自己按视频重分一遍。比例上 8:2 够用,数据少就 7:3,验证集至少留 200 张,不然指标波动大。
4. 训练与调参:让模型真正学会区分跌倒和蹲下
4.1 从预训练权重起步,别从零训
YOLOv8 的预训练权重是在 COCO 上训的,已经学会了「人」的通用特征。跌倒检测数据量通常不大,几千张算多的,从零训容易过拟合。加载yolov8n.pt或yolov8s.pt作为起点,n 版最快,s 版精度略高,6G 显存跑 s 版 batch=16 没问题。训练命令里pretrained=True是默认的,你只要把模型名写对,ultralytics 会自动下载权重。如果资源包里已经带了训练好的.pt,你可以直接拿来做推理,也可以在此基础上继续训自己的数据。
yolo detect train \ model=yolov8s.pt \ data=datasets/fall/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/fall \ name=exp1epochs=100是上限,patience=20表示 20 轮验证指标不升就早停,避免浪费时间。lr0=0.01是初始学习率,数据量小可以降到 0.005。imgsz=640是输入尺寸,跌倒检测里人占画面比例不大,640 够用,想提小目标可以上 1280,但显存和速度会翻倍。project和name决定输出目录,跑完在runs/fall/exp1/weights/下能看到best.pt和last.pt,部署用best.pt。
4.2 看损失曲线判断有没有训崩
训练日志里重点看三个值:box_loss、cls_loss、dfl_loss。正常情况三者都下降,cls_loss降得慢一点没关系。如果cls_loss震荡不降,多半是学习率太大或类别不平衡,把lr0减半,或者在data.yaml里给bending类少标一些。如果box_loss降到很低但cls_loss很高,说明框定位准了但类别分不清,这时候加数据比调参有用。资源包里如果有results.csv,用 pandas 画一下曲线,比盯着日志直观。
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['train/box_loss'], label='box') plt.plot(df['epoch'], df['train/cls_loss'], label='cls') plt.legend() plt.savefig('loss_curve.png')列名里可能有空格,str.strip()先清一下。这张图能帮你判断早停点是否合理,如果val/cls_loss在后期开始上升,说明过拟合了,回退到best.pt用就行。
4.3 推理时的置信度和 IoU 阈值怎么设
训练完直接yolo detect predict用的是默认conf=0.25、iou=0.7。跌倒检测里,conf太低会误报,太高会漏报。我的经验是先把conf设到 0.4 跑一批测试图,看漏报多还是误报多,再微调。iou控制 NMS 合并框的力度,人挨人时调低到 0.5 能减少框被吞。PyQt 界面里如果暴露了这两个滑块,让用户能实时调,演示效果会好很多。
from ultralytics import YOLO model = YOLO('runs/fall/exp1/weights/best.pt') results = model.predict('test.jpg', conf=0.4, iou=0.5, imgsz=640) for r in results: for box in r.boxes: print(r.names[int(box.cls)], float(box.conf), box.xyxy.tolist())r.names是类别 id 到名字的映射,box.conf是置信度,box.xyxy是左上右下坐标。拿到这些数据后,你可以加一个逻辑:连续 5 帧检测到fall且框的中心点低于画面 2/3 才触发告警,这样能过滤掉大部分单帧误报。
5. PyQt 界面集成:把模型塞进可视化窗口的常见问题
5.1 界面线程和推理线程必须分开
PyQt 的主线程负责刷新界面,如果你把推理直接写在按钮点击的回调里,视频一卡一卡,点关闭还会无响应。正确做法是用QThread把推理循环放到子线程,通过信号把带框的帧传回主线程显示。资源包里的main.py如果已经这么做了,你重点看signal的定义和emit的位置;如果没做,自己抽一个DetectThread出来。
from PyQt5.QtCore import QThread, pyqtSignal import cv2 from ultralytics import YOLO class DetectThread(QThread): frame_ready = pyqtSignal(object) def __init__(self, model_path, source): super().__init__() self.model = YOLO(model_path) self.source = source 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.predict(frame, conf=0.4, verbose=False) annotated = results[0].plot() self.frame_ready.emit(annotated) cap.release() def stop(self): self.running = False self.wait()frame_ready信号携带 numpy 数组,主线程接到后转成QImage再显示。verbose=False关掉每帧的控制台输出,不然日志刷屏。stop()里先置标志再wait(),避免线程还在跑就退出程序导致崩溃。这个结构你换成摄像头源或视频文件都行,source传 0 是默认摄像头,传路径是文件。
5.2 显示帧的格式转换别用错
OpenCV 读出来是 BGR,Qt 显示要 RGB,转换时注意通道顺序和内存连续性。常见写法是QImage(frame.data, w, h, bytesPerLine, QImage.Format_RGB888),但frame.data在 numpy 里不一定连续,先copy()一下。如果画面颜色发蓝,就是 BGR 没转 RGB;如果花屏,多半是bytesPerLine没算对,用frame.strides[0]传进去。
from PyQt5.QtGui import QImage, QPixmap import cv2 def show_frame(self, frame): rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qt_img = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888).copy() self.label.setPixmap(QPixmap.fromImage(qt_img)).copy()不能省,否则 QImage 引用的内存被下一帧覆盖,显示会闪。ch * w就是每行字节数,RGB 三通道所以是 3 乘宽度。这个函数挂在frame_ready信号的槽上,每来一帧调一次。
5.3 界面卡顿和内存泄漏的排查
跑久了界面越来越卡,多半是QPixmap没释放或者信号连接重复了。每次setPixmap会替换旧的,一般不用手动释放,但如果你在循环里connect信号,就会一次触发多次。检查__init__里信号连接只做一次,别放在按钮回调里。另外cv2.VideoCapture用完必须release(),否则摄像头被占着,下次打开失败。内存泄漏可以用tracemalloc抓一下,看是不是每帧都在新建大对象。
6. 避坑与排查:跌倒检测落地时最容易翻车的五件事
6.1 现象:模型把蹲下全判成跌倒
原因:训练集里bending类样本太少,或者根本没标这个类,模型只学过「人形+低姿态=跌倒」。解决:补标 200 张以上的蹲、坐、弯腰图,加进bending类重训;如果不想重标,在推理后加姿态判断,用 YOLOv8-pose 拿关键点,膝盖角度小于阈值才触发跌倒。
6.2 现象:PyQt 界面点开始后无响应
原因:推理跑在主线程,阻塞了 Qt 事件循环。解决:按 5.1 的QThread方案把推理挪到子线程,主线程只负责显示。检查run()里有没有死循环没加self.running判断,导致stop()调了也退不出来。
6.3 现象:训练 loss 正常但验证指标全是 0
原因:data.yaml里的val路径写错,或者验证集标签文件和图片没对上。解决:先确认val目录下图片和 txt 同名同数量,再用yolo detect val单独跑一次,看输出里Images数量是不是 0。是 0 就改路径,路径用相对项目根目录的写法,别用绝对路径。
6.4 现象:换了自己的数据集后类别全乱
原因:data.yaml的names顺序和标签里的 class id 不一致。解决:打开几个标签 txt,看第一个数字最大是几,names列表长度必须大于这个最大值加一,且顺序对应。改完nc也要同步改,不然训练时维度对不上会报错。
6.5 现象:推理速度慢到没法实时
原因:用了yolov8x这种大模型,或者imgsz设了 1280,或者没走 GPU。解决:换yolov8n.pt,imgsz降到 640,确认torch.cuda.is_available()是 True。如果必须用大模型,开half=True用半精度推理,速度能提 30% 左右,精度掉一点点。
7. 进阶技巧:用姿态关键点把误报再压一半
纯检测框做跌倒判断,天生分不清蹲和摔。我后来习惯在检测之后接一个姿态分支:YOLOv8-pose 拿 17 个关键点,算躯干与地面的夹角和膝盖弯曲角度。站立时躯干接近垂直,跌倒时躯干接近水平且头部关键点低于髋部。这个逻辑不复杂,但能把误报压到原来的三分之一。
from ultralytics import YOLO pose_model = YOLO('yolov8s-pose.pt') def is_fall(keypoints): # keypoints: 17x2 的 xy 坐标 left_hip, right_hip = keypoints[11], keypoints[12] left_shoulder, right_shoulder = keypoints[5], keypoints[6] hip_y = (left_hip[1] + right_hip[1]) / 2 shoulder_y = (left_shoulder[1] + right_shoulder[1]) / 2 # 肩在髋下方,说明身体倒置或水平 if shoulder_y > hip_y: return True return Falsekeypoints的索引是 COCO 标准,11 和 12 是左右髋,5 和 6 是左右肩。正常站立时肩的 y 坐标小于髋(画面坐标系 y 向下),肩在髋上方;跌倒时这个关系反转。实际用的时候加一个时间窗口,连续 10 帧里 7 帧满足才告警,单帧抖动就滤掉了。这个姿态模型可以和检测模型共用同一个 YOLO 实例,model.predict时指定task='pose'也行,省一次加载。
验证这套逻辑是否有效,我一般会录一段包含蹲下、坐下、真实跌倒的测试视频,跑完统计误报和漏报数。如果误报还是多,把shoulder_y > hip_y改成shoulder_y > hip_y + 20,给一个像素余量,避免弯腰时肩略低于髋就触发。阈值没有万能值,按你的摄像头高度和拍摄角度调,调完把参数写进配置文件,别硬编码在代码里。
从那以后我每次拿到新的跌倒检测数据,都强制先跑一遍姿态分支的阈值扫描,把误报率曲线画出来再定参数,不再拍脑袋设 0.5。希望帮到你。
本文还有配套的精品资源,点击获取