0%

摘要

  • 模型前处理相关学习笔记

深度学习模型部署 前处理

在深度学习模型的部署过程中,前处理是一个至关重要的步骤。前处理的目标是将原始数据转换为模型可以接受的格式,以确保推理结果的准确性和一致性。以下是前处理的一些常见操作及其实现方法。


前处理的主要步骤

1. 数据加载

  • 从文件、摄像头、传感器、API 等读取数据。
  • 支持的格式可能包括图像(JPEG/PNG)、文本、视频、音频等。
  • 常用库:
    • 图像:OpenCVPillow
    • 文本:NLTKspaCy
    • 音频:librosatorchaudio
    • 视频:OpenCVmoviepy

2. 尺寸调整 (Resizing)

  • 模型通常需要固定大小的输入,例如 224x224。
  • 工具:
    • Python:
      1
      2
      import cv2
      resized = cv2.resize(image, (224, 224))
    • C++ (OpenCV):
      1
      cv::resize(image, resized_image, cv::Size(224, 224));

3. 归一化 (Normalization)

  • 将输入数据的像素值范围调整到 [0, 1][-1, 1]
  • 公式:
    [
    x_{\text{normalized}} = \frac{x - \text{mean}}{\text{std}}
    ]
  • 常用库实现:
    • PyTorch:
      1
      2
      3
      import torchvision.transforms as transforms
      transform = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
      normalized_image = transform(image)

4. 数据格式转换

  • 深度学习模型可能需要特定的数据格式(例如 NHWC 或 NCHW)。
    • NHWC: [Batch, Height, Width, Channels](TensorFlow 通常使用)
    • NCHW: [Batch, Channels, Height, Width](PyTorch 通常使用)
  • 转换:
    1
    2
    import numpy as np
    nhwc_image = image.transpose(0, 2, 3, 1) # NCHW -> NHWC

5. 类型转换

  • 确保数据的类型与模型要求一致,例如 float32int8 等。
  • 转换:
    1
    float_image = image.astype('float32')

6. 数据归一化与标准化的结合

  • 对于图像:
    • 首先缩放到 [0, 1] 范围。
    • 再进行归一化(减去均值,除以标准差)。
  • 对于文本或序列数据:
    • 可能需要 Tokenization 和 Padding。

7. 其他特定处理

  • 数据增强:裁剪、旋转、翻转(在推理时一般用于 TTA)。
  • 嵌入:对文本进行词嵌入转换。
  • 频谱图:将音频信号转换为频谱图。

示例代码

以下是一个典型的图像模型的前处理示例,使用 Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np

def preprocess_image(image_path, input_size=(224, 224)):
# 加载图像
image = cv2.imread(image_path)
if image is None:
raise ValueError("Image not found or unable to read!")

# 调整大小
resized = cv2.resize(image, input_size)

# 转换为浮点数并归一化到 [0, 1]
normalized = resized / 255.0

# 转换为 NCHW 格式
nchw_image = np.transpose(normalized, (2, 0, 1))

# 增加 Batch 维度并转换为 float32
batch_image = np.expand_dims(nchw_image, axis=0).astype(np.float32)

return batch_image

部署中的前处理工具

  1. TensorFlow/TF-Serving

    • 使用 tf.image 模块进行前处理。
    • 可将前处理代码直接集成到模型中(SavedModel)。
  2. PyTorch/TorchServe

    • 使用 torchvision.transforms 执行前处理。
    • 在自定义 Handler 中实现前处理。
  3. ONNX Runtime

    • 在推理时依赖外部前处理,但支持优化后的动态尺寸输入。
  4. OpenCV/DNN 模块

    • OpenCV 支持直接加载和推理模型,可在前处理阶段使用其高效工具。

前处理常见问题与优化

  1. 不匹配问题

    • 原因:模型训练和推理时的前处理设置不同。
    • 解决:确保推理阶段与训练一致(归一化参数、数据格式等)。
  2. 性能问题

    • 原因:前处理耗时过长。
    • 解决:
      • 使用并行库(如 DALI)。
      • 在 GPU/TPU 上完成前处理。
  3. 动态尺寸

    • 原因:输入尺寸不固定。
    • 解决:使用动态尺寸模型,或在前处理阶段适配输入。

如果你有具体的部署场景(如平台或数据类型),我可以提供更细致的实现建议!

前处理 数据加载

在深度学习模型部署中,前处理的目的是将原始输入数据转换为模型可以接受的格式和结构。数据加载是前处理的重要组成部分,涉及从存储中读取数据、对数据进行解析、归一化、格式化等步骤,为后续的推理做好准备。

以下是深度学习模型部署中 数据加载 的详细解析:


1. 数据加载的核心任务

数据加载的主要任务包括:

  1. 从各种来源读取数据

    • 常见数据来源:文件系统、数据库、API、消息队列等。
    • 文件格式:图片(JPEG/PNG)、视频(MP4)、音频(WAV/MP3)、文本(JSON/CSV)、二进制数据等。
  2. 解析和转换数据

    • 解析数据格式(如图片解码、JSON 解析)。
    • 将数据转化为模型可用的张量(Tensor)格式。
  3. 批量加载和优化性能

    • 支持批量加载(Batching)以提高吞吐量。
    • 利用多线程或异步加载优化性能。
  4. 对输入数据进行前处理

    • 标准化、归一化。
    • 调整大小、裁剪、填充。
    • 数据增强(如旋转、翻转等)。

2. 数据加载的典型步骤

(1) 数据读取

根据数据来源和格式,选择适当的读取方法:

  • 本地文件系统: 使用操作系统原生文件读写函数(如 open()fopen())。
  • 云存储/网络资源: 使用库(如 requestsboto3)下载文件。
  • 数据库: 使用 SQL 或 NoSQL 查询获取数据。

示例(读取本地图片):

1
2
from PIL import Image
image = Image.open("image.jpg")

(2) 数据解析

  • 将原始数据解析为内存中的对象。例如:
    • 图片数据解码为像素矩阵。
    • JSON 数据解析为 Python 字典。
    • CSV 数据解析为 DataFrame 或数组。

(3) 数据转换为张量

将解析后的数据转换为深度学习框架支持的张量(如 PyTorch 的 torch.Tensor 或 TensorFlow 的 tf.Tensor)。

