【深度学习】【TensorRT】【C++】模型转化、环境搭建以及模型部署的详细教程

news2024/9/23 17:00:19

【深度学习】【TensorRT】【C++】模型转化、环境搭建以及模型部署的详细教程

提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论

文章目录

  • 【深度学习】【TensorRT】【C++】模型转化、环境搭建以及模型部署的详细教程
  • 前言
  • 模型转换--pytorch转engine
  • Windows平台搭建依赖环境
    • 安装TensorRT
    • 安装OpenCV
    • 简化部署
  • TensorRT调用onnx模型
    • TensorRT推理核心流程
    • ONNXRuntime推理代码
  • 总结


前言

NVIDIA TensorRT 是一款由英伟达推出的高性能深度学习推理优化库,专门针对NVIDIA的GPU硬件进行优化。它能够将深度学习模型转换为优化的推理引擎,从而在保持精度的同时提高推理速度和效率。TensorRT 专注于优化和加速机器学习模型的推理阶段,特别是对于大规模部署和实时应用场合。TensorRT 的设计目的是为了提供一个高度优化的执行环境,利用 NVIDIA GPU 的硬件特性来实现极致的性能优化。
TensorRT是对NVIDIA GPU最原生的支持。


模型转换–pytorch转engine

Pytorch模型转ENGINE并推理的步骤如下:

  1. 将PyTorch预训练模型文件( .pth 或 .pt 格式)转换成ONNX格式的文件(.onnx格式),这一转换过程在PyTorch环境中进行。
  2. 将转换得到的 .onnx 文件再次转换成ENGINE格式的文件(.engine格式),这一转换过程在安装的TensorRT版本的bin目录下通过trtexec.exe转化而成。
  3. 将转换得到的 .engine文件随后作为输入,调用TensorRT的C++ API来执行模型的推理。

博主使用AlexNet图像分类(五种花分类)进行演示,需要安装pytorch环境,对于该算法的基础知识,可以参考博主【AlexNet模型算法Pytorch版本详解】博文

conda create --name AlexNet python==3.10
conda activate AlexNet
# 根据自己主机配置环境
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# 假设模型转化出错则降级为指定1.16.1版本
pip install onnx==1.16.1

然后把训练模型好的AlexNet.pth模型转成AlexNet.onnx模型,pyorch2onnx.py转换代码如下:

import torch
from model import AlexNet
model = AlexNet(num_classes=5)
weights_path = "./AlexNet.pth"
# 加载模型权重
model.load_state_dict(torch.load(weights_path))
# 模型推理模式
model.eval()
model.cpu()
# 虚拟输入数据
dummy_input1 = torch.randn(1, 3, 224, 224)
# 模型转化函数
torch.onnx.export(model, (dummy_input1), "AlexNet.onnx", verbose=True, opset_version=11)


cmd进入安装好的tensorrt中的bin路径下,将AlexNet.onnx拷贝至此,执行以下命令转成AlexNet.engine模型。安装tensorrt在下一小节中具体讲述。

trtexec.exe --onnx=AlexNet.onnx --saveEngine=AlexNet.engine



【AlexNet.pth百度云链接,提取码:ktq5 】直接下载使用即可。


Windows平台搭建依赖环境

安装TensorRT

官网下载安装文件地址,根据自己的情况选择合适的版本【官方说明】。

推荐优先选择的8.6版本


博主的cuda版本是11.8,cudnn版本是8.9.7,因此选择TensorRT v8.6版本。

cmd命令框下输入nvcc -V 查询cuda版本;
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\vxx.x\include\cudnn_version.h 查看cuda版本.


在TensorRT 10.4 GA下选择TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8.zip下载

双击运行解压后即可:

打开VS 2019:新建新项目---->空项目---->配置项目---->项目路径以及勾选“将解决方案和项目放在同一目录中---->点击创建。

在解决方案–>源文件–>右键添加新建项。这里暂时可以默认空着不做处理。

配置tensorrt:项目---->属性。假设没有新建cpp文件,空项目的属性页就不会存在C/C++这一项目。

