YOLO系列——基于Ultralytics YOLOv11模型在C++ OpenCV4.8.0/OpenCV4.10.0两个版本DNN模块进行模型加载与推理(附源码)(十)

Ultralytics(YoloV8)开发环境配置,训练,模型转换,部署全流程测试记录 阅读详情

基于Ultralytics YOLOv11模型在C++ OpenCV4.8.0/OpenCV4.10.0两个版本 DNN模块进行模型加载与推理(附源码)

yolo导出模型

test_export.py

from ultralytics import YOLO

# Load a model
model = YOLO("yolo11n.pt")  # load an official model

# Export the model
model.export(format="onnx")

opencv 4.8.0版本

opencv dnn

inference.h

#ifndef INFERENCE_H
#define INFERENCE_H

#include <fstream>
#include <vector>
#include <string>
#include <random>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/dnn.hpp>

// 检测结果结构体
struct Detection
{
    int class_id{0};           // 类别ID
    std::string className{};   // 类别名称
    float confidence{0.0};     // 置信度
    cv::Scalar color{};        // 显示颜色
    cv::Rect box{};            // 边界框坐标
};

class Inference
{
public:
    // 构造函数:初始化模型路径、输入尺寸、是否使用CUDA、类别文件
    Inference(const std::string &onnxModelPath, const cv::Size &modelInputShape = {640, 640}, const bool &runWithCuda = true, const std::string &classesTxtFile = "");

    // 核心推理函数:输入图像,返回检测结果向量
    std::vector<Detection> runInference(const cv::Mat &input);

private:
    // 从文件加载类别名称
    void loadClassesFromFile();

    // 加载ONNX模型并配置计算后端(CUDA/CPU)
    void loadOnnxNetwork();

    // 图像预处理:保持宽高比的方形填充
    cv::Mat formatToSquare(const cv::Mat &source, int *pad_x, int *pad_y, float *scale);

    std::string modelPath{};        // ONNX模型文件路径
    std::string classesPath{};      // 类别文件路径
    bool cudaEnabled{};             // 是否启用CUDA加速

    // COCO数据集80个类别的默认名称
    std::vector<std::string> classes{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"};
    cv::Size2f modelShape{};                // 模型输入尺寸
    float modelConfidenceThreshold {0.25};  // 物体存在置信度阈值
    float modelScoreThreshold      {0.45};  // 类别得分阈值
    float modelNMSThreshold        {0.50};  // 非极大值抑制阈值
    bool letterBoxForSquare = true;         // 是否使用letter box预处理

    cv::dnn::Net net;                       // OpenCV DNN网络对象
};

#endif // INFERENCE_H

inference.cpp

#include "inference.h"

// 构造
Inference::Inference(const std::string &onnxModelPath, const cv::Size &modelInputShape, const bool &runWithCuda, const std::string &classesTxtFile)
{
    modelPath = onnxModelPath;
    modelShape = modelInputShape;
    cudaEnabled = runWithCuda;
    classesPath = classesTxtFile;

    loadOnnxNetwork();
    // loadClassesFromFile();
}

// 推理
std::vector<Detection> Inference::runInference(const cv::Mat &input)
{
    cv::Mat modelInput = input;
    int pad_x, pad_y;  // 填充尺寸
    float scale;       // 缩放比例

    // 如果启用letter box且输入为正方形,进行预处理
    if (letterBoxForSquare && modelShape.width == modelShape.height)
        modelInput = formatToSquare(modelInput, &pad_x, &pad_y, &scale);

    // 将图像转换为模型输入blob格式
    cv::Mat blob;
    cv::dnn::blobFromImage(modelInput, blob, 1.0/255.0, modelShape, cv::Scalar(), true, false);
    net.setInput(blob);

    // 前向传播,获取模型输出
    std::vector<cv::Mat> outputs;
    net.forward(outputs, net.getUnconnectedOutLayersNames());

    // 解析输出维度
    int rows = outputs[0].size[1];      // 检测框数量
    int dimensions = outputs[0].size[2]; // 每个检测框的维度数

    bool yolov8 = false;
    // YOLOv5: (batchSize, 25200, 85)  85 = 4坐标 + 1置信度 + 80类别
    // YOLOv8/v11: (batchSize, 84, 8400)  84 = 4坐标 + 80类别

    if (dimensions > rows) // 判断是否为YOLOv8格式
    {
        // 交换维度
        yolov8 = true;
        rows = outputs[0].size[2];
        dimensions = outputs[0].size[1];

        outputs[0] = outputs[0].reshape(1, dimensions);
        cv::transpose(outputs[0], outputs[0]);
    }
    float *data = (float *)outputs[0].data;

     // 存储原始检测结果的容器
    std::vector<int> class_ids;
    std::vector<float> confidences;
    std::vector<cv::Rect> boxes;

    // 遍历所有检测框
    for (int i = 0; i < rows; ++i)
    {
        if (yolov8)
        {
            float *classes_scores = data+4;

            cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);
            cv::Point class_id;
            double maxClassScore;

            minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);

            if (maxClassScore > modelScoreThreshold)
            {
                confidences.push_back(maxClassScore);
                class_ids.push_back(class_id.x);

                float x = data[0];
                float y = data[1];
                float w = data[2];
                float h = data[3];

                int left = int((x - 0.5 * w - pad_x) / scale);
                int top = int((y - 0.5 * h - pad_y) / scale);

                int width = int(w / scale);
                int height = int(h / scale);

                boxes.push_back(cv::Rect(left, top, width, height));
            }
        }
        else // yolov5
        {
            float confidence = data[4];

            if (confidence >= modelConfidenceThreshold)
            {
                float *classes_scores = data+5;

                cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);
                cv::Point class_id;
                double max_class_score;

                minMaxLoc(scores, 0, &max_class_score, 0, &class_id);

                if (max_class_score > modelScoreThreshold)
                {
                    confidences.push_back(confidence);
                    class_ids.push_back(class_id.x);

                    float x = data[0];
                    float y = data[1];
                    float w = data[2];
                    float h = data[3];

                    int left = int((x - 0.5 * w - pad_x) / scale);
                    int top = int((y - 0.5 * h - pad_y) / scale);

                    int width = int(w / scale);
                    int height = int(h / scale);

                    boxes.push_back(cv::Rect(left, top, width, height));
                }
            }
        }

        data += dimensions;
    }

    // 应用非极大值抑制(NMS)去除重叠框
    std::vector<int> nms_result;
    cv::dnn::NMSBoxes(boxes, confidences, modelScoreThreshold, modelNMSThreshold, nms_result);

    // 构建最终检测结果
    std::vector<Detection> detections{};
    for (unsigned long i = 0; i < nms_result.size(); ++i)
    {
        int idx = nms_result[i];// NMS筛选后的索引

        Detection result;
        result.class_id = class_ids[idx];
        result.confidence = confidences[idx];

        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<int> dis(100, 255);
        result.color = cv::Scalar(dis(gen), dis(gen), dis(gen));

        result.className = classes[result.class_id];
        result.box = boxes[idx];

        detections.push_back(result);
    }

    return detections;
}

