简介:面向计算机专业毕业设计与课程设计需求,这份人工智能深度学习项目整合YOLOv5人体检测与OpenPose姿态估计,实现视频场景中的摔倒行为识别,可作为高分毕业设计(评审98分)的完整参考。项目源码与模型打包为zip,压缩包约40.25MB,核心代码和预训练权重均包含在内,便于直接复现与二次开发。项目亮点在于将目标检测与关键点姿态估计相结合:YOLOv5负责实时定位人体,OpenPose提取骨骼关键点,再通过角度、速度等特征判定摔倒状态,覆盖数据预处理、模型加载、推理逻辑到结果可视化的完整流程。读者可从中学习两套算法的协作方式、参数调优思路以及摔倒判断阈值设计,对于做大作业、期末项目或毕设开题均有实用价值。当前已有167人学习下载,适合具备一定Python和深度学习基础、希望快速搭建完整落地方案的学习者。
1. 摔倒检测为什么绕不开YOLOv5人体检测+OpenPose姿态检测
在养老院、独居老人家庭和医院病房的监控画面里,摔倒检测真正要回答的问题不是“画面里有没有人”,而是“这个人是不是以异常姿态倒到地面”。标题把 YOLOv5 人体检测和 OpenPose 姿态检测放在一起,是实际项目里最常见的组合:YOLOv5 先把人从背景里捞出来,OpenPose 再提取骨骼关键点,最后真正判断摔倒的是关键点之上的规则。只做人体框加宽高比判断,遇到弯腰和下蹲会误报;只上 OpenPose,多人遮挡时关键点会互相串,速度也不够。
这个方向适合正在做监控告警、银发经济相关产品的工程师,也适合拿这套组合做毕业设计的学生。本文会按我自己的工程习惯拆开讲:模型选型、训练数据、摔倒判据、组合代码骨架、常见坑和验收方法。你可以照着写一套能跑的流程,也能提前避开那些让人反复返工的问题。
2. 用YOLOv5训练和部署人体检测:网络结构、微调命令与最小推理
2.1 YOLOv5网络结构图和人体检测的最小模型选型
YOLOv5 不是单个黑盒模型,它由三块组成:CSPDarknet 骨干网络负责提取图像特征,PANet 路径聚合结构负责把不同尺度的特征融合起来,最后的检测头在三个尺度上分别输出预测框。yolov5网络结构图里最容易看到的是两个关键点:骨干网络借鉴了 CSPNet 的做法,把特征图分成两路再合并,减少重复梯度计算;检测头输出三层特征,分别对应小目标、中目标和大目标。对摔倒检测来说,小目标这一层尤其重要,因为远端摄像头下的人可能只占几十个像素。
我一般会选 YOLOv5s 作为默认起点。摔倒检测需要的是“人框”和“框内裁剪区域”,不需要像素级精细分割,也不需要对人的身份做长期识别,所以没必要上 YOLOv5l 或更重的模型。如果部署在树莓派这类设备上,可以再降一档到 YOLOv5n,后续通过量化把推理速度拉回来。选型时还要看摄像头安装高度:斜向下 45 度视角最舒服,人体的长宽比变化和关键点可见度都比较理想;真正的俯视视角压力很大,建议不要只依赖预训练权重。
还有一种常见误区:拿 COCO 预训练模型直接做人体检测,认为已经包含 person 类,就不用再训练了。COCO 的 person 类在水平视角、日常场景下效果很好,但在病房、楼道、单人间这类固定视角下,会出现很多异常姿态,比如人蜷缩在地上、被被褥部分遮挡、只露出腿,预训练模型很容易漏检。所以凡是长期固定安装的摄像头,我都建议抽帧补训。后面几节就按“微调自有数据”来写。
2.2 用YOLOv5训练自己的数据集:数据准备、配置YAML和超参数
先解决标注。用 LabelImg 或 Labelme 都可以,导出 YOLO 格式的 txt 文件。每行是class x_center y_center width height,四个坐标值都除以了图片宽高,所以是 0 到 1 之间的比例值。标注时有个容易踩的问题:人体框只框可见部分还是框完整身体?我的经验是,可见部分超过一半就框可见区域,被严重遮挡的不要硬框。摔倒检测中“半个人”的框会造成关键点裁剪不完整,反而干扰后续 OpenPose。
标注内容不要只标行人。要从部署摄像头的真实视频里抽帧,既包含站姿、走路、坐姿,也要包含倒地、侧卧、蜷缩、被椅子遮挡。负样本也很重要,空房间、无人时段、光影变化剧烈的帧都留一部分,防止模型在没人时把其他物体当成人。数据规模上,我做过最小可用的项目是 2000 张左右,其中 400 张是倒地姿态,效果已经能支撑后续报警逻辑;如果只有几百张,建议直接用公开人体检测数据集的 person 类先凑。
目录结构按这样组织:
datasets/person/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── person.yamlperson.yaml的内容很简单:
path: datasets/person train: images/train val: images/val nc: 1 names: 0: person这个文件告诉训练脚本只有一类,类别名是 person。path建议写相对路径,或者写你当前机器上的绝对路径;换机器训练时,第一件事就是检查这个路径有没有对应错。
训练命令我常用下面这条:
python train.py --img 640 --batch 16 --epochs 50 \ --data datasets/person/person.yaml \ --weights yolov5s.pt \ --freeze 10--img 640是训练输入尺寸。监控画面如果超过 1080p,可以保留 640,因为人体检测不需要看清人脸。--batch 16在 8G 显存上下比较稳妥,显存紧张可以改成 8;批量太小时 BN 层统计会不稳定。--freeze 10表示冻结前 10 层,也就是 backbone 的大部分,让训练只更新 neck 和 head,用少量数据也能微调成功。--epochs 50对这种单类微调足够,更多轮次反而容易把预训练特征学坏。
训练超参数要改的话,我一般关注学习率、数据增强和早停。yolov5 的--hyp参数可以指定 hyp 文件,其中lr0控制初始学习率,微调时建议从 0.01 降到 0.005;mosaic是马赛克增强,开启后小幅提升目标检测能力,但会把人切成碎片,对监控场景不是越强越好。还有一点要克制:不要给degrees设置太大,监控摄像头不会旋转画面,旋转增强过多会让模型学习到“倒着的人”这种错误先验。训练结束看runs/train/exp/weights/best.pt,推理和后续组合都用这个权重。
2.3 人体检测最小推理脚本:一个人体框怎么从帧里出来
拿到best.pt后,先单独验证人体检测,不要急着接 OpenPose。下面这个脚本从本地 YOLOv5 仓库加载模型,推理一帧图并画出框:
import cv2 import torch # 从本地仓库加载自定义权重 model = torch.hub.load('.', 'custom', path='runs/train/exp/weights/best.pt', source='local') model.conf = 0.25 # 置信度阈值,摔倒检测常用 0.25~0.35 model.iou = 0.45 # NMS 的 IoU 阈值,yolov5 后处理默认 0.45 model.classes = [0] # 只保留 person 类,过滤其他 79 类 model.imgsz = 640 # 推理分辨率 cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break results = model(frame) dets = results.xyxy[0].cpu().numpy() # 每行: x1 y1 x2 y2 conf cls persons = [d for d in dets if int(d[5]) == 0] for x1, y1, x2, y2, conf, cls in persons: cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.imshow('person detect', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()torch.hub.load('.', 'custom', path=..., source='local')是从本地仓库加载,source='local'避免了每次都去访问远端库。model.conf和model.iou对应 YOLOv5 后处理里的两个关键参数:先按置信度阈值挑出候选框,再通过非极大值抑制去掉重叠框。model.classes = [0]会让所有非 person 类不出现在结果里,也减少后处理时间。
results.xyxy[0]是单张图的检测结果,每一行是x1, y1, x2, y2, conf, cls。这里我只保留了cls == 0的项,因为训练时 person 的类别 id 就是 0。需要注意conf不要调太高,监控场景中存在俯视、遮挡,0.5 以上的阈值会把倒地的人漏掉。我通常先用 0.25,把更多候选交给后面的姿态检测和状态机去过滤。待会把persons里的 x1 y1 x2 y2 传给 OpenPose 时,很多坑都出在“框太紧”“框太偏”上。
3. OpenPose姿态检测:摔倒的关键点特征从哪来
3.1 OpenPose在做什么:从关键点检测到摔倒判定的距离
OpenPose 的输出不是“这个人摔了”,而是每一个人的关键点坐标和置信度。它的核心思想是同时预测关键点热图(每个像素是某个关节的概率)和部分亲和场 PAF(编码关节之间连接方向的矢量场),再通过图匹配把关键点组装成完整骨架。常见权重输出 COCO 17 点或者 BODY_25 的 25 点,摔倒检测真正关心的是头、双肩、双肘、双髋、双膝、双踝这几组点。
很多人第一次用 OpenPose 会有错觉:既然它能输出所有人体的骨架,是不是可以替代 YOLOv5,直接用关键点框住人?实践里不行。OpenPose 在多人场景下的关键点匹配会互相串,尤其是两个人靠近、交叉、遮挡时,左肩可能接上另一个人的右手。摔倒检测要求的是“对同一个人的连续帧判断”,串点之后特征完全失真。另一个现实问题是速度:OpenPose 原版全图推理很慢,直接放在实时视频流里会卡。
所以我更愿意把 OpenPose 当“单人体检器”用:先用 YOLOv5 给出人的框,从框里裁剪出一个人,再让 OpenPose 输出这一人的关键点。这样既降低匹配难度,也减少算力消耗。不要被“项目源码”里的网络结构带偏,真正要设计的不是两个模型怎么拼接,而是关键点怎么变成告警信号。
3.2 摔倒检测三种特征:人体倾角、高度比率、速度突变
在关键点之上,我常用三种特征判断摔倒。第一个是躯干倾角,取左右肩中点与左右髋中点连线,计算它和竖直方向的夹角。正常站立时这个夹角接近 0 度,倒下去时会到 50 到 90 度。第二个是高度比率,用肩髋距离除以图片高度,或者除以这个人自己的站姿基准高度。倒地后这个值会明显变小。第三个是速度突变,取髋部中心的垂直速度,摔倒过程通常伴随一个明显加速下跌。
下面这个函数把三组特征都算出来:
def get_fall_features(kps, prev_kps, img_h): # kps 为 dict: {"left_shoulder":(x,y,conf), "right_shoulder":...} shoulder_c = midpoint(kps["left_shoulder"], kps["right_shoulder"]) hip_c = midpoint(kps["left_hip"], kps["right_hip"]) # 特征1:肩髋连线与重力方向的夹角 angle = angle_with_gravity(shoulder_c, hip_c) # 特征2:肩髋距离归一化到图像高度 body_h = distance(shoulder_c, hip_c) / img_h # 特征3:髋部垂直速度,单位像素/帧 vert_vel = 0.0 if prev_hip_c is not None: vert_vel = hip_c[1] - prev_hip_c[1] return angle, body_h, vert_vel单独用倾角一定会误判,弯腰捡东西时躯干倾角也能超过 50 度。所以判定逻辑要组合:先出现速度突变,随后 0.5 秒内倾角超过阈值,同时高度比率跌到该人的站立基准以下。用代码表达就是:
if vert_vel > 15 and angle > 50 and body_h < fall_ref_height * 0.6: fall_count += 1 else: fall_count = 0这里的fall_ref_height是这个人最初进入画面时记录下来的肩髋距离。为什么要用“该人自己的基准”?因为摄像头远近不同,同样的像素高度在不同距离代表不同真实高度。如果不做归一化,靠近摄像头的人弯腰会被当成摔倒,远处的人真摔了也检测不到。OpenPose 给出的关键点坐标是像素值,必须先除以基准尺度,再做阈值比较。
3.3 防抖与关键点置信:别让姿态噪声骗过判定器
关键点不是每次都可靠。OpenPose 输出的每个点带着置信度,遮挡、低分辨率、快速运动都会让置信度掉到 0.3 以下。如果直接拿低置信点算角度,会出现单帧内倾角来回跳 20 度。我的处理分两步:先过滤,再平滑。
过滤规则是:低于MIN_CONF的关键点直接置空,不参与计算;两个肩点缺一个时用另一个代替,两个都缺就跳过当前帧的倾角计算。平滑用指数移动平均,也就是常见的 EMA:
smooth_kps = alpha * current_kps + (1 - alpha) * previous_smooth_kpsalpha取 0.6 左右比较合适。太高会跟随噪声,太低会把摔倒那个“突然加速下跌”的信号抹平。摔倒过程通常在 0.5 到 1 秒内完成,如果 alpha 超过 0.8,速度突变几乎看不见。还有一个细节:左右肩点会在人体转身时交换,导致肩线方向突变。我不会直接用左右肩坐标算角度,而是先算肩线中点,再把“距离图像顶部更近的那个肩”记为上方,避免左右互换造成倾角虚假跳变。
4. YOLOv5+OpenPose组合工程:代码骨架、状态机与告警参数
4.1 组合流程和帧间关联:同一人跨帧怎么对应
组合流程我一般这样拆:第一,YOLOv5 检测当前帧,拿到所有人框;第二,对每个框做外扩裁剪,送入 OpenPose 得到该人的关键点;第三,用 IoU 方式把当前帧的人框和上一帧的人框关联起来,给同一人分配同一个轨迹 ID;第四,每个轨迹内部维护关键点历史和平滑结果,由摔倒状态机判断是否触发告警。顺序必须是检测、裁剪、姿态、跟踪、判定,不能反过来。
为什么要先做裁剪而不是全图姿态?因为 OpenPose 对输入分辨率很敏感。全图 1920x1080 直接送进去,人体只占很小区域,小目标关键点置信度普遍偏低;先用 YOLOv5 把范围缩小到一个人,再缩放到姿态模型输入尺寸,关键点质量会好很多。同时也能省算力。
裁剪时有个坑:不要按检测框严丝合缝地切。YOLOv5 的框通常紧贴身体,但 OpenPose 需要看到脚踝、手腕和头顶,框太紧会把关键点切掉一半。我会把检测框外扩 15% 左右:
def crop_person(frame, box, expand=0.15): x1, y1, x2, y2 = [int(v) for v in box] w, h = x2 - x1, y2 - y1 x1 = max(0, int(x1 - expand * w)) y1 = max(0, int(y1 - expand * h)) x2 = min(frame.shape[1], int(x2 + expand * w)) y2 = min(frame.shape[0], int(y2 + expand * h)) return frame[y1:y2, x1:x2]expand可以根据人体框长宽比调整:站立的人上下各留多一些,倒地的人左右留多一些。这块没有统一标准,建议先固定 0.15,再用几段真实视频看关键点缺失情况。
帧间关联采用 IoU 追踪器。对当前帧每个检测框,和上一帧所有轨迹的框计算 IoU,最大 IoU 超过 0.3 就认为属于同一个人,否则新建轨迹。这个阈值比多目标跟踪常用的 0.5 低,因为摔倒过程中人体框形变很大,同一人前后两帧的框重叠度不一定高。跌倒检测不要求长期身份识别,只要求摔倒前后 1 秒内不要换 ID,所以 0.3 够用。
4.2 摔倒状态机与告警代码骨架
组合工程的代码骨架,关键是维护每个轨迹的“连续摔倒帧数”。下面是一个简化实现,但结构可以迁移到你的项目里:
class FallDetector: def __init__(self, angle_thresh=50, vel_thresh=15, need_frames=5): self.angle_thresh = angle_thresh self.vel_thresh = vel_thresh self.need_frames = need_frames self.tracks = {} def update(self, dets, poses, frame_id): alerts = [] for det, pose in zip(dets, poses): track_id = self._match_track(det) if track_id is None: track_id = self._create_track(det) tr = self.tracks[track_id] features = tr.update_features(det, pose) if features and self._is_fall(features, tr): tr.fall_cnt += 1 else: tr.fall_cnt = 0 if tr.fall_cnt >= self.need_frames: alerts.append(track_id) tr.fall_cnt = 0 tr.last_alert_frame = frame_id return alerts def _is_fall(self, feat, tr): # tr.ref_height 是该人站姿基准 if tr.ref_height is None: return False return (feat.angle > self.angle_thresh and feat.vert_vel > self.vel_thresh and feat.body_h < tr.ref_height * 0.6)这个类没有包含追踪匹配的全部细节,但已经能表达核心:fall_cnt连续累加,累计到 5 帧才告警。need_frames=5按 25 帧视频算约 0.2 秒,既能滤掉单帧毛刺,也不会把告警拖到倒地之后好几秒。vel_thresh=15要小心,像素速度受图像分辨率影响,1080p 下取 15 像素/帧,如果改到 720p 需要等比缩小,改成 4K 则要放大。更稳妥的做法是把垂直速度除以肩髋基准高度,得到“每秒下跌几个身位”,这样阈值基本不随分辨率变化。
状态机里还有一个容易被忽略的点:告警后不要连续报警。同一个摔倒事件,如果状态机一直保持躺姿,fall_cnt可能会再累积,导致后台收到十几条重复消息。我习惯在告警后进入 10 秒冷却,冷却期间即使fall_cnt达标也不再推送。真正的中风和晕厥是摔倒后一直不动,需要的是 10 秒后进行一次“长时间未起身”复核,而不是反复报警。
4.3 yolov5后处理、量化与边缘部署参数取舍
把组合模型部署到边缘设备前,先要分清瓶颈在哪。yolov5 后处理包括缩放、锚框解码、NMS,这部分在 GPU 上耗时很低;OpenPose 的关键点热图计算才是大头。如果跑在树莓派 4B 这类设备上,官方 OpenPose 基本没有实时可能,常见做法是换用轻量姿态估计模型,或者把姿态分支输入降到 192x192。我自己在树莓派上部署时,把 YOLOv5 输入从 640 降到 320,再把 OpenPose 换成 ONNX 导出的单人姿态模型,勉强能跑到可用帧率,代价是远距离小目标会漏检。
对 rk3568 这类 NPU 平台,量化是绕不开的。YOLOv5 可以导出 ONNX 后用 RKNN Toolkit 做 int8 量化,姿态模型也可以量,但关键点热图对量化更敏感。量化后一定重新跑一遍测试集,不要只看正常帧,要看低对比度、暗光、快速倒地这几类帧。我的经验是姿态模型量化后误报概率会上升,常表现为脚踝点漂移到墙上、肩点跳到另一个人身上,所以边缘端更推荐把角度阈值调严格 5 到 10 度,用漏报率换误报率。
如果用 ROS 做机器人平台,可以单独把 YOLOv5 检测结果发成人体检测话题,姿态节点订阅话题后只处理人体框。这套解耦能让树莓派上的检测和姿态两个进程分开调度,避免一帧卡死整条链路崩溃。给后续维护留一点余地,代码结构比模型本身更重要。
5. 摔倒检测常见问题排查:五个坑与对应解法
5.1 俯视相机下YOLO漏检,人体框时有时无
【现象】摄像头装在屋顶或墙角高处,人走到画面正下方时,YOLOv5 经常不输出框;人侧躺在地上时,框只框住上半身,下半身被切掉。摔倒检测流程里最关键的人框都拿不到,后续姿态检测直接被跳过。
【原因】预训练 COCO 权重学到的“人”大多是水平视角的直立形象。俯视视角下,头顶、肩背占据画面大部分,腿脚被遮挡,这种特征分布和 COCO 差得很远。单纯把置信度阈值从 0.25 降到 0.1,只会把椅子、阴影、地上的包全检测成人,问题并没有解决。
【解决】必须从实际摄像头角度补样本。部署几台摄像头后抽 24 小时视频,把俯视、斜俯视、倒地蜷缩的姿态专门标出来,重新微调 YOLOv5。训练时关闭旋转增强,限制degrees=0,不然模型会学到“倒过来的人”这种不存在的情况。如果短期内拿不到足够的俯视数据,可以先保留预训练模型,同时加一个兜底检测规则:当 OpenPose 在全图模式下的关键点数量异常但 YOLO 没框时,触发一次弱置信度检测,不要直接丢掉。
5.2 关键点抖动导致摔倒误判,脚踝跳来跳去
【现象】OpenPose 输出的脚踝坐标在相邻帧之间跳动 20 到 30 像素,有时左脚踝和右脚踝互换位置。肩点在人体转身时也会左右交换,导致倾角特征瞬间从 10 度跳到 70 度,状态机因此误报。
【原因】一是单人裁剪图分辨率不够,脚踝只占十几个像素,热图峰值定位不稳定;二是 OpenPose 对左右关键点的匹配本身就按邻近关系,转身和遮挡时容易出现标签互换;三是对关键点没有做时间域平滑,单帧噪声直接进入判定。
【解决】把裁剪框外扩到 0.15 以上,并保留原始宽高比,用 letterbox 而不是拉伸方式缩放。关键点进入特征计算前做 EMA 平滑,alpha取 0.6 左右。左右肩、左右髋的计算改用中点,不要直接依赖某一侧坐标。如果脚踝抖动特别严重,可以在摔倒判定中降低脚踝权重,把主要判据放在肩髋连线和髋部下落速度上,脚踝只用来确认“人体已经接触地面”而不是用来计算角度。
5.3 弯腰捡东西误报成摔倒,特征阈值失效
【现象】老人在画面里弯腰捡钥匙,躯干倾角达到 60 度,持续时间超过 0.5 秒,状态机里fall_cnt一路累加,最后触发误报,护理人员在后台白跑一趟。
【原因】弯腰和摔倒的瞬间姿态非常像:躯干前倾、头部下移、肩髋连线倾斜。如果只靠倾角单特征判断,无论如何调阈值都会误伤。区别在于过程:弯腰时髋部高度变化不大,站立速度和倒地速度差一个数量级,而且弯腰结束后会主动恢复站姿,真正摔倒后基本不会在 1 秒内站起来。
【解决】把判定条件改成“速度突变 + 姿态保持”。先要求髋部垂直速度超过阈值,比如vert_vel > 12,再要求倾角大于 50 度且持续多帧。加入“倒地保持”后验条件:触发预备状态后,如果 0.8 秒内人体没有恢复到倾角小于 30 度,才推送告警。捡东西的人通常在 0.5 秒内恢复,不会跨过后验条件。代价是告警延迟增加 0.8 秒,对摔倒检测来说可以接受,因为摔倒过程本身会持续更久。
5.4 推理太慢帧率不足,管线串行卡成PPT
【现象】GPU 工作站上单看 YOLOv5 只要几毫秒,单看 OpenPose 也还好,但组合起来帧率掉到 5 到 8 帧,视频明显卡顿,摔倒动作被跳过很多细节。
【原因】很多工程把两个模型简单串在同一线程里,YOLOv5 跑完才跑 OpenPose,整帧延迟就是两者相加。OpenPose 在全图输入时计算量很大,而且 CPU 和 GPU 之间频繁拷贝图像数据,真正耗时在等待和数据搬运上。
【解决】先给系统分层。YOLOv5 固定跑在 GPU 上,只输出人体框;OpenPose 只对裁剪后的小图推理,不要全图跑。用两个线程或者两个队列解耦:检测线程持续出框,姿态线程从队列里取裁剪图处理,允许姿态结果比当前帧慢 100 到 200 毫秒。你可以在代码里把queue_size控制在 5,超过就丢弃最旧的帧,防止内存堆积。更极端的做法是每两帧跑一次姿态检测,中间帧用上一帧关键点线性插值,这样速度提升接近一倍,摔倒判断仍然连续。部署到树莓派 5 这类设备时,把 YOLOv5 输入降到 320,姿态输入降到 192,再做模型量化,比买更贵的硬件更实际。
5.5 画面边缘的小人体关键点全低置信,倒地检测不到
【现象】摔倒发生在画面左上角或者远端走廊,YOLOv5 给出的框只有 60x100 像素,裁剪后送入 OpenPose,所有关键点置信度都低于 0.3,过滤器直接把这些关键点置空,摔倒判定无法启动。最危险的是远处老人倒地,画面不是完全没人,但算法就是没反应。
【原因】OpenPose 对输入分辨率非常敏感。60x100 的人体缩小到姿态模型输入尺寸后,关键点真实信息只有几个像素,热图峰值不明确。另一方面,监控视频本身可能有 2K 甚至 4K 分辨率,但我们在读取帧时往往直接缩到 640,远端信息在预处理阶段就丢了一部分。
【解决】不要一上来就缩全图。用cv2.VideoCapture保持原始分辨率,YOLOv5 推理时使用 960 或 1280 输入,等拿到人体框再裁剪。注意model.imgsz越大,GPU 显存占用越高,实测时要看显存余量。另一个办法是为远端小目标单独开一路低置信度检测,conf=0.15,把结果交给跟踪器,用前一帧的姿态作为当前帧的预测先验,而不是直接丢弃。如果项目允许,优先调整摄像头点位,让关键区域的人体高度不小于画面的 5%,这是成本最低的解决方式。
6. 校准阈值:用难例视频做验收,别只看训练指标
模型训练完,yolov5 的 mAP 和 pose 的关键点准确率再高,也不代表报警系统能用。我在每个摔倒检测项目里都会单独建一个“验收视频集”,里面是现场采集的片段,标注出每一段是否真的摔倒、摔倒发生在第几秒。验收指标不用多,就要三个:召回率、误报次数、平均告警延迟。
计算方式可以很简单。把真实摔倒时间点记为true_fall,报警时间点记为alarm,在 2 秒内算一次命中:
true_fall = [12.5, 45.2] alarm = [12.4, 47.0, 103.8] tp = 0 for a in alarm: if any(abs(a - t) < 2 for t in true_fall): tp += 1 fp = len(alarm) - tp fn = len(true_fall) - tp recall = tp / len(true_fall) precision = tp / len(alarm)这段代码不是统计级严谨,但足够衡量状态机是否“能用”。真正要盯的是fp,也就是误报次数。养老院场景里,一周超过 3 次误报,护理人员就会习惯性忽略告警,系统直接失效。
我的调参习惯是:先把阈值调到偏灵敏,让所有可疑事件都触发,把angle_thresh降到 40、need_frames降到 3,跑完整个难例集;然后逐条看误报,把弯腰、下蹲、跪地这些片段对应的特征值打印出来,再决定阈值放宽还是收紧到多少。比如发现弯腰片段角度是 55 度,但髋部速度只有 3,就把“速度阈值从 12 提到 15”或“增加持续时间确认”作为对策,而不是单纯提高角度阈值。
最后一次调完,我会把难例视频单独存成一个目录,不删掉,每次改网络结构或者更换部署平台都重新跑一遍。方法是从这套代码帮你解决上线 80% 问题,但真正能不能上线,取决于你手里的难例视频够不够真实。摔倒检测最怕的不是模型不聪明,而是你以为它已经稳定工作了,结果被一个蹲下系鞋带的人轻易骗过。希望这套流程能帮你少走一段弯路,也希望你调试得顺手。
希望帮到你。
本文还有配套的精品资源,点击获取