TensorRT之LeNet5部署(onnx方式)

文章目录

  • 前言
  • LeNet-5部署
    • 1.ONNX文件导出
    • 2.TensorRT构建阶段(TensorRT模型文件)
      • 🧁创建Builder
      • 🍧创建Network
      • 🍭使用onnxparser构建网络
      • 🍬优化网络
      • 🍡序列化模型
      • 🍩释放资源
    • 3.TensorRT运行时阶段(推理)
      • 🍄创建Runtime
      • 🍅反序列化模型
      • 🍒创建ExecutionContext
      • 🍓执行推理
      • 🍎释放资源
    • 4.编译和运行
  • 结束语


  • 💂 个人主页:风间琉璃
  • 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
  • 💬 如果文章对你有帮助欢迎关注点赞收藏(一键三连)订阅专栏

前言

提示:这里可以添加本文要记录的大概内容:

本文记录一下TensorRT部署流程,上一篇使用wts文件构造网络结构,这篇会使用ONNX构造网络。关于TensorRT的基础知识,参考前一篇文章:TensorRT部署(wts)


LeNet-5部署

1.ONNX文件导出

关于LeNet-5网络模型的搭建、训练以及保存参考上面的链接文字。这一步导出ONNX文件默认你已经有了LeNet-5的权重文件(pth)。

导出ONNX文件源程序如下:

import torch
from model import LeNet

# s实例化网络
model = LeNet()
# 加载网络模型
model.load_state_dict(torch.load('Lenet.pth'))

model.eval()

input_names = ['input']
output_names = ['output']

# 创建一个示例输入
input_data = torch.randn(1, 1, 28, 28)  # 根据您的模型需要调整输入尺寸

# 定义输出路径
onnx_file_path = "LeNet.onnx"

# 转换为 ONNX 模型
torch.onnx.export(model, input_data, onnx_file_path, input_names=input_names, output_names=output_names, verbose=True)

将导出的ONNX文件使用Netron打开,Netron链接:Netron
在这里插入图片描述
可以看到和我们在model中定义的网络结构是一样的。

2.TensorRT构建阶段(TensorRT模型文件)

🧁创建Builder

// 创建TensorRT的Builder对象
auto builder = std::unique_ptr<nvinfer1::IBuilder>(nvinfer1::createInferBuilder(gLogger));
if (!builder)
{
	std::cerr << "Failed to create builder" << std::endl;
	return -1;
}

使用了TensorRT的createInferBuilder函数创建了一个nvinfer1::IBuilder实例,并将其包装在std::unique_ptr中,这样可以确保在作用域结束时正确释放资源。

std::unique_ptr 的模板参数是 nvinfer1::IBuilder,因此 builder 的类型是 std::unique_ptr< nvinfer1::IBuilder>。这表示 builder 是一个独占所有权的智能指针,管理一个 nvinfer1::IBuilder 类型的对象。在上一节中创建Builder如下

nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(gLogger);

这里 builder 是一个原始指针,你需要手动管理其生命周期和释放内存。这容易导致内存泄漏或悬挂指针问题,因为你需要确保在使用完 builder 后调用 delete 或相应的释放函数。

这里使用了 std::unique_ptr,它是一个 C++ 智能指针,能够自动管理对象的生命周期。当 builder 超出作用域时,std::unique_ptr 会自动释放其拥有的内存。这有助于防止内存泄漏,并提高代码的安全性。

🍧创建Network

在TensorRT中使用builder的成员函数createNetworkV2来构建network。

// 显性batch
const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
// 调用builder的createNetworkV2方法创建network
auto network = std::unique_ptr<nvinfer1::INetworkDefinition>(builder->createNetworkV2(explicitBatch));
if (!network)
{
	std::cout << "Failed to create network" << std::endl;
	return -1;
}

创建一个 TensorRT 网络,并使用显式批处理标志。显式批处理允许你在运行推理时动态设置批次大小,而不是在构建引擎时固定批次大小。

🍭使用onnxparser构建网络

