ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv7-POSE、Bytetrack与STGCN的实时人体行为识别系统实践

基于YOLOv7-POSE、Bytetrack与STGCN的实时人体行为识别系统实践 简介本资源是一套面向安防监控与智慧养老场景的端到端智能行为分析系统实现方案适用于计算机视觉方向的中级开发者、AI项目工程师及高校科研人员解决实时人体姿态感知、多目标连续追踪与跌倒等异常行为精准识别三大核心问题。压缩包共107个文件含41个核心Python脚本模型训练/推理/可视化、8个配置用YAML文件、7段测试MP4视频、5个说明文档含附赠资源.docx与README.md、3个Docker相关文件Dockerfile/sh脚本及预训练PTH模型整体65.07MB结构清晰模块解耦明确。已有76人学习下载提供完整可运行代码链路从YOLOv7-POSE关键点检测、Bytetrack轻量级ID关联跟踪到STGCN时空图卷积行为分类覆盖数据预处理、模型部署、结果可视化全流程并附实测GIF动图与典型场景截图便于快速验证与二次开发。1. 项目缘起从“看见”到“看懂”智能监控的下一站最近在做一个社区养老服务中心的安防升级项目甲方提了个挺有意思的需求他们不满足于传统的摄像头录像和移动侦测报警希望系统能“看懂”画面里的人在干什么特别是能自动识别老人是否摔倒或者长时间滞留在一个地方不动。这其实就是典型的“行为识别”需求也是目前智能安防和看护领域的一个热点。市面上现成的解决方案要么太“重”需要部署一整套昂贵的专用硬件和软件要么太“轻”识别准确率堪忧误报频繁根本没法用。所以我们决定自己动手基于几个前沿的开源算法搭建一套轻量、高效且可定制的智能监控系统。核心思路很清晰先“看见”人再“盯住”人最后“理解”人的行为。这正好对应了三个关键技术环节实时人体关键点检测姿态估计、稳定的多目标跟踪、以及基于时空图的行为识别。我们最终选定的技术栈是YOLOv7-POSE用于姿态估计Bytetrack用于多目标跟踪STGCN用于行为识别。这套组合拳打下来实测效果非常不错在消费级显卡上就能达到实时分析识别跌倒等异常行为的准确率也相当高。今天我就把这套方案的实现思路、踩过的坑以及核心代码逻辑毫无保留地分享出来。无论你是想复现一个类似的系统还是单纯对其中某个技术环节感兴趣相信都能从中找到有用的东西。2. 技术选型背后的逻辑为什么是YOLOv7-POSE、Bytetrack和STGCN搭建一个系统选型是第一步也是最关键的一步。选错了后面全是坑。我们的核心需求是实时性、准确性、轻量化和易部署。下面我逐一拆解为什么这三个组件是当前场景下的“黄金搭档”。2.1 姿态估计YOLOv7-POSE的平衡之道人体姿态估计简单说就是从图像中定位出人体的关键关节如头、肩、肘、腕、髋、膝、踝等。主流方法有两类自顶向下Top-Down和自底向上Bottom-Up。自顶向下先用目标检测框出每个人再对每个框内的人单独进行姿态估计。代表算法有HRNet、HigherHRNet配合检测器。它的优点是精度高因为每个都是独立处理的。但缺点也很明显速度受人数影响大人越多越慢并且非常依赖前端检测器的性能如果两个人挨得近被检成一个框那就完了。自底向上先检测出整张图片中所有关键点再通过聚类或关联算法将这些点“组装”成一个个独立的人。代表算法是OpenPose。它的优点是速度相对稳定与人数关系不大。但缺点是在人群密集、遮挡严重时“组装”环节容易出错导致关键点张冠李戴。我们的场景是监控视频需要实时处理且画面中人数不定。YOLOv7-POSE属于自顶向下范式但它巧妙地解决了传统自顶向下方法的痛点。YOLOv7本身就是一个速度和精度平衡得极好的检测器其POSE版本将关键点检测头直接集成到网络中实现了端到端的检测与姿态估计。这意味着网络在一次前向传播中直接输出每个目标的边界框和对应的关键点坐标省去了传统串联管道带来的延迟和误差累积。注意你可能听说过YOLOv8-POSE或YOLO11-POSE。v8-POSE同样优秀且生态更活跃。我们选择v7-POSE一方面是在项目启动时v8-POSE刚发布还不够稳定另一方面是v7-POSE在我们的测试集上表现出了稍好的精度-速度权衡。对于新项目可以基于YOLOv8/11-POSE进行其原理和部署方式大同小异。关键参数考量YOLOv7-POSE提供了不同大小的模型如yolov7-w6-pose.pt,yolov7-e6e-pose.pt。w6相对轻量e6e精度更高但更慢。经过测试在RTX 3060上使用yolov7-w6-pose处理1080p图像可以达到40 FPS完全满足实时需求。如果你的硬件更弱或需要处理更多路视频可以考虑剪枝、量化或使用更小的模型。2.2 多目标跟踪Bytetrack的“不抛弃不放弃”哲学有了每一帧的检测结果包括人的位置和姿态我们需要在视频序列中持续地识别同一个体为其分配唯一的ID。这就是多目标跟踪MOT。传统方法如SORT、DeepSORT严重依赖检测框的置信度分数。通常设定一个阈值如0.5只保留高置信度的检测结果进行关联低置信度的直接丢弃。但在实际监控中遮挡、模糊、快速运动是家常便饭这会导致目标在某些帧的检测置信度很低。如果简单丢弃就会造成ID切换同一个目标被赋予新ID或轨迹中断。Bytetrack的核心创新点就在于它重视低置信度检测框的价值。Bytetrack的工作流程可以简化为两个阶段第一次关联使用高置信度检测框如score 0.6与现有的跟踪轨迹进行关联通常使用卡尔曼滤波预测位置用IoU或ReID特征计算代价矩阵进行匈牙利匹配。第二次关联将第一次关联后剩余的低置信度检测框如0.1 score 0.6与第一次关联后仍未匹配上的跟踪轨迹进行再次关联。这些低置信度框很可能是被部分遮挡或模糊的目标直接丢弃会导致跟踪失败。这个“第二次机会”机制极大地提升了跟踪在复杂场景下的鲁棒性。对于行为识别来说稳定的跟踪轨迹是后续时序分析的基础频繁的ID跳变会让行为识别模型无所适从。实操心得Bytetrack的超参数特别是高低置信度的阈值需要根据你的实际检测模型性能进行微调。如果你的检测器在遮挡下性能下降严重可以适当降低低置信度阈值如降到0.05让更多候选框参与第二次关联。同时Bytetrack原论文提供了不同场景下的基准参数这是一个非常好的起点。2.3 行为识别STGCN如何理解时空模式这是让系统“看懂”行为的关键。我们识别的是“跌倒”这是一个典型的时空序列行为。它不仅在单帧图片上有特定的姿态如身体与地面夹角小更是一个连续的过程从站立到失衡再到倒地。STGCN时空图卷积网络是处理这类问题的利器。它的核心思想是将一段时间内一个人的姿态序列构建成一个图结构。图的节点人体的关键点如17个关节点。图的边分为两种空间边根据人体自然连接如肘连接肩和腕描述单帧内关节之间的连接关系。时间边同一个关节在连续帧之间的连接描述该关节随时间的运动轨迹。图卷积通过在构建的时空图上进行卷积操作网络能够同时捕捉关节间的空间关系姿态和关节随时间的变化运动从而有效地学习到“跌倒”这类行为的时空特征。相比于传统的3D卷积网络C3D或双流网络STGCN的参数更少效率更高并且显式地建模了人体结构先验知识对于基于姿态的行为识别任务尤其有效。技术选型总结YOLOv7-POSE负责“精准看见”Bytetrack负责“稳定跟随”STGCN负责“深刻理解”。三者通过管道串联形成了一个从像素到行为语义的完整解析链路。这套方案的优势在于组件都是当前领域内公认的强效且轻量的方法组合灵活便于在边缘设备上部署和优化。3. 系统管道搭建从视频流到行为告警的完整链路理论说清楚了我们来看怎么把它们拼装成一个可以运行的系统。整个处理管道可以看作一个多阶段流水线。下面我结合核心代码逻辑进行说明。3.1 第一阶段视频解码与帧管理这是所有视频分析项目的基础。我们使用OpenCV进行视频读取。为了提高效率特别是处理多路视频时通常会采用生产者-消费者模型或多线程/进程。import cv2 from queue import Queue import threading class VideoStream: def __init__(self, source): self.cap cv2.VideoCapture(source) self.queue Queue(maxsize30) # 设置缓冲队列大小 self.stopped False def start(self): threading.Thread(targetself.update, args()).start() return self def update(self): while not self.stopped: if not self.queue.full(): ret, frame self.cap.read() if not ret: self.stop() break self.queue.put(frame) else: time.sleep(0.01) # 队列满时稍作等待 def read(self): return self.queue.get() def stop(self): self.stopped True self.cap.release()提示对于RTSP等网络流OpenCV的read可能不稳定需要考虑丢帧重连机制。更专业的做法是使用FFmpeg或GStreamer绑定。3.2 第二阶段YOLOv7-POSE姿态估计推理我们需要加载训练好的YOLOv7-POSE模型并对每一帧进行推理。这里使用PyTorch和官方仓库的代码。import torch from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from utils.plots import plot_skeleton_kpts class PoseEstimator: def __init__(self, weights_path, devicecuda:0, conf_thres0.25): self.device torch.device(device) self.model attempt_load(weights_path, map_locationself.device) self.model.eval() self.conf_thres conf_thres self.stride int(self.model.stride.max()) def preprocess(self, frame): 将OpenCV BGR图像转换为模型输入张量 img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img letterbox(img, new_shape640, strideself.stride)[0] # 保持长宽比resize img img.transpose(2, 0, 1) # HWC to CHW img np.ascontiguousarray(img) img torch.from_numpy(img).to(self.device) img img.float() / 255.0 # 归一化 if img.ndimension() 3: img img.unsqueeze(0) # 增加batch维度 return img def infer(self, frame): img_tensor self.preprocess(frame) with torch.no_grad(): pred self.model(img_tensor, augmentFalse)[0] # 应用NMS获取检测框和关键点 pred non_max_suppression(pred, conf_thresself.conf_thres, iou_thres0.45, classesNone, agnosticFalse, max_det100, kpt_labelTrue) detections [] for det in pred: if len(det): # 将坐标映射回原图尺寸 det[:, :4] scale_coords(img_tensor.shape[2:], det[:, :4], frame.shape).round() for *xyxy, conf, cls, kpts in det: # xyxy: 边界框, conf: 置信度, cls: 类别, kpts: 17个关键点(x,y,visibility) detections.append({ bbox: [int(x) for x in xyxy], score: float(conf), keypoints: kpts.cpu().numpy() if kpts is not None else None }) return detections关键点解析kpts的shape通常是(17, 3)17个关键点每个点有(x, y, visibility)。visibility通常表示该点的可见性置信度。在后续处理中我们会利用这个值过滤掉不可靠的关键点。3.3 第三阶段Bytetrack多目标跟踪集成我们需要将每一帧的检测结果bbox, score, keypoints输入给Bytetrack跟踪器并获取带有Track ID的结果。# 假设我们使用bytetrack官方仓库的跟踪器 from byte_tracker import BYTETracker import numpy as np class MultiObjectTracker: def __init__(self, track_thresh0.6, match_thresh0.8, frame_rate30): # Bytetrack参数跟踪阈值、匹配阈值、轨迹缓冲区长度等 self.tracker BYTETracker(track_threshtrack_thresh, match_threshmatch_thresh, frame_rateframe_rate) self.track_history {} # 用于存储每个ID的历史轨迹用于STGCN def update(self, detections, frame): detections: 来自PoseEstimator的检测结果列表 frame: 当前帧用于可视化 if not detections: self.tracker.update([], frame) # 传入空检测 return [] # 将detections格式转换为Bytetrack需要的格式 [x1, y1, x2, y2, score] dets_for_tracking [] kpts_for_tracking [] for det in detections: bbox det[bbox] score det[score] kpts det[keypoints] dets_for_tracking.append([bbox[0], bbox[1], bbox[2], bbox[3], score]) kpts_for_tracking.append(kpts) dets_for_tracking np.array(dets_for_tracking) # 调用Bytetrack更新 online_targets self.tracker.update(dets_for_tracking, [frame.shape[1], frame.shape[0]], (frame.shape[1], frame.shape[0])) online_results [] for t in online_targets: tlwh t.tlwh # top-left width-height track_id t.track_id # 找到该track_id对应的原始检测框索引通过IOU匹配 # 这里简化处理实际需要根据bbox匹配回对应的keypoints matched_idx self._match_bbox_to_detection(tlwh, detections) if matched_idx is not None: keypoints kpts_for_tracking[matched_idx] else: keypoints None online_results.append({ track_id: track_id, bbox: [int(tlwh[0]), int(tlwh[1]), int(tlwh[0]tlwh[2]), int(tlwh[1]tlwh[3])], keypoints: keypoints }) # 更新轨迹历史 if track_id not in self.track_history: self.track_history[track_id] [] self.track_history[track_id].append({keypoints: keypoints, frame_idx: current_frame_idx}) # 保持固定长度历史例如最近30帧 if len(self.track_history[track_id]) 30: self.track_history[track_id].pop(0) return online_results def _match_bbox_to_detection(self, track_bbox, detections): # 通过计算IOU将跟踪框匹配回最接近的检测框以获取关键点 # 实现略... pass踩坑实录Bytetrack返回的跟踪框tlwh格式是[x, y, width, height]而YOLO通常输出[x1, y1, x2, y2]注意转换。更重要的是跟踪器输出的框是平滑后的经过卡尔曼滤波而关键点来自原始检测框。直接使用跟踪框的中心点去关联关键点可能会引入误差。更精确的做法是保存检测时的原始框与关键点对应关系在跟踪匹配时一并传递。3.4 第四阶段STGCN行为识别推理当对一个目标Track ID积累了足够长度的姿态序列例如30帧约1秒后我们就可以将其送入STGCN模型进行行为分类。import torch.nn as nn # 假设我们有一个定义好的STGCN模型类 class STGCN(nn.Module): # ... 模型定义省略可参考开源实现如MS-G3D或官方STGCN代码 pass class BehaviorRecognizer: def __init__(self, model_path, num_classes2, sequence_length30, devicecuda:0): self.device torch.device(device) self.model STGCN(num_classesnum_classes).to(self.device) self.model.load_state_dict(torch.load(model_path, map_locationdevice)) self.model.eval() self.sequence_length sequence_length self.class_names [正常, 跌倒] # 示例 def build_spatio_temporal_graph(self, keypoints_sequence): 将关键点序列构建为时空图数据。 keypoints_sequence: list of arrays, 每个array形状为(17, 3)或(17, 2) 返回图数据 (node_features, adjacency_matrix, ...) # 1. 节点特征通常使用关键点的2D坐标(x,y)或3D坐标(x,y,visibility) # 2. 空间邻接矩阵根据人体骨骼连接定义是一个固定的17x17矩阵连接处为1否则为0。 # 3. 时间连接在时间维度上同一关节在相邻帧之间相连。 # 具体实现需参考STGCN的数据预处理代码。 pass def recognize(self, track_history): track_history: 某个Track ID的历史记录包含多帧的keypoints if len(track_history) self.sequence_length: return None # 数据不足不进行识别 # 取出最近sequence_length帧的关键点数据 recent_frames track_history[-self.sequence_length:] keypoints_seq [frame[keypoints] for frame in recent_frames] # 数据预处理对齐、归一化等非常重要 processed_seq self._preprocess_sequence(keypoints_seq) # 构建图数据 graph_data self.build_spatio_temporal_graph(processed_seq) # 推理 with torch.no_grad(): inputs self._prepare_input(graph_data) # 将数据转为tensor并送入设备 outputs self.model(inputs) probs torch.softmax(outputs, dim1) pred_class torch.argmax(probs, dim1).item() confidence probs[0][pred_class].item() return self.class_names[pred_class], confidence def _preprocess_sequence(self, keypoints_seq): # 关键步骤消除全局平移和尺度影响。 # 常用方法以髋关节或骨盆中心为原点对所有关键点坐标进行归一化。 # 也可以使用相对坐标相对于躯干中心。 processed [] for kpts in keypoints_seq: # 示例以第0号关键点鼻子或髋关节均值作为原点 # 这里需要根据你的关键点定义索引 hip_center (kpts[11] kpts[12]) / 2 # 假设11,12是左右髋 kpts_normalized kpts[:, :2] - hip_center[:2] # 只使用x,y # 可选除以一个尺度因子如躯干长度 processed.append(kpts_normalized) return np.array(processed)核心要点STGCN的性能极度依赖输入数据的质量。姿态序列的预处理是重中之重。必须消除因为人在画面中位置不同、距离摄像头远近不同带来的坐标平移和尺度变化。通常的做法是基于人体自身结构进行归一化如以髋关节为中心以躯干长度为尺度。不进行归一化模型学到的将是“在画面某个位置以某种大小跌倒”泛化能力会非常差。4. 工程化落地优化、部署与踩坑全记录把管道跑通只是第一步要让它成为一个稳定、可用的系统还有大量的工程优化工作要做。这部分才是真正体现经验价值的地方。4.1 性能优化让实时分析成为可能在单路1080p25fps视频流上三个模型串行运行对计算资源是巨大挑战。我们的优化策略是异步流水线和模型优化。异步流水线设计 我们使用Python的multiprocessing模块创建了三个进程进程A生产者专责视频解码和帧抓取将原始帧放入队列Queue_raw。进程B消费者-生产者从Queue_raw取帧进行姿态估计YOLOv7-POSE将带有关键点的检测结果放入队列Queue_det。进程C消费者从Queue_det取结果进行跟踪Bytetrack和行为识别STGCN并生成告警或可视化结果。这样三个计算密集型的阶段可以并行执行充分利用多核CPU和GPU。Queue的大小需要仔细设置太小容易阻塞太大会增加延迟。模型推理优化TensorRT加速将PyTorch训练好的YOLOv7-POSE和STGCN模型转换为TensorRT引擎可以获得显著的推理速度提升通常有1.5-2倍。这对于边缘部署至关重要。半精度FP16推理在支持Tensor Core的GPU上使用FP16精度几乎不影响精度但能大幅减少显存占用和提升速度。ONNX Runtime作为TensorRT的备选ONNX Runtime也提供了多后端加速支持部署兼容性更好。# 以YOLOv7-POSE转TensorRT为例简化流程 # 1. 导出ONNX import torch model attempt_load(yolov7-w6-pose.pt) dummy_input torch.randn(1, 3, 640, 640).to(device) torch.onnx.export(model, dummy_input, yolov7-pose.onnx, opset_version12, input_names[images], output_names[output], dynamic_axes{images: {0: batch}, output: {0: batch}}) # 2. 使用trtexecTensorRT命令行工具或Python API转换并优化 # trtexec --onnxyolov7-pose.onnx --saveEngineyolov7-pose.trt --fp16 --workspace40964.2 关键点滤波与轨迹平滑提升数据质量直接从YOLOv7-POSE输出的关键点存在抖动尤其是低可见度visibility的点。直接使用这些抖动数据会影响跟踪稳定性更会导致STGCN误判。解决方案基于可见度的滤波对于visibility低于阈值如0.3的关键点我们将其坐标置为NaN或上一帧的有效值并在后续处理中标记为缺失。卡尔曼滤波或低通滤波对每个关键点的x, y坐标分别应用一个简单的卡尔曼滤波器或一阶低通滤波器如指数加权移动平均。这能有效平滑轨迹消除高频抖动。对于跟踪轨迹Bytetrack内部的卡尔曼滤波主要针对边界框中心点。我们可以扩展状态向量将关键点坐标也纳入卡尔曼滤波进行平滑。这需要修改Bytetrack的跟踪逻辑但能带来更鲁棒的关键点序列。4.3 STGCN模型训练数据、技巧与评估数据准备 我们收集和标注了一个小型的“跌倒检测”数据集。包含多种场景房间、走廊、客厅、多种跌倒姿势前倒、后倒、侧倒、以及大量的负样本行走、坐下、弯腰、蹲下。关键点数据由YOLOv7-POSE在视频帧上自动生成并进行了人工检查和修正。训练技巧数据增强除了常规的图像增强对于姿态序列我们使用了时序上的增强随机裁剪序列长度、轻微的时间抖动、以及空间上的仿射变换针对所有关键点统一进行旋转、平移、缩放模拟不同视角。损失函数使用标准的交叉熵损失。对于类别不平衡正常行为远多于跌倒可以尝试Focal Loss或给跌倒类别更高的权重。学习率与优化器使用AdamW优化器配合余弦退火学习率调度器。验证策略在验证集上我们不仅看分类准确率更关注召回率Recall。在安防场景下漏报跌倒没识别出来比误报正常行为误判为跌倒后果更严重。因此我们需要在准确率和召回率之间找到一个业务可接受的平衡点。一个常见的坑模型在测试集上表现很好但部署到新场景下效果骤降。这往往是数据分布差异导致的。解决方案包括在新场景下收集少量数据进行微调Fine-tuning使用领域自适应Domain Adaptation技术或者在预处理中加强归一化减少场景依赖。4.4 系统集成与告警逻辑将以上所有模块集成到一个主循环中并设计合理的告警逻辑。def main_loop(video_source): stream VideoStream(video_source).start() pose_estimator PoseEstimator(weights/yolov7-w6-pose.trt, devicecuda:0) # 使用TensorRT引擎 tracker MultiObjectTracker() behavior_recognizer BehaviorRecognizer(weights/stgcn_fall_detection.pth) alarm_cooldown {} # 为每个Track ID设置告警冷却防止连续误报 while True: frame stream.read() if frame is None: break # 1. 姿态估计 detections pose_estimator.infer(frame) # 2. 多目标跟踪 tracked_objects tracker.update(detections, frame) # 3. 行为识别与告警 for obj in tracked_objects: track_id obj[track_id] # 获取该ID的历史轨迹 history tracker.track_history.get(track_id, []) if len(history) 30: # 积累足够帧数 # 识别行为 pred_label, confidence behavior_recognizer.recognize(history) if pred_label 跌倒 and confidence 0.8: # 置信度阈值 # 检查告警冷却 last_alarm_time alarm_cooldown.get(track_id, 0) current_time time.time() if current_time - last_alarm_time 10: # 10秒内不重复告警 # 触发告警记录日志、发出声音、推送消息等 print(f[ALARM] Track ID {track_id} 跌倒 detected! Conf: {confidence:.2f}) # 在画面上框出并标注 cv2.rectangle(frame, (obj[bbox][0], obj[bbox][1]), (obj[bbox][2], obj[bbox][3]), (0, 0, 255), 2) cv2.putText(frame, fFall: {confidence:.2f}, (obj[bbox][0], obj[bbox][1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) alarm_cooldown[track_id] current_time # 显示结果 cv2.imshow(Smart Monitoring, frame) if cv2.waitKey(1) 0xFF ord(q): break stream.stop() cv2.destroyAllWindows()告警去重与防误报冷却时间如上代码所示对同一个目标在短时间内只告警一次避免刷屏。持续时长判断真正的跌倒后人会在地上保持一段时间。可以要求“跌倒”状态持续至少N帧如10帧才最终触发告警过滤掉瞬间的类似姿势如快速蹲下。区域规则可以设置虚拟警戒区域只在特定区域如卫生间、床边内检测跌倒行为减少其他区域的误报。这套系统从技术选型到工程实现完整地走通了“视频流 - 姿态 - 跟踪 - 行为 - 告警”的链路。它不仅仅是几个算法的简单堆砌其中涉及的性能优化、数据预处理、集成逻辑和业务规则才是项目能否真正落地的关键。希望这份详细的拆解能帮助你少走弯路更快地构建出属于自己的智能监控应用。本文还有配套的精品资源点击获取
返回列表