void Inference::loadClassesFromFile()
{
    std::ifstream inputFile(classesPath);
    if (inputFile.is_open())
    {
        std::string classLine;
        while (std::getline(inputFile, classLine))
            classes.push_back(classLine);
        inputFile.close();
    }
}

void Inference::loadOnnxNetwork()
{
    net = cv::dnn::readNetFromONNX(modelPath);
    if (cudaEnabled)
    {
        std::cout << "\nRunning on CUDA" << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
    }
    else
    {
        std::cout << "\nRunning on CPU" << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
    }
}

// 图像预处理:保持宽高比的方形填充(letter box)
cv::Mat Inference::formatToSquare(const cv::Mat &source, int *pad_x, int *pad_y, float *scale)
{
    int col = source.cols;
    int row = source.rows;
    int m_inputWidth = modelShape.width;
    int m_inputHeight = modelShape.height;

    // 计算缩放比例,保持宽高比
    *scale = std::min(m_inputWidth / (float)col, m_inputHeight / (float)row);
    int resized_w = col * *scale;
    int resized_h = row * *scale;

    // 计算填充尺寸,使图像居中
    *pad_x = (m_inputWidth - resized_w) / 2;
    *pad_y = (m_inputHeight - resized_h) / 2;

    // 缩放图像并填充到目标尺寸
    cv::Mat resized;
    cv::resize(source, resized, cv::Size(resized_w, resized_h));
    // 创建目标尺寸的黑色背景
    cv::Mat result = cv::Mat::zeros(m_inputHeight, m_inputWidth, source.type());
    // 将缩放后的图像复制到中央
    resized.copyTo(result(cv::Rect(*pad_x, *pad_y, resized_w, resized_h)));
    resized.release();
    return result;
}

main.cpp

#include <iostream>
#include <vector>
#include <getopt.h>
#include <opencv2/opencv.hpp>
#include "inference.h"