// 读取ONNX模型文件
char* onnxPath = "/home/mingfei/codeRT/test/lenet_onnx/LeNet.onnx";
std::ifstream onnxFile(onnxPath, std::ios::binary);
if (!onnxFile)
{
    std::cerr << "无法打开ONNX模型文件: " << onnxPath << std::endl;
    return 1;
}

// 创建onnxparser,用于解析onnx文件
auto parser = std::unique_ptr<nvonnxparser::IParser>(nvonnxparser::createParser(*network, gLogger));
// 调用onnxparser的parseFromFile方法解析onnx文件
auto parsed = parser->parseFromFile(onnxPath, static_cast<int>(gLogger.getReportableSeverity()));
if (!parsed)
{
    std::cout << "Failed to parse onnx file" << std::endl;
    return -1;
}

首先将上面导出的ONNX文件加载进来,然后使用 TensorRT 的 ONNX 解析器进行解析。

createParser函数创建一个 ONNX 解析器对象,这个解析器对象是一个用于解析 ONNX 模型的实例。

inline IParser* createParser(nvinfer1::INetworkDefinition& network, nvinfer1::ILogger& logger)
network:表示 TensorRT 网络的对象。解析器将根据 ONNX 模型的信息构建这个网络。
logger:日志记录器,用于记录解析器操作的日志信息

parseFromFile函数使用解析器解析来自 ONNX 模型文件的模型信息。

virtual bool parseFromFile(const char* onnxModelFile, int verbosity) = 0;
onnxModelFile:ONNX 模型文件的路径,指定要解析的 ONNX 模型文件。
verbosity:解析过程中的详细程度或冗余程度。这通常是一个整数值,用于控制解析器的输出信息的详细级别。

这两个函数的联合使用允许您创建一个 ONNX 解析器对象,然后使用该解析器对象从文件中读取 ONNX 模型并解析出 TensorRT 网络。解析完成后,您就可以使用 TensorRT 的网络进行后续的优化推理

🍬优化网络

添加相关Builder 的配置。createBuilderConfig接口被用来指定TensorRT应该如何优化模型。

// 优化网络
auto config = std::unique_ptr<nvinfer1::IBuilderConfig>(builder->createBuilderConfig());
if (!config)
{
    std::cout << "Failed to create config" << std::endl;
    return -1;
}

// 设置最大batchsize
builder->setMaxBatchSize(1);
// 设置最大工作空间(新版本的TensorRT已经废弃了setWorkspaceSize)
config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1 << 30);
// 设置精度,不设置是FP32,设置为FP16,设置为INT8需要额外设置calibrator
config->setFlag(nvinfer1::BuilderFlag::kFP16);

在示例代码中,仅配置workspace(workspace 就是 tensorrt 里面算子可用的内存空间 )大小、运行时batch size和精度。

🍡序列化模型

使用 TensorRT 的 builder 对象根据配置创建一个序列化的引擎,并将其保存到文件中。

// 使用buildSerializedNetwork方法创建engine,可直接返回序列化的engine(原来的buildEngineWithConfig方法已经废弃,需要先创建engine,再序列化)
auto plan = std::unique_ptr<nvinfer1::IHostMemory>(builder->buildSerializedNetwork(*network, *config));
if (!plan)
{
    std::cout << "Failed to create engine" << std::endl;
    return -1;
}

// 序列化保存engine
std::ofstream engine_file("lenet5.engine", std::ios::binary);
assert(engine_file.is_open() && "Failed to open engine file");
engine_file.write((char *)plan->data(), plan->size());
engine_file.close();

🍩释放资源

因为使用了智能指针,所以不需要手动释放资源。

构建阶段源程序

#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>

#include <NvInfer.h>
#include <NvOnnxParser.h> // onnxparser头文件
#include "logging.h"

using namespace nvinfer1;

static Logger gLogger;