添加附加包含目录:Release | x64---->C/C+±—>常规---->附加包含目录。

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\include
D:\C++_demo_tensort\TensorRT-8.6.1.6\include

链接器:Release | x64---->链接器---->常规---->附加库目录。

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\lib\x64
D:\C++_demo_tensort\TensorRT-8.6.1.6\lib

链接器:Release | x64---->链接器---->输入---->附加依赖项。

在C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\lib\x64和D:\C++_demo_tensort\TensorRT-10.4.0.26\lib下找到附加依赖项的文件(.lib文件)。

nvinfer_dispatch.lib
nvinfer_lean.lib
nvinfer_plugin.lib
nvinfer_vc_plugin.lib
nvonnxparser.lib
nvparsers.lib
nvinfer.lib
cublas.lib
cublasLt.lib
cuda.lib
cudadevrt.lib
cudart.lib
cudart_static.lib
cudnn.lib
cudnn64_8.lib
cudnn_adv_infer.lib
cudnn_adv_infer64_8.lib
cudnn_adv_train.lib
cudnn_adv_train64_8.lib
cudnn_cnn_infer.lib
cudnn_cnn_infer64_8.lib
cudnn_cnn_train.lib
cudnn_cnn_train64_8.lib
cudnn_ops_infer.lib
cudnn_ops_infer64_8.lib
cudnn_ops_train.lib
cudnn_ops_train64_8.lib
cufft.lib
cufftw.lib
cufilt.lib
curand.lib
cusolver.lib
cusolverMg.lib
cusparse.lib
nppc.lib
nppial.lib
nppicc.lib
nppidei.lib
nppif.lib
nppig.lib
nppim.lib
nppist.lib
nppisu.lib
nppitc.lib
npps.lib
nvblas.lib
nvinfer.lib
nvinfer_dispatch.lib
nvinfer_lean.lib
nvinfer_plugin.lib
nvinfer_vc_plugin.lib
nvjpeg.lib
nvml.lib
nvonnxparser.lib
nvparsers.lib
nvptxcompiler_static.lib
nvrtc-builtins_static.lib
nvrtc.lib
nvrtc_static.lib
OpenCL.lib

安装OpenCV

官网下载安装文件地址,博主使用opencv-4.8.0-windows.exe版本

双击运行解压后即可,博主重命名为opencv4.8.0:

直接下载的opencv-4.8.0-windows.exe部分功能不完整,如读取视频这类功能,假如需要完整的功能则需要自己编译,参考windows10下opencv4.8.0-cpu C++版本源码编译教程,这里博主也提供了编译好的opencv4.8.0-cpu.rar【百度网盘,提取码:22r3】。目录格式仿造着opencv-4.8.0-windows.exe的目录格式。

添加附加包含目录:Release | x64---->C/C++—>常规---->附加包含目录。

D:\C++_demo_tensort\opencv4.8.0\build\include

链接器:Release | x64---->链接器---->常规---->附加库目录。

D:\C++_demo_tensort\opencv4.8.0\build\x64\vc16\lib

链接器:Release | x64---->链接器---->输入---->附加依赖项。

opencv_world480.lib

简化部署

在Release x64模式下测试时,需要将TensorRT所需的.dll文件,以及OpenCV的.dll文件复制到自己项目的Release下。

D:\C++_demo_tensort\TensorRT-8.6.1.6\lib
D:\C++_demo_tensort\opencv4.8.0\build\x64\vc16\bin
===>
D:\C++_demo_tensort\tensorrt\x64\Release

没有Release目录时,需要在Release | x64模式下运行一遍代码,代码部分在下面提供,读者可以先行新建文件复制代码。

将所有的.dll文件和.exe文件放在同一个目录下可以简化应用程序的部署过程。用户无需手动配置环境变量或安装额外的组件即可运行程序。

为什么CUDA的.dll文件不需要复制,是因为在环境变量Path中设置了CUDA的全局访问。操作系统在加载 .dll 文件时会先检查当前目录(即.exe文件所在的目录),然后检查Path环境变量中列出的所有目录。


