ARTICLE DETAIL

资讯详情

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

YOLOv8姿势估计运动计数:关键点角度与Jetson部署实践

YOLOv8姿势估计运动计数:关键点角度与Jetson部署实践 简介面向NVIDIA Jetson平台的YOLOv8运动计数姿势估计演示项目适合嵌入式AI开发者与健身科技爱好者用于在边缘设备上自动识别深蹲、俯卧撑、仰卧起坐等动作并统计次数。下载页标签虽标注为java但实际为Python实现基于YOLOv8-Pose模型检测人体17个关键点通过计算连线夹角判断动作是否达标。ZIP压缩包共15个文件包括5个Python脚本、3个CSV数据文件、预训练模型权重、JSON配置及说明文档等整体约237KB目录结构清晰。目前已有80人浏览学习适合需要部署轻量级视觉计数应用或研究姿态估计算法的人员。其中demo.py可直接运行推理Inference.py与get_data_from_video.py支持数据提取和训练可在此基础上扩展更多运动类型是YOLOv8-Pose在Jetson上落地的实用参考。1. 先别急着搭环境YOLOv8 运动计数 姿势估计 demo 到底能做什么很多人看到 YOLOv8 运动计数 姿势估计 这个 demo第一反应是先在自己电脑上装一个完整的 YOLOv8 环境结果卡在 PyTorch 和 CUDA 版本组合上大半天。我在 Jetson 上复现这套资源之后的理解是最好先用项目里现成的 best_model.pt 把一条完整链路跑通再回头谈训练和调参。这套 demo 做的事情其实很具体——用 YOLOv8-Pose 检测人体 17 个关键点根据深蹲、俯卧撑、仰卧起坐动作中关节夹角的变化完成计数reComputer Jetson J4011 上实测可用其他 NVIDIA Jetson 设备也能迁移。适合做健身动作计数、AI 私教原型、边缘推理盒子演示的从业者也适合拿 YOLOv8 做毕业设计但不想从零开始写状态机的学生。下面我会从判定原理一路讲到 Jetson 部署命令再把数据整理、训练参数和踩坑记录都放出来。2. 运动计数是怎么算出来的从 17 个关键点、关节夹角到计数状态机2.1 YOLOv8-Pose 不是简单画骨骼输出里其实有 17 个关键点YOLOv8-Pose 是 YOLOv8 检测头之外再加一个关键点回归分支输入一帧画面后输出每个检测人的类别、边界框和关键点坐标。COCO 姿态定义里是 17 个关键点分别是鼻子、眼睛、耳朵、肩膀、手肘、手腕、髋部、膝盖和脚踝索引从 0 到 16。调用pred model(frame)之后pred[0].keypoints.data的形状是 (num_person, 17, 3)最后一维前两个值是归一化的 x、y第三个值是置信度。这里有个容易忽略的点默认输出的是归一化坐标范围在 0~1 之间是否要放大到像素尺寸取决于你后面怎么算角度。由于角度只和三条边的相对长度有关归一化坐标之间算余弦并不会影响角度但如果你要判断关键点是否在画面外就必须先乘上图像的宽和高。常见的关键点索引对应关系如下索引关键点索引关键点索引关键点0鼻子6右腕12右髋1左眼7左髋13左膝2右眼8右髋14右膝3左耳9左膝15左踝4右耳10右膝16右踝5左肩11左踝--我用这个索引表做开发的频率很高几乎每个动作都要回查一遍。比如深蹲看左腿的 11、13、15 三个点俯卧撑看左臂的 5、7、9 三个点仰卧起坐看躯干的 5、11、13 三个点。实际项目里应该把这份索引表放进idx_2_category.json旁边或者直接写成一个常量数组而不是每次都打开文档数。import torch from ultralytics import YOLO model YOLO(best_model.pt) # 或者换成 yolov8n-pose.pt results model(pushup_frame.jpg, verboseFalse)[0] boxes results.boxes.xyxy.cpu().numpy() keypoints results.keypoints.data.cpu().numpy() # (N, 17, 3) for person_id, kpts in enumerate(keypoints): nose_conf kpts[0, 2] left_hip kpts[11, :2] left_knee kpts[13, :2] left_ankle kpts[15, :2] print(person_id, nose_conf, left_hip, left_knee, left_ankle)逻辑说明这段代码先把 keypoints 从显存拷回 CPU再按 COCO 索引取出左腿三个关键点。results.keypoints.data是 YOLOv8 相对稳定的输出格式但不同小版本之间字段略有差异有的版本把置信度放在results.keypoints.conf而不是第三列所以打印一下维度最保险。参数说明verboseFalse可以关掉每次推理的日志输出boxes.xyxy是四列的像素坐标检测框keypoints.data是归一化坐标。如果要画骨骼可以使用results.plot()但要在画布上标注角度还是得自己取点并用 matplotlib 或 OpenCV 画线。很多人在这步踩坑是因为results.keypoints是 Keypoints 对象不是 Tensor直接.cpu()会报错建议先打印类型再决定怎么转。2.2 深蹲、俯卧撑、仰卧起坐的夹角判定逻辑先定关节再定阈值README 的核心思想是选有判别力的关键点计算关键点连线之间的夹角当夹角达到一定阈值就认为完成了某个动作。以深蹲为例判别力最强的是大腿和小腿之间的夹角也就是髋、膝、踝三点构成的膝角人体直立时这个角接近 180°蹲到底时会小于 90°。用统一的向量夹角公式可以覆盖所有动作import numpy as np def calc_angle(a, b, c): # a, b, c 是三个关键点的像素坐标b 是角点 ba a - b bc c - b cosine np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) 1e-6) return np.degrees(np.arccos(np.clip(cosine, -1.0, 1.0))) # 深蹲左髋11左膝13左踝15 hip, knee, ankle kpts[11, :2], kpts[13, :2], kpts[15, :2] squat_angle calc_angle(hip, knee, ankle) print(fsquat angle: {squat_angle:.1f})参数说明calc_angle的输入是三个点b是角点也就是关节所在位置加1e-6是为了除零保护坐标相同或关键点重叠时避免 NaNnp.clip把余弦值夹到 [-1, 1]因为浮点误差可能算出 1.0000001 导致arccos返回 NaN。这个函数在 demo_pro.py 里是最核心的底层函数几乎所有动作计数都建立在它上面。三种动作的关节组合和阈值可以直接映射成一张表我建议在做开发时把它做成配置文件而不是写死在代码里动作判别夹角关键点组合触发阈值示例复位阈值示例深蹲膝角髋-膝-踝膝角 90°膝角 150°俯卧撑肘角肩-肘-腕肘角 80°肘角 140°仰卧起坐髋角肩-髋-膝髋角 60°髋角 120°注意这些阈值不是死的它会受摄像头安装高度、人物距离和动作幅度影响。更稳的做法是把“下压角 阈值”和“回位角 阈值”成对保存即下文会讲到的状态机。只用一个角度超过阈值就计数通常会导致俯卧撑压到一半就重复计数这个现象我在实际部署时遇到至少三次。还有一个常被忽略的点用单帧算角度会有抖动尤其是手肘和膝盖这种小关节COCO 关键点本身存在几个像素的噪声换算成角度后可能抖动 5° 到 10°。我一般会在角度序列上做一个长度为 5 的滑动平均from collections import deque angle_buffer deque(maxlen5) def smooth_angle(angle): angle_buffer.append(angle) return sum(angle_buffer) / len(angle_buffer)逻辑说明deque(maxlen5)能自动丢弃最老的帧队列里始终保留最近五个角度值。每算完一帧就把原始角度进去取平均后作为当前帧角度。这样既能滤掉关键点抖动又不会带来太明显的延迟。代价是快速起落动作的响应会慢几帧如果你对实时性要求极高可以把窗口缩到 3。2.3 为什么选 Jetson 而不是普通 PC 或树莓派JetPack、TensorRT 和显存边界这个 demo 在 NVIDIA Jetson 上跑有硬件理由不是恰巧用 Jetson。Jetson 自带 JetPack 系统镜像JetPack 里已经整合了 CUDA、cuDNN 和 TensorRTYOLOv8 导出成 TensorRT engine 后在边缘设备上跑到实时的可能性比树莓派高得多。普通 PC 当然能跑但要扛住摄像头实时推理就得看显卡树莓派则连 YOLOv8-Pose 的 CPU 推理都很难跟上实时帧率。reComputer Jetson J4011 属于 Orin NX 模块的整机方案显存带宽和算力都不错跑 YOLOv8s-pose 级别的模型做单路视频计数压力不大。Jetson 和普通 Linux 主机的部署差异主要体现在三处系统镜像、PyTorch 来源和推理引擎。桌面 Ubuntu 上直接pip install torch通常会装上 x86_64 的 CUDA 版但 Jetson 是 ARM64 架构必须用 JetPack 配套的 wheel 包。很多关于 ubuntu20.04 搭建 yolov8 环境的教程到了 Jetson 上就失效原因就是把桌面版 CUDA 的安装方式照搬了过来。我一般建议用下表快速判断对比项Jetson普通 PC树莓派x86 的 torch wheel不适用适用不适用JetPack 自带 CUDA支持无无TensorRT 加速原生支持需要额外编译不支持实时 YOLOv8-Pose可跑到 20~30 FPS看显卡很难实时到这里原理部分已经能解释“为什么这类 demo 适合做成 Jetson 应用”。接下来的重点是把你手上的设备跑通所以我直接进入部署步骤。3. 在 Jetson 上把 demo 完整跑起来环境、数据整理、训练四步走3.1 先从 JetPack 装起系统镜像、pip 和基础依赖资源里的安装说明建议第一步把 JetPack 刷入 Jetson 设备。Jetson 刷机方式通常有两种用 NVIDIA SDK Manager 在主机上刷或者用厂商提供的镜像直接烧录到 eMMC/SD 卡。reComputer 整机一般出厂已烧好 JetPack但建议你先确认系统版本至少看到 CUDA 编译器再往下走nvcc --version sudo apt update sudo apt install -y python3-pip pip3 install --upgrade pip pip3 install ultralytics注意sudo apt update可能因为镜像源问题变慢Jetson 上不建议一上来就换源除非厂商镜像里的源已经失效。安装 ultralytics 时会自动拉起 numpy、opencv-python、torch 等依赖如果你已经装了 JetPack 自带的 torch就不要让 pip 随便覆盖先执行pip3 list | grep torch查看当前版本。安装完成后最快速的验证不是跑摄像头而是先跑一张图确认模型能够加载。把项目文件解压到~/pose_counter然后运行cd ~/pose_counter python3 Inference.py --model best_model.pt --source data/squat/test.jpg如果缺少--source参数就打开 Inference.py 看 argparse 的默认值。这类工程代码里常见的参数是--model、--source、--device和--conf--device在 Jetson 上写0表示第一张 CUDA 显卡写cpu则强制 CPU 推理通常没必要。资源里没有对 GPU 类型做特殊打包所以只要 torch 能调用 CUDA这一步就能直接看到一张画了骨骼点和角度的输出图。3.2 理清推理脚本demo.py、demo_pro.py 与 Inference.py 的分工项目里同时出现 demo.py、demo_pro.py 和 Inference.py很多人会困惑该跑哪个。按常见工程组织习惯Inference.py 是把模型加载、前处理、关键点后处理封装成类的推理模块demo.py 是调用这个模块把单张图或单段视频展示出来的最小示例demo_pro.py 则带有完整的动作计数和可视化逻辑。你完全可以只看 demo.py 理解加载流程但要改计次逻辑就改 demo_pro.py。# demo_pro.py 里常见的一段计数核心逻辑 from Inference import PoseCounter import cv2 counter PoseCounter(best_model.pt, device0) cap cv2.VideoCapture(0) # 0 是 Jetson 的 USB 摄像头 angle_threshold 90 # 膝角或肘角的下限 rep_state up # 初始状态 while cap.isOpened(): ok, frame cap.read() if not ok: break rep_state, count counter.update(frame, rep_state, angle_threshold) cv2.putText(frame, frep: {count}, (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2) cv2.imshow(pose counter, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明PoseCounter.update内部先跑model(frame)得到关键点再算动作对应的关节角最后根据前后两帧角度判断是否完成一次动作。参数rep_stateup表示当前动作处于回位状态只有从 up 进入 down 再回到 up 才会计数这个设计就是避免同一视角里角度抖动被误判成多次动作。angle_threshold是触发角度不同动作应该动态传入而不是在类内部写死成 90。参数说明cv2.VideoCapture(0)使用默认 USB 摄像头Jetson 的 CSI 摄像头通常不能直接这么用需要走 gstreamer 管道或者使用 Jetson 官方库。如果你手上的摄像头是 CSI 口的请先把 demo_pro.py 里的视频源换成 gstreamer 字符串否则打开会黑屏。3.3 data 目录和 get_data_from_video.py训练数据不是凭空来的资源里 data 目录下设 squat、situp、pushup 三个子目录生产级数据组织一般是这样的每个子目录放该动作的视频片段或者图片帧另外可能有一个 txt 或 json 记录每一帧的标注框和关键点。如果你只想测试计数直接用项目里已经剪切好的视频如果你想重新训练最省事的方式是把手机拍的视频切成帧python get_data_from_video.py --video data/squat/01.mp4 \ --out data/squat/images --fps 5常见做法是 get_data_from_video.py 里用 OpenCV 按固定间隔抽帧避免每帧都写入导致数据集全部是重复动作。抽帧后要自己用标注工具标注每个人的框和 17 个关键点再转成 YOLO-pose 的 txt 格式如果没有现成标注YOLOv8 官方也支持从 COCO 格式转换但原项目给的是从视频抽帧这一部分后续标注仍需人工完成。抽帧参数--fps 5表示每秒只取 5 帧比 30fps 全取能减少约六分之五的重复样本训练时也更快。YOLO-pose 的标注格式和检测任务类似只是每个点的坐标紧跟在框后面0 0.5 0.5 0.3 0.4 0.52 0.48 1 0.53 0.50 1 ... 0.48 0.60 1这一行的含义是第一列是类别第二到第五列是x_center y_center width height从第六列开始每三个数是一组关键点的 x、y、可见性。可见性用 0 表示未标注1 表示可见2 表示被遮挡但在框内。检查数据时最容易漏的是关键点数量17 个点意味着框后面必须有 51 个数如果漏了一个点训练会直接报 kpt shape 错误。3.4 用 train.py 训练自己的姿势估计模型参数 freeze、epochs 和数据划分训练脚本通常在 train.py 内部调用 ultralytics 的 YOLO 接口你需要改动的不是算法细节而是数据配置和训练参数。常见做法是在 data 同级准备一个 pose_dataset.yaml内容如下path: /home/user/pose_counter/data train: images/train val: images/val kpt_shape: [17, 3] names: 0: person然后运行python train.py --model yolov8n-pose.pt --data pose_dataset.yaml \ --epochs 100 --batch 32 --imgsz 640 --device 0 --freeze 10参数说明--model yolov8n-pose.pt是 COCO 预训练骨架用它做迁移学习可以少标样本--freeze 10表示冻结前 10 层网络权重适合数据量不大时防止过拟合--imgsz 640是训练分辨率Jetson 上如果显存不够可以降到 480。训练完成后会在runs/pose/train/exp/weights/best_model.pt输出模型把它覆盖到项目根目录的 best_model.pt再跑一次 demo.py 就能看到新动作的推断结果。这里特别提醒train.py 不一定把 yaml 路径写在命令行有的项目把数据路径写死在脚本里。建议打开 train.py 先看是不是model.train(datadata.yaml, ...)如果是就去改 yaml 的path字段改错了最常见的报错是Dataset ... not found或者 keypoint shape 对不上。另一点是 Jetson 上跑训练很慢如果是几百张图的小数据可以接受若是几千张图我一般会在 x86 显卡服务器上训练完再把 best_model.pt 拷到 Jetson这个 demo 并没有硬性要求必须在 Jetson 上训练。4. 部署避坑与常见问题先排查模型加载、NaN 和重复计数4.1 模型权重加载失败或 torch 版本冲突现象运行model YOLO(best_model.pt)时抛RuntimeError: PytorchStreamReader ... corrupted或直接段错误换成 yolov8n-pose.pt 又能跑。原因best_model.pt 是用某个 PyTorch 版本训练保存的当前 Jetson 上的 torch 版本不支持旧格式或者权重文件本身没拷贝完整SD 卡传输中断导致文件头损坏。解决先执行python3 -c import torch; print(torch.__version__)确认版本和训练环境尽量保持一致。如果是拷贝损坏重新传一次并比对 md5如果确认版本不一致就按 JetPack 版本找对应的 torch 和 torchvision wheel 包重装。这个“先查 torch 版本再谈模型”的习惯我吃过两次亏每次都能少走很多弯路。如果你的设备上还有别的项目建议用 venv 隔离环境避免覆盖系统 torch。4.2 关键点坐标突然变成 NaN 或者角度乱跳现象画面里只有一个人时计数正常两个人交叉走过时角度值偶尔变成 nan或者同一姿势下深蹲计数在 20 和 21 之间反复横跳。原因目标重叠导致某个人的关键点置信度很低被过滤之后剩下的坐标数量不够也可能calc_angle里两个向量模长为 0除零保护失效。解决在算角度前用keypoints[i, idx, 2] conf_thres过滤低置信度点无法满足三个点都有效时直接跳过这一帧。角度计算里除了1e-6还应该对坐标做合理性检查比如 x、y 必须在图像范围内。把 conf_thres 设置到 0.3~0.5 可以解决大部分乱跳但它也会降低动作捕捉的灵敏度需要按场景折中。更复杂的情况是多目标时应该选出画面中置信度最高或框最大的人来计数否则旁边有人路过关键点直接跳到路人身上。4.3 一个动作被连续记录两次或者漏计现象俯卧撑做一次却显示两次或者做完了计数不动等下一次动作快结束才突然加一。原因常见原因是状态机没有从 down 回到 up 的复位条件。如果只在“夹角 阈值”时计数那么身体停留在最低点的几帧都被当成新的一次动作漏计则是因为要求回到 up 的角度太高测试者没有完全伸直手臂就进行了下一次。解决把角度判断改成三段状态up - going_down - down - going_up - up每完成一次闭环才 count 1。触发阈值和复位阈值分开比如肘角低于 80° 判定 down高于 140° 判定 up两个阈值之间的范围是滞回区间能吸收轻微抖动。阈值不是对称的要故意留出 20° 到 30° 的死区。用下面的状态判断逻辑替换简单阈值判断state up count 0 for angle in angle_sequence: if state up and angle down_threshold: state down elif state down and angle up_threshold: state up count 1逻辑说明state只做 down 和 up 两个状态但触发和复位分别用两个阈值只有当状态从 up 进入 down再回到 up完整闭环才算一次。这样做以后角度在阈值附近抖动时不会反复计数。4.4 USB 摄像头打不开或者画面延迟严重现象cap cv2.VideoCapture(0)返回 True 但read()一直返回 False或者打开后画面明显滞后推理帧率只剩个位数。原因Jetson 的 USB 摄像头在插拔后设备号变化或者摄像头默认请求了 Jetson 不支持的像素格式延迟严重则是 OpenCV 默认会在缓冲区排队旧帧读出来的是半秒前的画面。解决先用ls /dev/video*确认设备节点再在代码里设置cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M, J, P, G)) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)一般推荐 MJPG 格式和 640x480 分辨率。只处理当前帧可以循环读几次再推理或者用cap.grab()丢弃过期帧实战中画面延迟问题多数不是推理慢而是缓冲队列没清理。如果你是在 Jetson 上用 CSI 摄像头需要走 gstreamer 管道设置方式和 USB 摄像头不同建议优先用 USB 摄像头跑通整个流程。5. 让它更像一个能上场的计数器状态机维护、新动作扩展和导出小技巧开发到这一步很多人会想给它加第四个动作比如引体向上。加动作的先决条件是确认 YOLOv8-Pose 的 17 个关键点里能提取出稳定夹角引体向上可以用肘角区分上拉和放下然后把你采集好的视频加入 data 目录新子文件夹按相同流程抽帧、标注、训练、导出再在 demo_pro.py 的动作表里加一行阈值。动作表最好做成 JSON不要散落在代码里{ squat: {point: [11, 13, 15], down_angle: 90, up_angle: 150}, pushup: {point: [5, 7, 9], down_angle: 80, up_angle: 140}, situp: {point: [5, 11, 13], down_angle: 60, up_angle: 120} }在 Inference.py 中读入这个 JSON 后不用改主循环就能动态配置动作。新动作上线前建议拿一段手工数过次数的视频做离线验证用标注工具或人工计数对照输出结果而不是直接上摄像头否则你很难判断是阈值问题还是标注问题。性能优化方面Jetson 上比起改模型结构更有效的是把 PyTorch 模型导出成 TensorRT engineyolo export modelbest_model.pt formatengine device0 halfTrue导出后的 engine 文件配合YOLO(best_model.pt.engine)可以在 Orin NX 设备上明显降低推理延迟代价是部署环境不能随意换。另一个性价比更高的技巧是在 model() 调用里传conf0.4, iou0.5YOLOv8-Pose 有时会把背景中的人形误检出来过滤掉低置信度目标后计数会稳定很多。从我在 Jetson 上复现这个资源的结果看它最大的价值不是把标准库跑通而是给了一套从关键点到计数状态机的完整闭环。刚拿到资源时我也想着一步到位改出五六个动作结果卡在阈值调参上浪费了一个晚上。从那以后我每次换设备部署都强制自己先跑到“能用原 demo 计一次标准动作”的阶段确认摄像头、权重和动作配置三者对齐然后再谈加需求和调阈值。希望帮到你。本文还有配套的精品资源点击获取
返回列表