1
2
3
4
5
import torch
from torchvision import transforms

transform = transforms.ToTensor()
tensor = transform(image)

(4) 数据预处理

根据模型需求对数据进行调整:

  • 归一化: 将像素值缩放到 [0, 1] 或 [-1, 1] 区间。
  • 调整大小: 将图片调整为固定尺寸。
  • 格式转换: 如 RGB -> BGR 或 NHWC -> NCHW。

示例:

1
2
3
4
5
6
transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整大小
transforms.ToTensor(), # 转为张量
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 归一化
])
processed_image = transform(image)

3. 高效数据加载技巧

(1) 批量加载

通过批量加载数据,可以减少 I/O 操作的开销,提高吞吐量。

  • 示例(PyTorch DataLoader):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from torch.utils.data import DataLoader, Dataset

    class CustomDataset(Dataset):
    def __init__(self, file_paths, transform=None):
    self.file_paths = file_paths
    self.transform = transform

    def __len__(self):
    return len(self.file_paths)

    def __getitem__(self, idx):
    image = Image.open(self.file_paths[idx])
    if self.transform:
    image = self.transform(image)
    return image

    dataset = CustomDataset(file_paths=["img1.jpg", "img2.jpg"], transform=transform)
    data_loader = DataLoader(dataset, batch_size=32, shuffle=True)

(2) 异步加载

使用多线程或多进程加载数据,在数据加载的同时进行推理。

  • 示例(PyTorch 的多进程加载):
    1
    data_loader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4)

(3) 数据缓存

对于频繁使用的数据,可以将其缓存到内存或更快的存储设备(如 SSD),减少加载时间。


4. 常见的数据加载场景

(1) 图片数据加载

  • 格式: JPEG、PNG、TIFF 等。
  • 前处理:
    • 调整大小、裁剪。
    • 归一化为 [0, 1] 或 [-1, 1]。
  • 库支持: OpenCV、Pillow、Torchvision、TensorFlow。

(2) 视频数据加载

  • 格式: MP4、AVI 等。
  • 前处理:
    • 抽帧、调整帧率。
    • 归一化。
  • 库支持: OpenCV、FFmpeg、Decord。

(3) 文本数据加载

  • 格式: JSON、CSV、纯文本。
  • 前处理:
    • 分词、去停用词。
    • 将文本转换为向量(如词嵌入)。
  • 库支持: pandas、NLTK、Hugging Face。

(4) 音频数据加载

  • 格式: WAV、MP3。
  • 前处理:
    • 采样率调整。
    • 转换为梅尔频谱或 MFCC 特征。
  • 库支持: librosa、torchaudio。

5. 数据加载中的常见问题及优化

问题 1:数据加载瓶颈

  • 原因: 数据加载速度不足以跟上推理速度。
  • 优化:
    • 增加 num_workers 使用多线程/多进程。
    • 缓存数据。
    • 使用高效的数据格式(如 TFRecord)。

问题 2:内存占用过高

  • 原因: 一次性加载过多数据。
  • 优化:
    • 使用生成器或流式加载。
    • 控制批量大小。

问题 3:数据预处理耗时长

  • 原因: 复杂的预处理步骤在加载时完成。
  • 优化:
    • 将预处理离线完成,保存为中间格式。
    • 使用 GPU 加速数据预处理(如 DALI)。

6. 高级工具与框架支持

  • NVIDIA DALI(Data Loading Library):

    • 高效的数据加载和预处理库,支持 GPU 加速。
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      from nvidia.dali.pipeline import Pipeline
      import nvidia.dali.ops as ops

      class SimplePipeline(Pipeline):
      def __init__(self, batch_size, num_threads, device_id):
      super(SimplePipeline, self).__init__(batch_size, num_threads, device_id)
      self.decode = ops.ImageDecoder(device="mixed")
      self.resize = ops.Resize(resize_x=224, resize_y=224)

      def define_graph(self):
      images = self.decode(self.input)
      return self.resize(images)

      pipe = SimplePipeline(batch_size=32, num_threads=4, device_id=0)
  • TensorFlow Data API:

    • 支持高效的输入管道。
    • 示例:
      1
      2
      3
      4
      5
      import tensorflow as tf

      dataset = tf.data.Dataset.list_files("images/*.jpg")
      dataset = dataset.map(lambda x: preprocess(x))
      dataset = dataset.batch(32).prefetch(tf.data.AUTOTUNE)

总结

数据加载在深度学习模型部署中至关重要。通过高效的读取、解析、转换、批量加载和预处理,能够显著提升模型推理的性能与可靠性。根据具体场景选择合适的工具和优化策略,可以大大改善部署效果。

图像数据转为模型可用的张量格式 详解

将图像数据转换为深度学习模型可用的张量格式,是图像处理和模型前处理中的关键步骤。不同的深度学习框架(如 TensorFlow、PyTorch)对张量的具体格式要求略有不同,但总体过程类似。

以下是将图像数据转为模型可用的张量格式的详细步骤和注意事项:


1. 张量的基本要求

张量是一个多维数组,常见的格式包括:

  1. 形状(Shape): 模型通常要求特定形状的输入,例如 (Batch, Channels, Height, Width)(Batch, Height, Width, Channels)
  2. 数据类型(Data Type): 常用 float32float16
  3. 数值范围:
    • 一般要求归一化为 [0, 1] 或标准化为均值为 0、方差为 1。
    • 部分模型(如 ResNet、MobileNet)可能要求特定的归一化范围或通道顺序。

2. 转换步骤

(1) 读取图像数据

  • 使用图像处理库读取图像文件,将其转化为像素矩阵(通常是 Height x Width x Channels 格式)。
  • 常用库:
    • OpenCV (cv2): 默认读取为 BGR 格式。
    • Pillow (PIL): 默认读取为 RGB 格式。

示例(读取图片):

1
2
from PIL import Image
image = Image.open("image.jpg") # 使用 Pillow 读取图片

(2) 调整尺寸

  • 深度学习模型通常要求固定尺寸的输入图像,例如 224x224
  • 可以通过调整大小(resize)、裁剪(crop)、填充(pad)等方式实现。

示例(调整大小):

1
image = image.resize((224, 224))  # 调整到 224x224

(3) 转换为数组

  • 将图像对象转换为数值数组(通常为 NumPy 数组)。

