简介计算机视觉目标检测技术正广泛应用于智慧养老、医院监护和安全监控等场景。YOLOv8作为新一代高效检测模型凭借速度快、精度高和工程化能力强的优势成为实时行为识别任务的首选。通过将PyTorch训练好的模型导出为ONNX格式可实现在CPU或GPU上的跨平台高效推理大幅降低部署门槛。结合PyQt5开发的精美GUI能够直观呈现检测结果并实时报警使系统具备实际落地价值。以行人跌倒检测系统为切入点完整梳理从数据集准备、模型训练、评估指标分析到ONNX导出与GUI集成的全流程并分享了训练调参、导出踩坑及推理优化等宝贵经验为构建智能安全监控系统提供可靠参考。 这几天在处理一个行人跌倒检测的需求正好把整套实现整理成了项目包基于YOLOv8的检测系统里面包含Python源码、导出的ONNX模型、评估指标曲线以及一个带GUI的精美界面压缩后就是“基于yolov8的行人跌倒检测系统python源码onnx模型评估指标曲线精美GUI界面.zip”。如果你正在做智慧养老、医院病房监护、工地安全这些场景或者想学YOLOv8的训练、ONNX部署、PyQt5界面开发这个项目就很有参考价值。我先把话说在前面这不是一个只能跑通Demo的玩具而是一个结构完整的工程。从数据准备、模型训练、指标评估到ONNX导出再到最后的GUI加载部署整条链路都是通的。下面我按实际开发顺序把每一步的设计思路、核心代码、踩坑经验都展开讲清楚。1. 项目整体设计与技术选型1.1 为什么是YOLOv8跌倒检测的核心任务是在视频画面中实时找出行人并判断行人是否处于跌倒状态。这里有两个难点一是目标检测本身要快因为跌倒过程往往只有几十帧检测速度跟不上就失去了预警意义二是跌倒姿态的判断要准确不能随便一个蹲下动作就触发报警。我做这个项目时选型对比过几套方案方案优点缺点是否适合本项目Faster R-CNN精度高小目标表现好速度慢视频流实时性差不适合YOLOv5成熟生态好需要自己写不少后处理逻辑可以用YOLOv8速度快精度高内置训练评估导出全家桶模型文件相对大一些最适合Transformer系列精度上限高部署门槛高速度不占优不适合最终选YOLOv8除了速度和精度平衡之外还看中它的工程一体化能力一条命令完成训练一条命令导出ONNX自带评估脚本和指标曲线输出这能省下很多折腾工程的时间。项目实际用的是yolov8n.pt作为预训练权重选择nano版本是因为在跌倒检测这种场景里实时性优先级很高后期还要导出ONNX跑在CPU上。如果你有比较好的GPU资源也可以换成yolov8s.pt或yolov8m.pt精度会更高但推理速度会下降。1.2 项目文件结构与功能划分拿到这个zip之后先别急着跑代码我建议你按下面的目录结构理解整个系统的设计逻辑fall_detection/ ├── weights/ │ ├── best.pt # PyTorch训练好的权重 │ └── best.onnx # 导出的ONNX模型 ├── data/ │ ├── dataset.yaml # 数据集配置文件 │ └── videos/ # 测试视频 ├── runs/ │ ├── train/ # 训练日志、权重、曲线图 │ └── val/ # 验证集评估结果 ├── gui/ │ ├── main_window.py # GUI主窗口 │ ├── camera_thread.py # 视频流处理线程 │ └── resources/ # 图标、报警音频资源 ├── inference/ │ ├── detect_onnx.py # ONNX模型推理脚本 │ └── detect_pt.py # PyTorch模型推理脚本 ├── utils/ │ ├── letterbox.py # 图像缩放填充工具 │ ├── nms.py # 非极大值抑制 │ └── fall_judge.py # 跌倒判断逻辑 ├── requirements.txt └── README.md这个结构把“训练产物”和“部署代码”分开好处是调试时不用在一个文件里来回翻。权重文件单独放weights/目录训练日志和指标统一放runs/目录utils/里放的是不依赖GUI也能单独测试的算法逻辑这样就算你不开界面也能用命令行跑推理。2. 跌倒检测数据集与训练细节2.1 训练数据怎么准备跌倒检测本质上是一个目标检测任务所以数据集标注的核心就是告诉模型“哪个框里是一个人哪个框里是一个跌倒的人”。我使用了公开的UR Fall Detection数据集和自制数据混合训练。UR Fall Detection包含多个摄像头视角的跌倒视频但样本量不算大如果只用它训练很容易过拟合。我的做法是在公开数据基础上补拍了几百张实际场景照片覆盖不同光照、不同遮挡程度、不同年龄段的人体姿态。YOLO格式的标注是TXT文件每一行代表一个目标class_id x_center y_center width height比如一张640x640的图上一个人跌倒后占据左上角区域边界框中心在(200, 300)宽200像素高120像素那么归一化后的标注就是0 0.3125 0.46875 0.3125 0.1875如果跌倒被定义为单独类别class_id可以是0如果同时检测普通行人和跌倒行人那就是两个类别class_id分别是0和1。我这套项目里采用了两类方案person和fall。这样训练出来的模型天然就能区分“站着的人”和“跌倒的人”后面做报警逻辑时会轻松很多。数据标注工具我用的LabelImg标注完直接导出YOLO格式就行。如果你要用YOLOv8-pose方案标注就会变成17个关键点操作复杂不少但对跌倒判断更精细适合需要区分“倒地”和“坐地”的场景。本项目以检测为主所以用矩形框标注就够了。2.2 训练配置与关键参数数据集准备好之后在data/dataset.yaml里配置路径和类别信息path: ./data/fall_dataset train: images/train val: images/val nc: 2 names: [person, fall]然后执行训练命令yolo detect train datadata/dataset.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0这里有几个参数值得单独讲epochs100跌倒检测不是特别复杂的分类任务100轮足够收敛。我实际训练到60轮之后mAP基本就不再明显上涨所以后期可以早停。imgsz640YOLOv8默认输入尺寸。如果你希望检测更远的行人可以调到768或960但推理时间会同比增加。batch16根据显存调整。8GB显存用默认16可能爆显存可以加到batch8或开梯度累积。device0指定GPU。如果没有GPU就用devicecpu但训练速度会非常慢。训练结束后结果都保存在runs/detect/train目录下里面有weights/best.pt、weights/last.pt、PR_curve.png、F1_curve.png、confusion_matrix.png、results.csv等文件这些就是项目里“评估指标曲线”的来源。训练中我踩过最大的坑是类别不平衡普通行人的样本远多于跌倒样本导致模型倾向于把摔倒的人识别成普通行人。解决办法是给fall类别增加采样权重或者在数据增强时对跌倒样本使用更强的Mosaic增强。另一个坑是边界框标注不统一有人把整个人体框进去有人只框躯干这会严重干扰模型学习所以标注规范一定要在开工前定好跌倒时框住包含四肢在内的全身区域。3. 模型导出与ONNX部署3.1 为什么非要用ONNX训练得到best.pt只是第一步实际部署时不可能每台机器都装PyTorch和CUDA环境。ONNX作为中间表示格式最大的优势是跨框架、跨设备ONNX Runtime可以直接在CPU上跑也能在GPU上跑还能很方便地转成各平台需要的格式。这正好匹配项目包里“模型GUI”的定位——给别人用时不能要求对方先装一个几GB的深度学习框架。导出命令很简单yolo export modelweights/best.pt formatonnx dynamicTrue opset12 simplifyTrue导出时我建议打开dynamicTrue这样ONNX模型可以接受任意尺寸的输入方便GUI里适配不同分辨率的摄像头画面。opset12是兼容性比较好的选择老版本ORT和新版本ORT都能正确处理。simplifyTrue会调用onnxsim对计算图做简化去掉冗余节点实测模型体积能缩小10%~20%推理速度也有小幅提升。3.2 ONNX Runtime推理代码拆解模型导出后我用onnxruntime库做CPU推理。核心代码在inference/detect_onnx.py里大致流程是图像预处理、推理、后处理NMS、返回检测框。import cv2 import numpy as np import onnxruntime as ort class ONNXDetector: def __init__(self, onnx_path, conf_thres0.45, iou_thres0.45): self.session ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.conf_thres conf_thres self.iou_thres iou_thres self.input_name self.session.get_inputs()[0].name def preprocess(self, img, size640): h, w img.shape[:2] scale min(size / w, size / h) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(img, (new_w, new_h)) canvas np.full((size, size, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] resized x canvas[:, :, ::-1].transpose(2, 0, 1) / 255.0 x np.expand_dims(x, axis0).astype(np.float32) return x, scale, new_w, new_h def postprocess(self, preds, scale, new_w, new_h, orig_shape): # preds shape: (1, 84, 8400) boxes [] preds preds[0] class_ids preds[4:, :].argmax(axis0) confs preds[4:, :].max(axis0) mask confs self.conf_thres preds preds[:, mask] class_ids class_ids[mask] confs confs[mask] if len(preds) 0: return [] cx preds[0] / scale cy preds[1] / scale bw preds[2] / scale bh preds[3] / scale x1, y1 cx - bw / 2, cy - bh / 2 x2, y2 cx bw / 2, cy bh / 2 boxes np.stack([x1, y1, x2, y2, confs, class_ids], axis1) return self.nms(boxes) def nms(self, boxes): # 这里用简化版的NMS实际项目中建议直接调用cv2.dnn.NMSBoxes keep cv2.dnn.NMSBoxes( boxes[:, :4].tolist(), boxes[:, 4].tolist(), score_threshold0.0, nms_thresholdself.iou_thres ) return boxes[keep.flatten()] if len(keep) 0 else []ONNX推理的速度取决于输入尺寸和CPU性能。在我自己的机器上640x640输入跑到20~30ms一帧基本能满足实时性要求。如果感觉卡顿可以把输入尺寸降到480或者把模型换成量化后的int8版本速度会提升不少代价是精度可能下降1~2个百分点。4. 评估指标曲线怎么验证系统靠谱4.1 核心指标到底代表什么训练完成后不能只看loss曲线必须通过验证集评估来确认模型真实水平。YOLOv8在训练过程中会自动算出一系列指标项目里的“评估指标曲线”就是从runs/detect/val目录中提取的。我重点看这几个指标指标含义本项目参考值Precision所有预测框中真正正确的比例0.90以上Recall所有真实目标中被找出来的比例0.85以上mAP0.5IoU阈值为0.5时的平均精度0.90以上mAP0.5:0.95不同IoU阈值下的平均精度更严格0.75以上F1-score精确率和召回率的调和平均0.88以上对于跌倒检测这类安全监控场景我更看重Recall而不是Precision。漏报一个跌倒事件可能造成严重后果而误报顶多是弹个窗、响个铃用户能容忍。所以推理时我会把conf_thres调低到0.35甚至0.3宁可多框出几个疑似目标也不放过真正的跌倒事件。4.2 曲线图怎么解读训练目录下的PR_curve.png是Precision-Recall曲线曲线越靠近右上角说明模型越强曲线下的面积就是AP值。F1_curve.png展示的是不同置信度阈值下F1分数的变化峰值对应的阈值就是理论最优置信度。confusion_matrix.png里可以看到每个类别的误检情况比如有多少跌倒目标被错判成了普通行人这能帮我判断是否还需要补充特定类型的数据。从results.png里还能看到训练过程中的loss曲线、精确率、召回率变化趋势。如果训练loss和验证loss在后期差距越来越大说明模型过拟合了需要加数据增强或提高dropout。如果验证loss还在降但mAP不涨通常说明数据集本身存在标注噪声需要回过去检查数据。我还做了一步额外工作把runs/detect/val里的预测图片翻出来人工过了一遍。这是最笨但最有效的评估方式。曲线图只能给你一个宏观数字具体是哪些场景容易漏检、哪些目标容易误检只有看实际预测框才知道。5. GUI界面实战把模型包成人人能用5.1 GUI技术选型PyQt5项目界面部分用的是PyQt5。选择它的原因很直接自带丰富的控件库能做出漂亮的深色主题界面QThread机制可以优雅地处理视频流和检测逻辑不会像Tkinter那样动不动就界面无响应。标题里强调“精美GUI界面”其实不是说要花里胡哨而是要做到信息清晰、布局合理、操作顺手。我的界面包含四个区域顶部标题栏和系统状态指示器左侧视频显示区实时显示检测框和类别标签右侧报警日志区记录跌倒事件的发生时间底部控制按钮区域包括打开视频、打开摄像头、停止检测、退出报警提示用红色闪烁边框和提示音一旦检测到fall类别就触发。为了让报警不刷屏我加了一个20秒的报警冷却时间同一个目标在短时间内不会重复报警。5.2 关键代码QThread与检测逻辑解耦GUI开发最大的坑就是长时间任务阻塞主线程。如果直接在按钮回调里跑推理界面会卡死用户点一下鼠标半天没反应。我的做法是把摄像头读取和模型推理放到QThread里主线程只负责刷新UI。from PyQt5.QtCore import QThread, pyqtSignal import cv2 class CameraThread(QThread): frame_ready pyqtSignal(object) fall_detected pyqtSignal(str) def __init__(self, detector, source0): super().__init__() self.detector detector self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break results self.detector.predict(frame) frame self.detector.draw_box(frame, results) self.frame_ready.emit(frame) for cls_id, conf in results: if cls_id 1 and conf 0.5: self.fall_detected.emit(time.strftime(%H:%M:%S)) self.msleep(30) cap.release() def stop(self): self.running False self.wait()主窗口里把frame_ready信号连接到显示控件的setImage槽函数fall_detected信号连接到报警槽函数。这样视频处理在后台线程跑界面刷新由Qt事件循环接管整个交互就非常流畅了。我实测过在不开启GPU加速的情况下CPU推理加上画面绘制整个界面依然能维持在25FPS以上完全能满足监控需求。如果你要处理4K摄像头建议先把画面缩放到1280x720再送进模型否则CPU会扛不住。6. 常见问题与排查实录6.1 训练与导出阶段的经典报错先说训练阶段最常遇到的CUDA显存溢出OutOfMemory问题。解决办法有几个调小batch、使用混合精度训练ampTrue、降低imgsz。YOLOv8默认开启amp但如果没有撑住还是得从batch下手。我建议先设batch8跑通流程再根据需要逐步加大。导出ONNX时如果报UnsupportedOperator多半是opset版本太低。把opset调到12或更高就能解决。还有一个细节dynamicTrue导出后有些ONNX Runtime版本对动态维度支持不完美如果报错可以把输入固定为(1, 3, 640, 640)代价是GUI里必须先把图像letterbox到640x640。6.2 推理和GUI阶段的坑GUI加载ONNX模型成功后画面黑屏但程序不报错这是典型的视频解码问题。我在Windows和Linux上都遇到过根源是OpenCV的FFmpeg后端不完整。解决办法是检查OpenCV版本建议使用opencv-python4.8以上版本同时确保视频文件编码是H.264而不是H.265后者在部分环境中无法解码。另一个常见问题是摄像头打开失败。如果cap.isOpened()返回False可以先检查摄像头是否被其他程序占用或者在代码里指定cv2.CAP_DSHOW后端cap cv2.VideoCapture(self.source, cv2.CAP_DSHOW)报警误报率偏高的处理思路我前面提过降低置信度阈值会提高召回率但也会带来误报。我的实践是双阈值策略当置信度超过0.5时立即触发报警当置信度在0.3~0.5之间时连续跟踪5帧都检测到fall才报警。这样既保留了低置信度下的检出能力又过滤了单帧误检实际效果比单纯调高阈值好很多。6.3 GPU与CPU推理选择建议项目包里默认让ONNX Runtime使用CPUExecutionProvider因为GUI要尽量做到开箱即用。但如果部署机器有NVIDIA显卡可以改成CUDAExecutionProvider推理速度会提升好几倍。前提是安装onnxruntime-gpu并且CUDA、cuDNN版本要和ORT要求的版本匹配。我建议在代码里做一个自动探测available_providers ort.get_available_providers() if CUDAExecutionProvider in available_providers: session ort.InferenceSession(path, providers[CUDAExecutionProvider, CPUExecutionProvider]) else: session ort.InferenceSession(path, providers[CPUExecutionProvider])这种方式能自动适配不同机器部署起来省心很多。最后分享一个我在实际项目中反复用到的小技巧跌倒检测系统的报警不要只看单帧结果要结合时间序列。一个人的状态不可能在1帧之内从站立突变到跌倒后趴在地上不动正常跌倒过程会有“站立→倒地→静止/蠕动”的过程。在fall_judge.py里维护一个轻量状态机比对前后若干帧的检测框中心高度变化可以大幅减少因为弯腰、蹲下、阴影遮挡造成的误报。这个逻辑同样适用于模型输出的后处理非常值得加进你自己的项目里。本文还有配套的精品资源点击获取