当前位置: 首页 > news >正文

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;}// 显性batchconst auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);// 调用builder的createNetworkV2方法创建networkauto 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;}// 设置最大batchsizebuilder->setMaxBatchSize(1);// 设置最大工作空间(新版本的TensorRT已经废弃了setWorkspaceSize)config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1 << 30);// 设置精度,不设置是FP32,设置为FP16,设置为INT8需要额外设置calibratorconfig->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;}// 序列化保存enginestd::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函数创建blobcv::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);  // 新版本中是enqueueV2context.enqueueV2(buffers, stream, nullptr);  // 新版本中是enqueueV2// 将推理结果从设备拷贝到主机上:outputCHECK(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函数创建blobcv::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 arrayfor (int i = 0; i < 10; ++i) {result[i] = std::exp(input[i]);sum += result[i];}// Normalize the values by dividing each element by the sumfor (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);  // 新版本中是enqueueV2context.enqueueV2(buffers, stream, nullptr);  // 新版本中是enqueueV2// 将推理结果从设备拷贝到主机上:outputCHECK(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;}// 创建推理运行时runtimeauto 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");// 反序列化生成engineauto mEngine = std::shared_ptr<nvinfer1::ICudaEngine>(runtime->deserializeCudaEngine(plan.data(), plan.size()));if (!mEngine){return -1;}// 创建执行上下文contextauto 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);// softmaxstd::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/)# opencvfind_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)

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

结束语

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

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

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

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

相关文章:

TensorRT之LeNet5部署(onnx方式)

文章目录 前言LeNet-5部署1.ONNX文件导出2.TensorRT构建阶段(TensorRT模型文件)&#x1f9c1;创建Builder&#x1f367;创建Network&#x1f36d;使用onnxparser构建网络&#x1f36c;优化网络&#x1f361;序列化模型&#x1f369;释放资源 3.TensorRT运行时阶段(推理)&#x…...

Xilinx FPGA平台DDR3设计详解(二):DDR SDRAM组成与工作过程

本文主要介绍一下DDR SDRAM的基本组成以及工作过程&#xff0c;方便大家更好的理解和掌握DDR的控制与读写。 一、DDR SDRAM的基本组成 1、SDRAM的基本单元 SDRAM的基本单元是一个CMOS晶体管和一个电容组成的电路。 晶体管最上面的一端&#xff0c;称作栅极&#xff0c;通过…...

ios(swiftui) 属性包装器详解

目录 1. State 2. Binding 3. ObservedObject 和Published 4. StateObject 5. EnvironmentObject和Environment 6. AppStorage 在 SwiftUI 中&#xff0c;属性包装器用于增强和管理视图的状态&#xff0c;以及处理视图与数据模型之间的绑定和交互。下面是一些常见…...

【智能家居】面向对象编程OOP和设计模式(工厂模式)

面向对象编程 类和对象 面向对象编程和面向过程编程区别 设计模式 软件设计模式按类型分 工厂模式 面向对象编程 面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是一种程序设计范式&#xff0c;其中程序被组织成对象的集合&#xff0c;每…...

Docker安装Memcached+Python调用

简介&#xff1a;Memcached是一个通用的分布式内存缓存系统。它通常用于通过在RAM中缓存数据和对象来加速动态数据库驱动的网站&#xff0c;以减少必须读取外部数据源&#xff08;如数据库或API&#xff09;的次数。Memcached的API提供了一个分布在多台机器上的非常大的哈希表。…...

网页开发 HTML

目录 HTML概述 HTML结构 HTML标签语法 基本标签 标题标签 换行标签 段落标签 文本格式化标签 特殊符号 div和span标签 超链接标签 锚点 img标签 列表标签 表格标签 表单标签 HTML概述 HTML&#xff0c;即超文本标记语言&#xff08;HyperText Markup Language …...

SHAP(五):使用 XGBoost 进行人口普查收入分类

SHAP&#xff08;五&#xff09;&#xff1a;使用 XGBoost 进行人口普查收入分类 本笔记本演示了如何使用 XGBoost 预测个人年收入超过 5 万美元的概率。 它使用标准 UCI 成人收入数据集。 要下载此笔记本的副本&#xff0c;请访问 github。 XGBoost 等梯度增强机方法对于具有…...

