Onnxruntime部署C++,基于YOLOV5,YoloV8(ONNXruntime逐行讲解)

一、ONNXruntime介绍

二、基于ONNXruntime,使用C++部署YOLO检测

2.1基本内容介绍

onnxruntime版本:1.20.0 网址csdn - 安全中心https://link.csdn.net/?target=https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fonnxruntime%2Freleases%3Fpage%3D2

opencv版本:4.5.5

将压缩得到的include和lib添加到程序运行所需环境中。我使用的是Clion,使用的cmkelist,代码如下。

include_directories(${CMAKE_SOURCE_DIR}/onnxruntime-win-x64-gpu-1.12.0/include)
link_directories(${CMAKE_SOURCE_DIR}/onnxruntime-win-x64-gpu-1.12.0/lib)

2.2 ONNXruntime初始化代码讲解

首先展示一下完整的初始化代码,如下所示

YOLO::YOLO(float nms_threshold, float objThreshold, const std::string& model_path, const std::vector<std::string>& class_names, int inpWidth, int inpHeight,bool useCuda)
        : nms_threshold(nms_threshold), objThreshold(objThreshold), class_names(class_names), inpWidth(inpWidth), inpHeight(inpHeight),useCuda(useCuda)
{
    this->env = Env(ORT_LOGGING_LEVEL_ERROR, "yolo");//日志记录级别为Error

    std::wstring widestr = std::wstring(model_path.begin(), model_path.end());

    if (useCuda){
        // 设置 GPU(CUDA)执行提供程序
        OrtCUDAProviderOptions cuda_options;
        cuda_options.device_id = 0;  // 使用CUDA设备
        sessionOptions.AppendExecutionProvider_CUDA(cuda_options);//配置选项
    }

    sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
    ort_session = std::make_unique<Ort::Session>(env, widestr.c_str(), sessionOptions);
    // 获取输入和输出节点的信息
    Ort::AllocatorWithDefaultOptions allocator;//内存分配器,用于管理和分配内存


    std::cout << "Model Input Nodes:" << std::endl;
    for (size_t i = 0; i < ort_session->GetInputCount(); ++i) {
        char* input_name = ort_session->GetInputName(i, allocator);
        input_names.push_back(input_name);
        std::cout << "Input " << i << ": " << input_name << std::endl;
    }


    for (size_t i = 0; i < ort_session->GetInputCount(); ++i)
    {
        input_names.push_back(ort_session->GetInputName(i, allocator));

    }



    for (size_t i = 0; i < ort_session->GetOutputCount(); ++i)
    {
        output_names.push_back(ort_session->GetOutputName(i, allocator));
        Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
        auto output_dims = output_type_info.GetTensorTypeAndShapeInfo().GetShape();
        output_node_dims.push_back(output_dims);
    }


    num_anchors = output_node_dims.at(0).at(1);
}

下面进行逐行讲解

this->env = Env(ORT_LOGGING_LEVEL_ERROR, "yolo");

这句是用于声明ONNXruntime运行的基本环境,ORT_LOGGING_LEVEL_ERROR代表检测检测过程是否出现error以上的错误,这可以保证运行过程中问题的发现检查。

 sessionOptions.AppendExecutionProvider_CUDA(cuda_options);//配置选项
 sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
sessionOptions可以认为是一种配置选项,用于设置运行过程中的一些基本配置,比如是否启动CUDA,线程数量设置,优化等级等(上面两行代码就表示启动cuda和优化等级为基础等级),在onnxruntime_cxx_api.h中可以看到可配置的选项,如图所示

在将需要用到的配置设置好后,就需要将所需要的一起加载(基本环境,模型,配置选项),也就是这句

ort_session = std::make_unique<Ort::Session>(env, widestr.c_str(), sessionOptions);

同时采用了智能指针的方式,可以自动释放内存。

随后就是将加载后的内容进行读取,在初始化阶段主要需要读取模型的输入和输出,也就是这样两句

input_names.push_back(ort_session->GetInputName(i, allocator));
output_names.push_back(ort_session->GetOutputName(i, allocator));