示例(Pillow 转 NumPy):

1
2
import numpy as np
image_array = np.array(image) # 转换为 NumPy 数组

(4) 归一化或标准化

  • 将像素值范围从 [0, 255] 转换为 [0, 1] 或 [-1, 1]。
  • 如果模型要求,可以使用特定均值和标准差进行标准化。

示例(归一化为 [0, 1]):

1
image_array = image_array / 255.0

示例(标准化):

1
2
3
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
image_array = (image_array - mean) / std

(5) 调整通道顺序

  • 不同框架要求的张量通道顺序可能不同:
    • PyTorch: 通道优先,即 (Channels, Height, Width)
    • TensorFlow/Keras: 通道最后,即 (Height, Width, Channels)
  • 可以通过 transpose 改变通道顺序。

示例(HWC 转为 CHW):

1
image_array = np.transpose(image_array, (2, 0, 1))  # 从 HWC 转为 CHW

(6) 转换为张量

  • 将 NumPy 数组转换为深度学习框架支持的张量。
  • 框架示例:
    • PyTorch:torch.Tensor
    • TensorFlow:tf.convert_to_tensor

示例(PyTorch 张量转换):

1
2
3
import torch
image_tensor = torch.tensor(image_array, dtype=torch.float32) # 转为张量
image_tensor = image_tensor.unsqueeze(0) # 增加 Batch 维度

示例(TensorFlow 张量转换):

1
2
3
import tensorflow as tf
image_tensor = tf.convert_to_tensor(image_array, dtype=tf.float32) # 转为张量
image_tensor = tf.expand_dims(image_tensor, axis=0) # 增加 Batch 维度

3. 不同框架的常见操作

(1) PyTorch

使用 torchvision.transforms 提供了高效的图像预处理工具。

示例:

1
2
3
4
5
6
7
8
9
10
11
from torchvision import transforms
from PIL import Image

transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整大小
transforms.ToTensor(), # 转为张量并归一化为 [0, 1]
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 标准化
])

image = Image.open("image.jpg")
image_tensor = transform(image).unsqueeze(0) # 增加 Batch 维度

(2) TensorFlow

使用 tf.image 提供了一系列操作。

示例:

1
2
3
4
5
6
7
import tensorflow as tf

image = tf.io.read_file("image.jpg")
image = tf.image.decode_jpeg(image, channels=3)
image = tf.image.resize(image, [224, 224]) # 调整大小
image = image / 255.0 # 归一化
image = tf.expand_dims(image, axis=0) # 增加 Batch 维度

4. 常见问题与优化

问题 1:通道顺序错误

  • 模型可能因通道顺序不同而报错(如 TensorFlow 使用 NHWC,而 PyTorch 使用 NCHW)。
  • 解决: 确认模型要求并调整顺序。

问题 2:未归一化或标准化

  • 部分模型对输入的数值范围敏感,未归一化可能导致推理结果异常。
  • 解决: 检查模型要求并按需处理。

问题 3:性能瓶颈

  • 数据加载和预处理可能成为瓶颈,尤其是在实时推理中。
  • 解决:
    • 使用多线程/多进程。
    • 将预处理步骤迁移到 GPU(如 NVIDIA DALI)。
    • 提前预处理并保存为高效格式(如 TFRecord)。

5. 总结

将图像数据转为模型可用的张量格式是模型推理的关键步骤,核心流程包括:

  1. 读取图像数据(文件、内存或流式读取)。
  2. 调整尺寸、归一化、标准化。
  3. 转换通道顺序和数据类型。
  4. 将数据打包为张量格式并加入批量维度。

不同框架提供了便利的工具和 API 支持,高效实现图像的张量化,能够显著提升模型的推理性能和兼容性。

前处理中的归一化 详解

深度学习前处理中的归一化详解

归一化是深度学习模型训练和推理中的重要步骤之一,旨在将输入数据的特征值范围调整到特定范围或分布,以提高模型的收敛速度和预测稳定性。本文将详细解析归一化的概念、目的、方法、应用场景和注意事项。


1. 为什么需要归一化?

  1. 减少特征尺度差异:

    • 输入数据可能具有不同的特征范围(例如像素值范围为 [0, 255],而温度值范围为 [-30, 50])。
    • 归一化可以让特征值具有相似的尺度,避免某些特征对模型的影响过大。
  2. 加速模型收敛:

    • 无归一化的数据可能导致梯度下降过程缓慢或震荡,归一化能够稳定梯度下降。
  3. 增强数值稳定性:

    • 极端数值可能导致计算不稳定,归一化可以避免溢出或下溢。
  4. 适应特定模型需求:

    • 某些预训练模型要求输入数据在特定范围(如 [0, 1][-1, 1])或具有特定的均值和标准差。

2. 常见的归一化方法

归一化的方法根据应用场景和数据类型有所不同,以下是常见的归一化技术。

(1) Min-Max 归一化

  • 公式:
    [
    x_{\text{norm}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}
    ]

  • 范围: 通常将数据归一化到 [0, 1],或通过线性变换调整为其他范围(如 [-1, 1])。

  • 应用场景:

    • 图像像素值归一化到 [0, 1]
    • 特征值范围固定的数据。
  • 示例:

    1
    2
    3
    4
    5
    6
    import numpy as np
    data = np.array([50, 100, 150, 200])
    data_min = np.min(data)
    data_max = np.max(data)
    normalized_data = (data - data_min) / (data_max - data_min)
    print(normalized_data) # 输出 [0. , 0.333, 0.666, 1. ]

(2) 均值-标准差标准化(Z-Score 标准化)

  • 公式:
    [
    x_{\text{norm}} = \frac{x - \mu}{\sigma}
    ]
    其中 ( \mu ) 是均值,( \sigma ) 是标准差。

  • 范围: 数据转换为均值为 0,标准差为 1 的分布。

  • 应用场景:

    • 特征值分布不固定(如自然语言处理中的词向量)。
    • 深度学习预训练模型(如 ImageNet 预训练模型)常使用固定均值和标准差归一化。
  • 示例:

    1
    2
    3
    4
    mean = np.mean(data)
    std = np.std(data)
    standardized_data = (data - mean) / std
    print(standardized_data)

