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

深度学习模型部署——基于Onnx Runtime的深度学习模型CPU与GPU部署(C++实现)

1.概述

许多机器学习和深度学习模型都是在基于 Python 的框架中开发和训练的,例如 PyTorch 和 TensorFlow 等。但是,当需要将这些训练好模型部署到生产环境中时,通常会希望将模型集成到生产流程中,而这些流程大多是用 C++ 编写的,因为 C++ 可以提供更快的实时性能。

目前有许多工具和框架可以帮助我们将预训练模型部署到 C++ 应用程序中。例如,ONNX Runtime 可用于边缘计算和服务器环境,NCNN 和 MNN 适用于移动设备,而 TensorRT 则适用于 Nvidia 的嵌入式平台。这里,通过一个完整的端到端示例来讨论如何利用 ONNX Runtime(GPU 版本)将一个预训练的 PyTorch 模型部署到 C++ 应用程序中。

这种部署方式不仅可以帮助开发者利用 C++ 的高效性能,还可以通过 ONNX Runtime 灵活地使用不同的硬件加速器,如 CPU、CUDA 或 TensorRT 等。通过这种方式,可以在保持应用程序性能的同时,快速集成和部署机器学习和深度学习模型。

2.onnx介绍

ONNX是一个为机器学习设计的开放文件格式,它被用来存储预训练的模型。ONNX 的主要目的是促进不同人工智能框架之间的互操作性,使得模型可以在这些框架之间轻松迁移和部署。这种格式支持统一的模型表示,因此,不同的训练框架,比如 Caffe2、PyTorch、TensorFlow 等,都可以使用相同的格式来存储模型数据,进而实现数据的交互和共享。

ONNX 的规范和代码主要由一些科技巨头公司开发,包括但不限于 Microsoft(微软)、Amazon(亚马逊)、Facebook(脸书)和 IBM。这些公司的贡献确保了 ONNX 作为一个开放标准能够持续发展,并且得到了业界的广泛支持和应用。

通过使用 ONNX,开发者可以更加灵活地选择适合自己项目的 AI 框架,而不必担心框架间的兼容性问题。此外,ONNX 也为模型的优化和加速提供了可能,因为它可以与各种硬件后端(如 CPU、GPU、TPU 等)配合工作,从而提高模型运行的效率。

3. ONNX Runtime介绍

ONNX Runtime 是一个跨平台的机器学习模型加速器,它提供了一个灵活的接口,允许集成针对特定硬件优化的库。这意味着 ONNX Runtime 可以适配多种硬件环境,并且能够充分利用硬件的特性来加速模型的执行。

ONNX Runtime 支持多种执行提供者,包括:

  • CPU:作为通用处理器,CPU 是执行机器学习模型的一个常见选择。
  • CUDA:NVIDIA 的 CUDA 为使用 NVIDIA GPU 加速提供了接口,可以显著提升模型在支持 CUDA 的 GPU 上运行的性能。

除了能够利用不同的执行提供者外,ONNX Runtime 还能够与多个框架训练的模型一起使用,这些框架包括但不限于:

  • PyTorch:一个流行的开源机器学习库,广泛用于计算机视觉和自然语言处理任务。
  • TensorFlow/Keras:Google 开发的 TensorFlow 是一个强大的机器学习平台,Keras 是其上的高级神经网络 API。
  • TFLite:TensorFlow 的轻量级解决方案,专门用于移动和嵌入式设备。
  • scikit-learn:一个简单高效的 Python 编程库,用于数据挖掘和数据分析。

通过 ONNX Runtime 的推理功能,开发者可以轻松地将预训练的 PyTorch 模型部署到 C++ 应用程序中。这使得在需要高性能和实时处理的生产环境中,利用机器学习模型成为可能。通过这种方式,开发者可以享受到 C++ 的性能优势,同时又能通过 ONNX Runtime 灵活地部署和运行各种机器学习模型。

4. 模型转换与依赖安装

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

模型转换:首先,需要将 PyTorch 预训练模型文件(可以是 .pth.pt 格式)转换成 ONNX 格式的文件(.onnx 格式)。这一转换过程在 PyTorch 环境中进行。
使用 ONNX Runtime:转换得到的 .onnx 文件随后作为输入,被用于 C++ 应用程序中。在这个应用程序中,调用 ONNX Runtime 的 C++ API 来执行模型的推理。

这个流程允许开发者利用 PyTorch 强大的训练能力来训练模型,并将训练好的模型通过 ONNX 这一开放格式,部署到 C++ 环境中,从而在多种不同的平台上实现高性能的推理计算。通过这种方式,开发者可以兼顾模型的开发效率和部署时的性能要求。

4.1 PyTorch 模型转换到 ONNX

为了演示方便,这里使用Yolov8的一个人脸检测的模型进行演示,首先安装ultralytics框架:

conda create --name yolov8 python==3.10
conda activate yolov8
pip install ultralytics

然后把模型好的.pt模型转成onnx模型,转换代码如下:

from ultralytics import YOLO
model = YOLO("yolov8n_face.pt")
success = model.export(format="onnx", simplify=True)  # export the model to onnx format
assert success
print("转换成功")

运行之后之后成功一个同名的onnx模型。

4.2 安装GPU依赖库

A: 我这里的测试环境是Win10,IDE 是VS2019,显卡是NVIDIA GeForce RTX 2070 SUPER ,8G的显存,cuda 是11.7,cudnn是8.5。
首先要安装cuda 11.7,从官网下载cudu 11.7 win10对应的版本:
在这里插入图片描述
在这里插入图片描述
B: 安装的时候按指示下一步下一步就可以了:
在这里插入图片描述
C: 指示安装,安装完成之后,在所先的安装路径上可以看到:
在这里插入图片描述
D: 并且它会自动把需要的路径加到系统环境变量:
在这里插入图片描述
E: 下载cudnn:
可以从官方下载到所指定的cudnn版本:
在这里插入图片描述
F: 下载完成之后,解压出三个目录,把三个目录复制一份放到上面C图所在的路径,该路径原本就存有相同的目录,直接粘贴就可以:
在这里插入图片描述
G: 启动命令行,输入nvcc -V,检查是否安装成功,如出现下面的提示表示安装成功,否则请重新检查以上的步骤是否正确:
在这里插入图片描述

4.3 安装编程所需的依赖库

4.3.1 ONNX Runtime下载

可以从https://github.com/microsoft/onnxruntime/releases下载到编译好的依赖库,这里的演示环境是Win10, IDE是vs2019,我下载v1.12.1 GPU这个版本。
在这里插入图片描述

4.3.2 OpenCV库

因为使用的模型需要显示出效果图,这里使用OpenCV来做图像预处理相关的操作,可以从OpenCV官网下载到所需的库:
在这里插入图片描述

5.使用 ONNX Runtime 进行推理

5.1 创建工程

A: 使用vs2019创建一个C++工程,然后把OnnxRuntime和OpenCV加到工程里面:
在这里插入图片描述
B: 把OnnxRuntime和OpenCV加入到工程包含路径,路径里面包含OnnxRuntime和OpenCV的头文件:
在这里插入图片描述
添加到目录:
在这里插入图片描述
C: 把lib添加到库目录,lib包含到OnnxRuntime和OpenCV 的lib文件:
在这里插入图片描述
把lib路径加到库目录:
在这里插入图片描述
把lib文件名添加到链接器:
在这里插入图片描述

5.2 ONNX Runtime推理流程

5.2.1 推理流程:

初始化 ONNX Runtime 环境

  • 首先,你需要初始化 ONNX Runtime 的运行时环境。这通常涉及到创建一个 Ort::Env 对象,它包含了线程池和其他运行时设置。
  • 设置会话选项
  • 接着,设置 ONNX Runtime 会话的选项。这可能包括配置 GPU 使用、优化器级别、执行模式等。
  • 加载模型并创建会话
  • 加载预训练的 ONNX 模型文件。
  • 使用运行时环境、会话选项和模型创建一个 Ort::Session 对象。
  • 获取模型输入输出信息
  • Ort::Session 对象中获取模型输入和输出的详细信息,包括数量、名称、类型和形状。
  • 推理准备
  • 创建输入和输出张量,这些张量是用于存储推理数据的内存块。
  • 分配内存给这些张量,以准备数据输入。
  • 执行推理
  • 调用 Ort::Session::Run 方法,传入输入张量、输出张量和其他必要的参数,执行推理。
  • 后处理推理结果
  • 推理完成后,从输出张量中获取结果数据。
  • 根据需要对结果进行后处理,比如解码、格式化等,以获得最终的预测结果。

5.2 ONNX Runtime推理代码

类的头文件:

# ifndef YOLOV8FACE
# define YOLOV8FACE
#include <fstream>
#include <sstream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <provider_options.h>
#include <onnxruntime_cxx_api.h>
#include "utils.h"class Yolov8Face
{
public:Yolov8Face(std::string modelpath, const float conf_thres=0.5, const float iou_thresh=0.4);void detect(cv::Mat srcimg, std::vector<Bbox> &boxes);
private:void preprocess(cv::Mat img);std::vector<float> input_image;int input_height;int input_width;float ratio_height;float ratio_width;float conf_threshold;float iou_threshold;Ort::Env env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "Face Detect");Ort::Session *ort_session = nullptr;Ort::SessionOptions sessionOptions = Ort::SessionOptions();std::vector<char*> input_names;std::vector<char*> output_names;std::vector<std::vector<int64_t>> input_node_dims; // >=1 outputsstd::vector<std::vector<int64_t>> output_node_dims; // >=1 outputsOrt::MemoryInfo memory_info_handler = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
};
#endif