int main(int argc, char **argv)
{
    std::string onnxModelPath = "../data/yolo11n.onnx";
    bool runOnGPU = true;
    cv::Size sizeTmp(640,640);
    Inference inf(onnxModelPath, sizeTmp, runOnGPU);

    std::vector<std::string> imageNames;
    imageNames.push_back("../data/bus.jpg");
    imageNames.push_back("../data/zidane.jpg");
    imageNames.push_back("../data/traffic.jpg");

    for (int i = 0; i < imageNames.size(); ++i) //遍历每一幅图像
    {
        cv::Mat frame = cv::imread(imageNames[i]);
        auto start = std::chrono::system_clock::now();
        std::vector<Detection> output = inf.runInference(frame);
        auto end = std::chrono::system_clock::now();
        std::chrono::duration<double> elapsed = end - start;
        std::cout << "------Images[" << i << "]------" << std::endl;
        std::cout << "\tInference time: " << elapsed.count() * 1000 << " ms" << std::endl;

        int detections = output.size();
        std::cout << "\tdetect " << detections << " of detections" << std::endl;

        for (int j = 0; j < detections; ++j)
        {
            Detection detection = output[j];

            cv::Rect box = detection.box;
            cv::Scalar color = detection.color;

            // 打印结果
            std::cout<<"\t\tdetections["<<j<<"]"<<std::endl;
            std::cout<< "\t\t\tclass_id:"<<detection.class_id<<std::endl;
            std::cout<< "\t\t\tclassName:"<<detection.className<<std::endl;
            std::cout<< "\t\t\tconfidence:"<<detection.confidence<<std::endl;
            std::cout<< "\t\t\tbox:"<<detection.box<<std::endl;

            // 绘制
            cv::rectangle(frame, box, color, 2);
            std::string classString = detection.className + ' ' + std::to_string(detection.confidence).substr(0, 4);
            cv::Size textSize = cv::getTextSize(classString, cv::FONT_HERSHEY_DUPLEX, 1, 2, 0);
            cv::Rect textBox(box.x, box.y - 40, textSize.width + 10, textSize.height + 20);

            cv::rectangle(frame, textBox, color, cv::FILLED);
            cv::putText(frame, classString, cv::Point(box.x + 5, box.y - 10), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 0, 0), 2, 0);
        }

        //保存
        cv::imwrite(std::to_string(i)+".jpg",frame);

        //显示
        float scale = 1.0;
        cv::resize(frame, frame, cv::Size(frame.cols*scale, frame.rows*scale));
        cv::imshow("Inference", frame);

        cv::waitKey(-1);
    }
}

结果

------Images[0]------
        Inference time: 154.151 ms
        detect 4 of detections
                detections[0]
                        class_id:5
                        className:bus
                        confidence:0.940209
                        box:[785 x 506 from (12, 228)]
                detections[1]
                        class_id:0
                        className:person
                        confidence:0.901381
                        box:[194 x 506 from (48, 398)]
                detections[2]
                        class_id:0
                        className:person
                        confidence:0.844137
                        box:[139 x 492 from (670, 387)]
                detections[3]
                        class_id:0
                        className:person
                        confidence:0.831744
                        box:[122 x 454 from (223, 405)]
------Images[1]------
        Inference time: 125.669 ms
        detect 3 of detections
                detections[0]
                        class_id:0
                        className:person
                        confidence:0.852391
                        box:[399 x 669 from (749, 41)]
                detections[1]
                        class_id:0
                        className:person
                        confidence:0.793089
                        box:[993 x 513 from (143, 200)]
                detections[2]
                        class_id:27
                        className:tie
                        confidence:0.481717
                        box:[165 x 281 from (359, 437)]
------Images[2]------
        Inference time: 131.012 ms
        detect 11 of detections
                detections[0]
                        class_id:2
                        className:car
                        confidence:0.771447
                        box:[48 x 48 from (454, 433)]
                detections[1]
                        class_id:2
                        className:car
                        confidence:0.749929
                        box:[37 x 34 from (224, 398)]
                detections[2]
                        class_id:2
                        className:car
                        confidence:0.745018
                        box:[82 x 72 from (514, 523)]
                detections[3]
                        class_id:2
                        className:car
                        confidence:0.674062
                        box:[35 x 32 from (281, 391)]
                detections[4]
                        class_id:2
                        className:car
                        confidence:0.640642
                        box:[28 x 25 from (248, 372)]
                detections[5]
                        class_id:7
                        className:truck
                        confidence:0.547953
                        box:[136 x 128 from (448, 670)]
                detections[6]
                        class_id:7
                        className:truck
                        confidence:0.53021
                        box:[154 x 161 from (43, 752)]
                detections[7]
                        class_id:2
                        className:car
                        confidence:0.524246
                        box:[93 x 110 from (53, 554)]
                detections[8]
                        class_id:2
                        className:car
                        confidence:0.522188
                        box:[36 x 32 from (370, 400)]
                detections[9]
                        class_id:2
                        className:car
                        confidence:0.503465
                        box:[28 x 19 from (411, 368)]
                detections[10]
                        class_id:2
                        className:car
                        confidence:0.465773
                        box:[38 x 36 from (430, 399)]

在这里插入图片描述
在这里插入图片描述

opencv 4.10版本

opencv dnn

inference_test.h

#ifndef INFERENCE_TEST_H
#define INFERENCE_TEST_H

#include <fstream>
#include <vector>
#include <string>
#include <random>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/core.hpp>

// 检测结果结构体
struct Detection
{
    int class_id{0};           // 类别ID
    std::string className{};   // 类别名称
    float confidence{0.0};     // 置信度
    cv::Scalar color{};        // 显示颜色
    cv::Rect box{};            // 边界框坐标
};

class Inference
{
public:
    // 构造函数:初始化模型路径、输入尺寸、是否使用CUDA、类别文件
    Inference(const std::string &onnxModelPath, const cv::Size &modelInputShape = {640, 640}, const bool &runWithCuda = true, const std::string &classesTxtFile = "");

    // 核心推理函数:输入图像,返回检测结果
    std::vector<cv::Mat> runInference(const cv::Mat &input);