(3) 归一化到特定均值和标准差

  • 公式:
    [
    x_{\text{norm}} = \frac{x - \mu}{\sigma}
    ]
    并根据目标分布调整:
    [
    x_{\text{scaled}} = x_{\text{norm}} \times \sigma_{\text{target}} + \mu_{\text{target}}
    ]

  • 应用场景:

    • 针对预训练模型(如 ResNet、VGG 等),调整到特定的均值和标准差。
  • 示例(PyTorch):

    1
    2
    from torchvision import transforms
    transform = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

(4) 对数归一化(Log Normalization)

  • 公式:
    [
    x_{\text{norm}} = \log(x + 1)
    ]

  • 应用场景:

    • 数据分布具有较大偏态(如高光强度图像、文档字频统计)。
  • 注意:

    • 输入值必须非负。
  • 示例:

    1
    log_normalized_data = np.log(data + 1)

(5) 最大绝对值归一化

  • 公式:
    [
    x_{\text{norm}} = \frac{x}{\max(|x|)}
    ]
  • 范围: 归一化到 [-1, 1]
  • 应用场景:
    • 数据具有正负值,但范围不固定。

(6) L2 归一化

  • 公式:
    [
    x_{\text{norm}} = \frac{x}{|x|_2}
    ]
    其中 ( |x|_2 = \sqrt{\sum x_i^2} )。
  • 范围: 单位向量,适合向量空间建模。
  • 应用场景:
    • 特征向量的相似性计算(如余弦相似度)。

3. 图像数据中的归一化

在图像处理中,归一化的目标是将原始像素值(通常是 [0, 255])转换为深度学习模型所需的格式。

(1) 常见归一化方法

  1. 归一化到 [0, 1]
    • 公式:x / 255.0
    • 应用于大多数深度学习框架。
  2. 归一化到 [-1, 1]
    • 公式:(x / 127.5) - 1
    • 常用于 GAN 和部分图像处理模型。
  3. 均值和标准差标准化:
    • 对应 ImageNet 的 RGB 通道均值和标准差:
      • 均值:[0.485, 0.456, 0.406]
      • 标准差:[0.229, 0.224, 0.225]

(2) 示例代码

归一化到 [0, 1]

1
2
3
import numpy as np
image = np.random.randint(0, 256, (224, 224, 3), dtype=np.uint8)
image_normalized = image / 255.0

归一化到特定均值和标准差:

1
2
3
4
5
6
7
8
9
10
import torch
from torchvision import transforms

transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 假设 image 是一个 PIL.Image 对象
normalized_image = transform(image)

4. 深度学习框架中的归一化工具

(1) PyTorch

torchvision.transforms.Normalize

  • 用法:
    1
    transforms.Normalize(mean, std)

(2) TensorFlow/Keras

tf.image.per_image_standardization 或手动实现归一化。


5. 注意事项

  1. 模型需求: 确保归一化过程符合模型的预处理要求。
  2. 一致性: 训练和推理时的归一化方式必须一致。
  3. 异常值处理: 对异常值进行裁剪或忽略,避免对归一化结果产生不利影响。

归一化是模型预处理中的关键步骤,能够显著提升模型性能。根据不同场景选择合适的归一化方法,有助于稳定和加速训练,同时提高推理精度。

归一化

  • 常用的方法是通过对原始数据进行线性变换把数据映射到 [0, 1] 或 [-1, 1]之间。主要是:
    • 为了数据处理方便,更加便捷快速。
    • 把有量纲表达式变为无量纲表达式,便于不同单位或量级的指标能够进行比较和加权。
  • 归一化是一种简化计算的方式,即将有量纲的表达式,经过变换,化为无量纲的表达式,成为 纯量
  • 变换函数为:
    归一化线性变换公式

标准化

  • 常用的方法是 z-score 标准化,经过处理后的数据均值为0,标准差为1
  • 在机器学习中,我们可能要处理不同种类的资料。例如,音讯和图片上的像素值,这些资料可能是高纬度的,资料标准化后使每个特征中的数值平均变为0(将每个特征的值都减掉原始资料中该特征的平均),标准差变为1,这个方法被广泛的使用在许多机器学习中,例如:支持向量机,逻辑回归和类神经网络
    标准化线性变换公式

归一化和标准化的区别

  • 归一化是将样本的特征值转换到同一量纲下把数据映射到 [0, 1]或者 [-1, 1]区间内,仅由变量的极值决定,因区间放缩法是归一化的一种。
  • 标准化是依照特征矩阵的列处理数据,其通过求z-score的方法,转换为标准正态分布,和整体样本分布相关,每个样本点都能对标准化产生影响。
  • 它们的相同点在于都能取消由于量纲不同引起的误差;都是一种线性变换,都是对向量X按照比例压缩再进行平移。

标准化和中心化的区别

  • 标准化是原始分数减去平均数然后除以标准差
  • 中心化是原始分数减去平均数。所以一般流程为先中心化再标准化。

无量纲

  • 无量纲,是通过某种方法能去表实际过程中的单位,从而简化计算。

为什么要归一化/标准化

  • 归一化/标准化实质是一种线性变换,线性变换有很多良好的性质,这些性质决定了对数据改变后不会造成失效,反而能提高数据的表现,这些性质是归一化/标准化的前提。比如有一个很重要的性质:线性变换不会改变原始数据的数值排序。

  • 简单来说有以下好处

    • 由于原始数据值的范围差异很大,因此在某些机器学习算法中,如果没有归一化,目标函数将无法正常工作。

摘要

本文介绍了在 Windows 环境下使用 .NET 命令行工具 dotnet 的基础用法和常用命令,包括如何创建、编译、运行和发布 .NET 应用程序,并提供了安装指南和适用场景。

Read more »

cv::Rect 类 详解

cv::Rect 是 OpenCV 中的一个类,表示一个矩形区域。它常用于图像处理中的矩形区域定义,尤其是在图像裁剪、目标检测、区域分析等任务中。cv::Rect 类非常简洁和高效,提供了多种构造方法和常用操作,使得矩形区域的处理变得简单方便。