类的实现文件:

// 构造函数,用于初始化 YOLOv8Face 类的实例
Yolov8Face::Yolov8Face(string model_path, const float conf_thres, const float iou_thresh)
{// 设置执行提供者为 CUDA,并添加到会话选项中OrtStatus* status = OrtSessionOptionsAppendExecutionProvider_CUDA(sessionOptions, 0); // 设置图优化级别为最高级别sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);// 将字符串路径转换为宽字符串,用于创建会话std::wstring widestr = std::wstring(model_path.begin(), model_path.end());ort_session = new Session(env, widestr.c_str(), sessionOptions);// 如果是Linux,则用以下的方式// ort_session = new Session(env, model_path.c_str(), sessionOptions); // 获取输入和输出节点的数量size_t numInputNodes = ort_session->GetInputCount();size_t numOutputNodes = ort_session->GetOutputCount();// 创建默认分配器AllocatorWithDefaultOptions allocator;// 获取输入节点的名称和维度信息for (int i = 0; i < numInputNodes; i++){input_names.push_back(ort_session->GetInputName(i, allocator)); // 新版本的Onnx Runtime使用的接口// AllocatedStringPtr input_name_Ptr = ort_session->GetInputNameAllocated(i, allocator);// input_names.push_back(input_name_Ptr.get()); Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();auto input_dims = input_tensor_info.GetShape();input_node_dims.push_back(input_dims);}// 获取输出节点的名称和维度信息for (int i = 0; i < numOutputNodes; i++){output_names.push_back(ort_session->GetOutputName(i, allocator));// 新版本的Onnx Runtime使用的接口// AllocatedStringPtr output_name_Ptr= ort_session->GetInputNameAllocated(i, allocator);// output_names.push_back(output_name_Ptr.get()); Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();auto output_dims = output_tensor_info.GetShape();output_node_dims.push_back(output_dims);}// 从输入节点维度中获取输入图像的高度和宽度this->input_height = input_node_dims[0][2];this->input_width = input_node_dims[0][3];// 设置置信度阈值和 IOU 阈值this->conf_threshold = conf_thres;this->iou_threshold = iou_thresh;
}// 对输入图像进行预处理
void Yolov8Face::preprocess(Mat srcimg)
{// 获取原始图像的高度和宽度const int height = srcimg.rows;const int width = srcimg.cols;// 创建临时图像副本Mat temp_image = srcimg.clone();// 如果图像的高度或宽度大于输入尺寸,则进行缩放if (height > this->input_height || width > this->input_width){const float scale = std::min((float)this->input_height / height, (float)this->input_width / width);Size new_size = Size(int(width * scale), int(height * scale));resize(srcimg, temp_image, new_size);}// 计算比例因子,用于将检测框映射回原始图像尺寸this->ratio_height = (float)height / temp_image.rows;this->ratio_width = (float)width / temp_image.cols;// 创建输入图像,如果必要的话,使用 copyMakeBorder 来填充边缘Mat input_img;copyMakeBorder(temp_image, input_img, 0, this->input_height - temp_image.rows, 0, this->input_width - temp_image.cols, BORDER_CONSTANT, 0);// 分离图像的 BGR 通道vector<cv::Mat> bgrChannels(3);split(input_img, bgrChannels);// 将每个通道的数据类型转换为 float,并进行归一化for (int c = 0; c < 3; c++){bgrChannels[c].convertTo(bgrChannels[c], CV_32FC1, 1 / 128.0, -127.5 / 128.0);}// 将所有通道的数据复制到一个连续的数组中const int image_area = this->input_height * this->input_width;this->input_image.resize(3 * image_area);size_t single_chn_size = image_area * sizeof(float);memcpy(this->input_image.data(), (float *)bgrChannels[0].data, single_chn_size);memcpy(this->input_image.data() + image_area, (float *)bgrChannels[1].data, single_chn_size);memcpy(this->input_image.data() + image_area * 2, (float *)bgrChannels[2].data, single_chn_size);
}// 执行目标检测
void Yolov8Face::detect(Mat srcimg, std::vector<Bbox> &boxes)
{// 对输入图像进行预处理this->preprocess(srcimg);// 设置输入张量的形状std::vector<int64_t> input_img_shape = {1, 3, this->input_height, this->input_width};// 创建输入张量,并分配内存Value input_tensor_ = Value::CreateTensor<float>(memory_info_handler, this->input_image.data(), this->input_image.size(), input_img_shape.data(), input_img_shape.size());// 设置运行选项Ort::RunOptions runOptions;// 运行推理,获取输出结果vector<Value> ort_outputs = this->ort_session->Run(runOptions, this->input_names.data(), &input_tensor_, 1, this->output_names.data(), output_names.size());// 获取第一个输出张量的数据指针float *pdata = ort_outputs[0].GetTensorMutableData<float>(); // 获取输出张量中的检测框数量const int num_box = ort_outputs[0].GetTensorTypeAndShapeInfo().GetShape()[2];// 存储原始检测框和得分vector<Bbox> bounding_box_raw;vector<float> score_raw;for (int i = 0; i < num_box; i++){const float score = pdata[4 * num_box + i];if (score > this->conf_threshold){// 将检测框的坐标从归一化映射到原始图像尺寸float xmin = (pdata[i] - 0.5 * pdata[2 * num_box + i]) * this->ratio_width;            float ymin = (pdata[num_box + i] - 0.5 * pdata[3 * num_box + i]) * this->ratio_height; float xmax = (pdata[i] + 0.5 * pdata[2 * num_box + i]) * this->ratio_width;            float ymax = (pdata[num_box + i] + 0.5 * pdata[3 * num_box + i]) * this->ratio_height;bounding_box_raw.emplace_back(Bbox{xmin, ymin, xmax, ymax});score_raw.emplace_back(score);}}// 使用非极大值抑制(NMS)算法去除重叠的检测框vector<int> keep_inds = nms(bounding_box_raw, score_raw, this->iou_threshold);// 获取保留的检测框数量const int keep_num = keep_inds.size();// 清空输出向量并设置为保留的检测框数量boxes.clear();boxes.resize(keep_num);// 将保留的检测框复制到输出向量中for (int i = 0; i < keep_num; i++){const int ind = keep_inds[i];boxes[i] = bounding_box_raw[ind];}
}