    // yolo后处理
    void yoloPostProcessing(std::vector<cv::Mat>& outs,std::vector<int>& keep_classIds,std::vector<float>& keep_confidences,std::vector<cv::Rect2d>& keep_boxes,
                            const std::string& model_name,const int nc);

    // 绘制预测
    void drawPrediction(int classId, float conf, int left, int top, int right, int bottom, cv::Mat& frame);

    // 从文件加载类别名称
    void loadClassesFromFile();

    // 加载ONNX模型并配置计算后端(CUDA/CPU)
    void loadOnnxNetwork();

    // 清空结果数据
    void clearData();

    // 图像预处理:保持宽高比的方形填充
    cv::Mat formatToSquare(const cv::Mat &source, int *pad_x, int *pad_y, float *scale);

private:
    std::string modelPath{};        // ONNX模型文件路径
    cv::Size2f modelShape{};        // 模型输入尺寸
    bool cudaEnabled{};             // 是否启用CUDA加速
    std::string classesPath{};      // 类别文件路径

    // COCO数据集80个类别的默认名称
    std::vector<std::string> classes{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"};
    float modelConfidenceThreshold {0.25};  // 物体存在置信度阈值
    float modelScoreThreshold      {0.45};  // 类别得分阈值
    float modelNMSThreshold        {0.50};  // 非极大值抑制阈值
    bool letterBoxForSquare = true;         // 是否使用letter box预处理
    cv::dnn::Net net;                       // OpenCV DNN网络对象
public:
    std::vector<int> keepClassIds;
    std::vector<float> keepConfidences;
    std::vector<cv::Rect2d> keepBoxes;
    std::vector<cv::Rect> boxes;
    const std::string modelName{"yolov11"};
    const int nc{80};
};

#endif // INFERENCE_TEST_H

inference_test.cpp

#include "inference_test.h"

/// 构造
Inference::Inference(const std::string &onnxModelPath, const cv::Size &modelInputShape, const bool &runWithCuda, const std::string &classesTxtFile)
{
    modelPath = onnxModelPath;
    modelShape = modelInputShape;
    cudaEnabled = runWithCuda;
    classesPath = classesTxtFile;

    loadOnnxNetwork();
    // loadClassesFromFile();
}

// 加载ONNX模型并配置计算后端(CUDA/CPU)
void Inference::loadOnnxNetwork()
{
    net = cv::dnn::readNetFromONNX(modelPath);
    if (cudaEnabled){
        std::cout << "\nRunning on CUDA" << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
    }
    else{
        std::cout << "\nRunning on CPU" << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
    }
}

// 从文件加载类别名称
void Inference::loadClassesFromFile()
{
    std::ifstream inputFile(classesPath);
    if (inputFile.is_open()){
        std::string classLine;
        while (std::getline(inputFile, classLine))
            classes.push_back(classLine);
        inputFile.close();
    }
}

/// 推理
std::vector<cv::Mat> Inference::runInference(const cv::Mat &input)
{
    cv::Mat modelInput = input;
    float paddingValue = 114;
    bool swapRB = true;
    int inpWidth = 640;
    int inpHeight = 640;
    cv::Scalar scale = cv::Scalar(1.0/255.0, 1.0/255.0, 1.0/255.0);
    cv::Scalar mean = cv::Scalar(0, 0, 0);
    cv::dnn::ImagePaddingMode paddingMode = cv::dnn::DNN_PMODE_LETTERBOX;
    cv::Size size(inpWidth, inpHeight);
    cv::dnn::Image2BlobParams imgParams(scale,size,mean,swapRB,CV_32F,cv::dnn::DNN_LAYOUT_NCHW,paddingMode,paddingValue);

    cv::Mat inp = cv::dnn::blobFromImageWithParams(modelInput, imgParams);
    net.setInput(inp);

    // 前向传播,获取模型输出
    std::vector<cv::Mat> outs;
    net.forward(outs, net.getUnconnectedOutLayersNames());

    yoloPostProcessing(outs, keepClassIds, keepConfidences, keepBoxes, modelName, nc);

    for (auto box : keepBoxes){
        boxes.push_back(cv::Rect(cvFloor(box.x), cvFloor(box.y), cvFloor(box.width - box.x), cvFloor(box.height - box.y)));
    }
    imgParams.blobRectsToImageRects(boxes, boxes, modelInput.size());

    return outs;
}

void Inference::yoloPostProcessing(std::vector<cv::Mat>& outs,std::vector<int>& keep_classIds,std::vector<float>& keep_confidences,std::vector<cv::Rect2d>& keep_boxes,
                                const std::string& model_name,const int nc)
{
    // Retrieve
    std::vector<int> classIds;
    std::vector<float> confidences;
    std::vector<cv::Rect2d> boxes;

    if (model_name == "yolov8" || model_name == "yolov10" ||
        model_name == "yolov9" || model_name == "yolov11"){
        cv::transposeND(outs[0], {0, 2, 1}, outs[0]);
    }

    if (model_name == "yolonas"){
        // outs contains 2 elements of shape [1, 8400, nc] and [1, 8400, 4]. Concat them to get [1, 8400, nc+4]
        cv::Mat concat_out;
        // squeeze the first dimension
        outs[0] = outs[0].reshape(1, outs[0].size[1]);
        outs[1] = outs[1].reshape(1, outs[1].size[1]);
        cv::hconcat(outs[1], outs[0], concat_out);
        outs[0] = concat_out;
        // remove the second element
        outs.pop_back();
        // unsqueeze the first dimension
        outs[0] = outs[0].reshape(0, std::vector<int>{1, outs[0].size[0], outs[0].size[1]});
    }

    // assert if last dim is nc+5 or nc+4
    CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, nc+5 or nc+4]");
    CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == nc + 4), true, "Invalid output shape: ");

    for (auto preds : outs){
        preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85]
        for (int i = 0; i < preds.rows; ++i){
            // filter out non object
            float obj_conf = (model_name == "yolov8" || model_name == "yolonas" ||
                            model_name == "yolov9" || model_name == "yolov10" ||
                            model_name == "yolov11") ? 1.0f : preds.at<float>(i, 4) ;
            if (obj_conf < modelConfidenceThreshold)
                continue;

            cv::Mat scores = preds.row(i).colRange((model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10" || model_name == "yolov11") ? 4 : 5, preds.cols);
            double conf;
            cv::Point maxLoc;
            minMaxLoc(scores, 0, &conf, 0, &maxLoc);

            conf = (model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10" || model_name == "yolov11") ? conf : conf * obj_conf;
            if (conf < modelConfidenceThreshold)
                continue;

            // get bbox coords
            float* det = preds.ptr<float>(i);
            double cx = det[0];
            double cy = det[1];
            double w = det[2];
            double h = det[3];

            // [x1, y1, x2, y2]
            if (model_name == "yolonas" || model_name == "yolov10"){
                boxes.push_back(cv::Rect2d(cx, cy, w, h));
            } else {
                boxes.push_back(cv::Rect2d(cx - 0.5 * w, cy - 0.5 * h,
                                        cx + 0.5 * w, cy + 0.5 * h));
            }
            classIds.push_back(maxLoc.x);
            confidences.push_back(static_cast<float>(conf));
        }
    }

    // NMS
    std::vector<int> keep_idx;
    cv::dnn::NMSBoxes(boxes, confidences, modelConfidenceThreshold, modelNMSThreshold, keep_idx);

    for (auto i : keep_idx){
        keep_classIds.push_back(classIds[i]);
        keep_confidences.push_back(confidences[i]);
        keep_boxes.push_back(boxes[i]);
    }
}

void Inference::drawPrediction(int classId, float conf, int left, int top, int right, int bottom, cv::Mat& frame)
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> dis(100, 255);
    cv::Scalar colorTmp = cv::Scalar(dis(gen), dis(gen), dis(gen));

    cv::rectangle(frame, cv::Point(left, top), cv::Point(right, bottom), colorTmp, 2);

    std::string label = cv::format("%.2f", conf);
    if (!classes.empty())
    {
        CV_Assert(classId < (int)classes.size());
        label = classes[classId] + ": " + label;
    }

    int baseLine;
    cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);

    top = cv::max(top, labelSize.height);
    cv::rectangle(frame, cv::Point(left, top - labelSize.height),
            cv::Point(left + labelSize.width, top + baseLine), colorTmp, cv::FILLED);
    cv::putText(frame, label, cv::Point(left, top), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar());
}

