1.模型训练测试(python)
打开 https://docs.ultralytics.com/zh/tasks/pose/#models,选择模型下载,这里选择 yolo26n-pose.pt,下载后放到你创建的项目的 model 文件夹。
前期 python 环境配置可参考相关文档。
在项目创建 cfg 文件夹,放入配置文件 lsp-pose.yaml。
预期目录结构如下:
yolo
通过终端在项目目录运行
yolo pose train \
data=cfg/lsp-pose.yaml \
model=model/yolo26n-pose.pt \
epochs=40 \
imgsz=640 \
batch=16 \
device=mps
epochs 是轮数,imgsz 是图片处理输入尺寸,batch 是每次输入图像的数量,device 是设备,没有 cuda 或 mps 的话用 cpu。
如果出现报错 AttributeError: Can't get attribute 'Pose26' on <module 'ultralytics.nn.modules.head' from '/Users/Zhuanz/Desktop/work/yolo/.venv/lib/python3.10/site-packages/ultralytics/nn/modules/head.py'>,可通过 uv pip install -U ultralytics 升级 ultralytics 到 ≥ 8.4.0。
训练结束。
查看训练情况,模型在训练过程学到了特征,逐渐收敛,也没有出现过拟合现象,仍有较小的提升空间,下次继续训练和提高数据增强的强度可能提升指标。
执行 yolo export model=runs/pose/train/weights/best.pt format=onnx imgsz=640 simplify=True nms=True 导出。
把 best.onnx 文件移到 model,并改名称为 yolo26n-pose_best.onnx。
编写代码 eval-yolo-pose.py,看看在测试集的表现。
""" Evaluate YOLO26 Pose ONNX model on test set - overall P / R / F1 - optional visualization """
from ultralytics import YOLO
from pathlib import Path
import argparse
def main(args):
model = YOLO(args.model)
metrics = model.val(
data=args.data,
imgsz=args.imgsz,
split="test",
conf=args.conf,
iou=args.iou,
plots=args.vis,
save_json=False,
verbose=True
)
print("\n===== Overall Pose Metrics =====")
print(f"Precision: {metrics.box.mp:.4f}")
print(f"Recall: {metrics.box.mr:.4f}")
print(f"F1-score: {2 * metrics.box.mp * metrics.box.mr / (metrics.box.mp + metrics.box.mr + 1e-6):.4f}")
print(f"mAP50: {metrics.box.map50:.4f}")
print(f"mAP50-95: {metrics.box.map:.4f}")
if args.vis:
print(f"\nVisualization saved to: {metrics.save_dir}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--model", type=str, default=r'model/yolo26n-pose_best.onnx', help="best.onnx or best.pt")
parser.add_argument("--data", type=str, default=r'cfg/lsp-pose.yaml')
parser.add_argument("--imgsz", type=int, default=640)
parser.add_argument("--conf", type=float, default=0.25)
parser.add_argument("--iou", type=float, default=0.65)
parser.add_argument("--vis", action="store_true", help="enable visualization")
args = parser.parse_args()
main(args)
执行 uv run eval-yolo-pose.py。
由此可以得出结论:
- 微调适配效果优异:该训练后的 yolo26n-pose 模型在 LSP-pose 测试集上实现了 mAP50=0.7502、mAP50-95=0.5971 的成绩,超过同量级模型的通用基准,说明训练过程充分学习了该数据集的姿势特征,专属适配的效果到位;
- 训练与泛化双健康:测试集指标和验证集高度吻合,无过拟合、无性能偏差,证明模型在 LSP-pose 数据集上的收敛性和泛化能力均为优秀,训练流程的可靠性无问题;
- 速度 - 精度权衡达标:49.6ms 的单图推理速度保持了 YOLO26n-pose 的轻量化优势,同时在专属数据集上实现了高精度,完美匹配了'LSP-pose 场景下边缘端实时姿势估计'的核心需求;
- 指标平衡合理:Precision=0.5515、Recall=0.8520 的取舍,是轻量化模型在姿势检测任务中的合理策略,且 F1-score=0.6695 实现了二者的良好平衡,无明显指标偏废。
2.模型预测展示(c++)
mac 电脑可通过 brew install opencv 安装 c++ 版 opencv。
到 https://github.com/microsoft/onnxruntime/releases 根据自己的系统下载库文件,比如 mac 可用 https://github.com/microsoft/onnxruntime/releases/download/v1.23.1/onnxruntime-osx-arm64-1.23.1.tgz。
编写纯 cpu 下 c++ 的推理及展示预测结果图片的代码。
#include <onnxruntime_cxx_api.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <chrono>
const std::vector<std::pair<int, int>> SKELETON = {
{0, 1}, {1, 2}, {2, 6}, {6, 3}, {3, 4}, {4, 5},
{6, 7}, {7, 8}, {8, 9},
{8, 12}, {12, 11}, {11, 10},
{8, 13}, {13, }, {, }
};
;
;
{
cv::Rect box;
score;
std::vector<cv::Point2f> kpts;
std::vector<> conf;
};
{
w = src.cols, h = src.rows;
scale = std::((target) / w, (target) / h);
nw = (w * scale), nh = (h * scale);
cv::Mat resized;
cv::(src, resized, cv::(nw, nh));
cv::Mat dst = cv::Mat::(target, target, CV_8UC3);
resized.((cv::((target - nw) / , (target - nh) / , nw, nh)));
dst;
}
{
std::vector<cv::Rect> boxes;
std::vector<> objConf;
std::vector<std::vector<cv::Point2f>> kpts;
std::vector<std::vector<>> kconf;
( i = ; i < rows; ++i) {
* row = out + i * cols;
objectness = row[];
(objectness < confTh) ;
x = row[], y = row[], w = row[], h = row[];
x1 = (x - w / ), y1 = (y - h / );
boxes.(x1, y1, (w), (h));
objConf.(objectness);
;
;
( k = ; k < ; ++k) {
kc[k] = row[ + k * + ];
kp[k].x = row[ + k * ];
kp[k].y = row[ + k * + ];
}
kpts.(kp);
kconf.(kc);
}
std::vector<> indices;
cv::dnn::(boxes, objConf, confTh, iouTh, indices);
std::vector<BboxKpts> ;
( idx : indices) {
BboxKpts b;
b.box = boxes[idx];
b.score = objConf[idx];
b.kpts = kpts[idx];
b.conf = kconf[idx];
.(b);
}
;
}
{
(argc < ) {
std::cerr << ;
;
}
std::string modelPath = ;
std::string imgPath = argv[];
;
Ort::SessionOptions sessOpt;
sessOpt.(GraphOptimizationLevel::ORT_ENABLE_ALL);
;
Ort::AllocatorWithDefaultOptions alc;
inputName = session.(, alc);
outputName = session.(, alc);
* inputNames[] = {inputName.()};
* outputNames[] = {outputName.()};
cv::Mat raw = cv::(imgPath);
(raw.()) {
std::cerr << << imgPath << ;
;
}
cv::Mat img640 = (raw);
cv::Mat blob;
cv::dnn::(img640, blob, / , cv::(, ), cv::(), , );
std::vector<> inputDims{, , , };
memoryInfo = Ort::MemoryInfo::(OrtDeviceAllocator, OrtMemTypeDefault);
Ort::Value inputTensor = Ort::Value::<>(
memoryInfo, blob.<>(), blob.(), inputDims.(), inputDims.());
t0 = std::chrono::steady_clock::();
outputTensors = session.(Ort::RunOptions{}, inputNames, &inputTensor, , outputNames, );
t1 = std::chrono::steady_clock::();
latency = std::chrono::<, std::milli>(t1 - t0).();
std::cout << << latency << ;
* outPtr = outputTensors[].<>();
std::vector<> outShape = outputTensors[].().();
candidates = outShape[], attr = outShape[];
dets = (outPtr, candidates, attr, , );
fx = (raw.cols) / ;
fy = (raw.rows) / ;
( & d : dets) {
;
cv::(raw, realBox, cv::(, , ), );
( k = ; k < ; ++k) {
(d.conf[k] < ) ;
;
cv::(raw, p, , KPTCOLOR, );
}
( & [s, t] : SKELETON) {
(d.conf[s] < || d.conf[t] < ) ;
;
;
cv::(raw, ps, pt, LINECOLOR, );
}
}
cv::(, raw);
cv::(, raw);
cv::();
;
}
运行编译,注意 onnxtime 库文件路径要根据自己的版本改。
g++ test_pose.cpp -o test_pose \
-Ionnxruntime-osx-arm64-1.23.1/include \
-Lonnxruntime-osx-arm64-1.23.1/lib -lonnxruntime \
`pkg-config --cflags --libs opencv4` \
-std=c++17 \
-Wl,-rpath,@executable_path/onnxruntime-osx-arm64-1.23.1/lib
执行 ./test_pose datasets/lsp-pose/images/test_0000.jpg,查看结果。