5.3 代码解释

5.3.1 选择GPU推理

   // 设置执行提供者为 CUDA,并添加到会话选项中,这里选择第一块GPUOrtStatus* status = OrtSessionOptionsAppendExecutionProvider_CUDA(sessionOptions, 0); // 设置图优化级别为最高级别,这里使用最大优化sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);

5.3.2 获取输入层

 // 获取输入节点的名称和维度信息for (int i = 0; i < numInputNodes; i++){input_names.push_back(ort_session->GetInputName(i, allocator)); // 新版本的Onnx Runtime使用的接口// AllocatedStringPtr input_name_Ptr = ort_session->GetInputNameAllocated(i, allocator);// input_names.push_back(input_name_Ptr.get()); Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();auto input_dims = input_tensor_info.GetShape();input_node_dims.push_back(input_dims);}

可以使用可视化看到模型输入层:
在这里插入图片描述

5.3.3 获取输出层

  // 获取输出节点的名称和维度信息for (int i = 0; i < numOutputNodes; i++){output_names.push_back(ort_session->GetOutputName(i, allocator));// 新版本的Onnx Runtime使用的接口// AllocatedStringPtr output_name_Ptr= ort_session->GetInputNameAllocated(i, allocator);// output_names.push_back(output_name_Ptr.get()); Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();auto output_dims = output_tensor_info.GetShape();output_node_dims.push_back(output_dims);}

可以使用可视化看到模型输出层:
在这里插入图片描述

5.4 使用CPU进行推理

注释掉调用GPU部分,运行代码,计算运行时间,可以看到每张图运行速度如下:
在这里插入图片描述

5.5 使用GPU进行推理

作用GPU推理设置参数如下:

sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
  • ORT_DISABLE_ALL:禁用所有图优化。
  • ORT_ENABLE_BASIC:启用基本的图优化。
  • ORT_ENABLE_EXTENDED:启用扩展的图优化,这通常包括基本优化加上一些更激进的优化策略,可能会提高模型的执行速度,但也可

能需要更多的时间来优化图。
使用GPU推理,运行代码,计算运行时间,可以看到每张图运行速度如下:
在这里插入图片描述
可以打开资源管理器看GPU的使用情况:
在这里插入图片描述

6.总结

以上就是在win 10下使用Onnx Runtime用CPU与GPU来对onnx模型进行推理部署的对比,可以明显的看出来,使用GPU之后的推理速度,但在正式的大型项目中,在win下使用GPU部署模型是不建议,一般都会选择Linux,那样对GPU的利用率会高出不少,毕竟蚊腿肉也是肉。

相关文章:

深度学习模型部署——基于Onnx Runtime的深度学习模型CPU与GPU部署(C++实现)

1.概述 许多机器学习和深度学习模型都是在基于 Python 的框架中开发和训练的&#xff0c;例如 PyTorch 和 TensorFlow 等。但是&#xff0c;当需要将这些训练好模型部署到生产环境中时&#xff0c;通常会希望将模型集成到生产流程中&#xff0c;而这些流程大多是用 C 编写的&a…...

Selenium 的四种等待方式及使用场景