LeetCode 8 字符串转整数

题目描述 字符串转换整数 (atoi) 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格检查下一…...

前缀和 LeetCode1423. 可获得的最大点数

几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#xff0c;最终你必须正好拿 k 张卡牌。 你的点数就是你拿到手中的所有卡牌的点数之和。 给你一个整数数组 cardPoi…...

探索意义的深度:自然语言处理中的语义相似性

一、说明 语义相似度&#xff0c;反应出计算机对相同内容&#xff0c;不同表达的识别能力。因而识别范围至少是个句子&#xff0c;最大范围就是文章&#xff0c;其研究方法有所区别。本文将按照目前高手的研究成绩&#xff0c;作为谈资介绍给诸位。 二、语义相似度简介 自然语言…...

WT2605-24SS高品质录音语音芯片:实时输出、不保存本地,引领音频技术新潮流

随着科技的快速发展&#xff0c;高品质音频技术成为了现代社会不可或缺的一部分。在这个追求高品质、高效率的时代&#xff0c;唯创知音推出的WT2605-24SS高品质录音芯片&#xff0c;以其独特的功能和卓越的性能&#xff0c;引领着音频技术的新潮流。 首先&#xff0c;WT2605-…...

Git 合并冲突解决步骤

Git 合并冲突解决步骤 1. 找到并打开冲突文件 定位到发生冲突的文件。可以通过 Git 的命令行输出找到这些文件。例如&#xff1a; pom.xmlsrc/main/java/com/zzm/config/SecurityConfig.javasrc/main/java/com/zzm/service/chat/UserConversationsServiceImpl.javasrc/main/…...

Windows核心编程 注册表

目录 注册表概述 打开关闭注册表 创建删除子健 查询写入删除键值 子健和键值的枚举 常用注册表操作 注册表概述 注册表是Windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心"数据库"&#xff0c;也可以说是一个非常巨大的树状分层结构的…...

【算法专题】二分查找

二分查找 二分查找1. 二分查找2. 在排序数组中查找元素的第一和最后一个位置3. 搜索插入位置4. x 的平方根5. 山脉数组的峰顶索引6. 寻找峰值7. 寻找旋转排序数组中的最小值8. 点名 二分查找 1. 二分查找 题目链接 -> Leetcode -704.二分查找 Leetcode -704.二分查找 题…...

中国消费电子行业发展趋势及消费者需求洞察|徐礼昭

一、引言 近年来&#xff0c;随着科技的飞速发展&#xff0c;消费电子行业面临着前所未有的挑战与机遇。本文将从行业发展趋势、消费者需求洞察以及企业数字化转型的方向和动作三个方面&#xff0c;对消费电子行业进行深入剖析。 二、消费电子行业发展趋势 5G技术的普及和应…...

UE学习C++(1)创建actor

创建新C类 在 虚幻编辑器 中&#xff0c;点击 文件&#xff08;File&#xff09; 下拉菜单&#xff0c;然后选择 新建C类...&#xff08;New C Class...&#xff09; 命令&#xff1a; 此时将显示 选择父类&#xff08;Choose Parent Class&#xff09; 菜单。可以选择要扩展的…...

【CTA认证】Android8实现android6以下的应用运行时也要申请权限

需求 CTA入网认证&#xff0c;要求低版本比如Android6以下的应用&#xff0c;运行时&#xff0c;也需要有运行时权限(Runtime Permission)功能&#xff0c;不能默认就取到权限&#xff0c;必须人工在设置中打开才可。 环境 Android 8 实现 frameworks 修改思路是所有APP都…...

gRPC Java、Go、PHP使用例子

文章目录 1、Protocol Buffers定义接口1.1、编写接口服务1.2、Protobuf基础数据类型 2、服务器端实现2.1、生成gRPC服务类2.2、Java服务器端实现 3、java、go、php客户端实现3.1、Java客户端实现3.2、Go客户端实现3.3、PHP客户端实现 本文例子是在Window平台测试&#xff0c;Ja…...

前端知识笔记(十九)———px,em,rem,vw,vh之间的区别

