简介:基于YOLO的射箭姿态分析项目资源,面向计算机视觉与深度学习方向的毕业设计学生,也适合对体育动作识别与目标检测感兴趣的开发者。项目将YOLO算法引入射箭场景,通过划分网格、预测边界框与类别概率,对箭、弓、手、臂等关键部位完成单次前向检测,为姿势标准性评估提供量化分析基础。压缩包共21个文件,以12个Python脚本为主体,覆盖主执行程序、核心检测逻辑、界面展示、设备管理、动作状态定义等模块;另含两张预览图、依赖配置文件、使用说明及数据目录,整体仅412KB,目录层次清晰,便于按需学习和二次开发。目前已有49人学习下载,适合作为毕业设计的代码骨架,也能用于拓展实时视频流姿态分析。读者可通过源码掌握YOLO的工程化部署方式、Python项目模块拆分思路,以及从数据组织到模型调用的基础流程,对快速搭建同类视觉识别项目有直接参考价值。
1. 从“射箭姿态”到“YOLO能干什么”的认知矫正
先说一个多数人第一次拿到这个标题时都会犯的错:以为YOLO负责“姿态估计”。实际不是。YOLO是目标检测器,它输出的是“画面里有没有人、人在哪个框里、框住的是谁”。而射箭姿态分析需要的“关节角度、躯干倾角、持弓臂是否伸直”,属于姿态估计(pose estimation)的范畴。所以这个项目的核心链路,是用YOLO把射手从复杂背景里干净地挖出来,再交给姿态估计模型或几何计算去做量化分析。YOLO在这里是“前置定位器”,不是终局模型。理解了这层关系,后面的数据标注、模型选型和推理加速才不走弯路。对5年以上的工程老手,这篇文章的价值在于:把YOLO检测框和姿态关键点做坐标对齐时的坑、训练数据标注的冗余度设计、以及在NVIDIA Jetson这类边缘设备上的部署参数,一次性讲透。
2. 射箭姿态分析的技术拆解与模型选型
2.1 射箭姿态里到底要分析哪些量
先盘一下射箭姿态分析的目标输出。射箭动作以“举弓—开弓—靠位—瞄准—撒放”为完整时序,其中可量化的姿态指标包括:
- 持弓臂与地面的夹角:反映举弓是否到位,一般在 170~180 度之间为佳,角度过小说明耸肩或沉肘。
- 拉弓臂肘关节角度:开弓后拉弓臂肘部应接近直线,角度应在 160~180 度,若小于 150 度说明用肩背代偿。
- 头部偏转角:瞄准时头应自然转向靶面,偏转角过大说明颈部代偿。
- 躯干前倾角:站姿射箭时躯干应基本垂直,前倾超过 5 度会影响一致性。
- 两肩连线与靶面方向的夹角:用于判断“直线力”是否建立。
这些量无法由 YOLO 的检测框直接给出。因此,标准的工程方案是"YOLO 做人的检测与裁剪,姿态估计模型(如 MMPose 里的 HRNet、RTMPose)在裁剪区域上提取 COCO 17 点或自定义的 14 点骨骼关键点,再用向量夹角的几何公式算出上述角度"。
2.2 为什么用 COCO 17 点而不是自定义关键点
COCO 17 点包含左右眼、耳朵、肩膀、手肘、手腕、髋、膝、踝等关节,但没有持弓手与弓身的接触点,也没有弓的端点。在射箭场景里,我们需要额外补充的关键点包括:
- 弓把中心点(持弓手与弓的接触处附近)
- 弓臂上端
- 弓弦与拉弓手的手指接触点
- 箭尾(搭箭点)
所以常见做法是:YOLO 只负责定位人框,姿态模型用 COCO 17 点估计人体骨架,弓相关的点用传统视觉方法(如颜色阈值分割或霍夫变换检测弓臂)单独定位。纯用关键点检测模型直接回归弓点,训练数据要单独标一套,成本高,且弓的尺寸在不同视频里差异大。我在实际项目中,通常会在 YOLO 检测框内,结合肤色模型找持弓手,再根据持弓手位置沿图像梯度方向连线搜索弓臂边缘,这样能稳定拿到弓的上下两个端点。
2.3 YOLO 版本与姿态分析场景的匹配
标题里只写了 YOLO,没说版本。热词里已经出现“yolo 8”“yolo 11”“yolo目前到几了”等检索,说明版本选型是读者真实困惑。2025 年时间点来看,YOLOv8 和 YOLO11 是两个常用选项。两者官方仓库都支持检测(detect)任务,且都能导出为 ONNX 和 TensorRT 格式。在射箭场景中建议按运行环境选:
| 环境 | 推荐版本 | 理由 |
|---|---|---|
| NVIDIA GPU 服务器 | YOLO11n / YOLO11s | 精度优于同规模 v8,训练更快 |
| Jetson Orin / Xavier 边缘设备 | YOLOv8n | TensorRT 生态更成熟,量化后延迟稳定 |
| CPU 推理 | YOLOv8n INT8 | 单帧耗时控制在 80ms 内需配合 OpenVINO |
实际选型还要考虑模型训练数据量。如果自建射箭数据集只有几千张,YOLOv8n 会比 YOLO11 更稳,因为 v8 的泛化更保守,不易过拟合。如果数据超过 2 万张,YOLO11s 的精度收益就开始明显。另一个经验是,射箭场景的背景通常比较固定(训练场、靶场),小模型足够用,不必上 YOLO L 或 X。
# 用 ultralytics 加载 YOLO11s,输出检测框和置信度 from ultralytics import YOLO model = YOLO("yolo11s.pt") results = model.predict("archery_pose_001.jpg", conf=0.35, iou=0.5, classes=[0]) boxes = results[0].boxes print("检测框数量:", len(boxes)) for box in boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() conf = box.conf[0].item() cls = int(box.cls[0].item()) print(f"person at ({x1:.1f}, {y1:.1f}, {x2:.1f}, {y2:.1f}), conf={conf:.3f}, cls={cls}")这里conf=0.35是置信度阈值,射箭场景中射手通常全身入镜,阈值可在 0.3~0.5 之间调。classes=[0]表示只保留类别 0(person),避免把弓、箭袋等误检物品输出。iou=0.5是 NMS 的 IoU 阈值,射手单人场景下保持默认即可,多人同时训练时建议降到 0.4,减少重叠框的误合并。
3. 射箭数据集标注与 YOLO 预处理的三个关键动作
3.1 图像采集与裁剪策略
YOLO 训练数据不需要整张原图都标。推荐先用一个预训练的 YOLO 模型跑一遍所有视频帧,把置信度高于 0.5 的 person 框裁剪出来,再对这些裁剪图做人工复核并删除误检帧。这样标注工作量约减少 40%,因为射箭背景里没有多余人物时,检测框基本都准确。
裁剪尺寸统一为 640x640。YOLO 要求输入固定尺寸,而原视频可能是 1920x1080 或 1280x720。直接拉伸会改变人体长宽比,从而影响关键点回归精度。正确做法是等比缩放后做 padding,例如 1080p 图像缩放到 640x360,再上下各贴 140 像素的黑边,变成 640x640。在 ultralytics 中这个动作由letterbox自动完成,但如果你自己写数据管线,要确保 label 里归一化的坐标值是基于 padding 前的图像尺寸计算的,否则标签会偏移。
3.2 YOLO 格式标注与 KITTI 转 YOLO 的坐标换算
YOLOv8/v11 的标注格式是class x_center y_center width height,坐标是归一化的。射箭数据集标注两类即可:0代表人,1代表弓(如果单独检测弓)。我自己常用 CVAT 标注,导出格式直接选 YOLO 1.1。如果接手的是 KITTI 格式的数据,需要做转换。
# KITTI label: class truncation occlusion alpha bbox_left bbox_top bbox_right bbox_bottom height width length x y z yaw def kitti_to_yolo(label_line, img_w, img_h): parts = label_line.strip().split() cls_name = parts[0] x1, y1, x2, y2 = map(float, parts[4:8]) if x1 >= x2 or y1 >= y2: return None x_center = ((x1 + x2) / 2.0) / img_w y_center = ((y1 + y2) / 2.0) / img_h box_w = (x2 - x1) / img_w box_h = (y2 - y1) / img_h return f"0 {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n"转换时要注意 KITTI 的 bbox 是像素坐标,不归一化,而 YOLO 要求归一化到 0~1。另外 KITTI 里一个目标可能出现多行(行人、自行车等),射箭里只保留 person 与 bow 两类的行,其余丢弃。如果出现x2 < x1或y2 < y1的负框,一定不要自动修复为绝对值,而是直接丢帧,否则模型会学到错误边界。
3.3 数据增强避免“过拟合到靶场背景”
射箭训练视频往往来自同一个场地,背景高度相似。如果直接把原始帧全量训练,模型会对地面纹理、远处靶面造成过拟合,换一个场馆后检测框漂移。增强策略上,我对射箭数据推荐这样设:
hsv_h=0.015:色相偏移幅度要小,弓把和靶心颜色不能剧烈变化。hsv_s=0.7和hsv_v=0.4:饱和度与明度可以稍大,模拟不同日照条件。translate=0.1:只允许 10% 的平移,太多会让射手身体部分出框。scale=0.4:尺度扰动 40% 有点激进,我把这个值调回 0.25,保证检测框内射手长度变化不超过四分之一。fliplr=0.5:水平翻转要慎用。射箭分左手持弓与右手持弓,翻转后弓从右边变左边,如果训练集里左利手样本不足,翻转反而增加类别混淆。若目标模型分析的是侧面姿态,建议fliplr=0,只在正面视角使用翻转。
from ultralytics import YOLO model = YOLO("yolo11s.pt") model.train( data="archery.yaml", epochs=150, imgsz=640, batch=16, lr0=0.005, augment=True, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, translate=0.1, scale=0.25, fliplr=0, mosaic=0.5 )mosaic=0.5表示一半的 epoch 使用 Mosaic 拼接。拼图会把不同场地的背景混在一起,有助于泛化,但弓的形状在拼接处会被切断时,检测器容易把弓当噪声,所以前 50 个 epoch 建议mosaic=0.0,等模型稳定后开 0.5 微调。
4. 从 YOLO 检测框到姿态关节角度计算
4.1 用 RTMPose 提取关键点的最小流程
YOLO 完成检测后,我们取检出的 person 框,按一定比例扩大 10%,再送入 RTMPose 模型。扩框是为了防止目标检测框边缘贴着四肢,导致姿态估计时关节被截断。RTMPose 在 COCO 17 点上性能不错,而且开源权重可以直接用。最小推理流程:
# 安装 extra 依赖 pip install mmcv==2.1.0 mmpose==1.3.0 mmdet==3.2.0from mmpose.apis import inference_topdown, init_model from mmpose.structures import merge_results from ultralytics import YOLO det_model = YOLO("yolo11s.pt") pose_model = init_model("rtmpose-m_8xb64-270e_coco-256x192.py", "rtmpose_m.pth", device="cuda:0") results = det_model.predict("archer.jpg", conf=0.4) img = cv2.imread("archer.jpg") for box in results[0].boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) w, h = x2 - x1, y2 - y1 x1 = max(0, int(x1 - 0.05 * w)) y1 = max(0, int(y1 - 0.05 * h)) x2 = min(img.shape[1], int(x2 + 0.05 * w)) y2 = min(img.shape[0], int(y2 + 0.05 * h)) pose_results = inference_topdown(pose_model, img, bboxes=[x1, y1, x2, y2]) keypoints = pose_results[0].pred_instances.keypoints[0].cpu().numpy()这里缩进逻辑与 YOLO 检测框对齐是易错点。inference_topdown输入的bboxes必须是原始图像坐标,不能传入被 letterbox 处理后的坐标。如果你的 YOLO 推理是经过预处理的副本图像,记得把检测框坐标映射回原图,否则姿态关键点会整体偏移。
4.2 关节角度计算:向量夹角与轴向校正
得到 17 个关键点后,需要计算射箭姿态角度。以“右手拉弓”为例,计算拉弓臂肘关节角度需要右侧肩、肘、腕三点:
import numpy as np def calc_angle(a, b, c): ba = a - b bc = c - b cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) + 1e-6) angle = np.degrees(np.arccos(np.clip(cos_angle, -1.0, 1.0))) return round(angle, 2) # keypoints 为 17x2 数组,索引 5 为左肩,6 为右肩,7 为左手肘,8 为右手肘,9 为左手腕,10 为右手腕 right_elbow = calc_angle(keypoints[6], keypoints[8], keypoints[10])这个过程最典型坑是np.arccos数值稳定性。三点共线时 cos 可能恰好等于 1.0000001,导致nan。所以必须np.clip。另外,肩、肘、腕的点坐标受画幅透视影响,正面拍摄的俯仰角大于 15 度时,算出的角度误差会很大。工程上我一般会在相机标定后做一次透视校正,但这需要已知相机内外参。对于大多数训练场监控摄像头,不建议做透视校正,归一化角度后用“同一射手前后对比相对值”衡量即可。
4.3 YOLO 后处理与坐标对齐的联动逻辑
YOLO 输出的检测框默认映射回原图尺寸后,还要考虑一个细节:ultralytics的predict返回的boxes.xyxy已经还原到原图坐标,可以直接给姿态模型用。但如果你用了自定义的推理脚本,仔细检查是否将 letterbox 的偏移量(pad_x, pad_y)和缩放比例ratio加回去:
box = [x1, y1, x2, y2] x1 = (x1 - pad_x) / ratio y1 = (y1 - pad_y) / ratio x2 = (x2 - pad_x) / ratio y2 = (y2 - pad_y) / ratio漏掉任何一步,关键点与弓箭位置就会错位 10 像素以上。在射箭场景中,弓弦在图像里只有 1~2 像素宽,错位会直接导致“撒放瞬间”的分析彻底不可用。
5. rk3588 等边缘设备部署 YOLO 姿态分析系统的参数优化
5.1 部署管线与算力分配
整套系统的实时性瓶颈不在 YOLO 检测,而在姿态估计。以 640x640 输入为例,YOLO11n 在 NVIDIA Jetson Orin Nano 上通过 TensorRT FP16 推理约 8ms,RTMPose-m 则需 15ms,两者加起来在 30ms 以下,能勉强跑 30 FPS。但如果用 rk3588 的 NPU,事情变得复杂:rk3588 对 YOLO 目标检测支持较好(RKNN 模型格式),对姿态估计模型的支持并不完善。我一般会这样分配:
- YOLO 检测:rk3588 NPU 上跑,转换为 RKNN 后精度损失在 1% 以内。
- RTMPose:CPU 上跑,启用 RKNN-Toolkit2 的量化后推理。若性能不够,退回 Rock-X 的轻量姿态估计模型。
在 jetson 上使用 tensorrt 时,YOLO 端注意关闭agnostic_nms,多人场景下不同类别的框不应混在一起做 NMS。RKNN 导出时,nms_threshold设置 0.45,post_process选 NMS 或不带 NMS,取决于你打算用 NPU 硬加速还是 CPU 后处理。
5.2 YOLO→RKNN 转换与后处理细节
# 将 YOLO11s 导出为 ONNX yolo export model=yolo11s.pt format=onnx opset=12# 使用 rknn-toolkit2 转换(简化) from rknn.api import RKNN rknn = RKNN() rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rk3588") rknn.load_onnx(model="yolo11s.onnx") rknn.build(do_quantization=True, dataset="dataset.txt") rknn.export_rknn("yolo11s.rknn")mean_values与std_values必须和训练时一致。ultralytics 默认推理是除以 255 归一化,所以这里mean=0、std=255是对的。do_quantization=True用 INT8 量化会让输出框稍微抖动,置信度阈值要相应降低 0.05。RKNN 的输出层格式是[x1, y1, x2, y2, objness, class1, class2, ...],与 YOLOv5 相同,注意你做 NMS 时把 box 坐标除以输入边长即可还原到 640x640 分辨率。
5.3 延迟与帧率调优的三组参数
部署时三个参数几乎决定了实时性:
| 参数 | 推荐值 | 影响 |
|---|---|---|
| 输入分辨率 | 640 | 降到 512 时延迟降 35%,但检测小目标能力明显下降 |
| YOLO 置信度阈值 | 0.4 | 射箭场景背景干净,0.4 就行;户外逆光调到 0.25 |
| 姿态推理帧数 | 每 3 帧跑一次关键点 | YOLO 每帧跑,姿态每 3 帧跑,减少功耗与延迟 |
“YOLO 每帧、姿态每 3 帧”是边缘部署的常见做法,因为射箭动作在开弓阶段变化慢,每 3 帧一次关键点足够捕捉到角度变化,而 YOLO 必须每帧跑才能保证目标不丢。
5.4 一键部署脚本要点
热词里有“一键部署脚本”,说明读者希望拿到手就能跑。我提供的部署脚本通常包含三步:
#!/bin/bash # setup_env.sh python3 -m venv .venv source .venv/bin/activate pip install ultralytics==8.2.0 onnxruntime-gpu opencv-python numpy然后是convert_model.sh,导出 ONNX 并调用trtexec构建 TensorRT 引擎。最后是run_demo.py合并检测、姿态与角度计算。真正关键的一键不是把所有依赖都装进一个脚本,而是把模型文件路径、输入输出目录做成配置文件,避免用户重复修改源码。
6. 验证姿态分析结果的三个数值化方法
做完系统后,别急着看可视化效果。先做量化验证:第一,用一张射手正面照,手动标注 17 个关键点,计算角度与模型输出对比,误差应小于 3 度;第二,截取连续 30 帧看角度曲线,撒放瞬间的肘关节变化应该是平滑的单峰或双峰,而不是抖动毛刺;第三,在不同光照下重复同一段视频,同一动作的检测框 IoU 应在 0.85 以上。
具体做法是把输出角度写成 CSV,随后画折线图。若发现帧与帧之间角度突然跳出 15 度,检查这一帧 YOLO 是否把背景里的人误检为射手,或者姿态模型的关键点编号左右互换。左右互换在射箭中特别常见:射手背对镜头时,模型容易把左右肩颠倒,导致角度计算全错。解决方法是对前后两帧的关键点做时间平滑,用卡尔曼滤波预测器辅助:
from scipy.ndimage import gaussian_filter1d smoothed_angles = gaussian_filter1d(raw_angles, sigma=2.0)sigma=2.0适合 10 FPS 输入,相当于窗口宽度约为 ±2 帧。如果视频是 30 FPS,sigma 提升到 4.0。光滑后的角度与手动标注值误差在 3 度内,即可认为姿态分析流程可靠。
本文还有配套的精品资源,点击获取