简介:面向计算机专业毕业设计、课程设计与期末大作业场景的深度学习实战资源,整合yolov5人体检测与openpose姿态估计算法,实现视频或图片中人体摔倒行为的自动识别。项目由导师指导完成,评审得分98分,具备完整的项目源码和训练模型,适合需要快速搭建人体姿态分析系统、理解目标检测与关键点提取联合应用逻辑的学生参考。资源共包含40余个文件,以Python源码、模型权重、配置文件及说明文档为主,压缩包整体约40.25MB,便于直接下载解压后对照学习。目前已吸引167人浏览学习,可帮助学习者快速掌握摔倒检测的技术流程、模型调用方式与关键代码结构,同时也可作为高分之选的高分项目范例进行扩展与二次开发。
1. 为什么“yolov5+openpose”组合能撑起一个摔倒检测项目
摔倒检测在计算机视觉里不算新课题,但真正落地过的人都知道,难点不在于“识别一个人”,而在于“稳定判断一个人是不是摔了”。单纯用目标检测模型,你能拿到人框,却拿不到姿态;单纯用姿态估计模型,人一多、遮挡一重,关键点就乱飘。这个项目把 yolov5 的人体检测和 openpose 的姿态估计串成一条流水线:yolov5 先把人从画面里框出来,openpose 再在框内提取姿态关键点,最后由几何规则判定摔倒与否。评审分 98 分,靠的不是堆模型,而是这套分工明确的检测链路。适合正在做毕业设计、课程设计的学生,也适合想跑通一套完整 CV 落地流程的开发者。下面按我实际拆过的路径,从原理、环境、判定逻辑到踩坑,逐层说清楚。
2. 两阶段流水线的基本原理:为什么先检测人,再估计姿态
2.1 yolov5 在人检任务里的选型理由
yolov5 是目前工程落地里性价比最高的一档检测器。它把模型按深度和宽度分成 s、m、l、x 四个尺度,摔倒检测这类单人或多人的室内监控场景,用 yolov5s 就够。s 版本在 COCO 上的 mAP 大约 37,推理速度在 GTX 1060 上能跑到 50 FPS 以上,这对后续接姿态估计很重要,因为姿态估计更吃算力,检测器如果能做到又快又稳,整个管线的帧率才有保障。
选 yolov5 而不是更早的 yolov3 或更重的 EfficientDet,核心原因是它的预处理和后处理接口简单。项目检测类别只需要 person 这一类,跑推理时直接读data/coco128.yaml,或者自定义一个只有 person 类别的 yaml,把nc改成 1,重新训练或者直接沿用 COCO 预训练权重里的 person 类别输出。实际部署时,你会用到它的detect.py,核心参数就是--weights、--source、--conf-thres、--iou-thres。
# yolov5 推理的核心调用路径(示例) import torch model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) model.classes = [0] # 只保留 person 类别,COCO 中 person 的 id 是 0 results = model(frame) # 输入 BGR 帧,返回检测结果 boxes = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls]这段代码里,model.classes = [0]是关键过滤逻辑,只保留 person 类的检测框,否则椅子、桌子这些物体会被一起框出来,后面的姿态估计会白算。results.xyxy返回的是归一化前的像素坐标,后续传给 openpose 之前,需要做一次坐标系转换。
2.2 openpose 姿态估计的底层逻辑
openpose 的核心原理是 PAF(Part Affinity Fields,部分亲和场)。简单说,它不只预测每个关键点的热力图,还预测关键点之间的连接方向和置信度,然后通过图匹配把这些连接拼成完整骨架。输出是 18 个或 25 个关键点的坐标与置信度,常见的是 COCO 格式的 17 点,包括鼻子、颈部、双肩、双肘、双腕、双髋、双膝、双踝。
摔倒检测里最有用的四个点是:双髋、双膝、双踝。判定摔倒的几何特征全部围绕这几个点展开。比如人站立时,髋关节离地面大约 90 厘米,摔倒后髋关节高度会骤降到 20 厘米以下;再比如站立时躯干(颈部到髋部连线的延长线)与地面的夹角接近 90 度,摔倒后这个夹角会跌到 30 度以下。这些指标用 openpose 输出的原始坐标就能算,不依赖额外的训练数据。
openpose 的模型体积是个现实问题。原版 caffe 模型有 200 多 MB,在 CPU 上跑 640x480 的一帧需要几百毫秒,帧率只有个位数。工程里常见的做法是替换成 lightweight-openpose 或 tensorrt 加速版本,前者只有 14MB 左右,精度略有下降但摔倒检测这种场景足够用。
2.3 两条模型串起来的完整管线
整个检测管线分四步:yolov5 跑目标检测拿到人物框 → 把每个人物框裁剪出来 → 对裁剪图执行 openpose 姿态估计 → 拿姿态关键点到主帧坐标还原,再做摔倒判定。这里最容易翻车的是坐标系还原,因为裁剪图里算出来的关键点坐标是相对裁剪图的,必须映射回原图,否则判定逻辑里的“离地高度”和“躯干角度”全是错的。
# 坐标映射的核心逻辑:从裁剪图坐标回到原图坐标 def map_keypoints_to_original(kp_cropped, box): x1, y1, x2, y2 = box scale_x = (x2 - x1) / crop_width # 裁剪图宽度与原框宽度的比例 scale_y = (y2 - y1) / crop_height kp_original = [] for (kx, ky, conf) in kp_cropped: orig_x = x1 + kx * scale_x orig_y = y1 + ky * scale_y kp_original.append((orig_x, orig_y, conf)) return kp_original这段代码的scale_x和scale_y必须分开算,不能图省事用同一个缩放系数,因为裁剪时不一定保持原始宽高比。很多开源项目里摔倒误报率高,问题就出在这里。等比缩放只适用于x1=x2这类极端情况,日常视频里人的检测框宽高比一直在变。
3. 环境搭建与源码复现:从下载到跑通第一帧检测
3.1 环境版本组合与安装细节
这个项目的依赖分两块:yolov5 的依赖和 openpose 的依赖。yolov5 官方推荐 Python 3.8 以上、PyTorch 1.7 以上;openpose 的 pytorch 实现(比如 tf-pose-estimation 或 lightweight-openpose)要求 PyTorch 版本不能太新,否则部分算子会报错。我实际跑通的一组组合是:
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.8 | 兼容性最好,torchvision 编译不出幺蛾子 |
| PyTorch | 1.10.0 | CPU/GPU 版本均可,GPU 需 CUDA 11.3 |
| opencv-python | 4.5.5 | 视频读取与图像预处理 |
| numpy | 1.21 | 关键点坐标运算 |
| Cython | 0.29.32 | openpose 的 setup 编译需要 |
安装时先装 torch 再装其他依赖。pip install torch==1.10.0+cu113 torchvision==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html是 GPU 版本的安装命令。如果只有 CPU 机器,把+cu113去掉就行。
3.2 源码目录结构与权重文件放置
项目解压后的目录通常是这样的:
fall_detection/ ├── models/ # yolov5 模型定义与权重 │ ├── yolov5s.pt │ └── openpose/ │ ├── pose_model.pth │ └── pose_cfg.yaml ├── utils/ # 通用工具:坐标映射、可视化 ├── detection/ # yolov5 推理封装 ├── pose/ # openpose 推理封装 ├── judge/ # 摔倒判定规则 ├── test_data/ # 测试视频与图片 ├── main.py # 主入口 └── requirements.txt第 1 次跑通前,先确认 yolov5s.pt 存在,这是官方预训练权重,只有 14MB。openpose 的权重如果是原版 caffe 模型,需要pose/coco/pose_iter_440000.caffemodel这个文件,约 200MB,下载后放在指定目录,路径不对会在加载时报FileNotFoundError。
3.3 主流程代码:跑通第一帧
# main.py 的最小可运行版本 import cv2 from detection.yolo_detector import YoloDetector from pose.openpose_estimator import OpenPoseEstimator from judge.fall_judger import FallJudger yolo = YoloDetector(weights='models/yolov5s.pt', conf_thres=0.4) pose = OpenPoseEstimator(model_path='models/openpose/pose_model.pth') judger = FallJudger(fall_height_ratio=0.45, angle_thresh=35) frame = cv2.imread('test_data/person_standing.jpg') boxes = yolo.detect(frame) # 返回 [x1,y1,x2,y2,conf] 列表 for box in boxes: crop = frame[int(box[1]):int(box[3]), int(box[0]):int(box[2])] keypoints = pose.estimate(crop) # 返回 17x3 的数组 [x,y,conf] orig_kps = map_keypoints_to_original(keypoints, box) is_fall = judger.judge(orig_kps) if is_fall: cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]), (0, 0, 255), 2) cv2.putText(frame, 'FALL', (box[0], box[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imwrite('test_data/result.jpg', frame)这段代码里有三个参数值得注意。conf_thres=0.4控制 yolov5 的人检置信度,低于 0.4 的框会被过滤,太高了容易漏检,太低了误检框多,openpose 会被带着多算好几轮空姿态。pose.estimate(crop)输入的裁剪图建议做一次 resize 到 368x368,这是 openpose 最常用的输入分辨率,直接传原始裁剪尺寸也能跑,但姿态精度会波动。judger.judge(orig_kps)里传入的必须是原图坐标,这一点前面强调过。
3.4 第一次跑通后的自检清单
跑通第一帧后不要急着接视频流,先做三个验证。
第一,打印关键点坐标,确认最大 x 不超过图像宽度、最大 y 不超过图像高度,如果超出说明坐标映射有 bug。第二,把关键点画在原图上检查,颈部到髋部的连线应该落人身体中心线上,如果斜着飘出去,说明 openpose 输入的分辨率处理有问题。第三,统计单帧耗时。yolov5s 加 lightweight-openpose 在 GPU 上应该在 30ms 以内,如果超过 100ms,检查是不是没用半精度推理或者模型版本选大了。
4. 摔倒判定逻辑与参数调优:从关键点坐标到每一个阈值
4.1 判定指标的数学定义
摔倒检测的判定指标通常有三个,互相配合使用。第一个是髋部离地高度比,计算公式是髋部平均高度除以人体身高。用绝对高度不方便,因为摄像头角度和安装高度会直接影响像素坐标,改成比例后鲁棒性更好。第二个是躯干倾斜角,取颈部到双髋中点的连线与水平线之间的夹角,站立时接近 90 度,摔倒后接近 0 度。第三个是重心垂直速度,用相邻帧髋部高度的差分除以帧间隔时间,突然快速下降本身就是一个强信号。
def compute_fall_metrics(keypoints, prev_keypoints, fps): # keypoints: 17x3,索引按 COCO 顺序 hip_left = keypoints[11] # 左髋 hip_right = keypoints[12] # 右髋 neck = keypoints[1] # 颈部 hip_center_y = (hip_left[1] + hip_right[1]) / 2 neck_y = neck[1] hip_to_ground = hip_center_y # 以图像底部为地面 body_height = abs(hip_center_y - neck_y) # 指标1:髋部高度比 height_ratio = body_height / hip_to_ground if hip_to_ground > 1e-6 else 0 # 指标2:躯干倾斜角(度) delta_x = (hip_left[0] + hip_right[0]) / 2 - neck[0] delta_y = hip_center_y - neck_y body_angle = abs(math.degrees(math.atan2(delta_y, delta_x))) # 指标3:垂直速度(像素/秒) if prev_keypoints is not None: prev_hip_y = (prev_keypoints[11][1] + prev_keypoints[12][1]) / 2 fall_speed = (hip_center_y - prev_hip_y) * fps else: fall_speed = 0 return height_ratio, body_angle, fall_speed这三个指标的含义要区分清楚。height_ratio衡量的是人体“竖着占多少比例”,站立时身体高度与髋部高度接近 1:1,比值趋近 1;摔倒平躺时身体高度贡献给了 x 方向,髋部高度接近地面,比值会很小。body_angle单独看不可靠,因为弯腰捡东西时角度也会大幅缩小,所以必须结合垂直速度一起判断。
4.2 多帧缓冲与状态机
单帧判定必然误报,这是所有摔倒检测项目的共性教训。合理解法是引入一个长度为 N 的滑动窗口,只有连续 N 帧中至少有 M 帧触发摔倒条件,才输出一次摔倒事件。这里 N 取 5、M 取 3 是个比较稳的起点。
class FallJudger: def __init__(self, height_ratio_thresh=0.45, angle_thresh=35, speed_thresh=450, buffer_size=5, trigger_count=3): self.buffer = [] self.buffer_size = buffer_size self.trigger_count = trigger_count def judge(self, kps, prev_kps, fps): ratio, angle, speed = compute_fall_metrics(kps, prev_kps, fps) # 核心条件:髋部高度比变小 + 躯干角度变平 + 垂直速度够大 is_falling = (ratio < self.height_ratio_thresh and angle < self.angle_thresh and speed > self.speed_thresh) self.buffer.append(1 if is_falling else 0) if len(self.buffer) > self.buffer_size: self.buffer.pop(0) if sum(self.buffer) >= self.trigger_count: self.buffer.clear() # 触发后清空,防止同一次摔倒重复报警 return True return False这段代码里最关键的是buffer.clear(),不清空的话,同一段摔倒视频会连续报警十几次。实际部署时,清空后还需要加一个冷却期,比如 10 秒内不再触发,等姿态恢复正常再重新武装。
4.3 阈值整定的实践经验
阈值不是拍脑袋定的,用测试视频跑一遍,把三个指标的时间序列打印出来,看站立、走路、弯腰、坐下、摔倒这几类动作分别在什么范围。
| 动作类型 | height_ratio | body_angle | fall_speed |
|---|---|---|---|
| 站立 | 0.85 ~ 1.1 | 75 ~ 90 | < 50 |
| 弯腰 | 0.30 ~ 0.55 | 25 ~ 45 | 50 ~ 150 |
| 坐下 | 0.35 ~ 0.60 | 40 ~ 60 | 80 ~ 250 |
| 摔倒 | 0.10 ~ 0.40 | 5 ~ 30 | 400 ~ 1000 |
从这张表能看出,height_ratio和angle都无法单独区分弯腰和摔倒,必须加上speed这个维度。最终选阈值时,height_ratio_thresh=0.45能覆盖大部分摔倒场景,angle_thresh=35会放过一些侧倒场景,侧倒时躯干角度可能还有 40 度,需要根据你的摄像头安装角度微调。
5. 避坑与常见问题排查:漏检、姿态错乱、显存溢出的真实原因
5.1 openpose 偶尔完全拿不到关键点
现象:yolov5 框到了人,但 openpose 输出的 keypoints 全是 0,置信度也是 0。
原因:裁剪出来的人像区域太小,或者 openpose 内部对输入做了固定 resize,小区域被放大后严重失真。还有一种可能是关键点置信度阈值设太高,比如 0.6 以上,部分遮挡场景下关键点置信度天然低。
解决:给 openpose 的输入加一个最小尺寸限制。裁剪区域短边小于 100 像素时,直接放弃该帧的姿态估计,让下一帧再试。置信度阈值默认降到 0.2 到 0.3 之间,摔倒检测更看重召回而不是精确,漏一个关键点比多一个噪点更致命。
# 在裁剪后做尺寸规整 if min(crop.shape[0], crop.shape[1]) < 100: continue # 跳过这种无法可靠估计姿态的框 crop_resized = cv2.resize(crop, (368, 368), interpolation=cv2.INTER_LINEAR)5.2 站着的人被频繁判定为摔倒
现象:画面里有人正常行走,偶尔出现一次摔倒误报,看输出指标是 body_angle 突然掉到 30 度。
原因:yolov5 检测框偶尔会丢一只脚,导致 openpose 只估计出上半身关键点,髋部位置被抬高,height_ratio 和 angle 同时失真。
解决:判定加上关键点完整性检查。至少要有 10 个置信度大于 0.3 的关键点才进入摔倒判定,而且双髋、双膝、双踝这六个点里不能少于 4 个。缺失超过一半就跳过当前帧,无脑输出“疑似摔倒”只会把项目变成狼来了的故事。
5.3 GPU 显存不足,跑几秒就 OOM
现象:在 8GB 显存的卡上跑视频流,后台日志显示 CUDA out of memory。
原因:openpose 的默认输入分辨率大,yolov5 的 batch size 没控制,加上 PyTorch 默认不用半精度,显存瞬间被吃光。
解决:三件事,输入分辨率降到 320x320,yolov5 推理时设置batch=1,openpose 模型换成 lightweight 版本,再把推理代码包一层torch.cuda.amp。
with torch.cuda.amp.autocast(): keypoints = pose.estimate(crop_resized)半精度在这类任务上精度损失很小,显存占用直接减半,是性价比最高的优化手段之一。
5.4 检测框剧烈抖动,姿态也跟着跳
现象:人物框每帧左右横跳,关键点坐标噪声很大,速度指标算出来的值忽高忽低。
原因:yolov5 的 NMS 阈值设置不合适,同一个人的多个框没有被合并干净,或者检测到的是两个人靠得很近的情况。
解决:iou-thres从默认的 0.45 提到 0.5 到 0.55,让重叠的框更激进地合并。速度指标对坐标噪声非常敏感,给关键点坐标做一个宽度为 3 帧的移动平均滤波,能显著减少速度误报。
# 三帧移动平均滤波,平滑关键点坐标 smoothed = (prev2 + prev1 + current) / 35.5 侧躺摔倒完全检测不到
现象:人对着摄像头侧面摔倒,躯干夹角始终在 50 度以上,你设定的 angle 阈值永远不会触发。
原因:侧躺时躯干在图像平面上的投影角度不够平,这是二维估计的天然盲区。
解决:不要依赖单一视角的角度,改用“髋部高度比 + 垂直速度”双条件并联逻辑。侧躺摔倒时髋部高度同样会大幅下降,速度同样会骤升,这两个指标不受视角旋转影响。只有这两个条件都触发时才判定摔倒,angle 只作为辅助参考。
6. 进阶:把摔倒检测接进视频流与告警管线
前面的测试都是单帧图片,真正要落地还得接视频流和告警。这里给一套我已经跑通的方案:用 OpenCV 的VideoCapture读摄像头流,做一个双线程结构,主线程读帧和画框,子线程跑检测,主线程只负责显示和推流。这个设计的核心是不要阻塞读帧,否则视频会越跑越卡。
def process_stream(rtsp_url, fall_callback): cap = cv2.VideoCapture(rtsp_url) fps = cap.get(cv2.CAP_PROP_FPS) while True: ret, frame = cap.read() if not ret: break boxes = yolo.detect(frame) for box in boxes: kps = get_keypoints(frame, box) if judger.judge(kps, prev_kps, fps): fall_callback(frame, box) # 告警回调 judger.cooldown_until = time.time() + 10 prev_kps = kps告警推送最简单是走钉钉或企业微信的 webhook,requests.post一发就完事。我把一张图片存下来,连同时间戳和摄像头编号组一条消息推出去,实测全链路延迟在 1 秒以内。
再往上走一步是提速。yolov5 转 ONNX 再走 TensorRT,openpose 同理,整套流程在 RTX 3060 上能从 20 帧提高到 40 帧左右。代价是模型变得难以调试,所以我一贯的原则是:先跑通纯 PyTorch 版本,确认判定逻辑没问题,再做转换。直接上半精度和 TensorRT 排错,你会分不清是模型问题还是代码问题。
另外提醒一句,模型权重文件的路径不要写绝对路径,项目换机器就得改。放进 models 目录后用相对路径引用,这是这个项目里最常见的低级翻车。从那以后我每次部署新环境,都强制走一遍完整的跌倒测试集,用五段标注好时刻的视频去验证触发时刻误差,确认没问题再交付。
希望这份笔记能帮你把摔倒检测项目顺利跑通,少走我走过的那些弯路。
本文还有配套的精品资源,点击获取