int main()
{
    // 读取ONNX模型文件
    char* onnxPath = "/home/mingfei/codeRT/test/lenet_onnx/LeNet.onnx";
    std::ifstream onnxFile(onnxPath, std::ios::binary);
    if (!onnxFile)
    {
        std::cerr << "无法打开ONNX模型文件: " << onnxPath << std::endl;
        return 1;
    }

    // 创建TensorRT的Builder对象
    auto builder = std::unique_ptr<nvinfer1::IBuilder>(nvinfer1::createInferBuilder(gLogger));
    if (!builder)
    {
        std::cerr << "Failed to create builder" << std::endl;
        return -1;
    }


    // 显性batch
    const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
    // 调用builder的createNetworkV2方法创建network
    auto network = std::unique_ptr<nvinfer1::INetworkDefinition>(builder->createNetworkV2(explicitBatch));
    if (!network)
    {
        std::cout << "Failed to create network" << std::endl;
        return -1;
    }
   

    // 创建onnxparser,用于解析onnx文件
    auto parser = std::unique_ptr<nvonnxparser::IParser>(nvonnxparser::createParser(*network, gLogger));
    // 调用onnxparser的parseFromFile方法解析onnx文件
    auto parsed = parser->parseFromFile(onnxPath, static_cast<int>(gLogger.getReportableSeverity()));
    if (!parsed)
    {
        std::cout << "Failed to parse onnx file" << std::endl;
        return -1;
    }

    // 优化网络
    auto config = std::unique_ptr<nvinfer1::IBuilderConfig>(builder->createBuilderConfig());
    if (!config)
    {
        std::cout << "Failed to create config" << std::endl;
        return -1;
    }

    // 设置最大batchsize
    builder->setMaxBatchSize(1);
    // 设置最大工作空间(新版本的TensorRT已经废弃了setWorkspaceSize)
    config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1 << 30);
    // 设置精度,不设置是FP32,设置为FP16,设置为INT8需要额外设置calibrator
    config->setFlag(nvinfer1::BuilderFlag::kFP16);

    // 使用buildSerializedNetwork方法创建engine,可直接返回序列化的engine(原来的buildEngineWithConfig方法已经废弃,需要先创建engine,再序列化)
    auto plan = std::unique_ptr<nvinfer1::IHostMemory>(builder->buildSerializedNetwork(*network, *config));
    if (!plan)
    {
        std::cout << "Failed to create engine" << std::endl;
        return -1;
    }


    // 序列化保存engine
    std::ofstream engine_file("lenet5.engine", std::ios::binary);
    assert(engine_file.is_open() && "Failed to open engine file");
    engine_file.write((char *)plan->data(), plan->size());
    engine_file.close();

    // 释放资源 
    // 因为使用了智能指针,所以不需要手动释放资源

    std::cout << "Engine build success!" << std::endl;

    return 0;
}

 


3.TensorRT运行时阶段(推理)

在生成Engine文件后,在推理阶段的流程和上一篇的基本是一样的,这里就简单介绍一下,具体的可以参考前面一篇。

🍄创建Runtime

// 创建推理运行时runtime
auto runtime = std::unique_ptr<nvinfer1::IRuntime>(nvinfer1::createInferRuntime(gLogger.getTRTLogger()));
if (!runtime)
{
    std::cout << "runtime create failed" << std::endl;
    return -1;
}

🍅反序列化模型

// 反序列化生成engine 

// 加载模型文件
auto plan = load_engine_file("lenet5.engine");
// 反序列化生成engine
auto mEngine = std::shared_ptr<nvinfer1::ICudaEngine>(runtime->deserializeCudaEngine(plan.data(), plan.size()));
if (!mEngine)
{
    return -1;
}

🍒创建ExecutionContext

// 创建执行上下文context
auto context = std::unique_ptr<nvinfer1::IExecutionContext>(mEngine->createExecutionContext());
if (!context)
{
    std::cout << "context create failed" << std::endl;
    return -1;
}

🍓执行推理

在进行推理之前需要对输入的图片的图片的进行预处理,预处理的操作需要保持在网络训练的时候的操作一样的,如归一化,减均值等。

cv::Mat preprocess(cv::Mat &image)
{
    // 获取图像的形状(高度、宽度和通道数)
    int height = image.rows;
    int width = image.cols;
    int channels = image.channels();

    // 打印图像的形状
    std::cout << "Image Shape: Height = " << height << ", Width = " << width << ", Channels = " << channels << std::endl;

     // 使用blobFromImage函数创建blob
    cv::Mat blob;
    cv::dnn::blobFromImage(image, blob, 1.0 / 255.0, cv::Size(28, 28), cv::Scalar(0.5));

    // 获取图像的形状(高度、宽度和通道数)
    height = blob.rows;
    width = blob.cols;
    channels = blob.channels();

    // 打印图像的形状
    std::cout << "Blob Shape: Height = " << height << ", Width = " << width << ", Channels = " << channels << std::endl;
    return blob;
}

