摘要
- ov::CompiledModel 类 相关学习笔记
在深度学习模型的部署过程中,前处理是一个至关重要的步骤。前处理的目标是将原始数据转换为模型可以接受的格式,以确保推理结果的准确性和一致性。以下是前处理的一些常见操作及其实现方法。
OpenCV
、Pillow
NLTK
、spaCy
librosa
、torchaudio
OpenCV
、moviepy
1 | import cv2 |
1 | cv::resize(image, resized_image, cv::Size(224, 224)); |
[0, 1]
或 [-1, 1]
。1 | import torchvision.transforms as transforms |
[Batch, Height, Width, Channels]
(TensorFlow 通常使用)[Batch, Channels, Height, Width]
(PyTorch 通常使用)1 | import numpy as np |
float32
、int8
等。1 | float_image = image.astype('float32') |
[0, 1]
范围。以下是一个典型的图像模型的前处理示例,使用 Python:
1 | import cv2 |
TensorFlow/TF-Serving
tf.image
模块进行前处理。PyTorch/TorchServe
torchvision.transforms
执行前处理。Handler
中实现前处理。ONNX Runtime
OpenCV/DNN 模块
不匹配问题
性能问题
动态尺寸
如果你有具体的部署场景(如平台或数据类型),我可以提供更细致的实现建议!
在深度学习模型部署中,前处理的目的是将原始输入数据转换为模型可以接受的格式和结构。数据加载是前处理的重要组成部分,涉及从存储中读取数据、对数据进行解析、归一化、格式化等步骤,为后续的推理做好准备。
以下是深度学习模型部署中 数据加载 的详细解析:
数据加载的主要任务包括:
从各种来源读取数据
解析和转换数据
批量加载和优化性能
对输入数据进行前处理
根据数据来源和格式,选择适当的读取方法:
open()
、fopen()
)。requests
、boto3
)下载文件。示例(读取本地图片):
1 | from PIL import Image |
将解析后的数据转换为深度学习框架支持的张量(如 PyTorch 的 torch.Tensor
或 TensorFlow 的 tf.Tensor
)。
1 | import torch |
根据模型需求对数据进行调整:
示例:
1 | transform = transforms.Compose([ |
通过批量加载数据,可以减少 I/O 操作的开销,提高吞吐量。
1 | from torch.utils.data import DataLoader, Dataset |
使用多线程或多进程加载数据,在数据加载的同时进行推理。
1 | data_loader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4) |
对于频繁使用的数据,可以将其缓存到内存或更快的存储设备(如 SSD),减少加载时间。
num_workers
使用多线程/多进程。NVIDIA DALI(Data Loading Library):
1 | from nvidia.dali.pipeline import Pipeline |
TensorFlow Data API:
1 | import tensorflow as tf |
数据加载在深度学习模型部署中至关重要。通过高效的读取、解析、转换、批量加载和预处理,能够显著提升模型推理的性能与可靠性。根据具体场景选择合适的工具和优化策略,可以大大改善部署效果。
将图像数据转换为深度学习模型可用的张量格式,是图像处理和模型前处理中的关键步骤。不同的深度学习框架(如 TensorFlow、PyTorch)对张量的具体格式要求略有不同,但总体过程类似。
以下是将图像数据转为模型可用的张量格式的详细步骤和注意事项:
张量是一个多维数组,常见的格式包括:
(Batch, Channels, Height, Width)
或 (Batch, Height, Width, Channels)
。float32
或 float16
。[0, 1]
或标准化为均值为 0、方差为 1。Height x Width x Channels
格式)。cv2
): 默认读取为 BGR 格式。PIL
): 默认读取为 RGB 格式。示例(读取图片):
1 | from PIL import Image |
224x224
。resize
)、裁剪(crop
)、填充(pad
)等方式实现。示例(调整大小):
1 | image = image.resize((224, 224)) # 调整到 224x224 |
示例(Pillow 转 NumPy):
1 | import numpy as np |
[0, 255]
转换为 [0, 1]
或 [-1, 1]。示例(归一化为 [0, 1]):
1 | image_array = image_array / 255.0 |
示例(标准化):
1 | mean = [0.485, 0.456, 0.406] |
(Channels, Height, Width)
。(Height, Width, Channels)
。transpose
改变通道顺序。示例(HWC 转为 CHW):
1 | image_array = np.transpose(image_array, (2, 0, 1)) # 从 HWC 转为 CHW |
torch.Tensor
tf.convert_to_tensor
示例(PyTorch 张量转换):
1 | import torch |
示例(TensorFlow 张量转换):
1 | import tensorflow as tf |
使用 torchvision.transforms
提供了高效的图像预处理工具。
示例:
1 | from torchvision import transforms |
使用 tf.image
提供了一系列操作。
示例:
1 | import tensorflow as tf |
将图像数据转为模型可用的张量格式是模型推理的关键步骤,核心流程包括:
不同框架提供了便利的工具和 API 支持,高效实现图像的张量化,能够显著提升模型的推理性能和兼容性。
归一化是深度学习模型训练和推理中的重要步骤之一,旨在将输入数据的特征值范围调整到特定范围或分布,以提高模型的收敛速度和预测稳定性。本文将详细解析归一化的概念、目的、方法、应用场景和注意事项。
减少特征尺度差异:
[0, 255]
,而温度值范围为 [-30, 50]
)。加速模型收敛:
增强数值稳定性:
适应特定模型需求:
[0, 1]
或 [-1, 1]
)或具有特定的均值和标准差。归一化的方法根据应用场景和数据类型有所不同,以下是常见的归一化技术。
公式:
[
x_{\text{norm}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}
]
范围: 通常将数据归一化到 [0, 1]
,或通过线性变换调整为其他范围(如 [-1, 1]
)。
应用场景:
[0, 1]
。示例:
1 | import numpy as np |
公式:
[
x_{\text{norm}} = \frac{x - \mu}{\sigma}
]
其中 ( \mu ) 是均值,( \sigma ) 是标准差。
范围: 数据转换为均值为 0
,标准差为 1
的分布。
应用场景:
示例:
1 | mean = np.mean(data) |
公式:
[
x_{\text{norm}} = \frac{x - \mu}{\sigma}
]
并根据目标分布调整:
[
x_{\text{scaled}} = x_{\text{norm}} \times \sigma_{\text{target}} + \mu_{\text{target}}
]
应用场景:
示例(PyTorch):
1 | from torchvision import transforms |
公式:
[
x_{\text{norm}} = \log(x + 1)
]
应用场景:
注意:
示例:
1 | log_normalized_data = np.log(data + 1) |
[-1, 1]
。在图像处理中,归一化的目标是将原始像素值(通常是 [0, 255]
)转换为深度学习模型所需的格式。
[0, 1]
:x / 255.0
[-1, 1]
:(x / 127.5) - 1
[0.485, 0.456, 0.406]
[0.229, 0.224, 0.225]
归一化到 [0, 1]
:
1 | import numpy as np |
归一化到特定均值和标准差:
1 | import torch |
torchvision.transforms.Normalize
1 | transforms.Normalize(mean, std) |
tf.image.per_image_standardization
或手动实现归一化。
归一化是模型预处理中的关键步骤,能够显著提升模型性能。根据不同场景选择合适的归一化方法,有助于稳定和加速训练,同时提高推理精度。
归一化/标准化实质是一种线性变换,线性变换有很多良好的性质,这些性质决定了对数据改变后不会造成失效,反而能提高数据的表现,这些性质是归一化/标准化的前提。比如有一个很重要的性质:线性变换不会改变原始数据的数值排序。
简单来说有以下好处
cv::Rect
是 OpenCV 中的一个类,表示一个矩形区域。它常用于图像处理中的矩形区域定义,尤其是在图像裁剪、目标检测、区域分析等任务中。cv::Rect
类非常简洁和高效,提供了多种构造方法和常用操作,使得矩形区域的处理变得简单方便。
cv::Rect
类定义1 | class cv::Rect |
**x
**:
**y
**:
**width
**:
**height
**:
默认构造函数:
1 | Rect(); // 默认值 x=0, y=0, width=0, height=0 |
指定坐标和宽高构造矩形:
1 | Rect(int _x, int _y, int _width, int _height); |
(x, y)
和矩形的 width
(宽度)和 height
(高度)来构造矩形。通过对角线两个点构造矩形:
1 | Rect(const Point& pt1, const Point& pt2); |
pt1
和 pt2
来创建矩形,pt1
和 pt2
表示矩形的对角线的两个点。拷贝构造函数:
1 | Rect(const Rect& r); |
**area()
**:
width * height
)。1 | int area() const; |
**empty()
**:
1 | bool empty() const; |
**br()
**:
1 | Point br() const; // 返回 (x + width, y + height) |
**tl()
**:
1 | Point tl() const; // 返回 (x, y) |
矩形相加:
1 | Rect r1(0, 0, 10, 10); |
矩形相交:
1 | Rect r1(0, 0, 10, 10); |
矩形移动:
1 | Rect r(10, 20, 30, 40); |
矩形包含检测:
1 | Rect r(10, 10, 50, 50); |
图像裁剪:
cv::Rect
用于指定感兴趣区域(ROI)。通过矩形定义裁剪区域,可以快速地对图像进行裁剪。1 | Mat img = imread("image.jpg"); |
目标检测:
图像窗口和标注:
cv::Rect
用于表示图像中的标注区域或绘制图像的窗口。在 OpenCV 中经常会使用矩形框来标记特定区域,如绘制矩形框标记目标。区域分析:
cv::Rect
常用于表示分析的区域,尤其是在分割、提取特征等任务中。1 | #include <opencv2/opencv.hpp> |
输出:
1 | Rect 1: (10, 20, 30, 40) |
cv::Rect
是一个简单而强大的类,用于表示矩形区域,在图像处理、目标检测、图像裁剪、区域分析等多种应用中都有广泛的用途。cv::Rect
主要用于图像处理中的区域定义、裁剪、目标检测、区域计算等任务,是 OpenCV 中不可或缺的重要类。WSAGetLastError()
是用于在 Windows Sockets API (也称为 WinSock) 中获取上一次操作的错误代码的函数。它与标准 C 库中的 GetLastError()
类似,但专门用于处理与网络相关的错误。
在调用 WinSock 函数时,某些操作可能会失败,而这些函数通常会返回一个特殊的值(例如 SOCKET_ERROR
或 INVALID_SOCKET
),以指示操作没有成功。在这种情况下,WSAGetLastError()
用于检索详细的错误代码,以帮助开发人员诊断问题。
1 | int WSAGetLastError(void); |
错误处理:当调用诸如 socket()
, bind()
, connect()
, send()
, recv()
等网络函数时,如果返回值指示失败,您可以立即调用 WSAGetLastError()
来获取更详细的错误信息。
排查问题:通过返回的错误代码,可以判断是什么原因导致操作失败,比如网络不可达、连接超时、权限不足等。
以下是一些常见的错误代码及其含义:
WSAEACCES (10013)
:权限被拒绝。可能是由于防火墙或权限问题。WSAEADDRINUSE (10048)
:地址已在使用中。尝试绑定一个已经被占用的端口。WSAEADDRNOTAVAIL (10049)
:指定的地址无效,无法连接到指定的 IP 地址。WSAENETDOWN (10050)
:网络子系统已失败。WSAENETUNREACH (10051)
:网络无法到达。通常表示无法访问远程主机的网络。WSAECONNABORTED (10053)
:软件导致的连接中止。通常是由于超时或其他问题导致连接中断。WSAECONNRESET (10054)
:远程主机强行关闭连接。WSAETIMEDOUT (10060)
:连接尝试超时。下面是一个简单的使用 WSAGetLastError()
的示例,展示如何处理网络函数失败时的错误:
1 | #include <winsock2.h> |
WSAGetLastError()
只会返回最近一次失败的 WinSock 操作的错误代码。因此,它需要在失败操作后立即调用,延迟调用可能导致错误信息被覆盖。WSAGetLastError()
是线程安全的,每个线程都有自己的错误代码,不会与其他线程冲突。通过 WSAGetLastError()
,你可以获取到更加精确的错误信息,以便进行有效的网络调试和处理。
QVector
是 Qt 框架提供的动态数组模板类,它是用来存储和操作一组数据的容器,类似于 C++ 的标准库 std::vector
。QVector
提供了一种高效且易于使用的方式来管理动态分配的数组,同时支持许多高级功能。
动态调整大小:QVector
会根据需要动态调整容量,不需要开发者手动管理内存。
高效的索引访问:
支持通过索引访问元素,时间复杂度为 (O(1))。
深拷贝优化:
当 QVector
被复制时,会使用 Qt 的隐式共享机制(Copy-on-Write),以减少不必要的内存开销。
内存连续性:
存储的数据在内存中是连续的,允许与低级别 C/C++ 数组无缝集成。
1 | QVector<int> vector; |
1 | QVector<int> vector(5, 42); // 创建包含5个元素的向量,值为42 |
1 | int arr[] = {1, 2, 3, 4}; |
1 | vector.append(10); // 在末尾添加 |
1 | vector.insert(1, 20); // 在索引1插入值20 |
1 | vector.remove(0); // 删除索引0处的元素 |
1 | int value = vector[2]; |
1 | int first = vector.first(); |
1 | int size = vector.size(); |
1 | bool empty = vector.isEmpty(); |
1 | vector.clear(); |
1 | int index = vector.indexOf(10); // 查找值10的索引 |
1 | std::sort(vector.begin(), vector.end()); |
隐式共享(Copy-on-Write)
多个 QVector
对象共享相同的数据副本,只有在其中一个被修改时才会创建实际的副本。
1 | QVector<int> vector1 = {1, 2, 3}; |
与低级数组的集成
可以通过 data()
方法获取底层 C 样式数组指针:
1 | int *array = vector.data(); |
互操作性
可以轻松与 std::vector
和其他容器进行转换:
1 | std::vector<int> stdVector = vector.toStdVector(); |
特性 | QVector | std::vector |
---|---|---|
内存管理 | 隐式共享(Copy-on-Write) | 无隐式共享,每次复制都会分配新内存 |
线程安全性 | 对读操作线程安全 | 不支持线程安全 |
Qt 集成 | 完全与 Qt 生态兼容 | 需要适配 Qt 的其他功能 |
性能 | 对于只读场景性能更优 | 在频繁修改场景下性能更高 |
Qt 应用开发中的动态数据管理:
用于存储和操作动态数据,例如处理 UI 元素的集合或动态列表。
需要与 Qt API 无缝集成的项目:
QVector 与其他 Qt 类(如 QListView
、QComboBox
)的接口无缝协作。
更多关于 QVector
的详细文档,请参考 Qt 官方文档。
Qt项目中,后缀为.ui的文件是可视化设计的窗体的定义文件,例如widget.ui。双击项目文件目录树中的文件widget.ui,会打开一个集成在Qt Creator中的Qt Designer对窗体进行可视化设计。
widget.ui文件,是窗体界面定义文件,是一个XML文件,定义了窗口上的所有组件的属性设置,布局,及其信号与槽函数的关联等。用UI设计器可视化设计的界面都由Qt自动解析,并以XML文件的形式保存下来。在设计界面时,只需在UI设计器里进行可视化设计即可。
ui_widget.h文件,是对widget.ui文件编译后生成的一个文件,ui_widget.h会出现在编译后的目录下,或与widget.ui同目录
ui_widget.h并不会出现在QtCreator的项目文件目录树里,当然可以手动将ui_widget.h添加到项目中
注意,ui_widget.h是对widget.ui文件编译后自动生成的,widget.ui又是通过UI设计器可视化设计生成的。所有,对ui_widget.h手动进行修改没有什么意义,所有涉及界面的修改都应该直接在UI设计器里进行。
查看ui_widget.h文件的内容,发现它主要做了以下的一些工作
1 | namespace Ui |
提示,ui_widget.h文件里实现界面功能的类是Ui_Widget。再定义一个类Widget从Ui_Widget继承而来,并定义在namespace Ui里,这样Ui::Widget与widget.h里的类Widget同名,但是用namespace区分开来。所以,界面的Ui::Widget类与文件widget.h里定义的Widget类实际上是两个类,但是Qt的处理让用户感觉不到Ui::Widget类的存在,只需要直到在Widget类里用ui指针可以访问可视化设计的界面组件就可以了。
1 | uic.exe test.ui -o test.h |
Qt Quick Designer 是 Qt Creator 集成开发环境(IDE)中的一个可视化设计工具,专用于设计和开发基于 Qt Quick(QML) 的用户界面。它提供了所见即所得(WYSIWYG)的编辑环境,使开发者能够通过拖放组件、调整属性等方式,快速构建现代化、响应式的用户界面,而无需手动编写大量的 QML 代码。
在深入了解 Qt Quick Designer 之前,先简单回顾一下 Qt Quick 和 QML:
Qt Quick:是 Qt 框架中用于创建动态用户界面的技术,特别适用于触摸屏和移动设备应用。它结合了 QML 和 JavaScript,实现了高性能的界面渲染和丰富的动画效果。
QML(Qt Modeling Language):是一种基于 JavaScript 的声明式语言,专门用于设计用户界面。通过 QML,可以以简洁直观的方式描述界面元素、布局和交互行为。
Main.qml
文件,双击打开。id
,方便在代码中引用。Qt Quick Designer 是开发 Qt Quick 应用程序的强大工具,提供了高效、直观的界面设计方式。通过结合拖放式设计、属性编辑、动画创建和代码同步等功能,它简化了用户界面的开发流程,适合从初学者到资深开发者的各种需求。熟练掌握 Qt Quick Designer,将有助于快速构建高质量的跨平台应用程序。
初始化连接操作
1 | socket.async_connect(server_endpoint, your_completion_handler); |
这个 your_completion_handler 是一个函数或函数对象,其函数签名如下
1 | void your_completion_handler(const asio::error_code& ec); |
这个IO对象将请求转发给IO执行体
IO执行体给操作系统发送一个信号,提示操作系统应该开始一个异步链接
当使用 io_context 当作IO执行体时,程序必须调用 io_context::run() 使异步任务的结果可以被接收并处理。当有未完成的异步操作时,调用 io_context::run() 将被阻塞。
当你开始第一个异步操作时,就应该调用 io_context::run()
在调用 io_context::run() 内部,IO执行体将操作的结果转换为error_code,然后将它传递给 your completion handler
在 Asio(或 Boost.Asio)中,asio::ip::tcp::endpoint
是一个类,表示一个 TCP 端点。它包含了 IP 地址和端口号,用于描述 TCP 网络通信中的目标或源。
asio::ip::tcp::endpoint
是 TCP 套接字(asio::ip::tcp::socket
)操作中的核心组件,用来:
asio::ip::tcp::endpoint
是 asio::ip::basic_endpoint<tcp>
的具体实现,用于 TCP 协议。
1 | namespace asio { |
1 | asio::ip::address addr = asio::ip::make_address("127.0.0.1"); |
这表示一个 TCP 端点,IP 地址是 127.0.0.1
,端口号是 8080
。
1 | asio::ip::tcp::endpoint endpoint; |
创建一个默认的端点,地址为 0.0.0.0
(未指定地址),端口为 0
。
当服务器需要绑定一个本地端口时,可以使用:
1 | asio::ip::tcp::acceptor acceptor(io_context); |
此处,endpoint
表示监听在 IPv4 地址的所有网络接口(0.0.0.0
)上的端口 8080
。
1 | asio::ip::tcp::resolver resolver(io_context); |
1 | asio::ip::tcp::socket socket(io_context); |
1 | asio::ip::tcp::endpoint endpoint(asio::ip::make_address("192.168.1.1"), 12345); |
输出:
1 | Endpoint: 192.168.1.1:12345 |
默认构造
0.0.0.0:0
或 [::]:0
)。1 | asio::ip::tcp::endpoint(); |
地址和端口构造
asio::ip::address
和端口初始化。1 | asio::ip::tcp::endpoint(const asio::ip::address& addr, unsigned short port); |
协议构造
1 | asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 8080); |
设置地址
1 | void address(const asio::ip::address& addr); |
修改端点的 IP 地址。
获取地址
1 | asio::ip::address address() const; |
返回端点的 IP 地址。
设置端口
1 | void port(unsigned short port); |
修改端点的端口号。
获取端口
1 | unsigned short port() const; |
返回端点的端口号。
转换为字符串
1 | std::string to_string() const; |
返回端点的字符串表示形式,例如 127.0.0.1:8080
。
1 | asio::io_context io_context; |
1 | asio::io_context io_context; |
asio::ip::tcp::endpoint
是 TCP 通信中的一个核心组件,用于描述通信双方的网络位置。它不仅简单易用,而且与其他 Asio 组件(如 tcp::resolver
、tcp::socket
)紧密配合,为构建高效的网络程序提供了强大支持。