TensorRT调用onnx模型

TensorRT推理核心流程

初始化推理运行时对象
返回一个指向IInferRuntime接口的指针,它包含了日志记录器设置。

nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(gLogger);
nvinfer1::createInferRuntime参数日志记录器 (ILogger)
作用用于记录 TensorRT 在执行过程中产生的日志信息,包括错误、警告和调试信息。通过这个参数,用户可以控制日志输出的行为,比如日志等级和输出目的地。
内容kINTERNAL_ERROR:内部错误,表示遇到了不应该发生的问题;kERROR:错误,表示应用程序出现了严重的问题,可能导致无法继续执行;kWARNING:警告,表示可能存在潜在问题,但程序可以继续运行;kINFO:信息,用于提供普通的信息性消息;kVERBOSE:详细信息,用于提供详细的调试信息。

加载预先构建好的引擎
从包含 TensorRT 引擎数据的序列化的字节流中反序列化恢复出一个可以用于执行推理的 CUDA 引擎对象。

nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size);
deserializeCudaEngine参数const void memory*size_t length
内容指向包含序列化引擎数据的内存区域的字节数组指针序列化引擎数据的实际长度

创建执行上下文
创建一个包含了所有必要的信息的执行上下文。

nvinfer1::IExecutionContext* context = engine->createExecutionContext();
createExecutionContext参数
作用用于在实际执行推理时绑定输入和输出缓冲区,并设置其他执行参数,负责管理执行时的内存分配和释放,以及执行引擎的初始化和清理工作。

获取模型输入输出信息
从nvinfer1::ICudaEngine对象中获取模型输入和输出的详细信息,包括数量、名称和形状。

int num_bindings = engine->getNbBindings();
const char* binding_name = engine->getBindingName(i);
int input_index = engine->getBindingIndex(input_names[0]);
int input_h = engine->getBindingDimensions(input_index).d[2];


getBindingIndex的名字可以直接通过【ONNX模型在线查看工具】直接查询输入输出的名字,而不需要再通过getBindingName去查询判断对应的输入输出名称。

预处理输入数据
对输入数据进行颜色空间转换,尺寸缩放、标准化以及形状维度扩展操作。

cv::cvtColor(image, rgb, cv::COLOR_BGR2RGB);
cv::resize(rgb, blob, cv::Size(input_w, input_h));
blob.convertTo(blob, CV_32F);
blob = blob / 255.0;
cv::subtract(blob, cv::Scalar(0.485, 0.456, 0.406), blob);
cv::divide(blob, cv::Scalar(0.229, 0.224, 0.225), blob);
cv::Mat tensor = cv::dnn::blobFromImage(blob);

这部分不是TensorRT核心部分,根据任务需求不同,代码略微不同。

推理准备
在设备内存(GPU)中分配输入输出缓冲区(一段连续的内存空间)。

cudaMalloc(&buffers[input_index], input_h*input_w*3*sizeof(float));
cudaMalloc(&buffers[output_index], output_h*output_w*sizeof(float));
cudaMalloc参数ptrsize
内容一个指向 void** 的指针分配以字节为单位的内存大小

执行推理
将主机内存(CPU)中的数据复制到设备内存(GPU)中,执行推理操作,然后再将结果从设备内存复制回主机内存。