然后将处理后的图片数据转成float的指针类型,为后面的推理做准备。

// 获取blob的数据指针
uchar* ucharData = blob.ptr<uchar>();  // 使用uchar*类型的指针
// 获取图像数据指针
float* data = reinterpret_cast<float*>(ucharData);

然后需要将CPU的数据传输到GPU上进行计算,计算结束后需要将结果传回CPU。

// 执行推理
float prob[OUTPUT_SIZE];
inference(*context, data, prob, 1);

// 执行推理
void inference(nvinfer1::IExecutionContext& context, float* input, float* output, int batchSize)
{
    // 获取与上下文相关的引擎
    const nvinfer1::ICudaEngine& engine = context.getEngine();

    // 为输入和输出设备缓冲区创建指针以传递给引擎
    assert(engine.getNbBindings() == 2);
    void* buffers[2];

    // 为了绑定缓冲区,需要知道输入和输出张量的名称
    const int inputIndex = engine.getBindingIndex(INPUT_BLOB_NAME);
    const int outputIndex = engine.getBindingIndex(OUTPUT_BLOB_NAME);

    // 在设备上创建输入和输出缓冲区
    CHECK(cudaMalloc(&buffers[inputIndex], batchSize * 1 * INPUT_H * INPUT_W * sizeof(float)));
    CHECK(cudaMalloc(&buffers[outputIndex], batchSize * OUTPUT_SIZE * sizeof(float)));

    // 创建流
    cudaStream_t stream;
    CHECK(cudaStreamCreate(&stream));

    // 将输入批量数据异步 DMA 到设备,异步对批量进行推理,然后异步 DMA 输出回主机
    CHECK(cudaMemcpyAsync(buffers[inputIndex], input, batchSize * 1 * INPUT_H * INPUT_W * sizeof(float), cudaMemcpyHostToDevice, stream));
    //context.enqueue(batchSize, buffers, stream, nullptr);  // 新版本中是enqueueV2
    context.enqueueV2(buffers, stream, nullptr);  // 新版本中是enqueueV2
    // 将推理结果从设备拷贝到主机上:output
    CHECK(cudaMemcpyAsync(output, buffers[outputIndex], batchSize * OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost, stream));
    cudaStreamSynchronize(stream);

    // 释放流和缓冲区
    cudaStreamDestroy(stream);
    CHECK(cudaFree(buffers[inputIndex]));
    CHECK(cudaFree(buffers[outputIndex]));
}

然后就是对结果进行处理,如softmax,这里由于的做的是分类模型,所以需要找到置信度最大的概率和标签。

// softmax
std::vector<float> result = softmax(prob);

// 找到最大值和索引
auto maxElement = std::max_element(result.begin(), result.end());
float maxValue = *maxElement;
int maxIndex = std::distance(result.begin(), maxElement);


