yolo26n-pose在lsp姿势估计数据集的训练预测流程(python/c++)
1.模型训练测试(python)
打开https://docs.ultralytics.com/zh/tasks/pose/#models,选择模型下载,这里选择yolo26n-pose.pt,下载后放到你创建的项目的model文件夹

前期python环境配置可参考《yolo11m端侧实验》
如果需要数据集,可以自行网上找找,或者可以通过网盘分享的文件:lsp.zip
链接: https://pan.baidu.com/s/1D_XUBfj0j_aXql0WE61G6g?pwd=cwkr 提取码: cwkr
若有兴趣知道用代码获取该数据集的方法,可以通过网盘分享的文件:LSPPoseCode.zip
链接: https://pan.baidu.com/s/1u0l_SADo_JrN-1oj11OUkQ?pwd=znph 提取码: znph
在项目创建cfg文件夹,放入配置文件lsp-pose.yaml
# cfg/lsp-pose.yaml path: datasets/lsp-pose train: train.txt val: val.txt test: test.txt kpt_shape: [14, 3] flip_idx: [0,1,2,10,11,12,3,4,5,6,7,8,9,13] # 根据 LSP 对称关键点 names: 0: person kpt_names: 0: [left_ankle, left_knee, left_hip, right_hip, right_knee, right_ankle, pelvis, thorax, upper_neck, head_top, left_wrist, left_elbow, left_shoulder, right_shoulder, right_elbow, right_wrist]预期目录结构如下:
yolo # 项目根,根据你的项目改动 ├── model # 放 *.pt和*.onnx 的地方 ├── cfg # 专门放各种 yaml │ └── lsp-pose.yaml # 下面内容 ├── datasets # 专门放所有数据集 │ └── lsp-pose # 具体数据集 └── runs # 训练输出(自动生成)通过终端在项目目录运行
yolo pose train \ data=cfg/lsp-pose.yaml \ model=model/yolo26n-pose.pt \ epochs=40 \ imgsz=640 \ batch=16 \ device=mpsepochs是轮数,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) # ========================= # 1. 官方 Pose 验证(核心) # ========================= 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 ) # ========================= # 2. 输出整体指标 # ========================= 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++的推理及展示预测结果图片的代码
/******************************************************* * YOLO26-Pose 单文件 C++ 推理(纯 CPU) * 功能:加载 ONNX 模型 → 读取图片 → 检测人体+14 个关键点 → 画框画骨架 → 保存结果 * 依赖:OpenCV4 + ONNX Runtime(CPU 版) * 编译:见文末命令 ******************************************************/ #include <onnxruntime_cxx_api.h> // ONNX Runtime C++ API #include <opencv2/opencv.hpp> // OpenCV 用于读写/画图 #include <iostream> #include <vector> #include <chrono> // 测速 /*=============== 1. 数据常量 ===============*/ // LSP 数据集 14 个关键点的人体骨架连接(用序号表示) 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, 14}, {14, 15} // 右臂 }; // 每根骨架颜色(这里全部用白色,可随意改) const cv::Scalar LINECOLOR(255, 255, 255); // 关键点颜色(循环用绿色) const cv::Scalar KPTCOLOR(0, 255, 0); // 单个人体的检测结果 struct BboxKpts { cv::Rect box; // 人体框 (x,y,w,h) float score; // 框置信度 std::vector<cv::Point2f> kpts; // 14 个关键点坐标(原图尺度) std::vector<float> conf; // 14 个关键点置信度 }; /*=============== 2. 图像预处理 ===============*/ // 将任意尺寸图片缩放到 640×640,保持宽高比,剩余区域用黑边填充 // 返回:8UC3 彩色图,大小 640×640 static cv::Mat letterbox(const cv::Mat& src, int target = 640) { int w = src.cols, h = src.rows; float scale = std::min(float(target) / w, float(target) / h); int nw = int(w * scale), nh = int(h * scale); cv::Mat resized; cv::resize(src, resized, cv::Size(nw, nh)); // 等比缩放 cv::Mat dst = cv::Mat::zeros(target, target, CV_8UC3); // 黑底 resized.copyTo(dst(cv::Rect((target - nw) / 2, (target - nh) / 2, nw, nh))); return dst; } /*=============== 3. 后处理 ===============*/ // 输入:ONNX 输出张量 float[1][300][57] // 57 = 4(box) + 1(obj置信度) + 14*3(关键点x,y,conf) // 输出:NMS 后的多人检测结果 static std::vector<BboxKpts> postprocess(const float* out, int rows, int cols, float confTh = 0.3f, float iouTh = 0.5f) { std::vector<cv::Rect> boxes; std::vector<float> objConf; std::vector<std::vector<cv::Point2f>> kpts; std::vector<std::vector<float>> kconf; // 逐行解析 300 个候选框 for (int i = 0; i < rows; ++i) { const float* row = out + i * cols; float objectness = row[4]; if (objectness < confTh) continue; // 先过滤低置信度框 // 1. 解析框 float x = row[0], y = row[1], w = row[2], h = row[3]; int x1 = int(x - w / 2), y1 = int(y - h / 2); boxes.emplace_back(x1, y1, int(w), int(h)); objConf.push_back(objectness); // 2. 解析 14 个关键点 std::vector<cv::Point2f> kp(14); std::vector<float> kc(14); for (int k = 0; k < 14; ++k) { kc[k] = row[5 + k * 3 + 2]; // 置信度 kp[k].x = row[5 + k * 3]; // x kp[k].y = row[5 + k * 3 + 1]; // y } kpts.push_back(kp); kconf.push_back(kc); } // NMS 去掉重复框 std::vector<int> indices; cv::dnn::NMSBoxes(boxes, objConf, confTh, iouTh, indices); std::vector<BboxKpts> final; for (int idx : indices) { BboxKpts b; b.box = boxes[idx]; b.score = objConf[idx]; b.kpts = kpts[idx]; b.conf = kconf[idx]; final.push_back(b); } return final; } /*=============== 4. 主函数 ===============*/ int main(int argc, char** argv) { if (argc < 2) { std::cerr << "用法:./test_pose <图片路径>\n"; return 1; } const std::string modelPath = "model/yolo26n-pose_best.onnx"; const std::string imgPath = argv[1]; /* 4.1 创建 ONNX Runtime 会话(纯 CPU)*/ Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "yolo26"); Ort::SessionOptions sessOpt; sessOpt.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); Ort::Session session(env, modelPath.c_str(), sessOpt); /* 4.2 获取输入/输出名字 */ Ort::AllocatorWithDefaultOptions alc; auto inputName = session.GetInputNameAllocated(0, alc); auto outputName = session.GetOutputNameAllocated(0, alc); const char* inputNames[] = {inputName.get()}; const char* outputNames[] = {outputName.get()}; /* 4.3 读取并预处理图片 */ cv::Mat raw = cv::imread(imgPath); if (raw.empty()) { std::cerr << "无法读取图片: " << imgPath << "\n"; return 1; } cv::Mat img640 = letterbox(raw); // 640×640 黑边 cv::Mat blob; cv::dnn::blobFromImage(img640, blob, 1.0 / 255.0, cv::Size(640, 640), cv::Scalar(), true, false); // NCHW, float32 /* 4.4 组装输入张量 */ std::vector<int64_t> inputDims{1, 3, 640, 640}; auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeDefault); Ort::Value inputTensor = Ort::Value::CreateTensor<float>( memoryInfo, blob.ptr<float>(), blob.total(), inputDims.data(), inputDims.size()); /* 4.5 推理 */ auto t0 = std::chrono::steady_clock::now(); auto outputTensors = session.Run(Ort::RunOptions{nullptr}, inputNames, &inputTensor, 1, outputNames, 1); auto t1 = std::chrono::steady_clock::now(); float latency = std::chrono::duration<double, std::milli>(t1 - t0).count(); std::cout << "[INFO] CPU 推理耗时: " << latency << " ms\n"; /* 4.6 后处理 */ float* outPtr = outputTensors[0].GetTensorMutableData<float>(); std::vector<int64_t> outShape = outputTensors[0].GetTensorTypeAndShapeInfo().GetShape(); int candidates = outShape[1], attr = outShape[2]; // [1,300,57] auto dets = postprocess(outPtr, candidates, attr, 0.3f, 0.5f); /* 4.7 把坐标映射回原图并画图 */ float fx = float(raw.cols) / 640.f; float fy = float(raw.rows) / 640.f; for (const auto& d : dets) { // 画框 cv::Rect realBox(d.box.x * fx, d.box.y * fy, d.box.width * fx, d.box.height * fy); cv::rectangle(raw, realBox, cv::Scalar(0, 255, 0), 2); // 画关键点和骨架 for (int k = 0; k < 14; ++k) { if (d.conf[k] < 0.3f) continue; // 低置信度跳过 cv::Point2f p(d.kpts[k].x * fx, d.kpts[k].y * fy); cv::circle(raw, p, 3, KPTCOLOR, -1); } for (const auto& [s, t] : SKELETON) { if (d.conf[s] < 0.3f || d.conf[t] < 0.3f) continue; cv::Point2f ps(d.kpts[s].x * fx, d.kpts[s].y * fy); cv::Point2f pt(d.kpts[t].x * fx, d.kpts[t].y * fy); cv::line(raw, ps, pt, LINECOLOR, 2); } } /* 4.8 保存 & 显示 */ cv::imwrite("result_lsp.jpg", raw); cv::imshow("YOLO26-Pose 结果", raw); cv::waitKey(0); return 0; } /*=============== 5. 编译命令 =============== 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 -O3 \ -Wl,-rpath,@executable_path/onnxruntime-osx-arm64-1.23.1/lib =======================================*/运行编译,注意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,查看结果

创作不易,禁止抄袭,转载请附上原文链接及标题