cudaMemcpyAsync(buffers[0], tensor.ptr<float>(), input_h * input_w * 3 * sizeof(float), cudaMemcpyHostToDevice, stream);
context->enqueueV2(buffers, stream, nullptr);
cudaMemcpyAsync(prob.data(), buffers[1], output_h * output_w * sizeof(float), cudaMemcpyDeviceToHost, stream);
函数cudaMemcpyAsyncenqueueV2
作用在不同内存区域之间异步复制数据。在给定的执行上下文中执行一次推理操作。
参数一dst:目标地址,数据将被复制到这里。bindings:指向 void* 数组的指针,包含了所有输入和输出缓冲区的指针,每个元素对应一个输入或输出绑定,顺序与模型中绑定的顺序一致。
参数二src:源地址,数据将从这里被复制。stream:CUDA 流对象,用于调度异步操作。
参数三count:要复制的字节数。sharedMem:一个指向共享内存的指针,通常传递 nullptr,用于高级用法,如使用专用的共享内存。
参数四kind:复制类型,指示复制的方向。cudaMemcpyHostToDevice:从主机内存复制到设备内存;cudaMemcpyDeviceToHost:从设备内存复制到主机内存;cudaMemcpyDeviceToDevice:在设备内存内部复制;cudaMemcpyHostToHost:在主机内存内部复制;cudaMemcpyDefault:由 CUDA 自动选择复制方向。
参数五stream:CUDA 流对象,用于调度异步操作。

后处理推理结果
推理完成后,从输出张量中获取结果数据,根据需要对结果进行后处理,以获得最终的预测结果。

cv::Mat probmat(output_h, output_w, CV_32F, (float*)prob.data());
cv::minMaxLoc(probmat, &minv, &maxv, &minL, &maxL);

这部分不是TensorRT核心部分,根据任务需求不同,代码基本不同。


ONNXRuntime推理代码

需要配置flower_classes.txt文件存储五种花的分类标签,并将其放置到工程目录下(推荐)。

daisy
dandelion
roses
sunflowers
tulips

这里需要将AlexNet.engine放置到工程目录下(推荐),并且将以下推理代码拷贝到新建的cpp文件中,并执行查看结果。

#include <fstream>
#include <iostream>
#include <sstream>
#include <opencv2/opencv.hpp>
#include "NvInfer.h"


// 记录 TensorRT 运行时的日志信息
class Logger : public nvinfer1::ILogger
{
	void log(Severity severity, const char* msg)  noexcept
	{
		// suppress info-level messages
		if (severity != Severity::kINFO)
			std::cout << msg << std::endl;
	}
} gLogger;

// 加载标签文件获得分类标签
std::string labels_txt_file = "D:/C++_demo_tensort/tensorrt/flower_classes.txt";
std::vector<std::string> readClassNames();
std::vector<std::string> readClassNames()
{
	std::vector<std::string> classNames;

	std::ifstream fp(labels_txt_file);
	if (!fp.is_open())
	{
		printf("could not open file...\n");
		exit(-1);
	}
	std::string name;
	while (!fp.eof())
	{
		std::getline(fp, name);
		if (name.length())
			classNames.push_back(name);
	}
	fp.close();
	return classNames;
}