如果将input_names output_names的内容打印出来,可以看到输入为images

 输出为

 可以看到输出有4个,也就是4个output,对其内容基本解释为

Output 0: 25200 个预测框(3*(80*80+40*40+20*20)),每个框有 85 个特征(如 x, y, w, h, objectness score,以及 80 个类别得分)。

Output 1:一次处理1张 每个单元格产生3个anchor box 80x80大小 85个输出

Output 2:一次处理1张 每个单元格产生3个anchor box 40x40大小 85个输出

Output 3:一次处理1张 每个单元格产生3个anchor box 20x20大小 85个输出

再进一步解释,Output0是由另外三个组成的,这三个分别是不同大小尺度,用于检测不同形状大小的物体就如下图所示。

 output0主要承载的信息,就是25200个预测框,每个框有85个数据(coco数据集)。如果要找一张图具体描述他的样子,我认为是在来自同济子豪兄的无私分享-关于YOLOv1模型的学习(一)_yolov1 陈子豪师兄-CSDN博客

 中的这一张图片。图中是49*30的长方体,而我们最终得到的是一个25200*85的长方形

 这一项就是初始化最终目的,也就是最后一行代码

num_anchors = output_node_dims.at(0).at(1);

这个num_anchors就是output[0]中的25200元素,每个元素包含了85个信息

2.3 ONNXruntime推理代码讲解

首先,检测的代码如下所示

void YOLO::detect(Mat& srcimg)
{
    int newh = 0, neww = 0, top = 0, left = 0;

    Mat cv_image = srcimg.clone();

    cvtColor(cv_image, cv_image, COLOR_BGR2RGB);

    Mat dst = resize_image(cv_image, &newh, &neww, &top, &left);

    vector<float> input_image_ = normalize_(dst);

    array<int64_t, 4> input_shape_{ 1, 3, inpHeight, inpWidth };

    auto allocator_info = MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);//内存分配cpu

    Value input_tensor_ = Value::CreateTensor<float>(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());

    vector<Value> ort_outputs = ort_session->Run(RunOptions{ nullptr }, &input_names[0], &input_tensor_, 1, output_names.data(), output_names.size());

    const float* outs = ort_outputs[0].GetTensorMutableData<float>();

    float ratioh = static_cast<float>(srcimg.rows) / newh, ratiow = static_cast<float>(srcimg.cols) / neww;
    int nout = class_names.size() + 5;

    for (int i = 0; i < num_anchors; i++)
    {
        const float* pdata = outs + i * nout;
        float obj_conf = pdata[4];

        if (obj_conf > objThreshold)
        {
            int max_ind = 0;
            float max_class_socre = 0;

            for (int j = 0; j < class_names.size(); j++)
            {
                if (pdata[5 + j] > max_class_socre)
                {
                    max_class_socre = pdata[5 + j];
                    max_ind = j;
                }
            }

            float x0 = max<float>((pdata[0] - 0.5f * pdata[2] - left) * ratiow, 0.f);
            float y0 = max<float>((pdata[1] - 0.5f * pdata[3] - top) * ratioh, 0.f);
            float x1 = min<float>((pdata[0] + 0.5f * pdata[2] - left) * ratiow, static_cast<float>(cv_image.cols));
            float y1 = min<float>((pdata[1] + 0.5f * pdata[3] - top) * ratioh, static_cast<float>(cv_image.rows));

            generate_boxes.push_back(BoxInfo{ x0, y0, x1, y1, max_class_socre * obj_conf, max_ind });
        }
    }

    nms(generate_boxes);

    for (const auto& box : generate_boxes)
    {
        rectangle(srcimg, Point(static_cast<int>(box.x1), static_cast<int>(box.y1)), Point(static_cast<int>(box.x2), static_cast<int>(box.y2)), Scalar(0, 0, 255), 2);
        string label = class_names[box.label] + ": " + format("%.2f%%", box.score * 100);
        putText(srcimg, label, Point(static_cast<int>(box.x1), static_cast<int>(box.y1) - 5), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1);
    }
}