// 清空结果数据
void Inference::clearData(){
    keepClassIds.clear();
    keepConfidences.clear();
    keepBoxes.clear();
    boxes.clear();
}

// 图像预处理:保持宽高比的方形填充(letter box)
cv::Mat Inference::formatToSquare(const cv::Mat &source, int *pad_x, int *pad_y, float *scale)
{
    int col = source.cols;
    int row = source.rows;
    int inputWidth = modelShape.width;
    int inputHeight = modelShape.height;

    // 计算缩放比例,保持宽高比
    *scale = std::min(inputWidth / (float)col, inputHeight / (float)row);
    int resized_w = col * *scale;
    int resized_h = row * *scale;

    // 计算填充尺寸,使图像居中
    *pad_x = (inputWidth - resized_w) / 2;
    *pad_y = (inputHeight - resized_h) / 2;

    // 缩放图像并填充到目标尺寸
    cv::Mat resized;
    cv::resize(source, resized, cv::Size(resized_w, resized_h));
    // 创建目标尺寸的黑色背景
    cv::Mat result = cv::Mat::zeros(inputHeight, inputWidth, source.type());
    // 将缩放后的图像复制到中央
    resized.copyTo(result(cv::Rect(*pad_x, *pad_y, resized_w, resized_h)));
    resized.release();
    return result;
}

test.cpp

#include <iostream>
#include <vector>
#include <getopt.h>
#include <opencv2/opencv.hpp>
#include "inference_test.h"