Selenium 的四种等待方式及使用场景 隐式等待&#xff08;Implicit Wait&#xff09;显式等待&#xff08;Explicit Wait&#xff09;自定义等待&#xff08;Custom Wait&#xff09;固定等待&#xff08;Sleep&#xff09; 1. 隐式等待 定义&#xff1a; 隐式等待是为 WebD…...

攻防世界 ics-07

点击之后发现有个项目管理能进&#xff0c;点进去&#xff0c;点击看到源码&#xff0c;如下三段 <?php session_start(); if (!isset($_GET[page])) { show_source(__FILE__); die(); } if (isset($_GET[page]) && $_GET[page] ! index.php) { include(flag.php);…...

一文读懂「LoRA」:大型语言模型的低秩适应

LoRA: Low-Rank Adaptation of Large Language Models 前言 LoRA作为大模型的微调框架十分实用&#xff0c;在LoRA出现以前本人都是通过手动修改参数、优化器或者层数来“炼丹”的&#xff0c;具有极大的盲目性&#xff0c;但是LoRA技术能够快速微调参数&#xff0c;如果LoRA…...

新车月交付突破2万辆!小鹏汽车“激活”智驾之困待解

首次突破月交付2万辆规模的小鹏汽车&#xff0c;稳吗&#xff1f; 本周&#xff0c;高工智能汽车研究院发布的最新监测数据显示&#xff0c;2024年11月&#xff0c;小鹏汽车在国内市场&#xff08;不含出口&#xff09;交付量&#xff08;上险口径&#xff0c;下同&#xff09…...

dockerfile 中 #(nop)

在 Dockerfile 中&#xff0c;#(nop) 通常出现在 docker history 命令的输出中。以下是对它的详细解释&#xff1a; 背景 当你使用 docker history <image_name> 命令查看 Docker 镜像的构建历史时&#xff0c;你可能会看到 #(nop) 这样的标记。这是因为 Docker 镜像由…...

升级 Spring Boot 3 配置讲解 —— 为何 SpringBoot3 淘汰了 JDK8?

学会这款 &#x1f525;全新设计的 Java 脚手架 &#xff0c;从此面试不再怕&#xff01; 随着 Spring Boot 3 的发布&#xff0c;许多开发者发现了一个重要的变化&#xff1a;Spring Boot 3 不再支持 JDK 8。这一变化引发了不少讨论&#xff0c;尤其是对于那些仍然在使用 JDK …...

IT面试求职系列主题-人工智能(一)

想成功求职&#xff0c;必要的IT技能一样不能少&#xff0c;再从人工智能基础知识来一波吧。 1&#xff09;您对人工智能的理解是什么&#xff1f; 人工智能是计算机科学技术&#xff0c;强调创造能够模仿人类行为的智能机器。这里智能机器可以定义为能够像人一样行动、像人一…...

JVM 优化指南

JVM 优化指南 1. JVM 参数配置 1.1 基础参数配置 设置堆内存大小 -Xms2048m -Xmx2048m 设置新生代大小 -Xmn1024m 设置元空间大小 -XX:MetaspaceSize256m -XX:MaxMetaspaceSize256m 设置线程栈大小 -Xss512k1.2 垃圾回收器配置 使用 G1 垃圾回收器 -XX:UseG1GC 设置期望停顿…...

windows下编写的shell脚本在Linux下执行有问题解决方法

前言&#xff1a; 这个问题在实际工作中经常会遇到(非语法错误)&#xff0c;脚本来源有些是自己在windows系统编写的、有些是从别人那里copy来的&#xff0c;还有些原本是好的被别人拿到windows下修改了一些内容&#xff0c;总之各种场景&#xff0c;但是如果是一个内容比较多的…...

使用 SQL 和表格数据进行问答和 RAG(6)—将指定目录下的 CSV 或 Excel 文件导入 SQLite 数据库

将指定目录下的 CSV 或 Excel 文件导入 SQLite 数据库。以下是详细代码逻辑&#xff1a; 1. 类结构 该类包含三个主要方法&#xff1a; _prepare_db&#xff1a;负责将文件夹中的 CSV 和 XLSX 文件转换为 SQL 表。_validate_db&#xff1a;用于验证 SQL 数据库中创建的表是否…...

【算法】算法大纲

这篇文章介绍计算机算法的各个思维模式。 包括 计数原理、数组、树型结构、链表递归栈、查找排序、管窥算法、图论、贪心法和动态规划、以及概率论:概率分治和机器学习。没有办法逐个说明,算法本身错综复杂,不同的算法对应着不同的实用场景,也需要根据具体情况设计与调整。…...

vue(2,3), react (16及以上)开发者工具资源

在前端开发的广阔领域中&#xff0c;Vue.js 和 React.js 作为两大主流框架&#xff0c;各自拥有庞大的用户群体和丰富的生态系统。为了帮助开发者更高效地进行调试和开发&#xff0c;Vue Devtools 和 React 开发者工具应运而生&#xff0c;成为这两个框架不可或缺的辅助工具。本…...