cv::Rect 类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class cv::Rect
{
public:
// 4个构造函数
Rect(); // 默认构造函数
Rect(int _x, int _y, int _width, int _height); // 使用坐标和宽高构造矩形
Rect(const Point& pt1, const Point& pt2); // 使用对角线两个点构造矩形
Rect(const Rect& r); // 拷贝构造函数

// 成员变量
int x; // 矩形左上角的 x 坐标
int y; // 矩形左上角的 y 坐标
int width; // 矩形的宽度
int height;// 矩形的高度
};

参数详解

  1. **x**:

    • 矩形左上角的 x 坐标。
  2. **y**:

    • 矩形左上角的 y 坐标。
  3. **width**:

    • 矩形的宽度,表示矩形水平方向的长度。
  4. **height**:

    • 矩形的高度,表示矩形垂直方向的长度。

构造方法

  1. 默认构造函数

    1
    Rect();  // 默认值 x=0, y=0, width=0, height=0
    • 创建一个默认的矩形,通常是 (0, 0) 为左上角,宽度和高度都为 0。
  2. 指定坐标和宽高构造矩形

    1
    Rect(int _x, int _y, int _width, int _height);
    • 通过指定矩形的左上角 (x, y) 和矩形的 width(宽度)和 height(高度)来构造矩形。
  3. 通过对角线两个点构造矩形

    1
    Rect(const Point& pt1, const Point& pt2);
    • 使用两个对角点 pt1pt2 来创建矩形,pt1pt2 表示矩形的对角线的两个点。
  4. 拷贝构造函数

    1
    Rect(const Rect& r);
    • 通过另一个矩形对象来拷贝构造一个新的矩形。

成员函数

  1. **area()**:

    • 返回矩形的面积(即 width * height)。
    1
    int area() const;
  2. **empty()**:

    • 检查矩形是否为空(即宽度或高度为 0)。
    1
    bool empty() const;
  3. **br()**:

    • 返回矩形的右下角点。
    1
    Point br() const;  // 返回 (x + width, y + height)
  4. **tl()**:

    • 返回矩形的左上角点。
    1
    Point tl() const;  // 返回 (x, y)

常用操作

  1. 矩形相加

    • 两个矩形相加,得到一个包含两个矩形的最小外接矩形。
    1
    2
    3
    Rect r1(0, 0, 10, 10);
    Rect r2(5, 5, 10, 10);
    Rect r3 = r1 | r2; // 得到一个包含两个矩形的外接矩形
  2. 矩形相交

    • 得到两个矩形的交集部分,返回一个新的矩形。
    1
    2
    3
    Rect r1(0, 0, 10, 10);
    Rect r2(5, 5, 10, 10);
    Rect r3 = r1 & r2; // 得到两个矩形的交集
  3. 矩形移动

    • 移动矩形的位置。
    1
    2
    3
    Rect r(10, 20, 30, 40);
    r.x += 5; // 修改 x 坐标
    r.y += 5; // 修改 y 坐标
  4. 矩形包含检测

    • 判断一个点是否在矩形内,或者一个矩形是否包含另一个矩形。
    1
    2
    3
    4
    5
    6
    7
    Rect r(10, 10, 50, 50);
    Point pt(30, 30);
    bool inside = r.contains(pt); // 判断点 (30, 30) 是否在矩形内

    Rect r1(10, 10, 50, 50);
    Rect r2(20, 20, 10, 10);
    bool contained = r1.contains(r2); // 判断 r2 是否完全包含在 r1 中

常见应用场景

  1. 图像裁剪

    • 在图像处理任务中,cv::Rect 用于指定感兴趣区域(ROI)。通过矩形定义裁剪区域,可以快速地对图像进行裁剪。
    1
    2
    3
    Mat img = imread("image.jpg");
    Rect roi(100, 100, 200, 200); // 定义感兴趣区域
    Mat cropped = img(roi); // 裁剪图像
  2. 目标检测

    • 在目标检测任务中,通常使用矩形框来表示检测到的目标。例如,检测到的人脸、车辆或其他物体通常会使用矩形框进行标记。
  3. 图像窗口和标注

    • cv::Rect 用于表示图像中的标注区域或绘制图像的窗口。在 OpenCV 中经常会使用矩形框来标记特定区域,如绘制矩形框标记目标。
  4. 区域分析

    • 在图像分析中,cv::Rect 常用于表示分析的区域,尤其是在分割、提取特征等任务中。

示例代码

创建矩形并输出其属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main() {
// 创建矩形
Rect r1(10, 20, 30, 40);
cout << "Rect 1: (" << r1.x << ", " << r1.y << ", " << r1.width << ", " << r1.height << ")" << endl;

// 计算矩形面积
cout << "Area: " << r1.area() << endl;

// 判断矩形是否为空
if (r1.empty()) {
cout << "Rect is empty" << endl;
} else {
cout << "Rect is not empty" << endl;
}

// 获取矩形的右下角
Point br = r1.br();
cout << "Bottom-right corner: (" << br.x << ", " << br.y << ")" << endl;

return 0;
}

输出

1
2
3
4
Rect 1: (10, 20, 30, 40)
Area: 1200
Rect is not empty
Bottom-right corner: (40, 60)

总结

  • cv::Rect 是一个简单而强大的类,用于表示矩形区域,在图像处理、目标检测、图像裁剪、区域分析等多种应用中都有广泛的用途。
  • 它提供了多种构造方法、成员函数和常用操作,使得矩形的使用非常方便。
  • cv::Rect 主要用于图像处理中的区域定义、裁剪、目标检测、区域计算等任务,是 OpenCV 中不可或缺的重要类。

摘要

本文介绍了微软提供的命令行工具 nmake,用于在 Windows 上构建和管理 C/C++ 项目。内容涵盖了 nmake 的核心功能、常见用途、与 make 的区别、基本用法、简单示例以及常见问题的解决方法。

Read more »

简介

  • windows平台下C++编程常用函数笔记

C++ WSAGetLastError()函数 详解

WSAGetLastError() 是用于在 Windows Sockets API (也称为 WinSock) 中获取上一次操作的错误代码的函数。它与标准 C 库中的 GetLastError() 类似,但专门用于处理与网络相关的错误。

在调用 WinSock 函数时,某些操作可能会失败,而这些函数通常会返回一个特殊的值(例如 SOCKET_ERRORINVALID_SOCKET),以指示操作没有成功。在这种情况下,WSAGetLastError() 用于检索详细的错误代码,以帮助开发人员诊断问题。

函数定义