首先用opencv对图像进行标准化归一化后(由于本文主要讲解ONNXruntime的相关内容,先跳过。。),首先就是创建一些输入的相关内容,

    auto allocator_info = MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);//内存分配cpu

这行代码的主要功能是设置内存分配器。MemoryInfo::CreateCpu 创建了一个内存分配器,指示使用 CPU 内存进行分配。

    Value input_tensor_ = Value::CreateTensor<float>(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());

 这一行代码创建了一个输入张量,用于将图像数据传递给模型进行推理。它包含了输入数据及其相关信息。其中

allocator_info 定义的内存分配器信息,表示内存是在 CPU 上分配的

input_image_.data( ) 输入图像的像素数据

input_image_.size( ) 输入图像的大小(像素总数)

input_shape_.data( ) 输入张量的形状,表示输入数据的维度,例如 [1, 3, 640, 640],即批大小为 1、3 通道的 RGB 图像,尺寸为 640x640。

input_shape_.size( ) 输入张量形状的维度数,这里为 4(即 [1, 3, 640, 640]

 

    vector<Value> ort_outputs = ort_session->Run(RunOptions{ nullptr }, &input_names[0], &input_tensor_, 1, output_names.data(), output_names.size());

RunOptions{ nullptr }代表推理时的选项,这里设置为nullptr使用默认推理方式

&input_names[0]指向输入张量的名称数组

&input_tensor_输入张量,包含图像的像素数据、形状等信息

1表示 输入张量的数量,这里是 1。

output_names.data()输出张量的名称数组,表示模型推理后要输出的内容,通常是一个或多个输出结果。

output_names.size()表示输出张量的数量。

### 回答1: 首先,您需要将Yolov5模型转换为ONNX格式。您可以使用PyTorch将模型转换为ONNX格式,然后使用ONNX Runtime C++ API加载和运行模型。 以下是一些步骤: 1. 安装PyTorch和ONNX Runtime 2. 使用PyTorch将Yolov5模型转换为ONNX格式。您可以使用以下代码: ``` import torch import torchvision # Load the model model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) # Export the model to ONNX format input_shape = (1, 3, 640, 640) torch.onnx.export(model, torch.randn(*input_shape), "yolov5s.onnx", opset_version=11) ``` 3. 在C++中加载和运行模型。您可以使用以下代码: ``` #include <iostream> #include <vector> #include <chrono> #include <opencv2/opencv.hpp> #include "onnxruntime_cxx_api.h" using namespace std; using namespace cv; using namespace std::chrono; using namespace onnxruntime; int main() { // Load the model Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test"); Ort::Session session(env, "yolov5s.onnx", session_options); // Get input and output names auto input_names = session.GetInputNames(); auto output_names = session.GetOutputNames(); // Create input tensor Ort::AllocatorWithDefaultOptions allocator; Ort::Value input_tensor(nullptr); Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); vector<int64_t> input_shape = {1, 3, 640, 640}; input_tensor = Ort::Value::CreateTensor<float>(memory_info, reinterpret_cast<float*>(new float[input_shape[0] * input_shape[1] * input_shape[2] * input_shape[3]]), input_shape.data(), input_shape.size()); // Load image Mat image = imread("test.jpg"); cvtColor(image, image, COLOR_BGR2RGB); resize(image, image, Size(640, 640)); float* input_data = input_tensor.GetTensorMutableData<float>(); for (int i = 0; i < 640 * 640 * 3; i++) { input_data[i] = image.data[i] / 255.0; } // Run inference auto start = high_resolution_clock::now(); vector<Ort::Value> output_tensors = session.Run(output_names, &input_names[0], &input_tensor, 1); auto end = high_resolution_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "Inference time: " << duration.count() << " ms" << endl; // Get output tensor Ort::Value& output_tensor = output_tensors[0]; float* output_data = output_tensor.GetTensorMutableData<float>(); // Process output for (int i = 0; i < 25200; i++) { if (output_data[i * 6 + 4] > 0.5) { int x1 = output_data[i * 6 + 0] * 640; int y1 = output_data[i * 6 + 1] * 640; int x2 = output_data[i * 6 + 2] * 640; int y2 = output_data[i * 6 + 3] * 640; cout << "Object detected: " << output_data[i * 6 + 5] << " (" << x1 << ", " << y1 << ") (" << x2 << ", " << y2 << ")" << endl; } } return 0; } ``` 这个例子假设您有一张名为“test.jpg”的图像,它将被用作模型的输入。它还假设您的模型输出是一个大小为[1, 25200, 6]的张量,其中25200是预测的边界框数,6是每个边界框的属性数(左上角和右下角坐标,置信度和类别)。 请注意,这只是一个简单的例子,您需要根据您的模型和数据进行适当的修改。 ### 回答2: 在使用ONNXRuntime C部署Yolov5之前,首先需要明确的是Yolov5是目标检测算法,而ONNXRuntime C则是一个高性能的推理框架,可以用来推理基于ONNX格式的深度学习模型,这包括Yolov5。 以下是ONNXRuntime C部署Yolov5的步骤参考: 1. 转换模型:由于Yolov5模型原先是以PyTorch格式存储,因此需要将其转化为ONNX格式。具体的转换方法可以参考ONNX官方文档,或者使用现成的转换脚本,如https://github.com/jkjung-avt/yolov5_onnx/blob/main/yolov5_onnx.py。 2. 编写C程序:根据ONNXRuntime C的API,编写C语言程序实现Yolov5模型的加载和推理。可以参考ONNXRuntime官方示例中的代码模板,进行修改和调整,完成模型的加载和推理功能。关键代码如下: ```c // 运行初始化,加载模型 OrtEnv* env; OrtCreateEnv(ORT_LOGGING_LEVEL_WARNING, "test", &env); OrtSessionOptions* session_options = OrtCreateSessionOptions(); OrtSession* session; OrtCreateSession(env, model_path, session_options, &session); // 获取模型输入输出信息 OrtStatus* status; OrtTensorTypeAndShapeInfo* input_info; OrtSessionGetInputTypeInfo(session, 0, &input_info); OrtAllocator* allocator; OrtCreateDefaultAllocator(&allocator); size_t num_inputs; OrtStatus* get_num_input = OrtSessionGetInputCount(session, &num_inputs); OrtValue** input_tensor = (OrtValue**)malloc(num_inputs * sizeof(OrtValue*)); OrtStatus* input_status = OrtCreateTensorAsOrtValue(allocator, input_info, &input_tensor[0]); OrtTypeInfo* type_info; OrtStatus* output_status = OrtSessionGetOutputTypeInfo(session, 0, &type_info); // 给输入tensor赋值 float* input = input_tensor[0]->GetTensorMutableData<float>(); for (int i = 0; i < input_size; i++) { input[i] = input_data[i]; } // 运行模型,获取结果 OrtValue* output_tensor = NULL; const char* output_names[] = { output_name }; OrtStatus* run_status = OrtRun(session, NULL, input_names, &input_tensor[0], num_inputs, output_names, 1, &output_tensor); float* output = output_tensor->GetTensorMutableData<float>(); ``` 3. 编译C程序:使用C编译器,如gcc,编译C程序,并将ONNXRuntime C的库文件链接到程序中,如: ```bash gcc main.c -lonnxruntime ``` 4. 运行C程序:运行编译后的程序,并输入Yolov5需要检测的图片或视频数据,程序将输出检测结果,包括检测框、置信度和类别等信息。 需要注意的几个点: 1. ONNXRuntime C需要先安装ONNXRuntime库,并将其包含到系统路径中。 2. 在程序中需要指定Yolov5的输入尺寸和类别数等信息。 3. 在使用Yolov5推理时,需要先对输入数据进行预处理,如尺寸缩放、通道变换和数据类型转换等。 4. 在编程时,需要对ONNXRuntime C的API进行深入学习,以保证程序的正确性和稳定性。同时,还需要对Yolov5的算法和原理有一定的了解,以便进行模型的参数调整和优化。 ### 回答3: 随着深度学习的广泛应用,越来越多的框架和工具被开发出来,但由于它们之间的差异,将模型从一个框架转换到另一个框架是一项具有挑战性和耗费时间的工作。ONNX(Runtime)是一种广泛接受的中间表示,它可以使不同的框架之间的模型转换变得容易。这篇文章将介绍如何使用ONNXRuntime C++ API来部署一个YOLOv5的模型。 首先,我们需要下载YOLOv5模型的权重和cfg文件,可以从Github上的YOLOv5仓库中下载。在下载完这两个文件后,我们需要用Python中的train.py将它们转换成ONNX文件,具体地,可以使用如下命令: ``` python3 train.py --weights yolov5s.pt --cfg models/yolov5s.yaml --img 640 --batch 1 --no-autoanchor --fuse python3 models/yolo.py --cfg models/yolov5s.yaml --weights yolov5s.pt --names models/coco.names ``` 这将生成名为“yolov5s.onnx”的模型文件。现在,我们可以使用ONNXRuntime C++ API加载和运行它。 首先,我们需要在C++安装ONNXRuntime的API,可以从官方网站(https://www.onnxruntime.ai/)下载ONNXRuntime C++ API安装文件。安装完成后,我们可以开始编写C++代码来加载和运行YOLOv5模型。 我们需要使用以下头文件: ``` #include "onnxruntime_cxx_api.h" #include <opencv2/opencv.hpp> ``` 接下来,我们需要定义一些变量来存储模型信息。我们可以使用onnxruntime::Env类初始化ONNXRuntime。 ``` std::string model_path = "yolov5s.onnx"; std::shared_ptr<onnxruntime::Environment> env = onnxruntime::Environment::Create(); ``` 然后,我们需要创建一个会话,该会话将包含模型。通过onnxruntime::Session类,我们可以加载模型并返回一个会话句柄。 ``` std::shared_ptr<onnxruntime::Session> session = std::make_shared<onnxruntime::Session>(*env, model_path, nullptr); ``` 我们还需要创建一个输入模型。我们需要使用onnxruntime::Tensor类,它将用于加载输入数据。 ``` std::vector<int64_t> input_node_dims = { 1, 3, 640, 640 }; auto memory_info = onnxruntime::MemoryInfo::CreateCpu(ONNXRUNTIME_CPU, onnxruntime::DeviceAllocatorRegistrationFlags::None); auto input_tensor = onnxruntime::make_unique<onnxruntime::Tensor>(onnxruntime::DataType::Float, onnxruntime::TensorShape(input_node_dims), memory_info); ``` 现在,我们有了将图像转换为ONNX模型所需的一切。我们需要加载图像,将其尺寸调整为模型的输入大小,并使用OpenCV库将图像像素值转换为浮点数。然后我们可以将数据复制到输入_tensor中。 ``` cv::Mat img = cv::imread("test.jpg"); cv::resize(img, img, cv::Size(640, 640), cv::INTER_AREA); img.convertTo(img, CV_32FC3, 1 / 255.0); memcpy(input_tensor->MutableData<float>(), img.data, img.total() * img.elemSize()); ``` 现在,我们可以将输入_tensor传递给模型并运行。 ``` std::vector<const char*> input_node_names = { "input" }; std::vector<const char*> output_node_names = { "output" }; std::vector<OrtValue> output_tensors; OrtTensorScales scales = { nullptr, 0 }; session->Run(Ort::RunOptions{ nullptr }, input_node_names.data(), &input_tensor, input_node_dims.size(), output_node_names.data(), output_node_names.size(), output_tensors, scales); ``` 最后,我们可以使用输出张量中的数据进行后处理,以获得目标框位置和类别预测结果等信息。 这些就是使用ONNXRuntime C++ API来部署YOLOv5模型的基本步骤。当然,如果我们要获得更高的精度和更快的速度,还可以使用各种技术来优化模型和代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值