系统编程(网络,文件基础)

网络链接 虚拟机和主机之间网络连接的主要模式有三种&#xff0c;分别是桥接模式&#xff08;Bridged&#xff09;、网络地址转换模式&#xff08;NAT&#xff09;以及主机模式&#xff08;Host-Only&#xff09;。以下是这三种模式的详细解释&#xff1a; 一、桥接模式&…...

重温设计模式--13、策略模式

策略模式介绍 文章目录 策略模式介绍C 代码示例 策略模式是一种行为设计模式&#xff0c;它允许在运行时选择算法的行为。该模式将算法的定义和使用分离开来&#xff0c;使得算法可以独立于使用它的客户端而变化&#xff0c;提高了代码的灵活性和可维护性。 其主要包含以下几个…...

数字IC设计高频面试题

在数字IC设计领域&#xff0c;面试是评估候选人技术能力和问题解决能力的重要环节。数字IC设计的复杂性和要求在不断提高。面试官通常会提出一系列面试题&#xff0c;以考察应聘者在数字设计、验证、时钟管理、功耗优化等方面的专业知识和实践经验。 这些题目不仅涉及理论知识…...

C#异步多线程——ThreadPool线程池

C#实现异步多线程的方式有多种&#xff0c;以下总结的是ThreadPool的用法。 线程池的特点 线程池受CLR管理&#xff0c;线程的生命周期&#xff0c;任务调度等细节都不需要我们操心了&#xff0c;我们只需要专注于任务实现&#xff0c;使用ThreadPool提供的静态方法把我们的任…...

矩母函数(MGF)

矩母函数&#xff08;MGF&#xff09;简介 矩母函数&#xff08;Moment Generating Function&#xff0c;MGF&#xff09;是概率统计中描述随机变量分布特征的重要工具。MGF的主要用途是通过导数来计算随机变量的矩&#xff08;比如均值、方差等&#xff09;&#xff0c;同时它…...

【技术支持】安卓无线adb调试连接方式

Android 10 及更低版本&#xff0c;需要借助 USB 手机和电脑需连接在同一 WiFi 下&#xff1b;手机开启开发者选项和 USB 调试模式&#xff0c;并通过 USB 连接电脑&#xff08;即adb devices可以查看到手机&#xff09;&#xff1b;设置手机的监听adb tcpip 5555;拔掉 USB 线…...

OpenCV相机标定与3D重建(46)将三维空间中的点投影到二维图像平面上函数projectPoints()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将3D点投影到图像平面上。 cv::projectPoints 是 OpenCV 库中的一个函数&#xff0c;用于将三维空间中的点投影到二维图像平面上。这个过程涉及到…...

Android wifi常见问题及分析

参考 Android Network/WiFi 那些事儿 前言 本文将讨论几个有意思的网络问题&#xff0c;同时介绍 Android 上常见WiFi 问题的分析思路。 网络基础Q & A 一. 网络分层缘由 分层想必大家很熟悉&#xff0c;是否想过为何需要这样分层&#xff1f; 网上大多都是介绍每一层…...

如何用 ESP32-CAM 做一个实时视频流服务器

文章目录 ESP32-CAM 概述ESP32-S 处理器内存Camera 模块MicroSD 卡槽天线板载 LED 和闪光灯其他数据手册和原理图ESP32-CAM 功耗 ESP32-CAM 引脚参考引脚排列GPIO 引脚哪些 GPIO 可以安全使用&#xff1f;GPIO 0 引脚MicroSD 卡引脚 ESP32-CAM 的烧录方式使用 ESP32-CAM-MB 编程…...

编译与汇编

本文来自《程序员的自我修养》 编译过程是把预处理完的文件进行一系列词法分析&#xff0c;语法分析&#xff0c;语义分析以及优化后生成相应的汇编文件代码。 现在版本的GCC把预编译和编译两个步骤合并为一个步骤。 gcc -S HelloWorld.c HelloWorld.sint main() {//test/* …...

Linux入门攻坚——43、keepalived入门-1

Linux Cluster&#xff08;Linux集群的类型&#xff09;&#xff1a;LB、HA、HPC&#xff0c;分别是负载均衡集群、高可用性集群、高性能集群。 LB&#xff1a;lvs&#xff0c;nginx HA&#xff1a;keepalived&#xff0c;heartbeat&#xff0c;corosync&#xff0c;cman HP&am…...

备考蓝桥杯:顺序表相关算法题

目录 询问学号 寄包柜 移动0 颜色分类 合并两个有序数组 物品移动 询问学号 我们的思路&#xff1a;创建一个顺序表存储从1开始依次存放进入教室的学生学号&#xff0c;然后查询 #include <iostream> #include <vector> using namespace std; const int N 2…...