int main(int argc, char **argv)
{
    std::string onnxModelPath = "../data/yolo11n.onnx";
    bool runOnGPU = true;
    cv::Size sizeTmp(640,640);
    Inference inf(onnxModelPath, sizeTmp, runOnGPU);

    std::vector<std::string> imageNames;
    imageNames.push_back("../data/bus.jpg");
    imageNames.push_back("../data/zidane.jpg");
    imageNames.push_back("../data/traffic.jpg");

    for (int i = 0; i < imageNames.size(); ++i) //遍历每一幅图像
    {
        cv::Mat imgTmp = cv::imread(imageNames[i]);
        auto start = std::chrono::system_clock::now();
        std::vector<cv::Mat> outputs = inf.runInference(imgTmp);
        auto end = std::chrono::system_clock::now();
        std::chrono::duration<double> elapsed = end - start;
        std::cout << "------Images[" << i << "]------" << std::endl;
        std::cout << "\tInference time: " << elapsed.count() * 1000 << " ms" << std::endl;

        for (size_t idx = 0; idx < inf.boxes.size(); ++idx){
            cv::Rect box = inf.boxes[idx];
            inf.drawPrediction(inf.keepClassIds[idx], inf.keepConfidences[idx], box.x, box.y,
                    box.width + box.x, box.height + box.y, imgTmp);
        }
        inf.clearData();
        cv::imwrite("opencv410_"+std::to_string(i)+".jpg", imgTmp);
        const std::string kWinName = "Yolo Object Detector";
        cv::namedWindow(kWinName, cv::WINDOW_NORMAL);
        cv::imshow(kWinName, imgTmp);
        cv::waitKey(-1);
    }
}

结果

------Images[0]------
        Inference time: 160.619 ms
------Images[1]------
        Inference time: 116.271 ms
------Images[2]------
        Inference time: 135.319 ms

在这里插入图片描述

YOLOv8旋转目标检测C++部署实战:OpenCV DNN实现与后处理详解 目标检测是计算机视觉的核心任务之一,旨在定位和识别图像中的物体。传统水平框检测在物体存在旋转或倾斜时,会引入大量背景噪声,影响后续处理精度。旋转目标检测技术通过预测带角度的矩形框,能更紧密地贴合物体轮廓,其技术价值在于显著提升了遥感图像分析、文档OCR、工业视觉测量等场景的精度。模型部署是将训练好的算法模型集成到实际应用的关键环节,而C++因其高性能和低资源消耗,成为工业级部署的首选语言。OpenCV DNN模块作为一个轻量、通用的推理后端,支持直接加载ONNX模型,为快速原型验证和跨平台部署提供了高效桥梁 阅读详情

相关推荐

OpenCV 5 DNN引擎升级实测:YOLO模型部署性能提升与迁移指南

深度神经网络(DNN)作为计算机视觉领域的核心技术,通过模拟人脑神经网络结构实现图像识别、目标检测等复杂任务。其原理基于多层感知器和反向传播算法,能够自动学习图像特征表示。在工程实践中,DNN模块的技术价值体现在高效模型推理和资源优化上,特别是在边缘计算和实时视觉应用中。OpenCV作为流行的计算机视觉库,其DNN模块支持ONNX、TensorFlow等多种模型格式,在YOLO目标检测、图像分类等场景广泛应用。本文基于OpenCV 5的DNN引擎升级,重点分析YOLOv8等模型在CUDA加速和CPU推理下的

491

【亲测免费】 推荐开源项目:C++实现的Ultralytics YOLOv5部署方案

推荐开源项目:C++实现的Ultralytics YOLOv5部署方案 在当今计算机视觉领域,实时目标检测是不可或缺的技术之一。而提到高效的目标检测模型,Ultralytics的YOLOv5无疑是众多开发者和研究者的首选。今天,我们要介绍的正是一个能够让YOLOv5的强大性能通过C++语言无缝接入你应用的开源项目——ONNX_yolov5。 项目介绍 ONNX_yolov5是一个旨在简化YOLOv...

gitblog_00563的博客 926

工业级YOLOv11部署实战:OpenCV DNN模块解析与应用

深度学习模型部署是计算机视觉从实验室走向工业应用的关键环节。OpenCV DNN模块作为轻量级推理引擎,通过统一的API支持多种框架模型转换,解决了工业场景对环境纯净性、实时性和硬件适配性的严苛要求。其核心原理是基于ONNX中间表示实现跨平台推理,支持CPU/GPU异构计算,特别适合需要快速部署的智能制造、质量检测等场景。针对YOLOv11这类实时检测模型,OpenCV DNN通过优化SiLU激活层计算、内置Letterbox预处理等特性,在保持精度的同时实现毫秒级延迟。实际应用中,结合CUDA加速和多线程

weixin_30673611的博客 463

OpenCV 5.0 DNN模块重构:YOLOv8实测性能提升与部署优化指南