// 打印结果
std::cout << "probability: " << maxValue << std::endl;
std::cout << "Number is : " << maxIndex << std::endl;
// 显示
std::ostringstream text;
text  << "Predict: " << maxIndex;
cv::resize(image,image,cv::Size(400,400));
cv::putText(image, text.str(), cv::Point(10, 50), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
// 保存图像到当前路径
cv::imwrite("output_image.jpg", image);

🍎释放资源

因为使用了unique_ptr,所以不需要手动释放

运行时阶段源程序


#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>
#include <algorithm>

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>

#include <NvInfer.h>
#include <NvOnnxParser.h> // onnxparser头文件
#include "logging.h"


static Logger gLogger;

static const int INPUT_H = 28;
static const int INPUT_W = 28;
static const int OUTPUT_SIZE = 10;

const char* INPUT_BLOB_NAME = "input";
const char* OUTPUT_BLOB_NAME = "output";

#define CHECK(status) \
    do\
    {\
        auto ret = (status);\
        if (ret != 0)\
        {\
            std::cerr << "Cuda failure: " << ret << std::endl;\
            abort();\
        }\
    } while (0)

// 加载模型文件
std::vector<unsigned char> load_engine_file(const std::string &file_name)
{
    std::vector<unsigned char> engine_data;
    // 打开二进制文件流
    std::ifstream engine_file(file_name, std::ios::binary);
    // 检查文件是否成功打开
    assert(engine_file.is_open() && "Unable to load engine file.");
    // 定位到文件末尾以获取文件长度
    engine_file.seekg(0, engine_file.end);
    int length = engine_file.tellg();

    // 调整容器大小以存储整个文件的数据
    engine_data.resize(length);

    // 重新定位到文件开头
    engine_file.seekg(0, engine_file.beg);

    // 读取文件数据到容器中
    engine_file.read(reinterpret_cast<char *>(engine_data.data()), length);
    return engine_data;
}


cv::Mat preprocess(cv::Mat &image)
{
    // 获取图像的形状(高度、宽度和通道数)
    int height = image.rows;
    int width = image.cols;
    int channels = image.channels();

    // 打印图像的形状
    std::cout << "Image Shape: Height = " << height << ", Width = " << width << ", Channels = " << channels << std::endl;

     // 使用blobFromImage函数创建blob
    cv::Mat blob;
    cv::dnn::blobFromImage(image, blob, 1.0 / 255.0, cv::Size(28, 28), cv::Scalar(0.5));

    // 获取图像的形状(高度、宽度和通道数)
    height = blob.rows;
    width = blob.cols;
    channels = blob.channels();

    // 打印图像的形状
    std::cout << "Blob Shape: Height = " << height << ", Width = " << width << ", Channels = " << channels << std::endl;
    return blob;
}
std::vector<float> softmax(const float input[10])
{
    std::vector<float> result(10);
    float sum = 0.0;

    // Calculate e^x for each element in the input array
    for (int i = 0; i < 10; ++i) {
        result[i] = std::exp(input[i]);
        sum += result[i];
    }

    // Normalize the values by dividing each element by the sum
    for (float& value : result) {
        value /= sum;
    }

    return result;
}


// 执行推理
void inference(nvinfer1::IExecutionContext& context, float* input, float* output, int batchSize)
{
    // 获取与上下文相关的引擎
    const nvinfer1::ICudaEngine& engine = context.getEngine();

    // 为输入和输出设备缓冲区创建指针以传递给引擎
    assert(engine.getNbBindings() == 2);
    void* buffers[2];

    // 为了绑定缓冲区,需要知道输入和输出张量的名称
    const int inputIndex = engine.getBindingIndex(INPUT_BLOB_NAME);
    const int outputIndex = engine.getBindingIndex(OUTPUT_BLOB_NAME);

    // 在设备上创建输入和输出缓冲区
    CHECK(cudaMalloc(&buffers[inputIndex], batchSize * 1 * INPUT_H * INPUT_W * sizeof(float)));
    CHECK(cudaMalloc(&buffers[outputIndex], batchSize * OUTPUT_SIZE * sizeof(float)));

    // 创建流
    cudaStream_t stream;
    CHECK(cudaStreamCreate(&stream));

    // 将输入批量数据异步 DMA 到设备,异步对批量进行推理,然后异步 DMA 输出回主机
    CHECK(cudaMemcpyAsync(buffers[inputIndex], input, batchSize * 1 * INPUT_H * INPUT_W * sizeof(float), cudaMemcpyHostToDevice, stream));
    //context.enqueue(batchSize, buffers, stream, nullptr);  // 新版本中是enqueueV2
    context.enqueueV2(buffers, stream, nullptr);  // 新版本中是enqueueV2
    // 将推理结果从设备拷贝到主机上:output
    CHECK(cudaMemcpyAsync(output, buffers[outputIndex], batchSize * OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost, stream));
    cudaStreamSynchronize(stream);

    // 释放流和缓冲区
    cudaStreamDestroy(stream);
    CHECK(cudaFree(buffers[inputIndex]));
    CHECK(cudaFree(buffers[outputIndex]));
}

int main()
{

    // 读取图像
    cv::Mat image = cv::imread("/home/mingfei/codeRT/test/lenet_onnx/8.jpg");
    // 检查图像是否成功加载
    if (image.empty()) {
        std::cerr << "Error: Unable to read the image." << std::endl;
        return -1;
    }

    // 创建推理运行时runtime
    auto runtime = std::unique_ptr<nvinfer1::IRuntime>(nvinfer1::createInferRuntime(gLogger.getTRTLogger()));
    if (!runtime)
    {
        std::cout << "runtime create failed" << std::endl;
        return -1;
    }
    
    // 反序列化生成engine 

    // 加载模型文件
    auto plan = load_engine_file("lenet5.engine");
    // 反序列化生成engine
    auto mEngine = std::shared_ptr<nvinfer1::ICudaEngine>(runtime->deserializeCudaEngine(plan.data(), plan.size()));
    if (!mEngine)
    {
        return -1;
    }

    // 创建执行上下文context
    auto context = std::unique_ptr<nvinfer1::IExecutionContext>(mEngine->createExecutionContext());
    if (!context)
    {
        std::cout << "context create failed" << std::endl;
        return -1;
    }

    // 图像预处理
    cv::Mat blob = preprocess(image);
    // 获取blob的数据指针
    uchar* ucharData = blob.ptr<uchar>();  // 使用uchar*类型的指针
     // 获取图像数据指针
    float* data = reinterpret_cast<float*>(ucharData);

    // 执行推理
    float prob[OUTPUT_SIZE];
    inference(*context, data, prob, 1);
    // softmax
    std::vector<float> result = softmax(prob);

    // 找到最大值和索引
    auto maxElement = std::max_element(result.begin(), result.end());
    float maxValue = *maxElement;
    int maxIndex = std::distance(result.begin(), maxElement);


    // 打印结果
    std::cout << "probability: " << maxValue << std::endl;
    std::cout << "Number is : " << maxIndex << std::endl;
    // 显示
    std::ostringstream text;
    text  << "Predict: " << maxIndex;
    cv::resize(image,image,cv::Size(400,400));
    cv::putText(image, text.str(), cv::Point(10, 50), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
    // 保存图像到当前路径
    cv::imwrite("output_image.jpg", image);

    // 释放资源 
    // 因为使用了unique_ptr,所以不需要手动释放

    return 0;
}

4.编译和运行

整个工程如下所示:
在这里插入图片描述
使用CMakeLists.txt来构建整个工程,lenet.cpp相当于集成了build.cu和runtime.cu,然后将生成的文件保存在build目录下。

  • 生成可执行程序:
    cmake -S . -B build (–> Makefile)
    cmake --build build (–>可执行程序)
  • 运行可执行程序:
    ./build/build
    ./build/runtime

CMakeLists.txt如下,相较于上一个wts工程,需要添加nvonnxparser库的链接,其他基本是一样的。

cmake_minimum_required(VERSION 3.10)

# 支持c++和cuda编译(nvcc)
project(lenet5  LANGUAGES CXX CUDA)  

add_definitions(-std=c++11)

option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)

include_directories(${PROJECT_SOURCE_DIR}/include)
# include and link dirs of cuda and tensorrt, you need adapt them if yours are different
# cuda
include_directories(/usr/local/cuda/include)
link_directories(/usr/local/cuda/lib64)
# tensorrt
include_directories(/usr/include/x86_64-linux-gnu/)
link_directories(/usr/lib/x86_64-linux-gnu/)

# opencv

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

# 生成engine
add_executable(build_engine ${PROJECT_SOURCE_DIR}/build.cu)
target_link_libraries(build_engine nvinfer)  
target_link_libraries(build_engine cudart)
target_link_libraries(build_engine nvonnxparser)
target_link_libraries(build_engine  ${OpenCV_LIBS})

# predict
add_executable(runtime ${PROJECT_SOURCE_DIR}/runtime.cu)
target_link_libraries(runtime nvinfer)  
target_link_libraries(runtime cudart)
target_link_libraries(runtime nvonnxparser)
target_link_libraries(runtime  ${OpenCV_LIBS})

add_definitions(-O2 -pthread)

​运行结果如下:
在这里插入图片描述
在这里插入图片描述

结束语

感谢阅读吾之文章,今已至此次旅程之终站 🛬。

吾望斯文献能供尔以宝贵之信息与知识也 🎉。

学习者之途,若藏于天际之星辰🍥,吾等皆当努力熠熠生辉,持续前行。

然而,如若斯文献有益于尔,何不以三连为礼?点赞、留言、收藏 - 此等皆以证尔对作者之支持与鼓励也 💞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/212641.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工业机器视觉megauging(向光有光)使用说明书(二,轻量级的visionpro)

测试程序暂时支持80万&#xff08;包含1024*768&#xff09;以上的gige工业相机&#xff0c;以后会支持640*480分辨率相机。 我们程序中使用注意力机制&#xff0c;其实就是感兴趣区域&#xff08;roi&#xff0c;你看过我前面博文&#xff0c;就应该明白&#xff09;精神的延…

mac截图Snagit 中文介绍

1.超越普通的屏幕截图 TechSmith Snagit 是唯一具有内置高级图像编辑和屏幕录制功能的屏幕捕获软件。因此&#xff0c;您可以在一个程序中轻松创建高质量的图像和视频。 2.最后&#xff0c;屏幕捕获软件可以完成您所做的一切 快速解释一个过程如果您正在努力清楚地沟通&…

JS前端逆向

前言 js逆向一直没有相关了解&#xff0c;虽然目前渗透遇见的不是很多&#xff0c;大多数遇见的要么不加密&#xff0c;要么无法实现其加密流程&#xff0c;不过最近看到了一个较为简单的站点正好能够逆向出来&#xff0c;就做了简单记录。本文旨在介绍js逆向的一些基础思路&am…

WebGL笔记:矩阵旋转运算的原理和实现

矩阵 矩阵&#xff08;Matrix&#xff09;是一个按照矩形纵横排列的复数集合 矩阵就像一个矩形的阵盘&#xff0c;通过其中纵横排列的元素我们可以摆出不同功能的阵法&#xff0c;比如位移矩阵、旋转矩阵、缩放矩阵 …在矩阵中的每一行&#xff0c;或者每一列数字构成的集合&a…

设计模式-结构型模式之代理设计模式

文章目录 八、代理设计模式 八、代理设计模式 代理设计模式通过代理控制对象的访问&#xff0c;可以详细访问某个对象的方法&#xff0c;在这个方法调用处理&#xff0c;或调用后处理。既(AOP微实现) 。 代理有分静态代理和动态代理&#xff1a; 静态代理&#xff1a;在程序…

POSTGRESQL中如何利用SQL语句快速的进行同环比?

1. 引言 在数据驱动的时代&#xff0c;了解销售、收入或任何业务指标的同比和环比情况对企业决策至关重要。本文将深入介绍如何利用 PostgreSQL 和 SQL 语句快速、准确地进行这两种重要分析。 2. 数据准备 为了演示&#xff0c;假设我们有一张 sales 表&#xff0c;存储了销…

微信订阅号和服务号的区别

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;我们都知道&#xff0c;服务号一个月只能发4次文章&#xff0c;但是订阅号每天都能发文章。不过在接收消息这一方面&#xff0c;服务号群发的消息有消息提醒&#xff0c;并显示在对话框&#xff1b…

重新认识Word——样式

重新认识Word Word样式给所有一级标题加上一级标题样式修改标题一样式&#xff0c;符合要求 正文样式标题前的小黑点导航窗格样式的相互复制Word一键转PPT 话说回来&#xff0c;一个程序员平时可能还看不起office全家桶的软件&#xff0c;但是&#xff0c;在实际的生活运用中&a…

音视频的功耗优化

前言 在应用中&#xff0c;录制与音视频模块往往是高耗能的模块&#xff0c;设备容易发热&#xff0c;影响体验。 什么是功耗优化 手机有多个耗电模块&#xff0c; SOC(CPU&#xff0c;GPU&#xff0c;DDR)&#xff0c;Display&#xff0c;Audio&#xff0c;Video&#xff0…

thinkphp 5.1 对数据库查出来的字段进行预处理

比如数据库的设计是下面这样子&#xff1a; 我想展示的是这个样子&#xff1a; 前端可以处理。 Think PHP的处理方式&#xff1a; 定义属性 &#xff1a; $this->customize 任意值;//这里的之没有作用 <?phpnamespace app\hs\controller\shop;use app\daogou\mo…

Windows用户相关Dos命令演示

Windows用户相关Dos命令演示 1、查看当前用户 命令&#xff1a;whoami 2、查看主机名 命令: hostname 3、查看所有用户 命令&#xff1a;net user 4、查看指定的用户 命令&#xff1a;net user 用户名 5、添加用户 命令&#xff1a;net user 用户名 密码 /add 注…

数据链路层之VLAN基本概念和基本原理

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

ASP.NET Core MVC过滤器

1、过滤器分为授权过滤、资源访问过滤、操作方法&#xff08;Action&#xff09;过滤、结果过滤、异常过滤、终结点过滤。上一次咱们没有说异常过滤和终结点过滤&#xff0c;不过老周后面会说的。对这些过滤器&#xff0c;你有印象就行了。 2、所有过滤器接口都有同步版本和异…

css 3D背景反转实现

body{/* 透视 */perspective: 800px; } div{transform-style:preserve-3d;width:259px;height:396px;margin: 100px auto;position: relative; } div img{position: absolute;width:259px;height:396px;left:0;top:0;transition: all linear 2s;z-index: 0; } div img:nth-chil…

[前 5 名] 最顶级的数据恢复软件解决方案列表

您是否在互联网上找到适用于 Windows PC 的前 5 名最受好评的数据恢复软件解决方案&#xff1f;嗯&#xff0c;在线市场上有很多工具可以恢复已删除的文件。但并不是所有的应用程序都值得使用它。值得信赖的文件恢复工具将有助于快速检索丢失、删除、格式化的数据并从计算机恢复…

flink源码分析之功能组件(四)-slot管理组件II

简介 本系列是flink源码分析的第二个系列&#xff0c;上一个《flink源码分析之集群与资源》分析集群与资源&#xff0c;本系列分析功能组件&#xff0c;kubeclient&#xff0c;rpc&#xff0c;心跳&#xff0c;高可用&#xff0c;slotpool&#xff0c;rest&#xff0c;metrics&…

机器学习笔记 - 异常检测之OneClass SVM算法简述

一、异常检测是什么? 如下图,理想中我们可以找到一个框住大部分正常样本的决策边界,而在边界外部的数据点(蓝点)即视为异常。 但实际情况下数据都没有标签,因此很难定义正常还是不正常。异常检测的主要挑战如下:正常与异常行为之间的界限往往并不明确、不同的应…

WEB安全之Python

WEB安全之python python-pyc反编译 python类似java一样&#xff0c;存在编译过程&#xff0c;先将源码文件*.py编译成 *.pyc文件&#xff0c;然后通过python解释器执行 生成pyc文件 创建一个py文件随便输入几句代码(1.py) 通过python交互终端 >>>import py_compil…

测试Centos上用Gunicorn启动的Django-Web服务在Django源文件有改变的情况下能否自动重载最新源码下的web服务

01-先上传最新的源码文件 参考博文 https://blog.csdn.net/wenhao_ir/article/details/134762966 进行 02-先在Django直接开web服务下修改源码测试 这是没有问题的&#xff0c;会自己重置。 03-开启gunicorn服务 cd /djangoproject/mmdj01/ gunicorn -c /djangoproject/mm…

泊车功能专题介绍 ———— 汽车全景影像监测系统性能要求及试验方法(国标未公布)

文章目录 术语和定义一般要求功能要求故障指示 性能要求响应时间图像时延单视图视野范围平面拼接视图视野平面拼接效果总体要求行列畸变拼接错位及拼接无效区域 试验方法环境条件仪器和设备车辆条件系统响应时间试验图像时延试验单视图视野范围试验平面拼接视图视野试验平面拼接…
最新文章