【STM32+QT项目】基于STM32与QT的智慧粮仓环境监测与管理系统设计(完整工程资料源码)

视频演示: 基于STM32与QT的智慧粮仓环境监测与管理系统设计 目录: 目录 视频演示: 目录: 前言:...

Vue3 自定义hook

文章目录 Vue3 自定义hook概述用法 Vue3 自定义hook 概述 Vue3推荐利用Vue的组合式API函数进行代码封装&#xff0c;这种封装方式统称为自定义hook。 用法 定义 hook/countHook.js&#xff1a; import {computed, ref, watch} from "vue";export default (initC…...

【VBA】【EXCEL】将某列内容横向粘贴到指定行

Sub CopyRowToColumn()On Error GoTo ErrorHandler 添加错误处理Application.ScreenUpdating FalseApplication.Calculation xlCalculationManualApplication.EnableEvents False 禁用事件处理Dim lastCol As LongDim lastRow As LongDim i As Long, colCount As LongDim …...

使用Llama 3.1创建合成数据集以调优你的大型语言模型

使用Llama 3.1创建合成数据集以调优你的大型语言模型 在数据驱动的人工智能领域&#xff0c;数据是核心资产。开发高质量数据集既复杂又昂贵&#xff0c;因此很多实验室和开发者选择使用合成数据集。本文将介绍如何利用大型语言模型Llama 3.1 405B创建合成数据集&#xff0c;并…...

【Ubuntu22.04】VMware虚拟机硬盘扩容

1.首先打开虚拟机设置 2.根据需要对硬盘扩展 这边提示我们还需要进入虚拟机在内部分区 3.安装界面化磁盘管理工具 # 安装 sudo apt install gparted# 启动 sudo gparted调整硬盘大小 调整的时候会提示我们硬盘是只读的&#xff0c;因此还要进行操作 新建终端重新挂载文件系…...

初学stm32 --- DMA直接存储器

目录 DMA介绍 STM32F1 DMA框图 DMA处理过程 DMA通道 DMA优先级 DMA相关寄存器介绍 F1 DMA通道x配置寄存器&#xff08;DMA_CCRx&#xff09; DMA中断状态寄存器&#xff08;DMA_ISR&#xff09; DMA中断标志清除寄存器&#xff08;DMA_IFCR&#xff09; DMA通道x传输…...

reactor中的并发

1. reactor中的并发有两种方式 1.1 flatmap&#xff0c;底层是多线程并发处理。在reactor的演讲中&#xff0c;flatmap对于io类型的并发效果较好. flamap有两个参数: int concurrency, int prefetch。分别代表并发的线程数和缓存大小 注意凡是参数中有prefetch的&#xff0c;都…...

HTML - <script>,<noscript>

<script>标签用于在网页插入脚本&#xff0c;<noscript>标签用于指定浏览器不支持脚本时的显示内容。 1.<script> <script>用于加载脚本代码&#xff0c;目前主要是加载 JavaScript 代码。 <script> console.log(hello world); </script&g…...

C#语言的函数实现

C#语言的函数实现 在现代编程语言中&#xff0c;函数&#xff08;Function&#xff09;是最基本也是最重要的组成部分之一。函数不仅提高了代码的复用性&#xff0c;还使得程序结构更清晰。C#作为一种多用途的编程语言&#xff0c;函数的知识是程序员必备的基本技能之一。本文…...

JAVA I/O流练习1

往D盘中的JAVA复习文件夹中写数据&#xff1a; 数据改了一下哈&#xff1a; import java.io.*; import java.util.Scanner; public class Test {public static void main(String[] args) throws IOException {String fileName"D:JAVA复习\\grade.txt";FileWriter w…...

HTML——75. 内联框架

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>内联框架</title><style type"text/css">iframe{width: 100%;height: 500px;}</style></head><body><!--iframe元素会创建包含…...

js获取当前浏览器地址,ip,端口号等等

前言&#xff1a; js获取当前浏览器地址&#xff0c;ip&#xff0c;端口号等等 window.location属性查询 具体属性&#xff1a; 1、获取他的ip地址 window.location.hostname 2、获取他的端口号 window.location.port 3、获取他的全路径 window.location.origin 4、获取…...

C++虚函数(八股总结)

什么是虚函数 虚函数是在父类中定义的一种特殊类型的函数&#xff0c;允许子类重写该函数以适应其自身需求。虚函数的调用取决于对象的实际类型&#xff0c;而不是指针或引用类型。通过将函数声明为虚函数&#xff0c;可以使继承层次结构中的每个子类都能够使用其自己的实现&a…...

【每日学点鸿蒙知识】跳转三方地图、getStringSync性能、键盘避让模式等

