简介:这份资源是面向计算机视觉初学者与智能安防开发者的YOLOv5摔倒检测完整源码包,可用于老人监护、体育赛事监控及公共安全监控等场景的算法学习与二次开发。压缩包共1721个文件,约678.44MB,以1264个txt标签文件、183张jpg图像、178个xml标注、22个yaml配置、21个py脚本及16段mp4视频为主,另含pt权重、Dockerfile与ipynb教程,覆盖数据加载、模型定义、训练与测试四大模块。源码涉及Focal Loss、SPP-Block与PANet等关键结构,并配有数据增强与训练策略,便于读者理解摔倒检测从数据预处理到模型评估的完整链路。目前已有3511人学习下载,适合希望掌握实时目标检测落地思路、快速搭建摔倒识别系统的读者参考。
1. 摔倒检测为什么总在“躺下”和“跌倒”之间翻车
摔倒检测这件事,听起来像是目标检测的常规应用,但真正落地过的人都知道,它比想象中难缠。YOLOv5 摔倒检测源码在网上能找到不少,但拿过来直接跑,你会发现模型把“蹲下系鞋带”“弯腰捡东西”“躺沙发”统统识别成摔倒,误报率高得离谱。这不是模型不行,而是摔倒这个动作本身在单帧图像里和“躺”“蹲”“坐”高度相似,缺乏时序信息时,YOLOv5 只能靠人体框的宽高比和位置去猜。
我最初做这个方向,是因为一个养老院场景的需求:摄像头要能自动发现老人跌倒并报警。当时试了三套开源方案,两套直接翻车,一套勉强能用但延迟高。后来我意识到,YOLOv5 摔倒检测源码的核心价值不在于“检测摔倒”这个结果,而在于它提供了一套可训练、可替换、可部署的框架——你可以换数据集、改后处理逻辑、加时序判断,最终让它在你的场景里稳定工作。
这篇文章面向两类人:一是手里已经有 YOLOv5 基础、想把它用到摔倒检测上的工程师;二是拿到一份摔倒检测源码但不知道怎么调、怎么改、怎么部署的开发者。我会从数据标注、训练参数、后处理逻辑、部署踩坑四个层面,把“能跑”和“能用”之间的差距讲清楚。如果你正在搜“yolov5摔倒检测源码”,希望拿到的不是一份跑完就完的 demo,而是一套能改、能调、能上线的方案,那接下来的内容会对你有用。
2. 摔倒检测数据集怎么标、怎么转、怎么划分
2.1 摔倒检测的数据集从哪来,怎么标才不白干
公开的摔倒检测数据集不多,常见的有 UR Fall Detection Dataset、Le2i Fall Detection Dataset,还有部分来自 AI Challenger 和 COCO 的跌倒子集。但这些数据集普遍存在两个问题:一是场景单一,多为实验室或走廊,背景干净;二是标注格式不统一,有的给帧级标签,有的给视频级标签,直接拿来训 YOLOv5 需要大量清洗。
我一般会先明确标注策略:摔倒检测在 YOLOv5 里通常按两类标——fall和person。person类标所有站立、行走、坐着的人,fall类标摔倒过程中和摔倒后的人。注意,摔倒后的“躺地”状态也要标成fall,否则模型学不会“躺地=摔倒”这个映射。但这样会带来一个问题:如果有人正常躺在地上休息,也会被标成fall,导致误报。解决办法是在数据集中加入“正常躺卧”的负样本,标成person,让模型学会区分“主动躺下”和“被动摔倒”。
标注工具用 LabelImg 或 CVAT 都行,输出 YOLO 格式的 txt 文件。每张图对应一个 txt,每行格式为class_id x_center y_center width height,坐标归一化到 0~1。这里有个血泪经验:摔倒瞬间的人体框往往不是标准矩形,尤其是侧摔时,人体呈对角线分布,框会拉得很长。如果标注时框得太紧,训练时模型会学到“长条框=摔倒”的捷径,换到其他场景就翻车。建议框稍微放宽一点,包含整个身体轮廓,但不要框到背景。
# 数据集目录结构建议 fall_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml是 YOLOv5 训练时的数据配置文件,内容如下:
# data.yaml path: ./fall_dataset train: images/train val: images/val test: images/test nc: 2 names: ['person', 'fall']nc是类别数,names按索引顺序对应类别名。注意person和fall的顺序不要写反,否则训练出来的模型会把摔倒的人标成person,把站着的人标成fall,这种低级错误我见过不止一次。
2.2 把视频数据转成 YOLOv5 可训练的帧:抽帧策略与脚本
很多摔倒检测数据是视频格式,需要抽帧成图片。抽帧不是随便抽,抽多了冗余,抽少了漏掉摔倒瞬间。我一般用“运动触发抽帧”:先用背景减除或帧差法检测画面中是否有大幅运动,有运动时以 5~10 FPS 抽帧,无运动时以 1 FPS 抽帧。这样既能覆盖摔倒过程,又不会让数据集被静止画面撑爆。
import cv2 import os def extract_frames(video_path, output_dir, motion_threshold=5000): """ 运动触发抽帧:有运动时高频抽帧,无运动时低频抽帧 video_path: 输入视频路径 output_dir: 输出图片目录 motion_threshold: 运动像素阈值,超过则视为有运动 """ cap = cv2.VideoCapture(video_path) os.makedirs(output_dir, exist_ok=True) prev_frame = None frame_idx = 0 saved_idx = 0 while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (21, 21), 0) if prev_frame is not None: diff = cv2.absdiff(prev_frame, gray) _, thresh = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY) motion_pixels = cv2.countNonZero(thresh) # 有运动时每2帧存一张,无运动时每30帧存一张 if motion_pixels > motion_threshold: if frame_idx % 2 == 0: cv2.imwrite(f"{output_dir}/frame_{saved_idx:06d}.jpg", frame) saved_idx += 1 else: if frame_idx % 30 == 0: cv2.imwrite(f"{output_dir}/frame_{saved_idx:06d}.jpg", frame) saved_idx += 1 prev_frame = gray frame_idx += 1 cap.release() print(f"共保存 {saved_idx} 帧")这段脚本的核心逻辑是:用帧差法计算运动像素数,超过阈值说明画面有动作,此时提高抽帧频率;否则降低频率。motion_threshold需要根据视频分辨率调整,1080P 视频一般设在 3000~8000 之间。抽完帧后,记得人工过一遍,把模糊、重复、无关的帧删掉,否则训练时 loss 会震荡得让你怀疑人生。
2.3 训练集、验证集、测试集怎么分才不泄露
划分数据集时,最大的坑是“同一视频的帧同时出现在训练集和验证集”。因为相邻帧高度相似,模型在训练集见过第 100 帧,验证集里第 101 帧几乎一样,验证指标会虚高,实际部署时性能暴跌。正确做法是按视频划分:同一视频的所有帧只进一个集合。通常训练集占 70%,验证集 15%,测试集 15%。如果视频数量少,可以用 5 折交叉验证,但每一折都要保证视频不跨集合。
import os import random import shutil def split_by_video(video_frames_dict, output_dir, train_ratio=0.7, val_ratio=0.15): """ 按视频划分数据集,避免同一视频的帧跨集合 video_frames_dict: {video_name: [frame_paths]} """ videos = list(video_frames_dict.keys()) random.shuffle(videos) train_videos = videos[:int(len(videos) * train_ratio)] val_videos = videos[int(len(videos) * train_ratio):int(len(videos) * (train_ratio + val_ratio))] test_videos = videos[int(len(videos) * (train_ratio + val_ratio)):] for split, video_list in [('train', train_videos), ('val', val_videos), ('test', test_videos)]: for video in video_list: for frame_path in video_frames_dict[video]: # 复制图片和对应的标注文件到对应目录 dst_img = os.path.join(output_dir, 'images', split, os.path.basename(frame_path)) dst_lbl = os.path.join(output_dir, 'labels', split, os.path.basename(frame_path).replace('.jpg', '.txt')) shutil.copy(frame_path, dst_img) if os.path.exists(frame_path.replace('.jpg', '.txt')): shutil.copy(frame_path.replace('.jpg', '.txt'), dst_lbl)这个脚本的关键是video_frames_dict的构建:先遍历所有视频,记录每个视频抽出的帧路径,然后按视频名 shuffle 后切分。这样能保证同一视频的帧不会同时出现在训练集和验证集。如果你拿到的源码里没有这个逻辑,大概率验证指标是假的,部署时必翻车。
3. YOLOv5 摔倒检测训练:参数怎么设、模型怎么选
3.1 选 yolov5s 还是 yolov5m:摔倒检测的模型规模权衡
YOLOv5 有 s、m、l、x 四个主要版本,参数量和精度依次递增。摔倒检测场景下,我一般推荐从yolov5s开始,原因是:摔倒检测的输入分辨率通常不高(640×640 足够),目标类别只有 2 类,场景相对固定,yolov5s的容量已经够用。如果你用yolov5x,训练时间翻倍,推理速度降到 10 FPS 以下,嵌入式设备根本跑不动。
但yolov5s有个问题:对小目标和遮挡场景的检测能力偏弱。如果摄像头架得高,人在画面里只占几十个像素,yolov5s可能会漏检。这时候可以换yolov5m,或者保持yolov5s但把输入分辨率提到 1280。我的经验是:先跑yolov5s+ 640,看验证集召回率,如果低于 0.85,再考虑换模型或提分辨率。不要一上来就上大模型,训练慢、部署难,调参周期长,容易半途而废。
# 训练命令示例 python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data fall_dataset/data.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name fall_yolov5s \ --cache--img 640是输入分辨率,--batch 16是批次大小,显存不够就降到 8。--weights yolov5s.pt是预训练权重,从 COCO 上迁移过来,能加快收敛。--hyp是超参数配置文件,摔倒检测建议用hyp.scratch-low.yaml,因为数据集规模通常不大,低学习率增强能防止过拟合。--cache把图片缓存到内存,加快训练速度,但内存小于 16G 就别开。
3.2 摔倒检测的训练超参数:学习率、锚框、数据增强怎么调
YOLOv5 的默认超参数是针对 COCO 数据集调的,直接拿来训摔倒检测,效果往往一般。需要重点调三个地方:学习率、锚框、数据增强。
学习率方面,hyp.scratch-low.yaml里lr0默认是 0.01,如果数据集小于 5000 张,建议降到 0.001,否则 loss 容易震荡。lrf是最终学习率因子,默认 0.01,保持即可。锚框方面,YOLOv5 默认锚框是基于 COCO 的,摔倒检测的人体框宽高比差异大,建议用kmeans重新聚类生成锚框:
import numpy as np def kmeans_anchors(bboxes, k=9, iterations=100): """ 对标注框做 kmeans 聚类,生成适配数据集的锚框 bboxes: numpy array, shape (N, 2), 每行是 (width, height) """ np.random.seed(42) indices = np.random.choice(len(bboxes), k, replace=False) centroids = bboxes[indices] for _ in range(iterations): distances = np.sqrt(((bboxes[:, np.newaxis] - centroids) ** 2).sum(axis=2)) labels = distances.argmin(axis=1) for i in range(k): if np.sum(labels == i) > 0: centroids[i] = bboxes[labels == i].mean(axis=0) # 按面积排序 areas = centroids[:, 0] * centroids[:, 1] sorted_idx = np.argsort(areas) return centroids[sorted_idx]把聚类结果按 YOLOv5 格式写进yolov5s.yaml的anchors字段。注意锚框要归一化到特征图尺度,具体计算方式是:锚框宽高除以对应特征图的步长(8、16、32)。这一步如果搞错,训练时框回归会完全乱掉。
数据增强方面,摔倒检测要慎用mosaic。mosaic会把四张图拼成一张,容易把摔倒的人拼到奇怪的位置,导致模型学到错误的上下文。建议把mosaic概率降到 0.5 以下,或者关闭。flipud(上下翻转)也要慎用,因为摔倒的人上下翻转后变成“倒立”,不符合物理规律。fliplr(左右翻转)可以保留,概率 0.5。
3.3 训练过程怎么看:loss 曲线、mAP、混淆矩阵的解读
训练启动后,YOLOv5 会在runs/train/下生成结果目录,里面有results.csv、confusion_matrix.png、val_batch0_pred.jpg等文件。重点看三个指标:box_loss、obj_loss、mAP@0.5。
box_loss下降说明框回归在收敛,如果震荡剧烈,检查锚框和学习率。obj_loss下降说明模型在学会区分前景和背景,如果一直很高,可能是负样本太少或标注质量差。mAP@0.5是主要精度指标,摔倒检测一般要求fall类的 AP 在 0.85 以上才算可用。如果person类 AP 高但fall类 AP 低,说明模型对摔倒特征学得不够,需要增加摔倒样本或调整损失权重。
混淆矩阵能看出误报方向。如果person被大量预测成fall,说明模型把“站立”和“摔倒”搞混了,需要加入更多站立负样本。如果fall被预测成person,说明摔倒样本不够或标注不一致。我一般会在训练 50 epoch 后看一次混淆矩阵,根据结果决定是否补充数据或调整超参数。
# 用 tensorboard 查看训练曲线 tensorboard --logdir runs/train如果mAP@0.5在 30 epoch 后不再上升,而box_loss还在降,说明模型过拟合了,可以提前停止训练,用最佳权重。YOLOv5 默认保存best.pt和last.pt,部署时用best.pt。
4. 摔倒检测后处理:从检测框到报警逻辑
4.1 单帧检测不够用:用宽高比和置信度做初筛
YOLOv5 输出的是每帧的检测框,但摔倒是一个动作过程,单帧检测结果直接拿来报警,误报率极高。我一般会在后处理阶段加一层“摔倒判定逻辑”,核心是用人体框的宽高比和置信度做初筛。
正常站立的人,人体框宽高比(width/height)通常在 0.3~0.6 之间;摔倒的人,宽高比会大于 0.8,甚至超过 1.5(侧躺时)。所以第一层过滤:如果fall类置信度 > 0.5 且宽高比 > 0.8,进入候选;否则丢弃。但这样会漏掉“蹲下”和“坐地”,因为蹲下时宽高比也可能接近 0.8。所以第二层过滤:结合person类检测框,如果同一位置同时有person和fall,且fall框与person框的 IoU > 0.5,说明模型在同一个目标上给出了矛盾判断,此时以person为准,不报警。
def filter_fall_detections(detections, conf_thres=0.5, ratio_thres=0.8): """ 对 YOLOv5 检测结果做摔倒初筛 detections: list of [x1, y1, x2, y2, conf, cls] """ fall_candidates = [] person_boxes = [] for det in detections: x1, y1, x2, y2, conf, cls = det w = x2 - x1 h = y2 - y1 ratio = w / h if h > 0 else 0 if cls == 1 and conf > conf_thres: # fall 类 if ratio > ratio_thres: fall_candidates.append(det) elif cls == 0: # person 类 person_boxes.append(det) # 去除与 person 框高度重叠的 fall 候选 final_falls = [] for fall in fall_candidates: overlap = False for person in person_boxes: iou = compute_iou(fall[:4], person[:4]) if iou > 0.5: overlap = True break if not overlap: final_falls.append(fall) return final_falls def compute_iou(box1, box2): """计算两个框的 IoU""" x1 = max(box1[0], box2[0]) y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]) y2 = min(box1[3], box2[3]) inter = max(0, x2 - x1) * max(0, y2 - y1) area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) union = area1 + area2 - inter return inter / union if union > 0 else 0这段逻辑的关键参数是conf_thres和ratio_thres。conf_thres建议设 0.5,太低会引入大量误报,太高会漏掉模糊的摔倒。ratio_thres建议设 0.8,但要根据摄像头角度调整:俯拍时宽高比偏大,可以降到 0.7;平拍时偏小,可以提到 0.9。
4.2 加时序滑窗:用多帧投票压住误报
单帧初筛后,仍然会有零星误报,比如有人快速蹲下、弯腰捡东西。这时候需要加时序滑窗:维护一个长度为 N 的队列,记录最近 N 帧的摔倒候选数量,如果超过 M 帧都有摔倒候选,才触发报警。我一般设 N=10,M=6,对应大约 0.5~1 秒的持续时间。这样能过滤掉瞬时误报,同时保证真实摔倒能在 1 秒内报警。
from collections import deque class FallAlarm: def __init__(self, window_size=10, trigger_count=6): self.window = deque(maxlen=window_size) self.trigger_count = trigger_count self.alarm_active = False def update(self, fall_detected): """ 每帧调用一次,fall_detected 为 bool 返回是否触发报警 """ self.window.append(1 if fall_detected else 0) if len(self.window) < self.window.maxlen: return False if sum(self.window) >= self.trigger_count: if not self.alarm_active: self.alarm_active = True return True else: self.alarm_active = False return False这个滑窗逻辑简单但有效。window_size和trigger_count需要根据帧率调整:如果摄像头 25 FPS,N=25、M=15 对应 1 秒内 60% 的帧有摔倒候选。如果帧率低,比如 10 FPS,N=10、M=6 就够了。注意报警后要加一个“冷却时间”,比如 30 秒内不重复报警,否则老人躺在地上会被连续报警,护理人员会疯。
4.3 把后处理集成到 YOLOv5 推理脚本里
YOLOv5 的detect.py默认只输出检测框,要把上面的后处理集成进去,可以写一个自定义推理脚本,或者修改detect.py的run函数。我一般会单独写一个fall_detect.py,加载best.pt,逐帧推理,然后调用filter_fall_detections和FallAlarm。
import torch import cv2 from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from utils.augmentations import letterbox class FallDetector: def __init__(self, weights_path, device='cpu', img_size=640): self.model = attempt_load(weights_path, map_location=device) self.device = device self.img_size = img_size self.alarm = FallAlarm(window_size=10, trigger_count=6) def detect(self, frame): # 预处理 img = letterbox(frame, self.img_size, stride=32, auto=True)[0] img = img.transpose((2, 0, 1))[::-1] # BGR to RGB, HWC to CHW img = torch.from_numpy(img).to(self.device).float() / 255.0 img = img.unsqueeze(0) # 推理 pred = self.model(img)[0] pred = non_max_suppression(pred, conf_thres=0.4, iou_thres=0.5) detections = [] for det in pred: if det is not None and len(det): det[:, :4] = scale_coords(img.shape[2:], det[:, :4], frame.shape).round() for *xyxy, conf, cls in det: detections.append([xyxy[0].item(), xyxy[1].item(), xyxy[2].item(), xyxy[3].item(), conf.item(), int(cls.item())]) # 后处理 falls = filter_fall_detections(detections) alarm = self.alarm.update(len(falls) > 0) return detections, falls, alarm这个脚本把预处理、推理、后处理串在一起。conf_thres=0.4比训练时的 0.5 略低,是为了让更多候选进入后处理,由后处理逻辑做最终判断。iou_thres=0.5是 NMS 的阈值,保持默认即可。注意letterbox的auto=True参数,它会自动计算 padding,保证输入尺寸是 32 的倍数。
5. 摔倒检测部署避坑:从服务器到边缘设备
5.1 模型导出 ONNX 和 TensorRT 的坑
训练完的best.pt要部署到生产环境,通常需要导出成 ONNX 或 TensorRT。YOLOv5 自带export.py,但摔倒检测模型导出时有两个坑:一是输出层名称,二是动态 batch。
# 导出 ONNX python export.py --weights best.pt --include onnx --img 640 --batch 1 # 导出 TensorRT(需要 NVIDIA GPU) python export.py --weights best.pt --include engine --img 640 --batch 1 --device 0导出 ONNX 时,--batch 1是必须的,因为摔倒检测通常是单帧推理,动态 batch 会增加复杂度。如果导出后 ONNX 模型推理结果和 PyTorch 不一致,检查--img是否和训练时一致,以及--dynamic是否误开。TensorRT 导出需要 CUDA 和 TensorRT 环境,版本匹配很关键:YOLOv5 的export.py对 TensorRT 版本有要求,版本不对会报Unsupported ONNX opset或serialization error。我一般用 Docker 镜像ultralytics/yolov5:latest来导出,环境是配好的,省去折腾。
5.2 树莓派和 RK3568 上跑 YOLOv5 摔倒检测的实测参数
边缘设备部署是摔倒检测的常见需求,但树莓派和 RK3568 的性能差异很大。树莓派 4B 用 PyTorch 直接推理 YOLOv5s,640 输入下大概 2~3 FPS,根本不够用。必须转成 ONNX 或 NCNN,用 ONNX Runtime 或 NCNN 推理,能到 8~12 FPS。树莓派 5 性能提升明显,ONNX Runtime 下能到 15~20 FPS,基本可用。
RK3568 自带 NPU,算力 0.8 TOPS,跑 YOLOv5s 需要转成 RKNN 格式。转换工具是 RKNN-Toolkit2,流程是:PyTorch → ONNX → RKNN。注意 RKNN 对算子支持有限,YOLOv5 的Focus层和SiLU激活函数需要替换或重写。我一般用rknn_model_zoo里的 YOLOv5 转换脚本,改一下类别数和锚框就行。实测 RK3568 上 YOLOv5s 640 输入能到 25~30 FPS,功耗 2~3W,适合嵌入式部署。
| 设备 | 推理框架 | 输入尺寸 | 实测 FPS | 功耗 |
|---|---|---|---|---|
| 树莓派 4B | ONNX Runtime | 640 | 8~12 | 5W |
| 树莓派 5 | ONNX Runtime | 640 | 15~20 | 8W |
| RK3568 | RKNN | 640 | 25~30 | 2~3W |
| Jetson Nano | TensorRT | 640 | 20~25 | 5~10W |
5.3 摄像头选型和视频流处理的延迟问题
摔倒检测的延迟不仅来自模型推理,还来自视频流处理。USB 摄像头延迟低但布线麻烦,IP 摄像头(RTSP)延迟高但部署灵活。我一般用 RTSP 摄像头 +ffmpeg拉流,但ffmpeg默认缓冲会引入 1~2 秒延迟。解决办法是设置ffmpeg参数-fflags nobuffer -flags low_delay -probesize 32 -analyzeduration 0,能把延迟降到 200~500ms。
import cv2 # RTSP 低延迟拉流 cap = cv2.VideoCapture('rtsp://admin:password@192.168.1.100:554/stream', cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 减少缓冲 while True: ret, frame = cap.read() if not ret: break # 推理和报警逻辑cv2.CAP_PROP_BUFFERSIZE设为 1 能减少 OpenCV 内部缓冲,但有些摄像头不支持,需要配合ffmpeg参数。如果延迟仍然高,考虑用GStreamer替代ffmpeg,延迟能再降 100ms 左右。
6. 摔倒检测的进阶技巧:用姿态估计补足 YOLOv5 的短板
6.1 为什么纯检测不够:姿态估计能提供什么额外信息
YOLOv5 摔倒检测的瓶颈在于:它只看人体框,不看人体姿态。一个人弯腰捡东西,人体框宽高比可能和摔倒一样,但骨骼关键点完全不同。弯腰时,头部和臀部的位置关系是“头在上、臀在下”,而摔倒时往往是“头臀同高”甚至“头低于臀”。所以,用姿态估计(如 YOLOv5-Pose、MediaPipe、OpenPose)提取关键点,再结合关键点角度做二次判断,能大幅降低误报。
我一般用 YOLOv5-Pose 或 YOLOv8-Pose,因为它们和 YOLOv5 共享推理框架,部署方便。提取 17 个 COCO 关键点后,计算三个特征:头部与髋部的垂直距离、躯干与地面的夹角、双肩与双髋的连线角度。如果头部与髋部垂直距离小于阈值,且躯干与地面夹角小于 30 度,判定为摔倒。
6.2 用 YOLOv5-Pose 关键点做二次验证的代码实现
import numpy as np def check_fall_by_pose(keypoints, conf_thres=0.3): """ 用 COCO 17 关键点判断是否摔倒 keypoints: shape (17, 3), 每行 (x, y, conf) 返回: bool """ # COCO 关键点索引 NOSE = 0 L_SHOULDER = 5 R_SHOULDER = 6 L_HIP = 11 R_HIP = 12 # 检查关键点置信度 if keypoints[NOSE][2] < conf_thres or keypoints[L_HIP][2] < conf_thres: return False nose_y = keypoints[NOSE][1] hip_y = (keypoints[L_HIP][1] + keypoints[R_HIP][1]) / 2 # 头部低于髋部,高度疑似摔倒 if nose_y > hip_y: return True # 计算躯干与垂直方向夹角 shoulder_x = (keypoints[L_SHOULDER][0] + keypoints[R_SHOULDER][0]) / 2 shoulder_y = (keypoints[L_SHOULDER][1] + keypoints[R_SHOULDER][1]) / 2 hip_x = (keypoints[L_HIP][0] + keypoints[R_HIP][0]) / 2 dx = shoulder_x - hip_x dy = shoulder_y - hip_y angle = abs(np.degrees(np.arctan2(dx, dy))) # 躯干倾斜超过 60 度,判定为摔倒 if angle > 60: return True return False这段代码的核心逻辑是:先用鼻子和髋部的垂直位置关系做粗判,再用躯干倾斜角做细判。conf_thres=0.3是关键点置信度阈值,太低会引入噪声,太高会漏掉遮挡场景。实际使用时,可以把 YOLOv5 检测框和 YOLOv5-Pose 关键点结合:先用检测框定位人体,再在框内跑姿态估计,最后用check_fall_by_pose做最终判断。这样误报率能从纯检测的 15% 降到 5% 以下。
6.3 我踩过的坑和最后留下的习惯
做摔倒检测这几年,最大的坑是“追求高精度而忽略实时性”。早期我用 YOLOv5x + 姿态估计 + LSTM 时序模型,精度确实高,但推理延迟 2 秒以上,老人摔倒了 2 秒后才报警,实际场景根本不可接受。后来我砍掉 LSTM,用滑窗投票替代,精度降了 2 个点,但延迟降到 300ms,反而更实用。
第二个坑是“忽略场景差异”。实验室数据集上 mAP 0.95,部署到养老院走廊,因为光照变化和摄像头角度,mAP 掉到 0.7。后来我养成了一个习惯:每到一个新场景,先采集 200 张现场图,人工标注后做一次微调,通常 10 个 epoch 就能把 mAP 拉回 0.9 以上。这个习惯让我少了很多“实验室能用、现场翻车”的尴尬。
第三个坑是“报警逻辑太激进”。早期版本只要检测到fall就报警,结果护理人员一天收到几十条误报,直接把系统关了。后来加了滑窗投票和冷却时间,误报降到每天 1~2 条,护理人员才愿意用。技术指标再好,用户体验不行,系统就是废的。
现在我做摔倒检测,习惯先跑通最小闭环:YOLOv5s + 单帧检测 + 宽高比过滤 + 滑窗投票,先让系统能跑起来,再根据误报情况逐步加姿态估计、加场景微调。不要一上来就堆模型,先让系统能用,再让它好用。希望帮到你。
本文还有配套的精品资源,点击获取