
简介面向计算机相关专业毕业设计、课程设计与期末大作业场景这是一份将YOLOv5人体检测与OpenPose姿态检测相结合实现摔倒检测的高分项目资源。整体思路清晰覆盖目标检测、人体关键点提取、姿态估计与摔倒判定逻辑可用于老人看护、智能监控等异常行为识别场景。项目源自导师指导并认可的高分毕业设计评审分98分适合正在寻求完整实战案例和可扩展代码框架的学生参考。资源包以zip压缩包形式提供大小40.25MB内含项目Python源码与训练好的模型解压后即可围绕YOLOv5行人定位、OpenPose骨骼关键点提取以及摔倒判断流程进行复现和二次开发已有166人浏览学习。通过该资源可以熟悉目标检测与姿态估计的搭配方式理解摔倒检测从模型推理到状态判定的完整工程实现细节对完成同类课题具有较强的借鉴价值。1. 先把场景讲清楚yolov5openpose为什么能做成摔倒检测摔倒检测这个需求真正做过的人都知道它不是一个静态分类问题。图片里一个人躺在地上你很难单靠一张图判断他是摔倒还是睡觉关键在「怎么倒的、倒得多快、倒地之后人体的几何形态发生了什么变化」。这套yolov5人体检测openpose姿态检测的源码工程就是先把人从画面里拎出来再把人的骨架关键点拉出来最后用关键点的角度、位置和速度变化去判定摔倒整个流程是双模型串联的完整可运行项目。我做毕业设计时也踩过不少类似的坑所以这篇笔记会把环境配置、代码结构、判定参数和翻车记录都拆开讲适合正在做课程设计、期末大作业或者想认真跑一遍双模型协作的读者。2. 环境配置与依赖清单从YOLOv5源码到OpenPose推理环境的完整装法2.1 环境版本与依赖对照先说结论这套工程对版本的要求不算苛刻但也不是随便装个最新版就能跑。我拆项目时先看了依赖清单发现最核心的约束来自two个地方一是YOLOv5的原生代码依赖二是OpenPose模型的推理后端。YOLOv5从v5.0到v6.0、v7.0的models/yolo.py结构有差异如果你用的是我这边拆到的这份源码建议直接按配套环境走不要自己升级到最新版。下面这张表是我整理后实际可用的版本组合也是这个项目里最常见的搭配组件推荐版本备注Python3.8 / 3.9 / 3.103.10以下最稳3.11以上部分算子会崩PyTorch1.8 ~ 1.121.10左右兼容性最好别用2.0以上跑旧权重torchvision与PyTorch对应版本错位会直接报ModuleNotFoundErroropencv-python4.5.x ~ 4.8.x用于视频读取和画框numpy1.21 ~ 1.24太高版本会遇到np.float被删的报错PyYAML5.4.1YOLOv5读配置文件必需onnxruntime1.14以上OpenPose走ONNX推理时用提示如果权重的后缀是.onnx那OpenPose部分走的是onnxruntime而不是原版Caffe或PyTorch推理这个区别决定了你装依赖时要不要多装onnx和onnxruntime。我一般习惯用conda建独立环境而不是直接在base环境里装因为YOLOv5对torch和torchvision的配对很敏感一旦混装后面报错的时候你根本分不清是代码问题还是环境问题。2.2 conda创建环境与安装步骤项目的环境配置可以直接用conda一步步来。下面这组命令是完整的安装流程每个命令的作用我在后面说明。conda create -n fall_detect python3.9 -y conda activate fall_detect pip install torch1.12.1 torchvision0.13.1 --index-url https://download.pytorch.org/whl/cu113 pip install opencv-python4.8.1.78 numpy1.23.5 pyyaml5.4.1 pip install onnxruntime onnx pip install -r requirements.txt逻辑说明先建独立环境python3.9是折中方案既避开3.8上某些新版本opencv的兼容问题也避开3.10以上PyTorch的编译报错。torch1.12.1搭配cu113是CUDA 11.3的构建版本如果你机器是CUDA 11.8或12.x可以把cu113改成cu118或cu121但一定要配套。最后用requirements.txt补齐YOLOv5自己需要的依赖比如tqdm、matplotlib、seaborn这些。参数说明--index-url指定PyTorch官方源装了GPU版torch之后要检查一下能否调用GPU可以在Python里执行import torch;print(torch.cuda.is_available())返回True再继续否则后面OpenPose推理的速度会让你怀疑人生。2.3 模型文件放哪里目录结构与权重加载路径这套工程的目录我没有看到特别花哨的组织方式基本就是YOLOv5骨架加一个fall_detect业务目录。权重文件的路径是硬编码写在配置里的所以你要注意别把模型文件随意挪位置否则加载时会报FileNotFoundError。我拆项目时习惯先把文件结构摸清楚这个项目大概是下面这样fall_detect_project/ ├── models/ │ ├── yolov5s.pt │ ├── yolov5s.onnx │ ├── openpose_18.onnx │ └── ... ├── detect.py ├── fall_detector.py ├── utils/ │ ├── pose_utils.py │ └── visualization.py └── requirements.txt逻辑说明yolov5s.pt是YOLO的官方预训练权重注意这个权重是在COCO上训练的包含person类别类别ID0所以才能直接用来做人体检测。openpose_18.onnx是OpenPose的18点人体姿态模型输出是18个关键点的热图用于提取人体骨架。参数说明如果你的工程目录里文件名不同也没关系关键是把detect.py里权重路径改成你实际的路径。这个路径通常是相对路径如果从项目根目录启动models/yolov5s.pt就能直接找到如果你在别的目录下用python xxx/detect.py启动相对路径会失效这也是一个高频报错点后面避坑章会再提。3. 主流程拆解yolov5人体检测与openpose姿态推理怎么串起来3.1 为什么总要先用yolo裁剪人体区域而不是直接整图跑openpose这是双模型串联中最关键的设计决策。OpenPose本身是可以对整图做多人姿态估计的但它的特征是「准确率高、速度慢」整图推理时输入尺寸只要稍微拉大一点点耗时就会翻倍。而YOLOv5的人体检测又是「速度快、但有框没有骨架」。两个模型各干各的活自然的组合方式就是YOLO先找到画面里每个person的边界框然后把每个人的框裁出来单独送进OpenPose做关键点提取。这样做的好处有两点。第一OpenPose的输入变成了一张小图而不是整张视频帧推理速度会快很多尤其是画面中人只占一小块区域时计算量差别非常明显。第二裁出来再推理会减少背景干扰关键点的置信度普遍更高不容易把背景里的柱子、桌角误判成人体骨骼。这个流程对应到代码里就是主循环的四步读帧 → yolo推理 → 按person类别过滤 → 把每个人体框交给openpose推理。下面我把这两段关键代码分别拆开讲。3.2 人体检测骨架加载yolov5模型并过滤person类别import torch import cv2 import numpy as np # 加载yolov5模型这里用官方权重文件 def load_yolo(weights_path, devicecuda:0): model torch.hub.load(ultralytics/yolov5, custom, pathweights_path, force_reloadFalse) model.conf 0.5 # 置信度阈值低于0.5的检测框会被丢掉 model.iou 0.45 # NMS的IOU阈值控制重叠框的抑制强度 model.classes [0] # 只保留person类别COCO中person的ID是0 model.to(device) model.eval() return model def detect_persons(model, frame_bgr): results model(frame_bgr) boxes_xyxy results.xyxy[0].cpu().numpy() # 格式为[x1, y1, x2, y2, conf, cls] persons [] for box in boxes_xyxy: x1, y1, x2, y2, conf, cls box if cls 0 and conf 0.5: x1, y1, x2, y2 int(x1), int(y1), int(x2), int(y2) persons.append((x1, y1, x2, y2, conf)) return persons逻辑说明torch.hub.load是YOLOv5标准加载方式custom参数表示加载本地自定义权重而不是网络预置权重。model.conf和model.iou是YOLOv5推理阶段的后处理参数前者控制检测框的最低置信度后者控制NMS的抑制强度。model.classes [0]是一个很省的技巧推理时模型只输出person类别的检测框过滤掉了其他79个类别的计算和输出。参数说明置信度设为0.5是兼顾准确和召回的选择。在监控场景下如果画面距离远、人尺寸小可以下调到0.3但代价是误检会增多如果摄像头离人很近、画面清晰上调到0.6能减少大量背景噪声。IOU阈值0.45是YOLOv5默认值如果同一人多框重叠严重可以调高到0.5。3.3 姿态推理骨架把人体框送入openpose提取18个关键点import onnxruntime as ort # 加载openpose的onnx模型 class OpenPoseInference: def __init__(self, onnx_path, input_size368): self.session ort.InferenceSession(onnx_path, providers[CUDAExecutionProvider, CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.input_size input_size def preprocess(self, crop_bgr): # 将裁剪出来的人体区域缩放到模型输入尺寸 h, w crop_bgr.shape[:2] ratio_w self.input_size / w ratio_h self.input_size / h resized cv2.resize(crop_bgr, (self.input_size, self.input_size)) img resized[:, :, ::-1].transpose(2, 0, 1) img img.astype(np.float32) / 255.0 img (img - 0.5) / 0.5 return img[np.newaxis, ...], ratio_w, ratio_h def get_keypoints(self, crop_bgr): img, ratio_w, ratio_h self.preprocess(crop_bgr) outputs self.session.run(None, {self.input_name: img}) # 输出shape为(1, 18, h, w)18表示关键点数量取每个通道的最大值位置 heatmaps outputs[0][0] keypoints [] h, w heatmaps.shape[1], heatmaps.shape[2] for i in range(heatmaps.shape[0]): heatmap heatmaps[i] _, max_val, _, max_loc cv2.minMaxLoc(heatmap) x, y max_loc[0] * crop_bgr.shape[1] / w, max_loc[1] * crop_bgr.shape[0] / h keypoints.append((x, y, max_val)) return keypoints逻辑说明整个OpenPoseInference类做的事情很简单把YOLO裁出来的人体区域做resize和归一化然后送到ONNX会话里拿到18个通道的热图。每个通道对应一个关键点类型通道里最大响应值的位置就是这个关键点在原始裁剪图上的坐标。关键是那18个关键点的索引含义0为鼻子1为颈部2为右肩3为右肘4为右手腕5为左肩6为左肘7为左手腕8为右髋9为右膝10为右踝11为左髋12为左膝13为左踝14为右眼15为左眼16为右耳17为左耳。后面摔倒判定主要用到的是颈部、双髋、双踝这几个点。参数说明input_size368是OpenPose的标准输入尺寸越小速度越快但关键点会偏移越大越准但耗时成倍增长。在摔倒检测这种实时性要求高的场景我一般不建议超过368。providers参数里优先选CUDAExecutionProvider没有GPU时会自动回退到CPU。4. 摔倒判定算法宽高比、关键点夹角与多帧确认的参数怎么调4.1 三个判据的物理含义与计算公式模型串联起来之后核心问题就变成拿到18个关键点怎么判断这个人是不是摔倒了这个项目用的是几何判据不是训练一个分类网络。几何判据的好处是直观、可解释、阈值可调特别适合答辩时跟老师讲清楚原理。我用到的核心判据是三个。第一个是人体检测框的宽高比站立时框高度远大于宽度比例通常小于0.6摔倒躺下之后宽度反而会大于高度比例会超过1。第二个是髋部高度变化摔倒的本质是人的重心从高处瞬间跌到低处我们取左右髋关键点的平均y坐标计算它在一段时间内的下滑速度。第三个是姿态角度用颈部和髋部连线与水平面的夹角来判断站立时这个夹角接近90度摔倒后接近0度。三个判据各有各的弱点宽高比会被弯腰、蹲下干扰高度速度会被坐下干扰角度会被侧躺干扰。所以工程里不会只用单一判据而是组合起来投票表决。我把这个规则整理成了表判据计算公式默认阈值说明宽高比ratio (y2-y1) / (x2-x1) 1.2判定疑似摔倒正常站立0.3~0.6蹲下约0.8~1.0髋部高度速度speed (h_prev - h_now) / frames 0.5判定快速下坠单位为归一化高度差/帧需连续观察肩髋夹角angle atan2(h_hip_y - neck_y, neck_x - hip_x) 30°判定接近水平用度表示站立时接近90°逻辑说明宽高比最容易算但最不可靠高度速度能捕捉摔倒那一瞬间的动态特征但需要帧间差分角度能描述躺平状态但对侧躺角度失真。三个判据同时满足两个以上才进入报警逻辑这样可以过滤掉相当一部分误报。参数说明阈值不是死的。摄像头安装高度在2.5米以上、俯视角度大时宽高比的基准值会和水平视角差很多你要先录一段正常走路的视频统计出正常范围再设定阈值别直接套默认值。4.2 摔倒判定的完整实现下面这段代码是摔倒判定模块的核心逻辑融合了三个判据和帧确认机制。import math from collections import deque class FallDetector: def __init__(self, aspect_thres1.2, angle_thres30, speed_thres0.5, confirm_frames5): self.aspect_thres aspect_thres # 宽高比阈值 self.angle_thres angle_thres # 角度阈值度 self.speed_thres speed_thres # 髋部下坠速度阈值 self.confirm_frames confirm_frames # 连续帧数确认 self.history deque(maxlenconfirm_frames) # 保存最近N帧的判定结果 self.prev_hip_y None def compute_ratio(self, box): x1, y1, x2, y2, _ box w max(1, x2 - x1) h max(1, y2 - y1) return h / w def compute_angle(self, keypoints): # 取颈部(索引1)、左髋(11)、右髋(8)的平均位置计算肩髋连线角度 neck_x, neck_y keypoints[1][0], keypoints[1][1] hip_x (keypoints[8][0] keypoints[11][0]) / 2 hip_y (keypoints[8][1] keypoints[11][1]) / 2 angle math.degrees(math.atan2(hip_y - neck_y, hip_x - neck_x)) return abs(angle) def compute_drop_speed(self, hip_y): if self.prev_hip_y is None: self.prev_hip_y hip_y return 0.0 speed (self.prev_hip_y - hip_y) / self.confirm_frames self.prev_hip_y hip_y return abs(speed) def update(self, box, keypoints): ratio self.compute_ratio(box) angle self.compute_angle(keypoints) hip_y (keypoints[8][1] keypoints[11][1]) / 2 speed self.compute_drop_speed(hip_y) votes 0 if ratio self.aspect_thres: votes 1 if angle self.angle_thres: votes 1 if speed self.speed_thres: votes 1 print(fratio{ratio:.2f}, angle{angle:.1f}, speed{speed:.2f}, votes{votes}) self.history.append(votes 2) if len(self.history) self.confirm_frames and sum(self.history) self.confirm_frames - 1: self.history.clear() return True # 连续5帧里有4帧以上命中判定摔倒 return False逻辑说明FallDetector类维护了一个长度为confirm_frames的双端队列每帧把votes结果是否≥2个判据命中追加进去。当队列满时统计队列里True的个数如果5帧里至少有4帧判定为疑似摔倒才触发最终的摔倒事件。这种多帧确认机制是摔倒检测工程落地必需的单帧瞬时判定会把坐下、蹲下、弯腰捡东西全部误报成摔倒。参数说明confirm_frames5对应视频25fps下约0.2秒的触发延迟既不会漏掉快速摔倒也能过滤掉单帧抖动。如果你需要在更短时间响应可以改成3但误报会明显增加。speed_thres0.5这个值是归一化的前提是人体框在画面中占比较大如果摄像头离得远、人体框很小髋部y坐标的绝对变化量也会变小这时需要适当把这个阈值下调到0.3左右。4.3 阈值与帧确认机制的调参方法调参的核心思路是不要凭空猜先录制三段视频——正常走路、蹲下系鞋带、侧身倒地然后跑一遍检测脚本把每帧打印出来的ratio、angle、speed三个值统计出来看正常动作和摔倒动作的数值分布差异。我一般会先固定confirm_frames5把三个判据的阈值都设置得宽松一点让模型对「疑似摔倒」敏感然后再逐段视频回放观察误报来自哪个判据。比如蹲下系鞋带时宽高比普遍在0.9左右、角度在40度左右那就不需要动aspect_thres而是把angle_thres从30度往下调到25度这样蹲下这个动作就自然被排除在外。反过来如果漏报发生在缓慢倒地场景速度判据很难触发就下调speed_thres。YOLOv5部分也有两个参数会影响摔倒判定质量model.conf如果太低会输出大量重叠的人体框一个人被框成两三个框每个框单独送openpose时关键点会被裁剪得不完整角度和髋部高度都会失真。所以我建议在摔倒检测场景里model.conf不低于0.4model.iou不低于0.4确保一个人只有一个框。5. 常见问题排查与避坑多人场景、误报和部署翻车实录5.1 侧卧、蹲下、坐下都被判成摔倒现象测试时画面里的人只是蹲下捡东西或者侧躺在沙发上休息检测器就疯狂报警。这几乎是所有摔倒检测项目第一个遇到的坑。原因三个判据中宽高比和角度都只看静态几何形状。蹲下时宽高比约0.9~1.0接近阈值1.2的边缘侧躺时宽高比超过1.2、角度接近0度和真实摔倒一模一样。单靠静态形状无法区分「主动躺下」和「意外摔倒」因为两者的静态姿态是一致的。解决必须把速度判据权重提上来并且把帧确认机制加严。做法是摔倒必须同时满足条件宽高比异常或角度异常静态姿态异常加上髋部在短时间内快速下坠动态过程异常。把confirm_frames从5加到8speed_thres从0.5降到0.35这样蹲下、坐下这些动作因为缺乏明显的下坠速度而不会触发报警。另外可以加入「摔倒后保持状态N帧」的条件倒地后至少2秒内姿态没有恢复站立才确认是摔倒这个逻辑在真实监控场景里能抹掉大量瞬时误报。5.2 openpose多人场景漏检与推理太慢现象画面里只有一个人时一切正常一旦有两个人同时入画openpose输出要么只有一个人有完整骨架要么两个人都缺胳膊少腿而且视频明显变卡FPS从20掉到5。原因这套工程的处理方式是先yolo检测出所有person框然后逐个裁剪、逐个跑openpose。多人场景下人的框可能有重叠裁出来的图里包含另一个人体的一部分另外每个人单独跑一次openpose算力是按人数线性增长的两个人和五个人的耗时差距非常大。解决两件事第一是把OpenPose的input_size从368降到320速度能提升约30%而精度损失有限。第二是检测单个人体框后先把框向外扩展20%再裁剪避免骨架关键点刚好卡在裁剪边界上导致左右肩少一边。如果场景里人数常年超过3人更稳妥的做法是放弃逐人裁剪直接把整帧原图送进openpose做多人姿态输出虽然慢一些但骨架完整性好很多。5.3 摄像头画面黑屏或延迟越来越大现象代码在视频文件上跑得好好的换成摄像头后画面一会儿黑一会儿正常或者延迟越来越大最终画面冻结在最后一帧但程序还在跑。原因这是视频处理的高频坑。摄像头读取是实时流OpenCV的VideoCapture内部有缓冲区如果推理速度跟不上摄像头的帧率缓冲区会持续堆积旧帧导致延迟持续增加。cap.read()读到的永远是几秒前的旧画面看起来就像画面卡死。解决在读帧循环里限制输入帧率比如摄像头输出30fps但只每2帧或每3帧处理一次另一个解决是关闭OpenCV的缓冲队列。更直接的办法是记录上一帧处理时间如果没超过间隔就cap.grab()丢弃当前帧而不是cap.read()这样不会解码没必要的帧。5.4 加载权重报维度错误或显存溢出现象加载yolov5s.pt时一切正常但第一帧推理时直接报RuntimeError: The size of tensor a (2) must match the size of tensor b (3)或者干脆报CUDA out of memory。原因维度错误几乎都是YOLOv5版本不匹配导致的。v5.0和v6.0的输出层结构不同旧权重配新代码或新权重配旧代码时anchor和输出通道数对不上。显存溢出则是因为yolo和openpose都默认压在GPU上两个模型同时占显存小显存显卡很容易爆掉。解决维度错误只有一个解法就是让权重和代码来自同一个YOLOv5版本如果是这份工程自带的yolov5s.pt代码就别用最新版yolov5去加载。显存溢出则考虑yolo放GPU、openpose放CPU用torch.device(cpu)加载yolo这样显存占用能减掉一大半。我实测一块6G显存的卡yoloopenpose都压GPU是跑不动的分开放置后视频能稳定在15fps左右。6. 跑通之后还能做什么FPS摸底、帧间隔复用与低算力部署技巧整套工程跑通只是第一步真正让这份源码发挥价值的是后续的性能优化。我拿到工程后做的第一件事不是调判据而是先统计单帧耗时分布。做法很简单在检测循环里记录每帧时间和yolo推理、openpose推理的耗时看瓶颈到底在哪个环节。import time fps_list [] prev_time time.time() frame_count 0 while True: ret, frame cap.read() if not ret: break frame_count 1 # 每隔1帧跑一次yolo中间帧沿用上一帧的人体框 if frame_count % 2 1: persons detect_persons(yolo_model, frame) prev_persons persons else: persons prev_persons for box in persons: x1, y1, x2, y2, conf box crop frame[y1:y2, x1:x2] if crop.size 0: continue keypoints pose_model.get_keypoints(crop) is_fall fall_detector.update(box, keypoints) if is_fall: cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 3) now time.time() fps 1.0 / (now - prev_time) fps_list.append(fps) prev_time now这段代码里最值钱的优化是「帧间隔复用」yolo检测不是每帧都跑而是每2帧跑一次中间帧直接沿用上一帧的人体框。因为视频帧之间人的位置变化通常很小人体框偏移几个像素不影响关键点提取但yolo的推理开销可以省掉一半。我在树莓派5上实测原来的流程跑在5~8fps加了帧间隔复用之后能稳定跑到12fpsopenpose的input_size从368降到320又能再涨一截。如果后面你想把它从毕设工程改成能实际部署的监控系统还有两个方向值得做。第一是把判据里的固定阈值改成动态自适应比如宽高比阈值根据画面中人体的平均尺寸自动调整第二是把结果输出接上微信推送或邮件告警摔倒事件触发时截图存证。这两个改动都不影响核心算法但能把工程变成真正可用的产品原型。做完这些之后我的感受是双模型串联的项目最大的问题永远不是模型本身而是两个模型之间的衔接和后处理规则。YOLO框的质量直接影响OpenPose关键点质量OpenPose关键点质量又直接影响摔倒判据的数值稳定性任何一环掉了链子最终报警逻辑就会飘。从那以后我每次跑这种双模型协作场景都会强制走一遍单帧FPS摸底、先crop再推理、多帧投票确认这三板斧确认稳定了再动阈值。这套流程放在这个摔倒检测工程上也一样适用希望帮到你。本文还有配套的精品资源点击获取