一&#xff0c;px&#xff08;像素&#xff09;&#xff1a;像素是屏幕上显示的最小单位&#xff0c;它是固定的&#xff0c;不随页面缩放而改变大小。在响应式设计中&#xff0c;使用像素单位可能会导致布局在不同屏幕尺寸上显示不一致。例如&#xff1a;现在在你电脑上一个字…...

docker部署frp穿透内网

文章目录 &#xff08;1&#xff09;部署frps服务器&#xff08;2&#xff09;部署frpc客户端&#xff08;3&#xff09;重启与访问frp&#xff08;4&#xff09;配置nginx反向代理 &#xff08;1&#xff09;部署frps服务器 docker安装参考文档&#xff1a;docker基本知识 1…...

使用pytorch从零开始实现迷你GPT

生成式建模知识回顾: [1] 生成式建模概述 [2] Transformer I&#xff0c;Transformer II [3] 变分自编码器 [4] 生成对抗网络&#xff0c;高级生成对抗网络 I&#xff0c;高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II…...

tp6框架 万级数据入库 php函数优化

将万级数据入库并判断有无 没有则新增 上篇是用mysql的replace into实现 本篇是另一种方法 这是我的数据格式&#xff1a; $data [ [ KCH > value1, other_column1 > value_other1_1, other_column2 > value_other2_1, ], [ KCH > value2, other_column…...

TwinCAT3一个PLC设备里多个程序工程之间通讯

目录 1、创建TwinCAT3工程&#xff0c;再分别创建两个PLC程序工程 2、PLC1工程中添加如下代码&#xff0c;然后编译重新生成PLC1工程 3、PLC2工程中添加如下代码&#xff0c;然后编译重新生成PLC2工程 4、变量关联 5、一个PLC运行多个PLC工程设置 7、工程下载链接 1、创建…...

python弹球小游戏

import pygame import random# 游戏窗口大小 WIDTH 800 HEIGHT 600# 定义颜色 WHITE (255, 255, 255) BLACK (0, 0, 0) RED (255, 0, 0) GREEN (0, 255, 0) BLUE (0, 0, 255)# 球的类 class Ball:def __init__(self):self.radius 10self.speed [random.randint(2, 4),…...

mongoose学习记录

mongoose安装和连接数据库 npm i mongoose导入mongoose const mongoose require(mongoose) mongoose.set("strictQuery",true)连接数据库 mongoose.connect(mongodb:127.0.0.1:27017/test)设置回调 mongoose.connection.on(open,()>{console.log("连接成…...

边缘与云或边缘加云:前进的方向是什么?

边缘计算使数据处理更接近数据源&#xff0c;以及由此产生的行动或决策的对象。通过设计&#xff0c;它可以改变数十亿物联网和其他设备存储、处理、分析和通信数据的方式。 边缘计算使数据处理更接近数据源&#xff0c;以及由此产生的行动或决策的对象。这与传统的体系结构形成…...

蓝桥杯第1037题子串分值和 C++ 字符串 逆向思维 巧解

题目 思路和解题方法 方案一——遍历哈希表 仅能过60%样例,大多数同学都用的该方法&#xff0c;就不过多赘述 #include <iostream> #include <unordered_map> using namespace std; int main() {string s;cin >> s;int n s.size();int res n;for (int i 0…...

力扣题:字符串的反转-11.23

力扣题-11.23 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;557. 反转字符串中的单词 III 解题思想&#xff1a;先读取单词&#xff0c;然后将单词进行翻转即可 class Solution(object):def reverseWords(self, s):""":type s…...

【软件测试】盘一盘工作中遇到的 Redis 异常测试

在测试工作中&#xff0c;涉及到与 redis 交互的场景变的越来越多了。关于redis本身就不作赘述了&#xff0c;网上随便搜&#xff0c;本人也做过一些整理。 今天只来复盘一下&#xff0c;在测试过程中与 redis 的二三事儿。其中提到的案例是经过抽象化的&#xff0c;用作辅助说…...

14.Oracle中RegExp_Like 正则表达式基本用法

--基本用法&#xff0c;是否包含某字符串 like %36% select * from k_micfo where regexp_like(loginid,36);if regexp_like(str,^[0-9\.]$) --只包含数字0-9&#xff0c;,小数点.--oracle判断字段是否是纯数字 (四种写法结果一样&#xff09; select * from k_micfo where r…...