int main(int argc, char** argv) {
	// 预测的目标标签数
	std::vector<std::string> labels = readClassNames();

	// engine训练模型文件
	std::string enginepath = "D:/C++_demo_tensort/tensorrt/AlexNet.engine";

	// 从文件中读取一个序列化的 TensorRT 模型
	std::ifstream file(enginepath, std::ios::binary);
	char* trtModelStream = NULL;
	int size = 0;
	if (file.good()) {
		// 将读指针移动到文件末尾
		file.seekg(0, file.end);
		// 获取文件大小
		size = file.tellg();
		// 将读指针移动到文件开始
		file.seekg(0, file.beg);
		trtModelStream = new char[size];
		assert(trtModelStream);
		// 从关联的输入流中读取了指定数量的字符
		file.read(trtModelStream, size);
		file.close();
	}

	// 初始化推理运行时对象
	nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(gLogger);
	assert(runtime != nullptr);

	// 加载预先构建好的引擎
	nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size);
	assert(this->engine != nullptr);

	// 创建执行上下文
	nvinfer1::IExecutionContext* context = engine->createExecutionContext();
	assert(this->context != nullptr);

	// 释放数组类型的内存
	delete[] trtModelStream;

	// 管理异步操作的流对象
	cudaStream_t stream;

	// 对象的绑定数量(即输入和输出的总数)
	int num_bindings = engine->getNbBindings();
	std::cout << " input/outpu : " << num_bindings << std::endl;
	// 输入和输出名称
	std::vector<const char*> input_names;
	std::vector<const char*> output_names;
	// 遍历所有绑定
	for (int i = 0; i < num_bindings; ++i) {
		// 获取绑定名称
		const char* binding_name = engine->getBindingName(i);

		// 判断当前绑定是输入还是输出,并保存到相应的向量中
		if (engine->bindingIsInput(i)) {
			input_names.push_back(binding_name);
		}
		else {
			output_names.push_back(binding_name);
		}
	}
	// 用于获取模型输入或输出张量的索引
	int input_index = engine->getBindingIndex(input_names[0]);
	int output_index = engine->getBindingIndex(output_names[0]);

	// 获取输入维度信息 NCHW
	int input_h = engine->getBindingDimensions(input_index).d[2];
	int input_w = engine->getBindingDimensions(input_index).d[3];
	printf("inputH : %d, inputW: %d \n", input_h, input_w);

	// 获取输出维度信息
	int output_h = engine->getBindingDimensions(output_index).d[0];
	int output_w = engine->getBindingDimensions(output_index).d[1];
	printf("outputH : %d, outputW: %d \n", output_h, output_w);

	// 推理准备
	// 包含所有输入和输出缓冲区的指针,每个元素对应一个输入或输出绑定,顺序与模型中绑定的顺序一致
	void* buffers[2] = { NULL, NULL };
	// 创建GPU显存输入/输出缓冲区(有几个就初始化几个)
	cudaMalloc(&buffers[input_index], input_h*input_w*3*sizeof(float));
	cudaMalloc(&buffers[output_index], output_h*output_w*sizeof(float));

	// 输出结果
	std::vector<float> prob;
	// 创建临时缓存输出
	prob.resize(output_h*output_w);
	// 创建cuda流
	cudaStreamCreate(&stream);
	
	// 测试图片
	cv::Mat image = cv::imread("D:/C++_demo_tensort/tensorrt/sunflowers.jpg");

	// 预处理输入数据
	cv::Mat rgb, blob;
	// 默认是BGR需要转化成RGB
	cv::cvtColor(image, rgb, cv::COLOR_BGR2RGB);
	// 对图像尺寸进行缩放
	cv::resize(rgb, blob, cv::Size(input_w, input_h));
	blob.convertTo(blob, CV_32F);
	// 对图像进行标准化处理
	blob = blob / 255.0;
	cv::subtract(blob, cv::Scalar(0.485, 0.456, 0.406), blob);
	cv::divide(blob, cv::Scalar(0.229, 0.224, 0.225), blob);
	// CHW-->NCHW 维度扩展
	cv::Mat tensor = cv::dnn::blobFromImage(blob);

	// 内存到GPU显存
	cudaMemcpyAsync(buffers[0], tensor.ptr<float>(), input_h*input_w*3*sizeof(float), cudaMemcpyHostToDevice, stream);
	// 模型推理
	context->enqueueV2(buffers, stream, nullptr);
	// GPU显存到内存
	cudaMemcpyAsync(prob.data(), buffers[1], output_h*output_w*sizeof(float), cudaMemcpyDeviceToHost, stream);

	// 后处理推理结果
	cv::Mat probmat(output_h, output_w, CV_32F, (float*)prob.data());
	cv::Point maxL, minL;		// 用于存储图像分类中的得分最小值索引和最大值索引(坐标)
	double maxv, minv;			// 用于存储图像分类中的得分最小值和最大值
	cv::minMaxLoc(probmat, &minv, &maxv, &minL, &maxL);
	int max_index = maxL.x;		// 获得最大值的索引,只有一行所以列坐标既为索引
	std::cout << "label id: " << max_index << std::endl;
	// 在测试图像上加上预测的分类标签
	cv::putText(image, labels[max_index], cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 255), 2, 8);
	cv::imshow("输入图像", image);
	cv::waitKey(0);

	// 同步结束,释放资源
	cudaStreamSynchronize(stream);
	cudaStreamDestroy(stream);

	if (!context) {
		context->destroy();
	}
	if (!engine) {
		engine->destroy();
	}
	if (!runtime) {
		runtime->destroy();
	}
	if (!buffers[0]) {
		delete[] buffers;
	}

	return 0;
}

