
简介本资源面向计算机视觉入门与进阶开发者、智能安防与养老监护方向的算法实践者提供一套可直接运行的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 而不是裸 pipYOLOv8 依赖 ultralyticsPyQt 依赖 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 python3.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/放训练好的.ptdatasets/放图片和标签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 对应第一个名字。改完这个文件训练命令里用datadata.yaml指过来就行。如果你只有两类把nc改成 2names删掉bending同时检查标签里有没有 class id 为 2 的行有的话要么删掉那些框要么重新映射。3.2 用 labelme 标完转 YOLO 格式的两个坑很多人习惯用 labelme 标矩形框导出的是 JSON需要转成 YOLO 的 txt。转换脚本网上很多但有两个坑一是 labelme 的坐标是左上角和右下角的绝对像素值YOLO 要的是归一化的中心点和宽高二是图片如果有旋转信息EXIFlabelme 读到的尺寸和实际渲染尺寸可能不一致导致框偏移。转换时先统一用 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, encodingutf-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 版 batch16 没问题。训练命令里pretrainedTrue是默认的你只要把模型名写对ultralytics 会自动下载权重。如果资源包里已经带了训练好的.pt你可以直接拿来做推理也可以在此基础上继续训自己的数据。yolo detect train \ modelyolov8s.pt \ datadatasets/fall/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/fall \ nameexp1epochs100是上限patience20表示 20 轮验证指标不升就早停避免浪费时间。lr00.01是初始学习率数据量小可以降到 0.005。imgsz640是输入尺寸跌倒检测里人占画面比例不大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], labelbox) plt.plot(df[epoch], df[train/cls_loss], labelcls) plt.legend() plt.savefig(loss_curve.png)列名里可能有空格str.strip()先清一下。这张图能帮你判断早停点是否合理如果val/cls_loss在后期开始上升说明过拟合了回退到best.pt用就行。4.3 推理时的置信度和 IoU 阈值怎么设训练完直接yolo detect predict用的是默认conf0.25、iou0.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, conf0.4, iou0.5, imgsz640) 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, conf0.4, verboseFalse) annotated results[0].plot() self.frame_ready.emit(annotated) cap.release() def stop(self): self.running False self.wait()frame_ready信号携带 numpy 数组主线程接到后转成QImage再显示。verboseFalse关掉每帧的控制台输出不然日志刷屏。stop()里先置标志再wait()避免线程还在跑就退出程序导致崩溃。这个结构你换成摄像头源或视频文件都行source传 0 是默认摄像头传路径是文件。5.2 显示帧的格式转换别用错OpenCV 读出来是 BGRQt 显示要 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.ptimgsz降到 640确认torch.cuda.is_available()是 True。如果必须用大模型开halfTrue用半精度推理速度能提 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时指定taskpose也行省一次加载。验证这套逻辑是否有效我一般会录一段包含蹲下、坐下、真实跌倒的测试视频跑完统计误报和漏报数。如果误报还是多把shoulder_y hip_y改成shoulder_y hip_y 20给一个像素余量避免弯腰时肩略低于髋就触发。阈值没有万能值按你的摄像头高度和拍摄角度调调完把参数写进配置文件别硬编码在代码里。从那以后我每次拿到新的跌倒检测数据都强制先跑一遍姿态分支的阈值扫描把误报率曲线画出来再定参数不再拍脑袋设 0.5。希望帮到你。本文还有配套的精品资源点击获取