""" 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)
#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, 14}, {14, 15}
};
const cv::Scalar LINECOLOR(255, 255, 255);
const cv::Scalar KPTCOLOR(0, 255, 0);
struct BboxKpts {
cv::Rect box;
float score;
std::vector<cv::Point2f> kpts;
std::vector<float> conf;
};
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;
}
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;
for (int i = 0; i < rows; ++i) {
const float* row = out + i * cols;
float objectness = row[4];
if (objectness < confTh) continue;
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);
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];
kp[k].y = row[5 + k * 3 + 1];
}
kpts.push_back(kp);
kconf.push_back(kc);
}
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;
}
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];
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);
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()};
cv::Mat raw = cv::imread(imgPath);
if (raw.empty()) {
std::cerr << "无法读取图片:" << imgPath << "\n";
return 1;
}
cv::Mat img640 = letterbox(raw);
cv::Mat blob;
cv::dnn::blobFromImage(img640, blob, 1.0 / 255.0, cv::Size(640, 640), cv::Scalar(), true, false);
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());
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";
float* outPtr = outputTensors[0].GetTensorMutableData<float>();
std::vector<int64_t> outShape = outputTensors[0].GetTensorTypeAndShapeInfo().GetShape();
int candidates = outShape[1], attr = outShape[2];
auto dets = postprocess(outPtr, candidates, attr, 0.3f, 0.5f);
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 * fx);
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);
}
}
cv::imwrite("result_lsp.jpg", raw);
cv::imshow("YOLO26-Pose 结果", raw);
cv::waitKey(0);
return 0;
}