深度学习模型部署是计算机视觉领域的核心技术环节,其核心原理是通过推理引擎将训练好的模型转化为实际应用。OpenCV DNN模块作为连接深度学习框架与生产环境的重要桥梁,在图像识别、目标检测等场景中具有重要技术价值。随着YOLO、Transformer等复杂模型的普及,传统推理架构在算子支持、内存管理等方面面临挑战。OpenCV 5.0的DNN重构针对这些痛点,通过计算图优化、内存池技术和硬件后端调度升级,显著提升了模型推理效率。特别是在YOLOv8等主流模型的部署中,新版本在CPU和GPU环境下均表现出显著

weixin_30740581的博客 408

YOLOv8工业级落地全链路:从环境配置到RK3588部署

YOLOv8作为当前最主流的目标检测框架,其核心价值在于工程可落地性——而非仅限于论文复现。理解其底层依赖关系(如CUDA与PyTorch的二进制兼容性)、掌握YOLO格式数据集的工业级构建规范、规避OpenCV 4.8等常见版本引发的API断裂风险,是实现稳定训练与可信推理的前提。技术价值体现在将算法能力转化为实际生产力:支持Windows/Linux GPU加速训练、Python API安全调用、ONNX跨平台导出,并最终适配RK3588等国产边缘芯片进行INT8量化部署。典型应用场景包括工业零件识别、

weixin_30522183的博客 445

旭日X3派YOLOv8部署实战:Python与C++性能对比全解析

目标检测是计算机视觉的核心任务之一,旨在识别图像中的物体并定位其位置。其原理通常基于深度学习模型,通过卷积神经网络提取特征并进行分类与回归。在边缘计算场景中,将目标检测模型部署到嵌入式设备能实现低延迟、高隐私的实时分析,具有重要的技术价值。这类应用广泛存在于智能安防、工业质检和自动驾驶等领域。本文聚焦于地平线旭日X3派这一典型边缘AI平台,详细探讨了如何利用其专用BPU加速核进行模型优化。通过**模型转换**和**混合推理**技术,将YOLOv8模型高效部署,并系统对比了Python与C++接口在真实场景下

weixin_34101784的博客 415

OpenCV 5.0 DNN引擎升级:解决YOLO GPU推理坐标异常,性能提升40%

深度学习模型部署是计算机视觉应用的核心环节,其原理涉及模型推理引擎对神经网络前向传播的优化执行。在技术价值层面,高效的推理引擎能显著降低计算延迟、提升吞吐量,广泛应用于实时目标检测、智能监控等场景。OpenCV作为经典的计算机视觉库,其DNN模块长期存在GPU推理兼容性问题,特别是YOLO模型部署中的检测框坐标异常。针对这一痛点,新发布的OpenCV 5.0对DNN引擎进行了深度优化,通过统一后端管理、增强ONNX支持、引入异步推理管道等改进,不仅彻底解决了坐标异常问题,还在实际测试中实现了40%的GPU推

weixin_34314962的博客 332

树莓派5部署YOLO26:ARM64轻量AI视觉落地实战

边缘AI视觉是指在资源受限设备上直接运行目标检测等智能算法的技术范式,其核心原理是通过模型压缩、硬件加速与场景适配的协同优化,在低功耗平台上实现近实时推理。技术价值在于降低云端依赖、保障数据隐私、缩短响应延迟,并显著削减单节点部署成本。典型应用场景包括农业病害监测、工业微缺陷识别、智能家居本地化感知及物流包裹分拣等。本文聚焦Raspberry Pi 5平台,深度解析Ultralytics官方优化的YOLO26(即YOLOv8.2.6-rpi)在ARM64架构下的INT8量化、TensorRT加速与动态缩放实

dixi7825的博客 505

OpenCV 5 DNN引擎升级:ONNX模型支持与GPU推理优化实践

深度学习模型推理作为计算机视觉的核心技术,通过优化神经网络前向传播过程实现高效预测。其原理基于预训练模型的权重加载和计算图执行,在边缘设备和实时系统中具有重要技术价值。OpenCV 5的DNN模块在ONNX模型支持和GPU推理方面实现显著突破,特别针对YOLO系列等现代视觉架构进行优化。该升级降低了模型部署门槛,为嵌入式设备和边缘计算场景提供轻量级解决方案,同时保持与传统图像处理项目的良好兼容性。通过多后端支持和硬件加速优化,开发者能够在资源受限环境中实现高效的深度学习应用部署。

weixin_30614109的博客 419

【UltralyticsYolo11图像分类完整项目-03】Onnx版Cpu预测C++实现

本文介绍了如何在仅使用CPU的电脑上,使用C++部署图像分类模型。首先,通过Visual Studio新建一个控制台应用程序,并配置OpenCV和ONNX Runtime环境。接着,详细说明了如何在项目中设置包含目录、库目录以及附加依赖项。然后,编写了C++代码,包括加载标签、初始化ONNX Runtime、创建会话、验证模型文件等步骤,并提供了日志记录功能以便调试。最终,实现了在CPU上预测单张图像的分类结果。

深耕CV 676

【UltralyticsYolo11图像分类完整项目-05】重构前的代码输出每一个类别的概率+代码详细解析