1、跳转三方地图导航页 类似于Android 跳转到地图APP 导航页面&#xff1a; // 目标地点的经纬度和名称 double destinationLat 36.547901; double destinationLon 104.258354; String destinationName "目的地名称"; // 构建URI Uri uri Uri.parse("…...

【线性代数】通俗理解特征向量与特征值

这一块在线性代数中属于重点且较难理解的内容&#xff0c;下面仅个人学习过程中的体会&#xff0c;错误之处欢迎指出&#xff0c;有更简洁易懂的理解方式也欢迎留言学习。 文章目录 概念计算几何直观理解意义 概念 矩阵本身就是一个线性变换&#xff0c;对一个空间中的向量应用…...

C#设计模式(行为型模式):备忘录模式,时光倒流的魔法

C#设计模式&#xff1a;备忘录模式&#xff0c;时光倒流的魔法 在软件开发中&#xff0c;我们经常会遇到需要保存对象状态&#xff0c;并在未来某个时刻恢复的场景。例如&#xff1a; 撤销操作&#xff1a; 文本编辑器中的撤销功能&#xff0c;游戏中的回退操作。事务回滚&am…...

服务器信息整理:用途、操作系统安装日期、设备序列化、IP、MAC地址、BIOS时间、系统

文章目录 引言I BIOS时间Windows查看BIOS版本安装日期linux查看BIOS时间II 操作系统安装日期LinuxWindowsIII MAC 地址IV 设备序列号Linux 查看主板信息知识扩展Linux常用命令引言 信息内容:重点信息:用途、操作系统安装日期、设备序列化、IP、MAC地址、BIOS时间、系统 Linux…...

用OpenCV实现UVC视频分屏

分屏 OpencvUVC代码验证后话 用OpenCV实现UVC摄像头的视频分屏。 Opencv opencv里有很多视频图像的处理功能。 UVC Usb 视频类&#xff0c;免驱动的。视频流格式有MJPG和YUY2。MJPG是RGB三色通道的。要对三通道进行分屏显示。 代码 import cv2 import numpy as np video …...

【C#学习】基类的静态变量 派生类会如何处理

来源GPT&#xff0c;仅记录学习 在C#中&#xff0c;子类继承父类的public static变量时&#xff0c;父类的静态变量对所有类&#xff08;包括子类&#xff09;都是共享的。子类并不会重新创建父类静态变量&#xff0c;而是共享父类的静态成员。 具体行为&#xff1a; 静态变量…...

Unity3D仿星露谷物语开发19之库存栏丢弃及交互道具

1、目标 从库存栏中把道具拖到游戏场景中&#xff0c;库存栏中道具数相应做减法或者删除道具。同时在库存栏中可以交换两个道具的位置。 2、UIInventorySlot设置Raycast属性 在UIInventorySlot中&#xff0c;我们只希望最外层的UIInventorySlot响应Raycast&#xff0c;他下面…...

SQL进阶实战技巧:如何利用 Oracle SQL计算线性回归置信区间?

目录 1 置信区间计算方法 步骤1:计算回归系数 步骤2:计算标准误差 步骤3:计算置信区间 2 数据准备 <...

计算机网络——网络层—IP数据报与分片

一、IP 数据报的格式 • 一个 IP 数据报由首部和数据两部分组成。 • 首部的前一部分是固定长度&#xff0c;共 20 字节&#xff0c;是所有 IP 数据报必须具有的。 • 在首部的固定部分的后面是一些可选字段&#xff0c;其长度是可变的。 IP 数据报首部的固定部分中的各字段 版…...

高山旅游景区有效降低成本,无人机山下到山上物资吊运技术详解

在高山旅游景区&#xff0c;传统的物资运输方式往往面临人力成本高昂、效率低下等问题&#xff0c;而无人机技术的引入为这一难题提供了新的解决方案。以下是对无人机从山下到山上进行物资吊运技术的详细解析&#xff1a; 一、无人机物资吊运技术的优势 1. 降低人力成本&#…...

Linux 注册线程化的中断处理程序

1. 注册线程化中断处理函数 devmem_request_threaded_irq 是 Linux 内核中的一个函数&#xff0c;用于请求并注册一个线程化的中断处理程序。这个函数允许开发者注册一个中断处理函数&#xff0c;这个函数会在中断发生时被调用&#xff0c;从而实现相应的中断处理逻辑。它通过…...

【狂热算法篇】解锁数据潜能:探秘前沿 LIS 算法

嘿&#xff0c;各位编程爱好者们&#xff01;今天带来的 LIS 算法简直太赞啦 无论你是刚入门的小白&#xff0c;还是经验丰富的大神&#xff0c;都能从这里找到算法的奇妙之处哦&#xff01;这里不仅有清晰易懂的 C 代码实现&#xff0c;还有超详细的算法讲解&#xff0c;让你轻…...