1
int WSAGetLastError(void);
  • 返回值:返回上一次失败的 WinSock 函数调用的错误代码。

使用场景

  1. 错误处理:当调用诸如 socket(), bind(), connect(), send(), recv() 等网络函数时,如果返回值指示失败,您可以立即调用 WSAGetLastError() 来获取更详细的错误信息。

  2. 排查问题:通过返回的错误代码,可以判断是什么原因导致操作失败,比如网络不可达、连接超时、权限不足等。

常见错误代码

以下是一些常见的错误代码及其含义:

  • WSAEACCES (10013):权限被拒绝。可能是由于防火墙或权限问题。
  • WSAEADDRINUSE (10048):地址已在使用中。尝试绑定一个已经被占用的端口。
  • WSAEADDRNOTAVAIL (10049):指定的地址无效,无法连接到指定的 IP 地址。
  • WSAENETDOWN (10050):网络子系统已失败。
  • WSAENETUNREACH (10051):网络无法到达。通常表示无法访问远程主机的网络。
  • WSAECONNABORTED (10053):软件导致的连接中止。通常是由于超时或其他问题导致连接中断。
  • WSAECONNRESET (10054):远程主机强行关闭连接。
  • WSAETIMEDOUT (10060):连接尝试超时。

示例代码

下面是一个简单的使用 WSAGetLastError() 的示例,展示如何处理网络函数失败时的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <winsock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib") // 链接 winsock 库

int main() {
// 初始化 WinSock 库
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cout << "WSAStartup failed with error: " << WSAGetLastError() << std::endl;
return 1;
}

// 创建一个套接字
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) {
std::cout << "Socket creation failed with error: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}

// 使用 socket 执行其他网络操作,如 bind() 或 connect()
// 如果操作失败,可以立即调用 WSAGetLastError() 进行错误处理

// 关闭 socket
closesocket(s);

// 清理 WinSock 库
WSACleanup();
return 0;
}

注意事项

  • 只获取最近的错误WSAGetLastError() 只会返回最近一次失败的 WinSock 操作的错误代码。因此,它需要在失败操作后立即调用,延迟调用可能导致错误信息被覆盖。
  • 线程安全WSAGetLastError() 是线程安全的,每个线程都有自己的错误代码,不会与其他线程冲突。

通过 WSAGetLastError(),你可以获取到更加精确的错误信息,以便进行有效的网络调试和处理。

摘要

本文介绍了 Windows 的包管理工具 winget,包括其基本功能、常用命令、安装方法、使用优点以及一些常用技巧。winget 允许用户通过命令行高效地安装、升级、配置、删除和搜索软件包。

Read more »

QVector 详解

QVector 是 Qt 框架提供的动态数组模板类,它是用来存储和操作一组数据的容器,类似于 C++ 的标准库 std::vectorQVector 提供了一种高效且易于使用的方式来管理动态分配的数组,同时支持许多高级功能。


QVector 的主要特点

  1. 动态调整大小
    QVector 会根据需要动态调整容量,不需要开发者手动管理内存。

  2. 高效的索引访问
    支持通过索引访问元素,时间复杂度为 (O(1))。

  3. 深拷贝优化
    QVector 被复制时,会使用 Qt 的隐式共享机制(Copy-on-Write),以减少不必要的内存开销。

  4. 内存连续性
    存储的数据在内存中是连续的,允许与低级别 C/C++ 数组无缝集成。


QVector 的常用操作

1. 构造函数

  • 默认构造:
    1
    QVector<int> vector;
  • 指定大小和默认值:
    1
    QVector<int> vector(5, 42); // 创建包含5个元素的向量,值为42
  • 从数组初始化:
    1
    2
    int arr[] = {1, 2, 3, 4};
    QVector<int> vector = QVector<int>::fromStdVector(std::vector<int>(arr, arr + 4));

2. 添加和移除元素

  • 添加元素:
    1
    2
    vector.append(10); // 在末尾添加
    vector.prepend(5); // 在开头添加
  • 插入元素:
    1
    vector.insert(1, 20); // 在索引1插入值20
  • 移除元素:
    1
    2
    3
    vector.remove(0); // 删除索引0处的元素
    vector.removeFirst(); // 删除第一个元素
    vector.removeLast(); // 删除最后一个元素

3. 访问元素

  • 按索引访问:
    1
    int value = vector[2];
  • 获取第一个或最后一个元素:
    1
    2
    int first = vector.first();
    int last = vector.last();

4. 查询大小和容量

  • 获取大小:
    1
    int size = vector.size();
  • 检查是否为空:
    1
    bool empty = vector.isEmpty();

5. 其他常用操作

  • 清空向量:
    1
    vector.clear();
  • 查找元素:
    1
    int index = vector.indexOf(10); // 查找值10的索引
  • 排序:
    1
    std::sort(vector.begin(), vector.end());

QVector 的高级功能

  1. 隐式共享(Copy-on-Write)
    多个 QVector 对象共享相同的数据副本,只有在其中一个被修改时才会创建实际的副本。

    1
    2
    3
    QVector<int> vector1 = {1, 2, 3};
    QVector<int> vector2 = vector1; // 两者共享相同数据
    vector1[0] = 5; // 此时 vector1 会创建自己的副本
  2. 与低级数组的集成
    可以通过 data() 方法获取底层 C 样式数组指针:

    1
    int *array = vector.data();
  3. 互操作性
    可以轻松与 std::vector 和其他容器进行转换:

    1
    std::vector<int> stdVector = vector.toStdVector();

QVector 与 std::vector 的对比

特性 QVector std::vector
内存管理 隐式共享(Copy-on-Write) 无隐式共享,每次复制都会分配新内存
线程安全性 对读操作线程安全 不支持线程安全
Qt 集成 完全与 Qt 生态兼容 需要适配 Qt 的其他功能
性能 对于只读场景性能更优 在频繁修改场景下性能更高

使用场景

  1. Qt 应用开发中的动态数据管理
    用于存储和操作动态数据,例如处理 UI 元素的集合或动态列表。

  2. 需要与 Qt API 无缝集成的项目
    QVector 与其他 Qt 类(如 QListViewQComboBox)的接口无缝协作。


更多关于 QVector 的详细文档,请参考 Qt 官方文档

简介

  • quick designer 使用笔记