这篇文章详细解析了使用C++和ONNX Runtime部署YOLOv11图像分类模型的完整流程,包括模型加载、图像预处理、推理执行和结果可视化。重点讲解了如何获取并展示所有类别的预测概率,而不仅仅是最高概率类别。通过代码解析和性能优化建议,帮助开发者理解C++实现与Python的区别,并提供了完整的错误处理和日志记录方案。

深耕CV 656

YOLOv8 Windows环境安装排错手册:CUDA-PyTorch-ultralytics全链路验证

YOLOv8作为主流实时目标检测框架,其Windows部署常因环境依赖复杂而失败。核心在于理解深度学习环境的本质——它不是单个库的安装,而是Python解释器、CUDA驱动、PyTorch二进制包、ultralytics源码及OpenCV等多组件的ABI级协同。WinError 1114错误本质是Visual C++ Runtime版本冲突,而ultralytics 8.1.30中C3k2模块消失则反映模型架构演进与权重兼容性断层。技术价值在于建立可复现、可诊断的四阶段验证体系:环境隔离基底→CUDA-Py

weixin_34006965的博客 542

OpenCV 4.8.0编译与DNN推理实战:新特性与踩坑指南

在计算机视觉领域,以OpenCV为代表的图像处理库已成为工程落地的核心基础设施。从图像预处理、特征提取到目标检测与深度学习推理,OpenCV的版本迭代始终影响着开发者的技术选型与项目效率。其中,深度学习推理模块(DNN)的兼容性与性能表现,更是迁移到新版本时需要重点考量的因素。OpenCV 4.8.0作为一次稳中有进的升级,在ONNX算子支持、CUDA加速优化、GStreamer稳定性等方面均有显著改进,为模型部署与视频流处理提供了更可靠的基础。对于需要自研视觉方案的开发者,从源码编译OpenCV 4.8.

weixin_34056162的博客 524

YOLO模型C++部署实战:OpenCV DNN与ONNX Runtime双引擎方案

模型部署是将训练好的深度学习模型集成到生产环境的关键环节,其核心在于实现高效、稳定的推理服务。从技术原理上看,部署过程涉及模型格式转换、推理引擎集成、前后处理优化等步骤,旨在平衡性能、资源消耗与开发效率。对于计算机视觉领域,尤其是目标检测任务,C++因其高性能和低延迟成为生产环境的首选语言。OpenCV DNN模块以其易集成性和对图像处理的天然支持,适合快速原型验证;而ONNX Runtime作为专用推理引擎,通过其丰富的Execution Providers(如CUDA、TensorRT)能最大化硬件加速

weixin_34025151的博客 425

YOLOv8源码实战:从环境搭建到模型部署的完整指南

目标检测是计算机视觉的核心任务之一,旨在识别图像中的物体并定位其位置。其原理通常基于深度学习模型,通过卷积神经网络提取特征,并预测边界框和类别。这项技术具有极高的实用价值,是实现自动化视觉感知的关键。在工业质检、安防监控、自动驾驶等众多应用场景中,目标检测都扮演着重要角色。本文将围绕YOLOv8这一先进的实时目标检测框架,深入解析其源码结构,并分享从零开始搭建环境、准备自定义数据集、进行模型训练与调优,直至最终将模型导出为ONNX等格式并进行部署的完整工程实践流程。过程中,我们将重点探讨如何解决环境依赖、C

weixin_34204722的博客 303

使用C++部署yolov11目标检测的tensorrt模型支持图片视频推理演示源码+项目说明

【测试通过环境】 vs2019 cmake==3.24.3 cuda11.7.1+cudnn8.8.0 tensorrt==8.6.1.6 opencv==4.8.0 【博文地址】https://blog.csdn.net/FL1623863129/article/details/142685415

Ultralytics Conda 环境构建:解决 CUDA/PyTorch/ONNX ABI 兼容性断层

Conda 不是 pip 的替代品,而是管理二进制兼容性契约的工程基础设施。其核心价值在于精确约束 CUDA Toolkit 版本、PyTorch 编译 ABI、ONNX Runtime 后端绑定等底层依赖关系,从而保障 YOLO 模型训练、导出与推理的可重现性。在多硬件平台(RTX 4090、Jetson Orin、M1 Mac、WSL2)上,Ultralytics 的稳定运行高度依赖 conda channel 优先级配置、cudatoolkit 与驱动版本对齐、以及 onnxruntime-gpu 与

weixin_30292745的博客 470

sklearn2pmml xgboost缺失值(missing)处理的坑

sklearn2pmml xgboost缺失值(missing)如何处理 今天同事在部署xgboost pmml模型时遇到了大坑,线上spark预测和本地python预测结果怎么都不对应,记录一下处理过程。 看了下同事的代码,貌似也没有问题 from sklearn2pmml import PMMLPipeline from sklearn2pmml import sklearn2pmml fro...

Skycrab 8969
上一篇: YOLO系列——模型查看器Netron(六)
下一篇: YOLO系列——Ubuntu20.04下通过conda虚拟环境安装Labelme(七)
JANGHIGH
博客等级 码龄16年 1万+粉丝 315原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值