图片正确预测为向日葵:


总结

尽可能简单、详细的介绍了pytorch模型到engnie模型的转化,C++下TensorRT环境的搭建以及engnie模型的TensorRT部署。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[C#]winform 使用opencvsharp实现玉米粒计数

【算法介绍】 这段代码是使用OpenCvSharp库&#xff08;OpenCV的C#封装&#xff09;对图像进行处理&#xff0c;主要流程包括图像的二值化、腐蚀操作、距离变换、轮廓检测&#xff0c;并在原图上标出检测到的轮廓位置及数量。下面是对代码的详细解读&#xff1a; 初始化&…

网络通信——路由器、交换机、集线器(HUB)

注意&#xff1a;传输层&#xff0c;应用层没有网路设备 一.路由器&#xff08;网络层设备&#xff09; 1.分割广播域 2.一个接口就是一个广播域 3.一般接口位4&#xff0c;8&#xff0c;12。 4.数据转发 &#xff08;由路由表转发数据&#xff09; 5.根据路由表来进行路径选…

基于微信小程序的美食外卖管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

面试速通宝典——1

1. 内存有哪几种类型&#xff1f; ‌‌‌‌  内存分为五个区&#xff0c;堆&#xff08;malloc&#xff09;、栈&#xff08;如局部变量、函数参数&#xff09;、程序代码区&#xff08;存放二进制代码&#xff09;、全局/静态存储区&#xff08;全局变量、static变量&#…

米壳AI:自媒体如何获取高清原画质!真8K视频是这样下载的!

作为一名新手自媒体博主&#xff0c;你是不是也在各种短视频平台上疯狂搜索保存外网视频的方法和软件呢&#xff1f;&#x1f603;然而&#xff0c;真正能下载真 4K 视频的却寥寥无几。 别苦恼啦&#xff01;今天我就来给大家分享一个小编亲测过后真实好用的工具 —— 米壳 AI。…

网页通知设计灵感:CSS 和 JS 的 8 大创意实现

文章目录 前言正文1.霓虹灯风格的通知系统2.垂直时间轴通知3.动画徽章通知4.项目式通知5.多种状态通知&#xff1a;成功、错误、警告6.信息、警告、提示组件7.扁平化风格通知8.社交媒体风格弹出通知 总结 前言 网页通知如今已成为电商、社交平台等网站的常见功能&#xff0c;它…

Pandas -----------------------基础知识(二)

dataframe读写数据操作 import pandas as pd# 准备数据(字典) data [[1, 张三, 1999-3-10, 18],[2, 李四, 2002-3-10, 15],[3, 王五, 1990-3-10, 33],[4, 隔壁老王, 1983-3-10, 40] ]df pd.DataFrame(data, columns[id, name, birthday, age]) df写到csv文件中 &#xff0c;…

SOLIDWORKS 2025 重点新功能大放送(壹)

SOLIDWORKS 2025涵盖全新以用户为中心的增强功能&#xff0c;致力实现更智能、更快速地与团队和外部合作伙伴协同工作。 小索是设计部负责人&#xff0c;SOLIDWORKS资深使用者&#xff0c;使用SOLIDWORKS软件多年&#xff0c;喜欢分享&#xff0c;正在体验SOLIDWORKS 2025版本…

tensorboard展示不同运行的曲线结果

运行tensorboard曲线如下&#xff1a; tensorboard --logdir .有时候&#xff0c;曲线图会展示多条曲线&#xff0c;以至于我们想分辨哪条线来自哪次训练都做不到了。如下图是设置smoothing-0.6的结果&#xff1a; smoothing可以在页面找到设置按钮&#xff0c;呼出设置侧边…

【算法笔记】二分查找 红蓝染色法

目录 二分查找 红蓝染色法&#xff08;感谢灵神&#xff09;闭区间[left, right]左闭右开区间[left, right)开区间(left, right)变式 二分查找 红蓝染色法&#xff08;感谢灵神&#xff09; 这里是灵神的教学视频&#xff1a;二分查找 红蓝染色法_哔哩哔哩_ bilibili 学了二分…

玩转RabbitMQ声明队列交换机、消息转换器

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

中兴交换机三层配置

中兴交换机三层配置 目的&#xff1a;将1-10端口划分到3001vlan&#xff0c;11-20端口划分到3002vlan中去 客户端客户端IPvlan网关主机A88.88.1.1203001192.168.1.254主机B192.168.100.1303002192.168.100.254 1、通过Console线登录设备 **********************************…

导出导入Oracle数据库使用黑框命令方式exp、imp【亲测】

下载工具 根据自己数据库的版本下载&#xff0c;以v19为例&#xff1a; 下载基础包Basic Package和工具包Tools Package 两个压缩包中的文件夹一样&#xff0c;但内容不一样&#xff0c;将两个压缩包中的文件解压合并到一起 https://www.oracle.com/database/technologies/inst…

TLV解码 - 华为OD统一考试(E卷)

2024华为OD机试&#xff08;E卷D卷C卷&#xff09;最新题库【超值优惠】Java/Python/C合集 题目描述 TLV编码是按 [Tag Length Value] 格式进行编码的&#xff0c;一段码流中的信元用Tag标识&#xff0c;Tag在码流中唯一不重复&#xff0c;Length表示信元Value的长度&#xff…

Zotero(7.0.5)+123云盘同步空间+Z-library=无限存储文献pdf/epub电子书等资料

选择123云盘作为存储介质的原因 原因1&#xff1a; zotero个人免费空间大小&#xff1a;300M&#xff0c;如果zotero云端也保存文献pdf资料则远远不够 原因2&#xff1a; 百度网盘同步文件空间大小&#xff1a;1G123云盘同步文件空间大小&#xff1a;10G 第一台电脑实施步骤…

微服务--Gateway网关

在微服务架构中&#xff0c;Gateway&#xff08;网关&#xff09;是一个至关重要的组件&#xff0c;它扮演着多种关键角色&#xff0c;包括路由、负载均衡、安全控制、监控和日志记录等。 Gateway网关的作用 统一访问入口&#xff1a; Gateway作为微服务的统一入口&#xff0c…

DNF Decouple and Feedback Network for Seeing in the Dark

DNF: Decouple and Feedback Network for Seeing in the Dark 在深度学习领域&#xff0c;尤其是在低光照图像增强的应用中&#xff0c;RAW数据的独特属性展现出了巨大的潜力。然而&#xff0c;现有架构在单阶段和多阶段方法中都存在性能瓶颈。单阶段方法由于域歧义&#xff0c…

计算机网络 --- Socket 编程

序言 在上一篇文章中&#xff0c;我们介绍了 协议&#xff0c;协议就是一种约定&#xff0c;规范了双方通信需要遵循的规则、格式和流程&#xff0c;以确保信息能够被准确地传递、接收和理解。  在这篇文章中我们将介绍怎么进行跨网络数据传输&#xff0c;在这一过程中相信大家…

常⻅中间件漏洞(WebLogic)靶场

1.后台弱⼝令GetShell 启动环境 默认账号密码&#xff1a;weblogic/Oracle123 weblogic常⽤弱⼝令&#xff1a;https://cirt.net/passwords?criteriaweblogic 这⾥注意&#xff0c; 单个账号错误密码5次之后就会⾃动锁定。 172.16.1.51:7001/console/login/LoginForm.jsp …

视频剪辑软件排行榜前十名推荐!从入门到专业领域都有!

随着短视频的流行&#xff0c;视频剪辑已成为表达创意、分享故事、获取流量的重要工具。无论是专业视频制作人还是业余爱好者&#xff0c;选择一款合适的视频剪辑软件都至关重要。今天&#xff0c;我们就来盘点一下视频剪辑软件排行榜前十名&#xff0c;帮助你找到最适合自己的…