Qt项目界面文件(.ui)及其作用

  • 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文件的内容,发现它主要做了以下的一些工作

    • 定义了一个类Ui_Widget,用于封装可视化设计的界面
    • 自动生成了界面各个组件的类成员变量定义。在public部分为界面上每个组件定义了一个指针变量,变量的名称就是设置的objectName。例如,在窗体上放置了一个QLable和一个QPushButton并命名后,自动生成的定义是: QLabel *LabDemo; QPushButton *btnClose;
    • 定义了setupUi()函数,这个函数用于创建各个界面组件,并设置其位置,大小,文字内容,字体等属性,设置信号与槽的关联。setupUi()函数体的第一部分是根据可视化设计的界面内容,用C++代码创建界面上各组件,并设置其属性
      • 接下来,setupUi()调用了函数retranslateUi(Widget),用来设置界面各组件的文字内容属性,例如标签的问题,按键的文字,窗体的标题等。将界面上的文字设置的内容独立出来作为一个函数retranslateUi(),在设计多语言界面时会用到这个函数
      • setupUi()函数的第三部分是设置信号与槽的关联。
      • 所以,在Widget的构造函数里调用ui->setupUi(this),就实现了窗体上组件的创建,属性设置,信号与槽的关联。
    • 定义namespace Ui,并定义一个从Ui_Widget继承的类Widget
      1
      2
      3
      4
      namespace Ui
      {
      class Widget::public Ui_Widget{};
      }
  • 提示,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指针可以访问可视化设计的界面组件就可以了。

把Qt的界面文件(.ui文件)生成源文件(.h或.cpp)

  • 在用Qt做开发时,为了方便快速,一般都使用Qt设计师界面类来做界面相关的布局,这个类在当前工程中是没有.cpp或.h文件的,但主类又有引入了这个头文件,点开转到定义或声明时,时打不开的。
  • 下面的一种办法
    • 新建一个工程,包含ui文件,然后打开ui文件拖入想要的控件,保存,编译ui文件
    • 在qt安装路径下找到 uic.exe 文件,使用这个工具将ui文件生成.h或.cpp文件。例如
      1
      2
      uic.exe test.ui -o test.h
      uic.exe test.ui -o test.cpp
    • .h或.cpp这两个文件只要一个就可以了
    • 把刚才生成的.h文件导入当前工程,然后打开刚刚生成的源码,可以看到整个类的所有属性和所有成员函数

Qt quick designer 详解

Qt Quick Designer 详解

一、概述

Qt Quick Designer 是 Qt Creator 集成开发环境(IDE)中的一个可视化设计工具,专用于设计和开发基于 Qt Quick(QML) 的用户界面。它提供了所见即所得(WYSIWYG)的编辑环境,使开发者能够通过拖放组件、调整属性等方式,快速构建现代化、响应式的用户界面,而无需手动编写大量的 QML 代码。


二、Qt Quick 和 QML 简介

在深入了解 Qt Quick Designer 之前,先简单回顾一下 Qt Quick 和 QML:

  • Qt Quick:是 Qt 框架中用于创建动态用户界面的技术,特别适用于触摸屏和移动设备应用。它结合了 QML 和 JavaScript,实现了高性能的界面渲染和丰富的动画效果。

  • QML(Qt Modeling Language):是一种基于 JavaScript 的声明式语言,专门用于设计用户界面。通过 QML,可以以简洁直观的方式描述界面元素、布局和交互行为。


三、Qt Quick Designer 的主要功能

1. 拖放式界面设计

  • 组件面板:提供了丰富的预定义 UI 组件,如按钮、文本、图像、列表等。
  • 拖放操作:直接将组件从面板拖拽到设计区域,快速构建界面布局。

2. 属性编辑器

  • 实时调整:选中组件后,可以在属性编辑器中修改其属性,如大小、位置、颜色等。
  • 绑定支持:支持属性绑定,可以将组件的属性与数据模型或其他组件属性关联。

3. 层次结构视图

  • 组件管理:展示当前界面中所有组件的层次关系,方便组织和管理。
  • 快速导航:通过层次结构视图,快速定位并选中特定的界面元素。

4. 动画和过渡

  • 动画编辑器:可视化地创建和调整动画效果,支持时间轴和关键帧。
  • 状态和过渡:定义组件的不同状态,并设置状态之间的过渡动画。

5. 实时预览

  • 即时反馈:无需编译,即可在设计器中实时查看界面效果。
  • 多设备预览:支持模拟不同设备的屏幕尺寸和分辨率,确保界面自适应性。

6. 代码与设计视图同步

  • 双向编辑:在设计视图和代码视图之间切换,所有更改都会同步更新。
  • 代码提示:在代码视图中提供智能提示和自动补全,加速手动编码过程。

四、使用 Qt Quick Designer 的步骤

1. 创建项目

  • 新建项目:在 Qt Creator 中,选择“新建项目”,并选择“Qt Quick 应用程序”模板。
  • 配置项目:按照向导提示,设置项目名称、位置和其他配置。

2. 设计界面

  • 打开主 QML 文件:在项目导航器中,找到 Main.qml 文件,双击打开。
  • 切换到设计视图:在编辑器窗口顶部,选择“设计”选项卡。

3. 添加和配置组件

  • 添加组件:从组件面板中拖放所需的组件到设计区域。
  • 设置属性:选中组件,在属性编辑器中调整其属性,如位置、大小、样式等。

4. 管理组件层次

  • 调整层次结构:在层次结构视图中,通过拖放改变组件的父子关系。
  • 命名组件:为组件设置 id,方便在代码中引用。

5. 添加交互和动画

  • 定义状态:在状态编辑器中添加新的状态,描述组件在不同情况下的表现。
  • 创建动画:使用动画编辑器,为属性变化添加动画效果。

6. 预览和测试

  • 实时预览:点击预览按钮,查看界面的实时效果。
  • 调试:使用 Qt Creator 的调试工具,检测和解决潜在的问题。

五、与代码编辑的结合

1. 代码视图

  • 查看代码:在设计器中,随时切换到代码视图,查看生成的 QML 代码。
  • 手动编辑:直接在代码中添加逻辑或自定义组件。

2. 双向同步

  • 设计与代码一致性:所有在设计视图中的更改都会反映到代码视图中,反之亦然。
  • 冲突解决:当手动编辑的代码无法在设计视图中解析时,设计器会给出提示。

六、优势与应用场景

1. 提高开发效率

  • 快速构建:拖放式设计减少了手动编码的时间,加快了界面开发速度。
  • 直观性:所见即所得的编辑方式,使得界面设计更直观。

2. 降低学习难度

  • 适合初学者:对于不熟悉 QML 或编程的新手,Qt Quick Designer 提供了友好的入门途径。
  • 丰富的组件库:预定义的组件和样式,降低了设计复杂界面的门槛。

3. 促进团队协作

  • 设计师参与:界面设计师可以直接使用 Qt Quick Designer 进行原型设计和界面布局。
  • 开发与设计分离:开发者和设计师可以各自专注于逻辑实现和界面美化。

七、注意事项

1. 理解生成的代码

  • 代码质量:尽管设计器生成的代码可用,但手动检查和优化代码有助于提高应用性能。
  • 学习 QML:深入理解 QML 语言,有助于更好地利用 Qt Quick Designer。

2. 兼容性问题

  • Qt 版本:确保使用的 Qt Creator 和 Qt 版本与项目需求一致,避免兼容性问题。
  • 平台差异:在不同平台上测试界面,确保跨平台一致性。

3. 性能优化

  • 避免过度嵌套:过深的组件嵌套可能影响性能,需合理组织界面结构。
  • 谨慎使用动画:大量或复杂的动画效果可能导致性能问题,应适度使用。

八、结论

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 asio::ip::tcp::endpoint 是什么

Asio(或 Boost.Asio)中,asio::ip::tcp::endpoint 是一个类,表示一个 TCP 端点。它包含了 IP 地址和端口号,用于描述 TCP 网络通信中的目标或源。

作用

asio::ip::tcp::endpoint 是 TCP 套接字(asio::ip::tcp::socket)操作中的核心组件,用来:

  1. 指定服务器的地址和端口,作为客户端的目标。
  2. 指定本地地址和端口,用于绑定监听服务器或设置客户端绑定信息。
  3. 在连接建立后获取对等端的网络信息(如对端 IP 和端口)。

类定义

asio::ip::tcp::endpointasio::ip::basic_endpoint<tcp> 的具体实现,用于 TCP 协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace asio {
namespace ip {
class tcp::endpoint {
public:
// 构造函数
endpoint(); // 默认构造,地址为 unspecified,端口为 0
endpoint(const address& addr, unsigned short port); // 使用地址和端口初始化

// 设置/获取地址
void address(const address& addr);
address address() const;

// 设置/获取端口
void port(unsigned short port);
unsigned short port() const;

// 比较运算符
bool operator==(const endpoint& other) const;
bool operator!=(const endpoint& other) const;

// 其他函数
std::string to_string() const; // 返回可读的字符串表示
};
}
}

常见操作

1. 创建端点

1
2
asio::ip::address addr = asio::ip::make_address("127.0.0.1");
asio::ip::tcp::endpoint endpoint(addr, 8080);

这表示一个 TCP 端点,IP 地址是 127.0.0.1,端口号是 8080

2. 默认构造

1
asio::ip::tcp::endpoint endpoint;

创建一个默认的端点,地址为 0.0.0.0(未指定地址),端口为 0

3. 动态绑定(服务器)

当服务器需要绑定一个本地端口时,可以使用:

1
2
3
4
5
asio::ip::tcp::acceptor acceptor(io_context);
asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), 8080);
acceptor.open(endpoint.protocol());
acceptor.bind(endpoint);
acceptor.listen();

此处,endpoint 表示监听在 IPv4 地址的所有网络接口(0.0.0.0)上的端口 8080

4. 解析客户端或服务器端地址

  • 客户端连接时,解析服务器端点:
    1
    2
    asio::ip::tcp::resolver resolver(io_context);
    auto endpoints = resolver.resolve("example.com", "80");
  • 在服务器端,从已接受的连接中获取对端的 IP 和端口:
    1
    2
    3
    4
    5
    asio::ip::tcp::socket socket(io_context);
    acceptor.accept(socket);
    asio::ip::tcp::endpoint remote_endpoint = socket.remote_endpoint();
    std::cout << "Connected to " << remote_endpoint.address().to_string()
    << ":" << remote_endpoint.port() << std::endl;

5. 转换为字符串

1
2
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("192.168.1.1"), 12345);
std::cout << "Endpoint: " << endpoint.to_string() << std::endl;

输出:

1
Endpoint: 192.168.1.1:12345

重要成员函数

构造函数

  1. 默认构造

    • 创建一个未指定地址和端口的端点(0.0.0.0:0[::]:0)。
      1
      asio::ip::tcp::endpoint();
  2. 地址和端口构造

    • 使用指定的 asio::ip::address 和端口初始化。
      1
      asio::ip::tcp::endpoint(const asio::ip::address& addr, unsigned short port);
  3. 协议构造

    • 使用协议(IPv4/IPv6)和端口初始化,地址为未指定。
      1
      asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 8080);

成员函数

  1. 设置地址

    1
    void address(const asio::ip::address& addr);

    修改端点的 IP 地址。

  2. 获取地址

    1
    asio::ip::address address() const;

    返回端点的 IP 地址。

  3. 设置端口

    1
    void port(unsigned short port);

    修改端点的端口号。

  4. 获取端口

    1
    unsigned short port() const;

    返回端点的端口号。

  5. 转换为字符串

    1
    std::string to_string() const;

    返回端点的字符串表示形式,例如 127.0.0.1:8080


典型用例

客户端

1
2
3
4
5
asio::io_context io_context;
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("127.0.0.1", "8080");
tcp::socket socket(io_context);
asio::connect(socket, endpoints);

服务器

1
2
3
4
5
6
7
asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));
tcp::socket socket(io_context);
acceptor.accept(socket);

tcp::endpoint remote_endpoint = socket.remote_endpoint();
std::cout << "Connected from " << remote_endpoint.to_string() << std::endl;

总结

asio::ip::tcp::endpoint 是 TCP 通信中的一个核心组件,用于描述通信双方的网络位置。它不仅简单易用,而且与其他 Asio 组件(如 tcp::resolvertcp::socket)紧密配合,为构建高效的网络程序提供了强大支持。