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

YOLOv8-ultralytics-8.2.103部分代码阅读笔记-ops.py

ops.py

ultralytics\utils\ops.py

目录

ops.py

1.所需的库和模块

2.class Profile(contextlib.ContextDecorator): 

3.def segment2box(segment, width=640, height=640): 

4.def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None, padding=True, xywh=False): 

 5.def make_divisible(x, divisor):

6.def nms_rotated(boxes, scores, threshold=0.45): 

7.def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, labels=(), max_det=300, nc=0, max_time_img=0.05, max_nms=30000, max_wh=7680, in_place=True, rotated=False,): 

8.def clip_boxes(boxes, shape): 

9.def clip_coords(coords, shape): 

10.def scale_image(masks, im0_shape, ratio_pad=None): 

11.def xyxy2xywh(x): 

12.def xywh2xyxy(x): 

13.def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0): 

14.def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0): 

15.def xywh2ltwh(x): 

16.def xyxy2ltwh(x): 

17.def ltwh2xywh(x): 

18.def xyxyxyxy2xywhr(x): 

19.def xywhr2xyxyxyxy(x): 

20.def ltwh2xyxy(x): 

21.def segments2boxes(segments):

22.def resample_segments(segments, n=1000): 

23.def crop_mask(masks, boxes): 

24.def process_mask(protos, masks_in, bboxes, shape, upsample=False): 

25.def process_mask_native(protos, masks_in, bboxes, shape): 

26.def scale_masks(masks, shape, padding=True): 

27.def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None, normalize=False, padding=True): 

28.def regularize_rboxes(rboxes): 

29.def masks2segments(masks, strategy="largest"): 

30.def convert_torch2numpy_batch(batch: torch.Tensor) -> np.ndarray: 

31.def clean_str(s): 


1.所需的库和模块

# Ultralytics YOLO 🚀, AGPL-3.0 license

import contextlib
import math
import re
import time

import cv2
import numpy as np
import torch
import torch.nn.functional as F

from ultralytics.utils import LOGGER
from ultralytics.utils.metrics import batch_probiou

2.class Profile(contextlib.ContextDecorator): 

# 这段代码定义了一个名为 Profile 的类,它是一个上下文管理器(使用 contextlib.ContextDecorator ),用于测量代码块的执行时间。
# 定义了一个名为 Profile 的类,它继承自 contextlib.ContextDecorator ,使其成为一个上下文管理器。
class Profile(contextlib.ContextDecorator):
    # YOLOv8 Profile 类。使用 @Profile() 作为装饰器,或使用 'with Profile():' 作为上下文管理器。
    """
    YOLOv8 Profile class. Use as a decorator with @Profile() or as a context manager with 'with Profile():'.

    Example:
        ```python
        from ultralytics.utils.ops import Profile

        with Profile(device=device) as dt:
            pass  # slow operation here

        print(dt)  # prints "Elapsed time is 9.5367431640625e-07 s"
        ```
    """

    #  定义了 Profile 类的构造函数,它接受以下参数。
    # 1.t (默认为 0.0):初始化时的时间累积值。
    # 2.device (默认为 None ):指定的设备,如果为 CUDA 设备,则会同步 CUDA 时间。
    def __init__(self, t=0.0, device: torch.device = None):
        # 初始化 Profile 类。
        """
        Initialize the Profile class.

        Args:
            t (float): Initial time. Defaults to 0.0.
            device (torch.device): Devices used for model inference. Defaults to None (cpu).
        """
        # 将构造函数的参数赋值给实例变量。
        self.t = t
        self.device = device
        # 检查指定的设备是否为 CUDA 设备,并设置 self.cuda 为 True 或 False 。
        self.cuda = bool(device and str(device).startswith("cuda"))

    # 定义了 __enter__ 方法,它是上下文管理器的一部分,用于在进入 with 块时执行的操作。
    def __enter__(self):
        # 开始计时。
        """Start timing."""
        # 记录开始时间。
        self.start = self.time()
        # 返回实例本身。
        return self

    # 定义了 __exit__ 方法,它是上下文管理器的一部分,用于在退出 with 块时执行的操作。
    def __exit__(self, type, value, traceback):  # noqa
        """Stop timing."""
        # 计算执行时间差。
        self.dt = self.time() - self.start  # delta-time
        # 将时间差累加到 self.t 。
        self.t += self.dt  # accumulate dt

    # 定义了 __str__ 方法,返回一个人类可读的字符串,表示累积的经过时间。
    def __str__(self):
        # 返回一个人类可读的字符串,代表分析器中累计的运行时间。
        """Returns a human-readable string representing the accumulated elapsed time in the profiler."""
        return f"Elapsed time is {self.t} s"

    # 定义了 time 方法,用于获取当前时间。
    def time(self):
        # 获取当前时间。
        """Get current time."""
        # 如果 self.cuda 为 True ,则调用 torch.cuda.synchronize(self.device) 来同步 CUDA 设备的时间。
        if self.cuda:
            torch.cuda.synchronize(self.device)
        # 返回当前的 Python 时间,使用 time.time() 。
        return time.time()
# 使用 Profile 类的示例:
# import torch
# # 创建一个 CUDA 设备
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# # 创建 Profile 实例
# profiler = Profile(device=device)
# # 使用 with 语句测量代码块的执行时间
# with profiler:
#     # 在这里执行一些操作
#     torch.randn((100, 3), device=device).sum(dim=1)
# 在这个例子中, profiler 会测量 with 块内代码的执行时间,并将其累积到 profiler.t 。退出 with 块后,可以通过 str(profiler) 获取累积的经过时间。如果 device 是 CUDA 设备, Profile 会确保在测量时间之前同步 CUDA 设备,以获得准确的时间测量。

3.def segment2box(segment, width=640, height=640): 

# 这段代码定义了一个名为 segment2box 的函数,它将一个分割掩码(segment)转换为一个边界框(bounding box)。
# 定义了一个名为 segment2box 的函数,它接受以下参数。
# 1.segment :一个包含分割点坐标的数组,通常是一个二维数组,其中每一列代表一个点的 (x, y) 坐标。
# 2.width (默认为 640):图像的宽度。
# 3.height (默认为 640):图像的高度。
def segment2box(segment, width=640, height=640):
    # 将 1 个片段标签转换为 1 个框标签,应用图像内部约束,即 (xy1, xy2, ...) 到 (xyxy)。
    """
    Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy).

    Args:
        segment (torch.Tensor): the segment label
        width (int): the width of the image. Defaults to 640
        height (int): The height of the image. Defaults to 640

    Returns:
        (np.ndarray): the minimum and maximum x and y values of the segment.
    """
    # 将输入的 segment 数组转置,并将 x 和 y 坐标分别赋值给 x 和 y 数组。
    x, y = segment.T  # segment xy
    # 创建一个布尔数组 inside ,用于标记位于图像边界内的点。
    inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
    # x = x[inside] 和 y = y[inside] 仅保留位于图像边界内的 x 和 y 坐标。
    x = x[inside]
    y = y[inside]
    # 计算边界框的坐标,如果存在有效的 x 坐标(即 x 数组不为空),则返回一个包含 (x_min, y_min, x_max, y_max) 的数组;否则返回一个长度为 4 的零数组。
    return (
        np.array([x.min(), y.min(), x.max(), y.max()], dtype=segment.dtype)
        if any(x)
        else np.zeros(4, dtype=segment.dtype)
    )  # xyxy
# 这个函数的作用是将一个包含点坐标的分割掩码转换为一个边界框,这个边界框由最小外接矩形定义,其左上角和右下角的坐标分别由 (x_min, y_min) 和 (x_max, y_max) 给出。如果输入的 segment 数组为空或者所有点都在图像外部,则返回一个零数组。

4.def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None, padding=True, xywh=False): 

# 这段代码定义了一个名为 scale_boxes 的函数,它用于将边界框按照一定的比例缩放并调整到原始图像的形状。
# 定义了一个名为 scale_boxes 的函数,它接受以下参数。
# 1.img1_shape :变换后图像的形状,格式为 (height, width) 。
# 2.boxes :边界框的数组,形状为 (nx4) ,其中 n 是边界框的数量,4 是边界框的坐标 (x1, y1, x2, y2) 或 (x, y, w, h) 。
# 3.img0_shape :原始图像的形状,格式为 (height, width) 。
# 4.ratio_pad (默认为 None ):一个包含缩放比例和填充的元组,格式为 (gain, (pad_x, pad_y)) 。
# 5.padding (默认为 True ):是否应用填充。
# 6.xywh (默认为 False ):边界框的格式是否为 (x, y, w, h) 。
def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None, padding=True, xywh=False):
    # 将边界框(默认格式为 xyxy)从最初指定的图像形状(img1_shape)重新缩放为不同图像的形状(img0_shape)。
    """
    Rescales bounding boxes (in the format of xyxy by default) from the shape of the image they were originally
    specified in (img1_shape) to the shape of a different image (img0_shape).

    Args:
        img1_shape (tuple): The shape of the image that the bounding boxes are for, in the format of (height, width).
        boxes (torch.Tensor): the bounding boxes of the objects in the image, in the format of (x1, y1, x2, y2)
        img0_shape (tuple): the shape of the target image, in the format of (height, width).
        ratio_pad (tuple): a tuple of (ratio, pad) for scaling the boxes. If not provided, the ratio and pad will be
            calculated based on the size difference between the two images.
        padding (bool): If True, assuming the boxes is based on image augmented by yolo style. If False then do regular
            rescaling.
        xywh (bool): The box format is xywh or not, default=False.

    Returns:
        boxes (torch.Tensor): The scaled bounding boxes, in the format of (x1, y1, x2, y2)
    """
    # 如果没有提供 ratio_pad ,则根据 img0_shape 和 img1_shape 计算缩放比例和填充。
    if ratio_pad is None:  # calculate from img0_shape
        # 计算缩放比例,取高度和宽度比例的最小值。
        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new
        # 计算水平和垂直方向的填充。
        # -0.1 的目的是为了确保在计算填充时,如果存在小的浮点数误差,不会导致填充的计算结果向上取整。这在图像处理中是一个常见的技巧,用于确保在计算边界或填充时,结果是保守的,即不会超出预期的范围。
        # 具体来说, round 函数会将一个浮点数四舍五入到最接近的整数。如果在计算填充时,结果恰好是 0.5,那么 round 函数会将其四舍五入到 1。
        # 但是,如果我们希望在计算填充时,结果是 0.5 时向下取整,即取 0,那么我们可以在计算结果上减去一个很小的数,如 0.1,这样 0.5 就会变成 0.4,而 round 函数会将其四舍五入到 0。
        # 因此, -0.1 的目的是为了确保在计算填充时,结果是保守的,即不会因为小的浮点数误差而超出预期的范围。
        pad = (
            round((img1_shape[1] - img0_shape[1] * gain) / 2 - 0.1),
            round((img1_shape[0] - img0_shape[0] * gain) / 2 - 0.1),
        )  # wh padding
    # 如果提供了 ratio_pad ,则直接使用提供的缩放比例和填充。
    else:
        gain = ratio_pad[0][0]
        pad = ratio_pad[1]

    # 如果需要应用填充,则对边界框的坐标进行调整:
    if padding:
        # 将 x1 坐标向左移动填充值。
        boxes[..., 0] -= pad[0]  # x padding
        # 将 y1 坐标向上移动填充值。
        boxes[..., 1] -= pad[1]  # y padding
        if not xywh:
            # 将 x2 坐标向左移动填充值(如果边界框格式为 xywh ,则不执行此操作)。
            boxes[..., 2] -= pad[0]  # x padding
            # 将 y2 坐标向上移动填充值(如果边界框格式为 xywh ,则不执行此操作)。
            boxes[..., 3] -= pad[1]  # y padding
    # 将边界框的坐标按缩放比例进行缩放。
    boxes[..., :4] /= gain
    # 使用 clip_boxes 函数将缩放后的边界框剪辑到 原始图像 的形状内,并返回结果。
    # def clip_boxes(boxes, shape): -> 用于将边界框(boxes)剪辑到特定的图像形状(shape)内,确保边界框的坐标不会超出图像的边界。返回剪辑后的边界框数组。 -> return boxes
    return clip_boxes(boxes, img0_shape)
# 这个函数的作用是将边界框从变换后的图像形状调整回原始图像形状,同时考虑了缩放和填充。这对于在不同分辨率的图像之间转换边界框非常有用。

 5.def make_divisible(x, divisor):

# 这段代码定义了一个名为 make_divisible 的函数,其目的是将给定的数字 x 调整为最接近的、能被 divisor 整除的数字。这个函数在深度学习模型设计中很有用,尤其是在确定网络层的通道数时,以确保模型的某些属性(如乘法运算的效率)。
# 它接受两个参数。
# 1.x :需要调整的数字。
# 2.divisor :除数,可以是一个整数或一个 PyTorch 张量。
def make_divisible(x, divisor):
    # 返回能被给定除数整除的最接近的数。
    """
    Returns the nearest number that is divisible by the given divisor.

    Args:
        x (int): The number to make divisible.
        divisor (int | torch.Tensor): The divisor.

    Returns:
        (int): The nearest number divisible by the divisor.
    """
    # 这行代码检查 divisor 是否是一个 PyTorch 张量。
    if isinstance(divisor, torch.Tensor):
        # 如果 divisor 是一个张量,这行代码将张量中的最大值取出来,并转换为整数,作为除数。
        divisor = int(divisor.max())  # to int
    # 这行代码计算 x 除以 divisor 的结果,使用 math.ceil 函数向上取整到最近的整数,然后乘以 divisor ,从而得到最接近 x 的、能被 divisor 整除的数字。
    return math.ceil(x / divisor) * divisor
# 这个函数在设计模型时非常有用,尤其是在需要确保模型的某些参数(如通道数)能被特定的数值整除以提高计算效率时。

6.def nms_rotated(boxes, scores, threshold=0.45): 

# 这段代码定义了一个名为 nms_rotated 的函数,它实现了旋转边界框的非极大值抑制(Non-Maximum Suppression, NMS)。NMS 是一个用于去除重叠边界框的算法,常用于目标检测任务中,以减少冗余的检测结果。
# 定义了一个名为 nms_rotated 的函数,它接受以下参数 :
# 1.boxes :一个包含旋转边界框的数组,每个边界框由 5 个值组成: (x_center, y_center, width, height, angle) 。
# 2.scores :与 boxes 中的边界框对应的置信度分数数组。
# 3.threshold (默认为 0.45):用于决定是否抑制边界框的 IoU 阈值。
def nms_rotated(boxes, scores, threshold=0.45):
    # 使用 probiou 和 fast-nms 进行定向边界框的 NMS。
    """
    NMS for oriented bounding boxes using probiou and fast-nms.

    Args:
        boxes (torch.Tensor): Rotated bounding boxes, shape (N, 5), format xywhr.
        scores (torch.Tensor): Confidence scores, shape (N,).
        threshold (float, optional): IoU threshold. Defaults to 0.45.

    Returns:
        (torch.Tensor): Indices of boxes to keep after NMS.
    """
    # 检查 boxes 数组是否为空。
    if len(boxes) == 0:
        # 如果 boxes 为空,则返回一个空的 NumPy 数组。
        return np.empty((0,), dtype=np.int8)
    # 对 scores 进行排序,获取按置信度分数降序排列的索引。
    sorted_idx = torch.argsort(scores, descending=True)
    # 根据 sorted_idx 对 boxes 进行重新排序。
    boxes = boxes[sorted_idx]

    # torch.triu_(input, diagonal=0, *, out=None) -> Tensor
    # triu_() 是 PyTorch 中的一个函数,它用于返回输入张量的上三角部分,并将其他元素设置为0。这个函数就地修改输入张量,因此在使用时需要注意,它会改变原始数据。
    # 参数 :
    # input (Tensor): 输入张量。
    # diagonal (int, 可选): 要考虑的对角线。 diagonal=0 表示主对角线,正值表示主对角线上方的对角线,负值表示主对角线下方的对角线。
    # out (Tensor, 可选): 输出张量。
    # 功能 :
    # 返回输入张量的上三角部分,其余位置为0。
    # diagonal 参数控制要考虑的对角线。如果 diagonal = 0 ,则保留主对角线及以上的所有元素。正值排除主对角线上方同样多的对角线,负值包括主对角线下方同样多的对角线。

    # 计算 boxes 中所有边界框之间的 ProbIoU(一种 IoU 的变体,适用于旋转边界框)。 batch_probiou 函数计算矩阵中每对边界框之间的 ProbIoU 值。 triu_(diagonal=1) 方法返回一个上三角矩阵,其中对角线以上(不包括对角线)的元素被保留,用于避免重复计算和自我比较。
    ious = batch_probiou(boxes, boxes).triu_(diagonal=1)

    # torch.nonzero(input, as_tuple=False, **kwargs) → LongTensor
    # torch.nonzero 是 PyTorch 中的一个函数,它返回输入张量(tensor)中非零元素的索引。这个函数对于查找满足特定条件的元素位置非常有用,类似于 NumPy 中的 np.nonzero 函数。
    # 参数 :
    # input (Tensor) :输入的张量。
    # as_tuple (bool,可选) :如果设置为 True ,则返回一个元组,其中每个元素是一个包含非零元素索引的张量。每个张量对应输入张量的一个维度。默认值为 False 。
    # **kwargs :其他关键字参数,用于控制输出的设备和dtype等。
    # 返回值 :
    # 返回一个包含非零元素索引的 LongTensor。如果 as_tuple=True ,则返回一个元组。
    # 注意事项 :
    # 返回的索引是基于 0 的,即第一个元素的索引是 0。
    # 如果输入张量中没有非零元素, torch.nonzero 将返回一个空的张量或元组。
    # torch.nonzero 可以用于任何维度的张量。
    # torch.nonzero 是 PyTorch 中处理张量时常用的函数之一,它在索引、筛选和条件操作中非常有用。

    # 找到上三角矩阵中 ProbIoU 值小于 threshold 的边界框索引。 max(dim=0)[0] 获取每一列最大值, < threshold 过滤出 IoU 值小于阈值的边界框, nonzero 获取这些边界框的索引, squeeze_(-1) 移除单维度条目。
    pick = torch.nonzero(ious.max(dim=0)[0] < threshold).squeeze_(-1)
    # 返回排序后保留的边界框索引。
    return sorted_idx[pick]
# 这个函数的作用是对旋转边界框进行非极大值抑制,去除重叠的边界框,只保留最佳的边界框。这在目标检测任务中非常有用,尤其是在处理旋转对象时。

7.def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, labels=(), max_det=300, nc=0, max_time_img=0.05, max_nms=30000, max_wh=7680, in_place=True, rotated=False,): 

# 这段代码定义了一个名为 non_max_suppression 的函数,它实现了非极大值抑制(Non-Maximum Suppression, NMS)算法,用于在目标检测任务中筛选出最佳的边界框。
# 定义了一个名为 non_max_suppression 的函数,它接受以下参数 :
# prediction :模型输出的预测结果。
# conf_thres (默认为 0.25):置信度阈值。
# iou_thres (默认为 0.45):交并比(IoU)阈值。
# classes :要筛选的类别。
# agnostic :是否进行类别无关的NMS。
# multi_label :是否允许多标签。
# labels :自动标签。
# max_det (默认为 300):最大检测数量。
# nc :类别数量(可选)。
# max_time_img :处理每个图像的最大时间。
# max_nms :NMS操作的最大边界框数量。
# max_wh :最大宽度和高度。
# in_place :是否在原地修改预测结果。
# rotated :是否使用旋转边界框。
def non_max_suppression(
    prediction,
    conf_thres=0.25,
    iou_thres=0.45,
    classes=None,
    agnostic=False,
    multi_label=False,
    labels=(),
    max_det=300,
    nc=0,  # number of classes (optional)
    max_time_img=0.05,
    max_nms=30000,
    max_wh=7680,
    in_place=True,
    rotated=False,
):
    # 对一组框执行非最大抑制 (NMS),支持每个框的掩码和多个标签。
    """
    Perform non-maximum suppression (NMS) on a set of boxes, with support for masks and multiple labels per box.

    Args:
        prediction (torch.Tensor): A tensor of shape (batch_size, num_classes + 4 + num_masks, num_boxes)
            containing the predicted boxes, classes, and masks. The tensor should be in the format
            output by a model, such as YOLO.
        conf_thres (float): The confidence threshold below which boxes will be filtered out.
            Valid values are between 0.0 and 1.0.
        iou_thres (float): The IoU threshold below which boxes will be filtered out during NMS.
            Valid values are between 0.0 and 1.0.
        classes (List[int]): A list of class indices to consider. If None, all classes will be considered.
        agnostic (bool): If True, the model is agnostic to the number of classes, and all
            classes will be considered as one.
        multi_label (bool): If True, each box may have multiple labels.
        labels (List[List[Union[int, float, torch.Tensor]]]): A list of lists, where each inner
            list contains the apriori labels for a given image. The list should be in the format
            output by a dataloader, with each label being a tuple of (class_index, x1, y1, x2, y2).
        max_det (int): The maximum number of boxes to keep after NMS.
        nc (int, optional): The number of classes output by the model. Any indices after this will be considered masks.
        max_time_img (float): The maximum time (seconds) for processing one image.
        max_nms (int): The maximum number of boxes into torchvision.ops.nms().
        max_wh (int): The maximum box width and height in pixels.
        in_place (bool): If True, the input prediction tensor will be modified in place.
        rotated (bool): If Oriented Bounding Boxes (OBB) are being passed for NMS.

    Returns:
        (List[torch.Tensor]): A list of length batch_size, where each element is a tensor of
            shape (num_boxes, 6 + num_masks) containing the kept boxes, with columns
            (x1, y1, x2, y2, confidence, class, mask1, mask2, ...).
    """
    # 这段代码是 non_max_suppression 函数的一部分,它执行了一系列检查和预处理步骤,为后续的非极大值抑制(NMS)做准备。
    # 导入 PyTorch 的 torchvision 库,这通常用于使用其中的函数,如 nms (非极大值抑制)。
    import torchvision  # scope for faster 'import ultralytics'

    # Checks
    # 确保置信度阈值 conf_thres 在有效范围内(0到1之间)。
    assert 0 <= conf_thres <= 1, f"Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0"    # 置信度阈值 {conf_thres} 无效,有效值介于 0.0 和 1.0 之间。
    # 确保 IoU 阈值 iou_thres 在有效范围内(0到1之间)。
    assert 0 <= iou_thres <= 1, f"Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0"    # IoU {iou_thres} 无效,有效值介于 0.0 至 1.0 之间。
    # 检查 prediction 是否为列表或元组,这可能是因为模型在验证模式下输出了推理结果和损失值。
    if isinstance(prediction, (list, tuple)):  # YOLOv8 model in validation model, output = (inference_out, loss_out)    验证模型中的 YOLOv8 模型,输出 = (inference_out, loss_out)。
        # 如果 prediction 是列表或元组,只选择推理输出部分。
        prediction = prediction[0]  # select only inference output    # 仅选择推理输出。
    # 如果指定了要筛选的类别,则将它们转换为张量,并确保它们位于与 prediction 相同的设备上。
    if classes is not None:
        classes = torch.tensor(classes, device=prediction.device)

    # 检查预测结果的形状是否为6,这通常表示端到端模型的输出,其中每个预测包含类别概率。
    if prediction.shape[-1] == 6:  # end-to-end model (BNC, i.e. 1,300,6)    端到端模型(BNC,即 1,300,6)。
        # 对于每个预测结果,筛选出置信度大于 conf_thres 的预测,并限制最大检测数量为 max_det 。
        output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction]
        # 如果指定了类别,则进一步筛选出属于这些类别的预测结果。
        if classes is not None:
            output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output]
        # 返回筛选后的预测结果。
        return output

    # 获取批量大小,即图像的数量。
    bs = prediction.shape[0]  # batch size (BCN, i.e. 1,84,6300)
    # 确定类别数量。如果未提供 nc ,则从预测结果的形状中推断。
    nc = nc or (prediction.shape[1] - 4)  # number of classes
    # 确定掩码数量。
    nm = prediction.shape[1] - nc - 4  # number of masks
    # 确定掩码的起始索引。
    mi = 4 + nc  # mask start index
    # 获取置信度大于阈值的候选预测结果。
    xc = prediction[:, 4:mi].amax(1) > conf_thres  # candidates
    # 这段代码的主要作用是验证输入参数的有效性,预处理预测结果,并为后续的 NMS 步骤准备数据。它确保了只有置信度高于阈值的预测结果会被考虑,并且如果指定了类别,则只考虑这些类别的预测结果。

    # 这段代码是 non_max_suppression 函数的一部分,它继续执行了一些设置和预处理步骤,为后续的非极大值抑制(NMS)做准备。
    # Settings
    # min_wh = 2  # (pixels) minimum box width and height
    # 计算处理时间的限制。这个限制是基于 max_time_img (每个图像的最大处理时间)和批量大小 bs 来确定的。如果处理时间超过这个限制,函数将提前退出。
    time_limit = 2.0 + max_time_img * bs  # seconds to quit after
    # 更新 multi_label 标志,如果类别数量 nc 大于1,则允许每个边界框有多个标签。
    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)

    # 将预测结果的维度进行转置,以便于后续处理。这将形状从 (1, 84, 6300) 转换为 (1, 6300, 84) ,其中 84 可能是特征数量, 6300 是边界框的数量。
    prediction = prediction.transpose(-1, -2)  # shape(1,84,6300) to shape(1,6300,84)
    # 检查是否处理旋转边界框。如果 rotated 为 False ,则执行以下步骤。
    if not rotated:
        # 检查是否需要在原地修改预测结果。
        if in_place:
            # 如果需要在原地修改,则将边界框从 xywh 格式转换为 xyxy 格式。
            prediction[..., :4] = xywh2xyxy(prediction[..., :4])  # xywh to xyxy
        # 如果不在原地修改,则创建一个新的张量,包含转换后的边界框和原始预测结果的其他部分。
        else:
            # 将 xywh 格式转换为 xyxy 格式,并与预测结果的其他部分连接。
            prediction = torch.cat((xywh2xyxy(prediction[..., :4]), prediction[..., 4:]), dim=-1)  # xywh to xyxy

    # 记录当前时间,用于后续计算处理时间。
    t = time.time()
    # 初始化输出列表,每个元素是一个形状为 (0, 6 + nm) 的零张量,其中 6 是边界框的坐标数量, nm 是掩码数量。 bs 是批量大小,确保输出列表有足够的元素来存储每个图像的结果。
    output = [torch.zeros((0, 6 + nm), device=prediction.device)] * bs
    # 这段代码的主要作用是设置处理时间限制,调整预测结果的形状,将边界框格式转换为 xyxy (如果需要),并初始化输出列表。这些步骤为后续的 NMS 算法和结果收集做好准备。
    # 这段代码是 non_max_suppression 函数的核心部分,它执行了非极大值抑制(NMS)算法的主体逻辑。
    # 遍历预测结果 prediction , xi 是图像索引, x 是当前图像的预测结果。
    for xi, x in enumerate(prediction):  # image index, image inference
        # Apply constraints
        # x[((x[:, 2:4] < min_wh) | (x[:, 2:4] > max_wh)).any(1), 4] = 0  # width-height
        # 筛选出置信度大于阈值的预测结果。
        x = x[xc[xi]]  # confidence

        # Cat apriori labels if autolabelling    Cat apriori 标签(如果自动标记)。
        # 如果提供了 自动标签 且当前图像有标签且不是旋转框,将标签添加到预测结果中。
        if labels and len(labels[xi]) and not rotated:
            lb = labels[xi]
            # 创建一个零张量,用于存储标签信息。
            v = torch.zeros((len(lb), nc + nm + 4), device=x.device)
            # 将标签的边界框从 xywh 格式转换为 xyxy 格式。
            v[:, :4] = xywh2xyxy(lb[:, 1:5])  # box
            # 设置标签的类别索引。
            # 这行代码是 non_max_suppression 函数中的一部分,它用于将自动标签信息添加到预测结果中。
            # v 是一个零张量,其形状为 (len(lb), nc + nm + 4) ,其中 len(lb) 是当前图像的标签数量, nc 是类别数量, nm 是掩码数量, 4 是边界框的坐标数量。
            # range(len(lb)) 生成一个从 0 到 len(lb) - 1 的整数序列,表示每个标签的索引。
            # lb[:, 0].long() 获取标签张量 lb 的第一列,即类别索引,并将其转换为长整型( long )。
            # lb[:, 0].long() + 4 将类别索引加上 4 ,得到类别在 v 张量中的起始索引。这是因为 v 张量的前 4 列用于存储边界框的坐标,所以类别信息从第 5 列开始。
            # v[range(len(lb)), lb[:, 0].long() + 4] = 1.0 使用索引将 v 张量中对应于每个标签的类别位置设置为 1.0 。
            # 这行代码的作用是将自动标签的类别信息编码到 v 张量中,以便可以将这些标签添加到预测结果中。这在目标检测任务中非常有用,尤其是在处理自动标签时。
            v[range(len(lb)), lb[:, 0].long() + 4] = 1.0  # cls
            # 将标签信息添加到预测结果中。
            x = torch.cat((x, v), 0)

        # If none remain process next image    如果没有剩余,则处理下一个图像。
        # 如果筛选后的预测结果为空,则跳过当前图像。
        if not x.shape[0]:
            continue

        # Detections matrix nx6 (xyxy, conf, cls)    检测矩阵 nx6 (xyxy、conf、cls)。
        # 将预测结果分割为 边界框 、 类别 和 掩码 。
        box, cls, mask = x.split((4, nc, nm), 1)

        # 如果允许多标签,则按照置信度和类别进行筛选。
        if multi_label:
            # 找到所有类别概率大于置信度阈值 conf_thres 的位置。 i 是边界框的索引, j 是类别的索引。
            i, j = torch.where(cls > conf_thres)
            # 将 边界框 、 置信度 、 类别 和 掩码 拼接在一起。这里, box[i] 是边界框, x[i, 4 + j, None] 是置信度, j[:, None].float() 是类别, mask[i] 是掩码。
            x = torch.cat((box[i], x[i, 4 + j, None], j[:, None].float(), mask[i]), 1)
        # 如果 multi_label 为 False ,则只考虑每个边界框的最大置信度类别。
        else:  # best class only
            # 找到每个边界框的最大置信度和对应的类别索引。 conf 是最大置信度, j 是类别索引。
            conf, j = cls.max(1, keepdim=True)
            # 将 边界框 、 最大置信度 、 类别 和 掩码 拼接在一起,并筛选出置信度大于 conf_thres 的边界框。
            x = torch.cat((box, conf, j.float(), mask), 1)[conf.view(-1) > conf_thres]

        # Filter by class    按类别过滤。
        # 如果指定了类别,则筛选指定类别的预测结果。
        if classes is not None:
            x = x[(x[:, 5:6] == classes).any(1)]

        # Check shape
        # 获取筛选后的预测结果数量。
        n = x.shape[0]  # number of boxes
        if not n:  # no boxes
            continue
        # 如果预测结果数量超过 NMS 操作的最大数量,则按照置信度进行筛选。
        if n > max_nms:  # excess boxes
            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence and remove excess boxes

        # Batched NMS
        # 根据是否进行类别无关的NMS,计算类别偏移。
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes
        # 获取预测结果的置信度分数。
        scores = x[:, 4]  # scores
        # 如果处理旋转框,则使用旋转框的 NMS 函数。
        if rotated:
            boxes = torch.cat((x[:, :2] + c, x[:, 2:4], x[:, -1:]), dim=-1)  # xywhr
            i = nms_rotated(boxes, scores, iou_thres)
        # 否则,使用标准的 NMS 函数。
        else:
            boxes = x[:, :4] + c  # boxes (offset by class)
            i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS
        # 限制检测数量。
        i = i[:max_det]  # limit detections

        # # Experimental
        # merge = False  # use merge-NMS
        # if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)
        #     # Update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
        #     from .metrics import box_iou
        #     iou = box_iou(boxes[i], boxes) > iou_thres  # IoU matrix
        #     weights = iou * scores[None]  # box weights
        #     x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes
        #     redundant = True  # require redundant detections
        #     if redundant:
        #         i = i[iou.sum(1) > 1]  # require redundancy

        # 将筛选后的预测结果存储到输出列表中。
        output[xi] = x[i]
        #  如果处理时间超过限制,则输出警告并终止处理。
        if (time.time() - t) > time_limit:
            LOGGER.warning(f"WARNING ⚠️ NMS time limit {time_limit:.3f}s exceeded")    # 警告 ⚠️ 已超出 NMS 时间限制 {time_limit:.3f}s。
            break  # time limit exceeded

    # 返回最终的检测结果。
    return output
    # 这段代码实现了 NMS 算法的主要逻辑,包括筛选置信度大于阈值的预测结果、处理多标签、筛选指定类别、执行 NMS 操作以及限制检测数量。它能够处理不同格式的边界框,并能够根据类别和置信度阈值筛选出最佳的检测结果。
# 这个函数实现了一个完整的 NMS 流程,包括置信度筛选、类别筛选、多标签处理、旋转框处理以及标准的 NMS 操作。它能够处理不同格式的边界框,并能够根据类别和置信度阈值筛选出最佳的检测结果。

8.def clip_boxes(boxes, shape): 

# 这段代码定义了一个名为 clip_boxes 的函数,它用于将边界框(boxes)剪辑到特定的图像形状(shape)内,确保边界框的坐标不会超出图像的边界。
# 定义了一个名为 clip_boxes 的函数,它接受两个参数。
# 1.boxes :边界框的数组,可以是 PyTorch 张量或 NumPy 数组,形状为 (nx4) ,其中 n 是边界框的数量,4 是边界框的坐标 (x1, y1, x2, y2) 。
# 2.shape :一个元组,表示图像的形状,格式为 (height, width) 。
def clip_boxes(boxes, shape):
    # 获取边界框列表和形状(高度、宽度),并将边界框剪裁为该形状。
    """
    Takes a list of bounding boxes and a shape (height, width) and clips the bounding boxes to the shape.

    Args:
        boxes (torch.Tensor): the bounding boxes to clip
        shape (tuple): the shape of the image

    Returns:
        (torch.Tensor | numpy.ndarray): Clipped boxes
    """
    # 检查 boxes 是否为 PyTorch 张量。如果 boxes 是 PyTorch 张量,则使用 clamp 函数分别对 x1 、 y1 、 x2 和 y2 坐标进行剪辑。
    if isinstance(boxes, torch.Tensor):  # faster individually (WARNING: inplace .clamp_() Apple MPS bug)
        # 将 x1 坐标剪辑到 [0, width] 范围内。
        boxes[..., 0] = boxes[..., 0].clamp(0, shape[1])  # x1
        # 将 y1 坐标剪辑到 [0, height] 范围内。
        boxes[..., 1] = boxes[..., 1].clamp(0, shape[0])  # y1
        # 将 x2 坐标剪辑到 [0, width] 范围内。
        boxes[..., 2] = boxes[..., 2].clamp(0, shape[1])  # x2
        # 将 y2 坐标剪辑到 [0, height] 范围内。
        boxes[..., 3] = boxes[..., 3].clamp(0, shape[0])  # y2
    # 否则,如果 boxes 是 NumPy 数组,则使用 clip 函数对坐标进行分组剪辑:
    else:  # np.array (faster grouped)
        # 将 x1 和 x2 坐标剪辑到 [0, width] 范围内。
        boxes[..., [0, 2]] = boxes[..., [0, 2]].clip(0, shape[1])  # x1, x2
        # 将 y1 和 y2 坐标剪辑到 [0, height] 范围内。
        boxes[..., [1, 3]] = boxes[..., [1, 3]].clip(0, shape[0])  # y1, y2
    # 返回剪辑后的边界框数组。
    return boxes
# 这个函数确保边界框的坐标不会超出图像的边界,这对于处理图像边界附近的对象尤为重要。通过检查 boxes 的类型,函数可以针对 PyTorch 张量和 NumPy 数组采用不同的优化方法。

9.def clip_coords(coords, shape): 

# 这段代码定义了一个名为 clip_coords 的函数,它用于将坐标剪辑到特定的图像形状内,确保坐标不会超出图像的边界。
# 定义了一个名为 clip_coords 的函数,它接受两个参数。
# 1.coords :坐标数组,可以是 PyTorch 张量或 NumPy 数组,形状为 (n, 2) ,其中 n 是坐标点的数量,2 表示 (x, y) 坐标。
# 2.shape :一个元组,表示图像的形状,格式为 (height, width) 。
def clip_coords(coords, shape):
    # 将线坐标剪切到图像边界。
    """
    Clip line coordinates to the image boundaries.

    Args:
        coords (torch.Tensor | numpy.ndarray): A list of line coordinates.
        shape (tuple): A tuple of integers representing the size of the image in the format (height, width).

    Returns:
        (torch.Tensor | numpy.ndarray): Clipped coordinates
    """
    # 检查 coords 是否为 PyTorch 张量。如果 coords 是 PyTorch 张量,则使用 clamp 函数分别对 x 和 y 坐标进行剪辑
    if isinstance(coords, torch.Tensor):  # faster individually (WARNING: inplace .clamp_() Apple MPS bug)
        # 将 x 坐标剪辑到 [0, width] 范围内。
        coords[..., 0] = coords[..., 0].clamp(0, shape[1])  # x
        # 将 y 坐标剪辑到 [0, height] 范围内。
        coords[..., 1] = coords[..., 1].clamp(0, shape[0])  # y
    # 否则,如果 coords 是 NumPy 数组,则使用 clip 函数对坐标进行分组剪辑。
    else:  # np.array (faster grouped)
        # 将 x 坐标剪辑到 [0, width] 范围内。
        coords[..., 0] = coords[..., 0].clip(0, shape[1])  # x
        # 将 y 坐标剪辑到 [0, height] 范围内。
        coords[..., 1] = coords[..., 1].clip(0, shape[0])  # y
    # 返回剪辑后的坐标数组
    return coords
# 这个函数确保坐标点不会超出图像的边界,这对于处理图像边界附近的对象坐标非常有用。通过检查 coords 的类型,函数可以针对 PyTorch 张量和 NumPy 数组采用不同的优化方法。

10.def scale_image(masks, im0_shape, ratio_pad=None): 

# 这段代码定义了一个名为 scale_image 的函数,它用于将图像中的掩码(masks)从一个尺寸( im1_shape )缩放到另一个尺寸( im0_shape )。
#  定义了一个名为 scale_image 的函数,它接受以下参数。
# 1.masks :要缩放的掩码,可以是二维或三维数组。
# 2.im0_shape :目标图像的形状,格式为 (height, width) 。
# 3.ratio_pad (默认为 None ):一个包含缩放比例和填充的元组,格式为 (gain, (pad_x, pad_y)) 。
def scale_image(masks, im0_shape, ratio_pad=None):
    # 获取掩膜,并将其调整为原始图像大小。
    """
    Takes a mask, and resizes it to the original image size.

    Args:
        masks (np.ndarray): resized and padded masks/images, [h, w, num]/[h, w, 3].
        im0_shape (tuple): the original image shape
        ratio_pad (tuple): the ratio of the padding to the original image.

    Returns:
        masks (np.ndarray): The masks that are being returned with shape [h, w, num].
    """
    # Rescale coordinates (xyxy) from im1_shape to im0_shape
    # 获取掩码的形状。
    im1_shape = masks.shape
    #  如果掩码的形状与目标形状相同,则直接返回掩码。
    if im1_shape[:2] == im0_shape[:2]:
        return masks
    # 如果没有提供 ratio_pad ,则根据 im0_shape 计算缩放比例和填充。
    if ratio_pad is None:  # calculate from im0_shape
        # 计算缩放比例,取高度和宽度比例的最小值。
        gain = min(im1_shape[0] / im0_shape[0], im1_shape[1] / im0_shape[1])  # gain  = old / new
        # 计算水平和垂直方向的填充。
        pad = (im1_shape[1] - im0_shape[1] * gain) / 2, (im1_shape[0] - im0_shape[0] * gain) / 2  # wh padding
    else:
        # gain = ratio_pad[0][0]
        pad = ratio_pad[1]
    # 计算填充的顶部和左侧坐标。
    top, left = int(pad[1]), int(pad[0])  # y, x
    # 计算填充的底部和右侧坐标。
    bottom, right = int(im1_shape[0] - pad[1]), int(im1_shape[1] - pad[0])

    # 检查掩码的形状是否至少为二维。
    if len(masks.shape) < 2:
        raise ValueError(f'"len of masks shape" should be 2 or 3, but got {len(masks.shape)}')    # 遮罩形状的长度”应为 2 或 3,但结果为 {len(masks.shape)}。
    # 裁剪掩码以去除不必要的填充。
    masks = masks[top:bottom, left:right]
    # 使用 OpenCV 的 resize 函数将掩码缩放到目标尺寸。
    masks = cv2.resize(masks, (im0_shape[1], im0_shape[0]))
    # 如果掩码是二维的,添加一个新的轴,使其成为三维的。
    if len(masks.shape) == 2:
        masks = masks[:, :, None]

    # 返回缩放后的掩码。
    return masks
# 这个函数的作用是将掩码从原始图像的形状缩放到目标图像的形状,同时考虑了填充和缩放比例。这对于在不同分辨率的图像之间转换掩码非常有用。

11.def xyxy2xywh(x): 

# 这段代码定义了一个名为 xyxy2xywh 的函数,它将边界框的坐标从 xyxy 格式转换为 xywh 格式。 xyxy 格式表示边界框的左上角和右下角坐标,而 xywh 格式表示边界框的中心点坐标以及宽度和高度。
# 定义了一个名为 xyxy2xywh 的函数,它接受一个参数。
# 1.x :这是一个包含边界框坐标的张量或数组。
def xyxy2xywh(x):
    # 将边界框坐标从 (x1, y1, x2, y2) 格式转换为 (x, y, width, height) 格式,其中 (x1, y1) 是左上角,(x2, y2) 是右下角。
    """
    Convert bounding box coordinates from (x1, y1, x2, y2) format to (x, y, width, height) format where (x1, y1) is the
    top-left corner and (x2, y2) is the bottom-right corner.

    Args:
        x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x1, y1, x2, y2) format.

    Returns:
        y (np.ndarray | torch.Tensor): The bounding box coordinates in (x, y, width, height) format.
    """
    # 确保输入坐标的形状是 nx4 ,其中 n 是边界框的数量,4 表示 xyxy 格式的四个坐标值。
    assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
    # 创建一个新的张量或数组 y ,其形状与输入 x 相同,用于存储转换后的 xywh 格式坐标。
    y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x)  # faster than clone/copy
    # 计算边界框的中心点 x 坐标,方法是取左上角 x 坐标和右下角 x 坐标的平均值。
    y[..., 0] = (x[..., 0] + x[..., 2]) / 2  # x center
    # 计算边界框的中心点 y 坐标,方法是取左上角 y 坐标和右下角 y 坐标的平均值。
    y[..., 1] = (x[..., 1] + x[..., 3]) / 2  # y center
    # 计算边界框的宽度,方法是取右下角 x 坐标和左上角 x 坐标的差值。
    y[..., 2] = x[..., 2] - x[..., 0]  # width
    # 计算边界框的高度,方法是取右下角 y 坐标和左上角 y 坐标的差值。
    y[..., 3] = x[..., 3] - x[..., 1]  # height
    # 返回转换后的 xywh 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 xyxy 格式转换为 xywh 格式,这在目标检测和图像处理任务中很常见。

12.def xywh2xyxy(x): 

# 这段代码定义了一个名为 xywh2xyxy 的函数,它将边界框的坐标从 xywh 格式转换为 xyxy 格式。 xywh 格式表示边界框的中心点坐标以及宽度和高度,而 xyxy 格式表示边界框的左上角和右下角坐标。
# 定义了一个名为 xywh2xy 的函数,它接受一个参数。
# 1.x :这是一个包含边界框坐标的张量或数组。
def xywh2xyxy(x):
    # 将边界框坐标从 (x, y, width, height) 格式转换为 (x1, y1, x2, y2) 格式,其中 (x1, y1) 是左上角,(x2, y2) 是右下角。注意:每 2 个通道的操作比每个通道的操作更快。
    """
    Convert bounding box coordinates from (x, y, width, height) format to (x1, y1, x2, y2) format where (x1, y1) is the
    top-left corner and (x2, y2) is the bottom-right corner. Note: ops per 2 channels faster than per channel.

    Args:
        x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x, y, width, height) format.

    Returns:
        y (np.ndarray | torch.Tensor): The bounding box coordinates in (x1, y1, x2, y2) format.
    """
    # 确保输入坐标的形状是 nx4 ,其中 n 是边界框的数量,4 表示 xywh 格式的四个坐标值。
    assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
    # 创建一个新的张量或数组 y ,其形状与输入 x 相同,用于存储转换后的 xyxy 格式坐标。
    y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x)  # faster than clone/copy
    # 提取边界框的中心点坐标。
    xy = x[..., :2]  # centers
    # 提取边界框的宽度和高度,并除以 2 得到半宽和半高。
    wh = x[..., 2:] / 2  # half width-height
    # 计算边界框的左上角坐标,方法是将中心点坐标减去半宽和半高。
    y[..., :2] = xy - wh  # top left xy
    # 计算边界框的右下角坐标,方法是将中心点坐标加上半宽和半高。
    y[..., 2:] = xy + wh  # bottom right xy
    # 返回转换后的 xyxy 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 xywh 格式转换为 xyxy 格式,这在目标检测和图像处理任务中很常见。

13.def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0): 

# 这段代码定义了一个名为 xywhn2xyxy 的函数,它将边界框的坐标从归一化的 xywh 格式(其中 x 和 y 是相对于图像宽度和高度的比例)转换为实际的 xyxy 格式。
# 定义了一个名为 xywhn2xyxy 的函数,它接受以下参数。
# 1.x :包含边界框坐标的数组或张量,格式为 nx4 ,其中 n 是边界框的数量,4 表示 xywh 格式的四个坐标值(中心点的 x 和 y 坐标,以及宽度和高度)。
# 2.w (默认为 640):图像的宽度。
# 3.h (默认为 640):图像的高度。
# 4.padw (默认为 0):图像宽度的填充。
# 5.padh (默认为 0):图像高度的填充。
def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
    # 将规范化的边界框坐标转换为像素坐标。
    """
    Convert normalized bounding box coordinates to pixel coordinates.

    Args:
        x (np.ndarray | torch.Tensor): The bounding box coordinates.
        w (int): Width of the image. Defaults to 640
        h (int): Height of the image. Defaults to 640
        padw (int): Padding width. Defaults to 0
        padh (int): Padding height. Defaults to 0
    Returns:
        y (np.ndarray | torch.Tensor): The coordinates of the bounding box in the format [x1, y1, x2, y2] where
            x1,y1 is the top-left corner, x2,y2 is the bottom-right corner of the bounding box.
    """
    # 确保输入坐标的形状是 nx4 。
    assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
    # 创建一个新的张量或数组 y ,其形状与输入 x 相同,用于存储转换后的 xyxy 格式坐标。
    y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x)  # faster than clone/copy
    # 计算边界框左上角的 x 坐标,方法是将归一化的中心点 x 坐标转换为实际坐标,并减去宽度的一半,然后加上图像宽度的填充。
    y[..., 0] = w * (x[..., 0] - x[..., 2] / 2) + padw  # top left x
    # 计算边界框左上角的 y 坐标,方法是将归一化的中心点 y 坐标转换为实际坐标,并减去高度的一半,然后加上图像高度的填充。
    y[..., 1] = h * (x[..., 1] - x[..., 3] / 2) + padh  # top left y
    # 计算边界框右下角的 x 坐标,方法是将归一化的中心点 x 坐标转换为实际坐标,并加上宽度的一半,然后加上图像宽度的填充。
    y[..., 2] = w * (x[..., 0] + x[..., 2] / 2) + padw  # bottom right x
    # 计算边界框右下角的 y 坐标,方法是将归一化的中心点 y 坐标转换为实际坐标,并加上高度的一半,然后加上图像高度的填充。
    y[..., 3] = h * (x[..., 1] + x[..., 3] / 2) + padh  # bottom right y
    # 返回转换后的 xyxy 格式坐标。
    return y
# 这个函数的作用是将归一化的边界框坐标转换为实际的像素坐标,同时考虑了图像的宽度、高度和填充。这对于将模型输出的归一化坐标转换为实际图像中的坐标非常有用。

14.def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0): 

# 这段代码定义了一个名为 xyxy2xywhn 的函数,它将边界框的坐标从 xyxy 格式转换为归一化的 xywh 格式,并且提供了一个可选的剪辑步骤,以确保边界框不会超出图像的边界。
# 将边界框坐标从 (x1, y1, x2, y2) 格式转换为 (x, y, width, height, normalized) 格式。x、y、width 和 height 已标准化为图像尺寸。
# 定义了一个名为 xyxy2xywhn 的函数,它接受以下参数 :
# 1.x :包含边界框坐标的数组或张量,格式为 nx4 ,其中 n 是边界框的数量,4 表示 xyxy 格式的四个坐标值(左上角的 x 和 y 坐标,以及右下角的 x 和 y 坐标)。
# 2.w (默认为 640):图像的宽度。
# 3.h (默认为 640):图像的高度。
# 4.clip (默认为 False):布尔值,如果为 True,则将边界框剪辑到图像边界内。
# 5.eps (默认为 0.0):用于调整图像边界的小值,以避免边界框恰好在图像边缘。
def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0):
    """
    Convert bounding box coordinates from (x1, y1, x2, y2) format to (x, y, width, height, normalized) format. x, y,
    width and height are normalized to image dimensions.

    Args:
        x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x1, y1, x2, y2) format.
        w (int): The width of the image. Defaults to 640
        h (int): The height of the image. Defaults to 640
        clip (bool): If True, the boxes will be clipped to the image boundaries. Defaults to False
        eps (float): The minimum value of the box's width and height. Defaults to 0.0

    Returns:
        y (np.ndarray | torch.Tensor): The bounding box coordinates in (x, y, width, height, normalized) format
    """
    # 如果 clip 为 True,则调用 clip_boxes 函数将边界框剪辑到图像边界内。
    if clip:
        # def clip_boxes(boxes, shape): -> 用于将边界框(boxes)剪辑到特定的图像形状(shape)内,确保边界框的坐标不会超出图像的边界。返回剪辑后的边界框数组。 -> return boxes
        x = clip_boxes(x, (h - eps, w - eps))
    # 确保输入坐标的形状是 nx4 。
    assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
    # 创建一个新的张量或数组 y ,其形状与输入 x 相同,用于存储转换后的 xywh 格式坐标。
    y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x)  # faster than clone/copy
    # 计算边界框的中心点 x 坐标,方法是取左上角 x 坐标和右下角 x 坐标的平均值,然后除以图像宽度进行归一化。
    y[..., 0] = ((x[..., 0] + x[..., 2]) / 2) / w  # x center
    # 计算边界框的中心点 y 坐标,方法是取左上角 y 坐标和右下角 y 坐标的平均值,然后除以图像高度进行归一化。
    y[..., 1] = ((x[..., 1] + x[..., 3]) / 2) / h  # y center
    # 计算边界框的宽度,方法是取右下角 x 坐标和左上角 x 坐标的差值,然后除以图像宽度进行归一化。
    y[..., 2] = (x[..., 2] - x[..., 0]) / w  # width
    # 计算边界框的高度,方法是取右下角 y 坐标和左上角 y 坐标的差值,然后除以图像高度进行归一化。
    y[..., 3] = (x[..., 3] - x[..., 1]) / h  # height
    # 返回转换后的归一化 xywh 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 xyxy 格式转换为归一化的 xywh 格式,这在目标检测和图像处理任务中很常见,尤其是在需要将边界框坐标归一化到图像尺寸时。

15.def xywh2ltwh(x): 

# 这段代码定义了一个名为 xywh2ltwh 的函数,它将边界框的坐标从 xywh 格式(其中 x 和 y 是中心点坐标, w 和 h 是宽度和高度)转换为 ltwh 格式(其中 l 和 t 是左上角坐标, w 和 h 是宽度和高度)。
# 定义了一个名为 xywh2ltwh 的函数,它接受一个参数。
# 1.x :这是一个包含边界框坐标的张量或数组。
def xywh2ltwh(x):
    # 将边界框格式从 [x, y, w, h] 转换为 [x1, y1, w, h],其中 x1、y1 为左上角坐标。
    """
    Convert the bounding box format from [x, y, w, h] to [x1, y1, w, h], where x1, y1 are the top-left coordinates.

    Args:
        x (np.ndarray | torch.Tensor): The input tensor with the bounding box coordinates in the xywh format

    Returns:
        y (np.ndarray | torch.Tensor): The bounding box coordinates in the xyltwh format
    """
    # 创建一个新的张量或数组 y ,它是输入 x 的副本。如果 x 是 PyTorch 张量,则使用 clone() 方法;如果 x 是 NumPy 数组,则使用 copy() 方法。
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    # 计算边界框左上角的 x 坐标,方法是将中心点的 x 坐标减去宽度的一半。
    y[..., 0] = x[..., 0] - x[..., 2] / 2  # top left x
    # 计算边界框左上角的 y 坐标,方法是将中心点的 y 坐标减去高度的一半。
    y[..., 1] = x[..., 1] - x[..., 3] / 2  # top left y
    # 返回转换后的 ltwh 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 xywh 格式转换为 ltwh 格式,这在目标检测和图像处理任务中很常见。

16.def xyxy2ltwh(x): 

# 这段代码定义了一个名为 xyxy2ltwh 的函数,它将边界框的坐标从 xyxy 格式(左上角和右下角的坐标)转换为 ltwh 格式(左上角坐标和宽度、高度)。
# 定义了一个名为 xyxy2ltwh 的函数,它接受一个参数。
# 1.x :这是一个包含边界框坐标的张量或数组,格式为 xyxy 。
def xyxy2ltwh(x):
    # 将 nx4 个边界框从 [x1, y1, x2, y2] 转换为 [x1, y1, w, h],其中 xy1=左上角,xy2=右下角。
    """
    Convert nx4 bounding boxes from [x1, y1, x2, y2] to [x1, y1, w, h], where xy1=top-left, xy2=bottom-right.

    Args:
        x (np.ndarray | torch.Tensor): The input tensor with the bounding boxes coordinates in the xyxy format

    Returns:
        y (np.ndarray | torch.Tensor): The bounding box coordinates in the xyltwh format.
    """
    # 创建一个新的张量或数组 y ,它是输入 x 的副本。如果 x 是 PyTorch 张量,则使用 clone() 方法;如果 x 是 NumPy 数组,则使用 copy() 方法。
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    # 计算边界框的宽度,方法是取右下角的 x 坐标减去左上角的 x 坐标。
    y[..., 2] = x[..., 2] - x[..., 0]  # width
    # 计算边界框的高度,方法是取右下角的 y 坐标减去左上角的 y 坐标。
    y[..., 3] = x[..., 3] - x[..., 1]  # height
    # 返回转换后的 ltwh 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 xyxy 格式转换为 ltwh 格式,这在目标检测和图像处理任务中很常见,尤其是在需要将边界框的尺寸表示为宽度和高度时。

17.def ltwh2xywh(x): 

# 这段代码定义了一个名为 ltwh2xywh 的函数,它将边界框的坐标从 ltwh 格式(左上角坐标和宽度、高度)转换为 xywh 格式(中心点坐标和宽度、高度)。
# 定义了一个名为 ltwh2xywh 的函数,它接受一个参数。
# 1.x :这是一个包含边界框坐标的张量或数组,格式为 ltwh 。
def ltwh2xywh(x):
    # 将 nx4 个框从 [x1, y1, w, h] 转换为 [x, y, w, h],其中 xy1=左上角,xy=中心。
    """
    Convert nx4 boxes from [x1, y1, w, h] to [x, y, w, h] where xy1=top-left, xy=center.

    Args:
        x (torch.Tensor): the input tensor

    Returns:
        y (np.ndarray | torch.Tensor): The bounding box coordinates in the xywh format.
    """
    # 创建一个新的张量或数组 y ,它是输入 x 的副本。如果 x 是 PyTorch 张量,则使用 clone() 方法;如果 x 是 NumPy 数组,则使用 copy() 方法。
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    # 计算边界框的中心点 x 坐标,方法是将左上角的 x 坐标加上宽度的一半。
    y[..., 0] = x[..., 0] + x[..., 2] / 2  # center x
    # 计算边界框的中心点 y 坐标,方法是将左上角的 y 坐标加上高度的一半。
    y[..., 1] = x[..., 1] + x[..., 3] / 2  # center y
    # 回转换后的 xywh 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 ltwh 格式转换为 xywh 格式,这在目标检测和图像处理任务中很常见,尤其是在需要将边界框的尺寸表示为以中心点为基准的宽度和高度时。

18.def xyxyxyxy2xywhr(x): 

# 这段代码定义了一个名为 xyxyxyxy2xywhr 的函数,它将一系列点的坐标(假设这些点构成一个边界框)从 xyxy 格式(每个点的坐标)转换为 xywhr 格式(其中 x 和 y 是旋转边界框的中心点坐标, w 和 h 是宽度和高度, r 是旋转角度)。
# 定义了一个名为 xyxyxyxy2xywhr 的函数,它接受一个参数。
# 1.x :这是一个包含一系列点坐标的张量或数组。
def xyxyxyxy2xywhr(x):
    # 将批量定向边界框 (OBB) 从 [xy1, xy2, xy3, xy4] 转换为 [xywh, rotation]。旋转值以弧度形式返回,范围从 0 到 pi/2。
    """
    Convert batched Oriented Bounding Boxes (OBB) from [xy1, xy2, xy3, xy4] to [xywh, rotation]. Rotation values are
    returned in radians from 0 to pi/2.

    Args:
        x (numpy.ndarray | torch.Tensor): Input box corners [xy1, xy2, xy3, xy4] of shape (n, 8).

    Returns:
        (numpy.ndarray | torch.Tensor): Converted data in [cx, cy, w, h, rotation] format of shape (n, 5).
    """
    # 检查输入 x 是否为 PyTorch 张量。
    is_torch = isinstance(x, torch.Tensor)
    # 如果 x 是 PyTorch 张量,则将其转换为 NumPy 数组;否则,直接使用 x 。
    points = x.cpu().numpy() if is_torch else x
    # 将点的坐标重塑为 (len(x), num_points, 2) 的形状,其中 len(x) 是边界框的数量, num_points 是每个边界框的点数。
    points = points.reshape(len(x), -1, 2)
    # 初始化一个空列表,用于存储转换后的 xywhr 格式的边界框。
    rboxes = []
    # 遍历每个边界框的点集。
    for pts in points:
        # NOTE: Use cv2.minAreaRect to get accurate xywhr,
        # especially some objects are cut off by augmentations in dataloader.

        # cv2.minAreaRect(points)
        # cv2.minAreaRect() 是 OpenCV 库中的一个函数,它用于计算给定点集的最小外接旋转矩形(也称为最小面积外接矩形)。这个函数返回一个 RotatedRect 对象,其中包含了矩形的中心点坐标、宽度和高度以及旋转角度。
        # 参数 :
        # points :一个点集,可以是一个 numpy 数组,形状为 (n, 1, 2) 或 (n, 2) ,其中 n 是点的数量,每个点由 (x, y) 坐标组成。
        # 返回值 :
        # 返回一个 RotatedRect 对象,包含以下属性。center :矩形的中心点坐标 (x, y) 。 size :矩形的宽度和高度 (width, height) 。 angle :矩形的旋转角度,以度为单位,表示矩形相对于水平轴的旋转角度。
        # 功能 :
        # cv2.minAreaRect() 函数计算并返回一个旋转矩形,该矩形能够完全包含输入的点集,并且具有最小的面积。这个矩形可能是倾斜的,其角度由 angle 属性给出。

        # 使用 OpenCV 的 minAreaRect 函数计算最小外接矩形的中心点坐标、宽度、高度和旋转角度。这个函数返回的旋转角度是以度为单位。
        (cx, cy), (w, h), angle = cv2.minAreaRect(pts)
        # 将计算出的 xywhr 格式的边界框添加到 rboxes 列表中。注意,角度从度转换为弧度。
        rboxes.append([cx, cy, w, h, angle / 180 * np.pi])
    # 如果输入 x 是 PyTorch 张量,则将 rboxes 转换为 PyTorch 张量并返回;否则,将 rboxes 转换为 NumPy 数组并返回。
    return torch.tensor(rboxes, device=x.device, dtype=x.dtype) if is_torch else np.asarray(rboxes)
# 这个函数的作用是将一系列点构成的边界框转换为旋转边界框的 xywhr 格式,这在处理旋转目标检测任务中非常有用。

19.def xywhr2xyxyxyxy(x): 

# 这段代码定义了一个名为 xywhr2xyxyxyxy 的函数,它将旋转边界框的坐标从 xywhr 格式(其中 x 和 y 是中心点坐标, w 和 h 是宽度和高度, r 是旋转角度)转换为 xyxyxyxy 格式(四个角点的坐标)。
# 定义了一个名为 xywhr2xyxyxyxy 的函数,它接受一个参数。
# 1.x :这是一个包含旋转边界框坐标的张量或数组。
def xywhr2xyxyxyxy(x):
    # 将分批定向边界框 (OBB) 从 [xywh, rotation] 转换为 [xy1, xy2, xy3, xy4]。旋转值应以弧度为单位,范围从 0 到 pi/2。
    """
    Convert batched Oriented Bounding Boxes (OBB) from [xywh, rotation] to [xy1, xy2, xy3, xy4]. Rotation values should
    be in radians from 0 to pi/2.

    Args:
        x (numpy.ndarray | torch.Tensor): Boxes in [cx, cy, w, h, rotation] format of shape (n, 5) or (b, n, 5).

    Returns:
        (numpy.ndarray | torch.Tensor): Converted corner points of shape (n, 4, 2) or (b, n, 4, 2).
    """
    # 根据 x 的类型(PyTorch 张量或 NumPy 数组),选择相应的数学函数和数组操作函数。
    cos, sin, cat, stack = (
        (torch.cos, torch.sin, torch.cat, torch.stack)
        if isinstance(x, torch.Tensor)
        else (np.cos, np.sin, np.concatenate, np.stack)
    )

    # 提取边界框的中心点坐标。
    ctr = x[..., :2]
    # 提取边界框的 宽度 、 高度 和 旋转角度 。
    w, h, angle = (x[..., i : i + 1] for i in range(2, 5))
    # 计算旋转角度的余弦和正弦值。
    cos_value, sin_value = cos(angle), sin(angle)
    # 计算宽度方向的向量。
    vec1 = [w / 2 * cos_value, w / 2 * sin_value]
    # 计算高度方向的向量。
    vec2 = [-h / 2 * sin_value, h / 2 * cos_value]
    # 将向量拼接成张量或数组。
    vec1 = cat(vec1, -1)
    vec2 = cat(vec2, -1)
    # 计算第一个角点的坐标。
    pt1 = ctr + vec1 + vec2
    # 计算第二个角点的坐标。
    pt2 = ctr + vec1 - vec2
    # 计算第三个角点的坐标。
    pt3 = ctr - vec1 - vec2
    # 计算第四个角点的坐标。
    pt4 = ctr - vec1 + vec2
    # 将四个角点的坐标堆叠成一个张量或数组,并返回。
    return stack([pt1, pt2, pt3, pt4], -2)
# 这个函数的作用是将旋转边界框的坐标从 xywhr 格式转换为 xyxyxyxy 格式,这在处理旋转目标检测任务时非常有用。

20.def ltwh2xyxy(x): 

# 这段代码定义了一个名为 ltwh2xyxy 的函数,它将边界框的坐标从 ltwh 格式(左上角坐标和宽度、高度)转换为 xyxy 格式(左上角和右下角坐标)。
# 定义了一个名为 ltwh2xyxy 的函数,它接受一个参数。
# 1.x :这是一个包含边界框坐标的张量或数组,格式为 ltwh 。
def ltwh2xyxy(x):
    # 它将边界框从 [x1, y1, w, h] 转换为 [x1, y1, x2, y2],其中 xy1=左上角,xy2=右下角。
    """
    It converts the bounding box from [x1, y1, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right.

    Args:
        x (np.ndarray | torch.Tensor): the input image

    Returns:
        y (np.ndarray | torch.Tensor): the xyxy coordinates of the bounding boxes.
    """
    # 创建一个新的张量或数组 y ,它是输入 x 的副本。如果 x 是 PyTorch 张量,则使用 clone() 方法;如果 x 是 NumPy 数组,则使用 copy() 方法。
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    # 计算边界框右下角的 x 坐标,方法是将左上角的 x 坐标与宽度相加。
    y[..., 2] = x[..., 2] + x[..., 0]  # width
    # 计算边界框右下角的 y 坐标,方法是将左上角的 y 坐标与高度相加。
    y[..., 3] = x[..., 3] + x[..., 1]  # height
    # 返回转换后的 xyxy 格式坐标。
    return y
# 这个函数的作用是将边界框的坐标从 ltwh 格式转换为 xyxy 格式,这在目标检测和图像处理任务中很常见,尤其是在需要将边界框的尺寸表示为左上角和右下角坐标时。

21.def segments2boxes(segments):

# 这段代码定义了一个名为 segments2boxes 的函数,它将一系列线段(segments)转换为边界框(boxes)。
# 定义了一个名为 segments2boxes 的函数,它接受一个参数。
# 1.segments :这是一个包含多个线段的数组,每个线段由一系列点组成。
def segments2boxes(segments):
    # 它将线段标签转换为框标签,即 (cls, xy1, xy2, ...) 转换为 (cls, xywh)。
    """
    It converts segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh).

    Args:
        segments (list): list of segments, each segment is a list of points, each point is a list of x, y coordinates

    Returns:
        (np.ndarray): the xywh coordinates of the bounding boxes.
    """
    # 初始化一个空列表 boxes ,用于存储转换后的边界框。
    boxes = []
    # 遍历每个线段 s 。
    for s in segments:
        # 将线段 s 的点的坐标转置,从而分离出 x 和 y 坐标。
        x, y = s.T  # segment xy
        # 对于每个线段,计算它的最小 x 坐标、最小 y 坐标、最大 x 坐标和最大 y 坐标,然后将这些值作为一个边界框添加到 boxes 列表中。这个边界框是以 xyxy 格式表示的。
        boxes.append([x.min(), y.min(), x.max(), y.max()])  # cls, xyxy
    # 将 boxes 列表转换为 NumPy 数组,然后调用 xyxy2xywh 函数将其从 xyxy 格式转换为 xywh 格式。 xyxy2xywh 函数将边界框的坐标从左上角和右下角坐标转换为中心点坐标和宽度、高度。
    return xyxy2xywh(np.array(boxes))  # cls, xywh
# 这个函数的作用是将一系列线段转换为边界框,这在图像处理和目标检测任务中很常见,尤其是在需要从线段标注转换为边界框标注时。

22.def resample_segments(segments, n=1000): 

# 这段代码定义了一个名为 resample_segments 的函数,它用于对输入的线段进行重新采样,使得每个线段包含 n 个点。
# 定义了一个名为 resample_segments 的函数,它接受两个参数。
# 1.segments :一个包含多个线段的数组,每个线段由一系列点组成。
# 2.n (默认为 1000):重新采样后每个线段的点的数量。
def resample_segments(segments, n=1000):
    # 输入一个片段列表 (n,2),并返回一个片段列表 (n,2),每个片段上采样到 n 个点。
    """
    Inputs a list of segments (n,2) and returns a list of segments (n,2) up-sampled to n points each.

    Args:
        segments (list): a list of (n,2) arrays, where n is the number of points in the segment.
        n (int): number of points to resample the segment to. Defaults to 1000

    Returns:
        segments (list): the resampled segments.
    """
    # 遍历每个线段 s ,并使用 enumerate 函数获取线段的索引 i 。
    for i, s in enumerate(segments):
        # 将线段的第一个点添加到线段的末尾,以闭合线段。
        s = np.concatenate((s, s[0:1, :]), axis=0)
        # 创建一个从 0 到线段长度减 1 的等间隔序列,用于确定新的采样点的位置。
        x = np.linspace(0, len(s) - 1, n)
        # 创建一个从 0 到线段长度减 1 的序列,用于原始线段的每个点。
        xp = np.arange(len(s))
        # 对线段进行重新采样,替换原始线段。
        # np.interp(x, xp, s[:, i]) :对每个维度( x 和 y )使用 np.interp 函数进行插值,根据新的采样点位置 x 和原始点位置 xp 计算插值后的点。
        # for i in range(2) :分别对 x 和 y 坐标进行操作。
        # np.concatenate(...) :将插值后的 x 和 y 坐标连接起来。
        # reshape(2, -1).T :将连接后的数组重塑为 (2, n) 的形状,并转置,使其成为 n x 2 的形状,其中 n 是重新采样后的点的数量。
        segments[i] = (
            np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)], dtype=np.float32).reshape(2, -1).T
        )  # segment xy
    # 返回重新采样后的线段数组。
    return segments
# 这个函数的作用是对输入的线段进行重新采样,使得每个线段包含指定数量的点,这在图像处理和计算机视觉任务中很常见,尤其是在需要统一线段表示或平滑线段时。

23.def crop_mask(masks, boxes): 

# 这段代码定义了一个名为 crop_mask 的函数,它用于根据给定的边界框 boxes 从掩码 masks 中裁剪出对象的区域。这个函数在实例分割任务中非常有用,因为它允许模型只关注与特定对象相关的掩码部分,从而提高损失计算的准确性。
# 1.masks :一个包含多个对象掩码的张量,形状为 (N, H, W) ,其中 N 是掩码的数量, H 是掩码的高度, W 是掩码的宽度。
# 2.boxes :一个包含边界框坐标的张量,形状为 (N, 4) ,其中每个边界框由四个值组成: (x1, y1, x2, y2) 。
def crop_mask(masks, boxes):
    # 它需要一个 掩膜 和一个 边界框 ,并返回裁剪到边界框的 掩膜 。
    """
    It takes a mask and a bounding box, and returns a mask that is cropped to the bounding box.

    Args:
        masks (torch.Tensor): [n, h, w] tensor of masks
        boxes (torch.Tensor): [n, 4] tensor of bbox coordinates in relative point form

    Returns:
        (torch.Tensor): The masks are being cropped to the bounding box.
    """
    # 这行代码提取 masks 张量的高度 h 和宽度 w 。
    _, h, w = masks.shape

    # torch.chunk(input, chunks, dim=0)
    # torch.chunk 是 PyTorch 中的一个函数,它将张量(tensor)分割成指定数量的块(chunks)。每个块在指定的维度上具有相等的大小。如果张量不能被均匀分割,则最后一个块可能会比其他块小。
    # 参数 :
    # input :要被分割的输入张量。
    # chunks :一个整数,表示要将输入张量分割成多少块。
    # dim :一个整数,指定沿着哪个维度进行分割。默认是0,即第一个维度。
    # 返回值 :
    # 返回一个包含分割后块的元组,每个块都是一个张量。

    # 这行代码将 boxes 张量沿着新的维度(第三维)分割成四个部分,分别对应边界框的 x1 、 y1 、 x2 和 y2 坐标。
    x1, y1, x2, y2 = torch.chunk(boxes[:, :, None], 4, 1)  # x1 shape(n,1,1)
    # 这行代码创建一个包含从0到 w-1 的整数序列的张量 r ,代表掩码的列索引,并将其扩展为形状 (1, 1, W) 的张量。
    r = torch.arange(w, device=masks.device, dtype=x1.dtype)[None, None, :]  # rows shape(1,1,w)
    # 这行代码创建一个包含从0到 h-1 的整数序列的张量 c ,代表掩码的行索引,并将其扩展为形状 (1, H, 1) 的张量。
    c = torch.arange(h, device=masks.device, dtype=x1.dtype)[None, :, None]  # cols shape(1,h,1)

    # 这行代码执行以下步骤 :
    # r >= x1 和 r < x2 :创建一个布尔张量,表示 r 中的每个元素是否在边界框的 x 坐标范围内。
    # c >= y1 和 c < y2 :创建一个布尔张量,表示 c 中的每个元素是否在边界框的 y 坐标范围内。
    # 将上述四个布尔张量相乘,得到一个形状为 (N, H, W) 的张量,其中每个元素表示掩码中对应的像素是否在边界框内。
    # 将这个布尔张量与 masks 张量相乘,得到最终的裁剪后的掩码,其中边界框外的像素被设置为0,边界框内的像素保持不变。
    return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
# 这个方法的目的是将每个对象的掩码裁剪到其对应的边界框区域内,以便在计算损失时只考虑对象内部的像素。这样可以提高损失计算的准确性,因为模型的预测只与对象相关的区域进行比较。

24.def process_mask(protos, masks_in, bboxes, shape, upsample=False): 

# 这段代码定义了一个名为 process_mask 的函数,它用于处理和转换模型输出的掩码(masks)和边界框(bboxes)。
# 定义了一个名为 process_mask 的函数,它接受以下参数。
# 1.protos :模型输出的原型掩码,形状为 (c, mh, mw) ,其中 c 是类别数量, mh 和 mw 是原型掩码的高度和宽度。
# 2.masks_in :模型输出的掩码,形状为 (n, c) ,其中 n 是边界框的数量。
# 3.bboxes :边界框的坐标,形状为 (n, 4) ,格式为 xyxy 。
# 4.shape :目标图像的形状,格式为 (height, width) 。
# 5.upsample (默认为 False ):是否对掩码进行上采样。
def process_mask(protos, masks_in, bboxes, shape, upsample=False):
    # 使用掩码头的输出将掩码应用于边界框。
    """
    Apply masks to bounding boxes using the output of the mask head.

    Args:
        protos (torch.Tensor): A tensor of shape [mask_dim, mask_h, mask_w].
        masks_in (torch.Tensor): A tensor of shape [n, mask_dim], where n is the number of masks after NMS.
        bboxes (torch.Tensor): A tensor of shape [n, 4], where n is the number of masks after NMS.
        shape (tuple): A tuple of integers representing the size of the input image in the format (h, w).
        upsample (bool): A flag to indicate whether to upsample the mask to the original image size. Default is False.

    Returns:
        (torch.Tensor): A binary mask tensor of shape [n, h, w], where n is the number of masks after NMS, and h and w
            are the height and width of the input image. The mask is applied to the bounding boxes.
    """
    # 获取原型掩码的形状。
    c, mh, mw = protos.shape  # CHW
    # 获取目标图像的形状。
    ih, iw = shape
    # 将 masks_in 与原型掩码 protos 进行矩阵乘法,然后将结果重塑为与原型掩码相同的形状。
    masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw)  # CHW
    # 计算宽度和高度的比例。
    width_ratio = mw / iw
    height_ratio = mh / ih

    # 克隆边界框。
    downsampled_bboxes = bboxes.clone()
    # 将 边界框 的 坐标 乘以相应的 比例 ,以将它们转换到 原型掩码 的尺寸。
    downsampled_bboxes[:, 0] *= width_ratio
    downsampled_bboxes[:, 2] *= width_ratio
    downsampled_bboxes[:, 3] *= height_ratio
    downsampled_bboxes[:, 1] *= height_ratio

    # 使用 crop_mask 函数将掩码裁剪到边界框的尺寸。
    masks = crop_mask(masks, downsampled_bboxes)  # CHW
    # 如果需要上采样,则使用 F.interpolate 函数将掩码上采样到目标图像的尺寸。
    if upsample:
        masks = F.interpolate(masks[None], shape, mode="bilinear", align_corners=False)[0]  # CHW
    # 返回掩码,其中大于 0 的值为 True ,否则为 False 。
    return masks.gt_(0.0)
# 这个函数的作用是将模型输出的原型掩码和边界框转换为最终的二进制掩码,这在目标检测和图像分割任务中非常有用。

25.def process_mask_native(protos, masks_in, bboxes, shape): 

# 这段代码定义了一个名为 process_mask_native 的函数,它用于处理模型输出的掩码(masks)和边界框(bboxes),并将其转换为与目标图像形状相匹配的二进制掩码。
# 定义了一个名为 process_mask_native 的函数,它接受以下参数。
# 1.protos :模型输出的原型掩码,形状为 (c, mh, mw) ,其中 c 是类别数量, mh 和 mw 是原型掩码的高度和宽度。
# 2.masks_in :模型输出的掩码,形状为 (n, c) ,其中 n 是边界框的数量。
# 3.bboxes :边界框的坐标,形状为 (n, 4) ,格式为 xyxy 。
# 4.shape :目标图像的形状,格式为 (height, width) 。
def process_mask_native(protos, masks_in, bboxes, shape):
    # 它获取 mask head 的输出,并在上采样到边界框后将其裁剪。
    """
    It takes the output of the mask head, and crops it after upsampling to the bounding boxes.

    Args:
        protos (torch.Tensor): [mask_dim, mask_h, mask_w]
        masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms
        bboxes (torch.Tensor): [n, 4], n is number of masks after nms
        shape (tuple): the size of the input image (h,w)

    Returns:
        masks (torch.Tensor): The returned masks with dimensions [h, w, n]
    """
    # 获取原型掩码的形状。
    c, mh, mw = protos.shape  # CHW
    # 将 masks_in 与原型掩码 protos 进行矩阵乘法,然后将结果重塑为与原型掩码相同的形状。
    masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw)
    # 使用 scale_masks 函数将掩码缩放到目标图像的形状。 masks[None] 增加了一个批次维度,以便与 scale_masks 函数的期望输入匹配。 [0] 用于移除额外的批次维度。
    masks = scale_masks(masks[None], shape)[0]  # CHW
    # 使用 crop_mask 函数将掩码裁剪到边界框的尺寸。
    masks = crop_mask(masks, bboxes)  # CHW
    # 返回掩码,其中大于 0 的值为 True ,否则为 False 。
    return masks.gt_(0.0)
# 这个函数的作用是将模型输出的原型掩码和边界框转换为最终的二进制掩码,这在目标检测和图像分割任务中非常有用。

26.def scale_masks(masks, shape, padding=True): 

# 这段代码定义了一个名为 scale_masks 的函数,它用于将掩码缩放到指定的图像形状。
# 定义了一个名为 scale_masks 的函数,它接受以下参数。
# 1.masks :要缩放的掩码,形状为 NCHW 。
# 2.shape :目标图像的形状,格式为 (height, width) 。
# 3.padding (默认为 True ):是否在缩放前对掩码进行填充。
def scale_masks(masks, shape, padding=True):
    # 将片段掩码重新缩放为 shape 。
    """
    Rescale segment masks to shape.

    Args:
        masks (torch.Tensor): (N, C, H, W).
        shape (tuple): Height and width.
        padding (bool): If True, assuming the boxes is based on image augmented by yolo style. If False then do regular
            rescaling.
    """
    # 获取掩码的高度和宽度。
    mh, mw = masks.shape[2:]
    # 计算缩放比例,取高度和宽度比例的最小值。
    gain = min(mh / shape[0], mw / shape[1])  # gain  = old / new
    # 计算水平和垂直方向需要填充的量。
    pad = [mw - shape[1] * gain, mh - shape[0] * gain]  # wh padding
    # 如果需要填充,则计算填充的量并除以 2,以便在两个维度上均匀分布。
    if padding:
        pad[0] /= 2
        pad[1] /= 2
    # 计算填充的顶部和左侧坐标。
    top, left = (int(pad[1]), int(pad[0])) if padding else (0, 0)  # y, x
    # 计算填充的底部和右侧坐标。
    bottom, right = (int(mh - pad[1]), int(mw - pad[0]))
    # 裁剪掩码以去除不必要的填充。
    masks = masks[..., top:bottom, left:right]

    # torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialiasing=False)
    # 在PyTorch中, F.interpolate 函数是 torch.nn.functional.interpolate 的别名,它用于对图像或特征图进行上采样(放大)或下采样(缩小)。这个函数非常灵活,支持多种插值方法,可以用于深度学习模型中的特征图尺寸调整。
    # 参数解释 :
    # input :要进行插值的输入张量,通常是4维的,形状为 (batch_size, channels, height, width) 。
    # size :目标输出尺寸,可以是整数或者元组。如果为 None ,则使用 scale_factor 来计算输出尺寸。
    # scale_factor :缩放因子,可以是浮点数或者元组。如果为 None ,则使用 size 参数。
    # mode :插值模式,常用的有 :
    # 'nearest' :最近邻插值。
    # 'linear' :线性插值(仅适用于1维数据)。
    # 'bilinear' :双线性插值(适用于2维数据,如图像)。
    # 'bicubic' :双三次插值(适用于2维数据,比双线性更平滑)。
    # 'trilinear' :三线性插值(适用于3维数据)。
    # align_corners :在某些插值模式下,这个参数控制角落对齐的行为。如果设置为 None ,则对于不同的插值模式有不同的默认行为。
    # recompute_scale_factor :这个参数用于重新计算缩放因子,通常在 align_corners=None 时使用。
    # antialiasing :在下采样时是否应用反锯齿,以减少混叠效应。

    # 使用双线性插值将掩码缩放到目标图像的形状。
    masks = F.interpolate(masks, shape, mode="bilinear", align_corners=False)  # NCHW
    # 返回缩放后的掩码。
    return masks
# 这个函数的作用是将掩码从原始图像形状缩放到目标图像形状,同时考虑了填充和缩放比例。这对于在不同分辨率的图像之间转换掩码非常有用。

27.def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None, normalize=False, padding=True): 

# 这段代码定义了一个名为 scale_coords 的函数,它用于将坐标从一个图像尺寸缩放到另一个图像尺寸,并提供了可选的归一化和填充功能。
# 定义了一个名为 scale_coords 的函数,它接受以下参数。
# 1.img1_shape :变换后图像的形状,格式为 (height, width) 。
# 2.coords :要缩放的坐标,形状为 (nx2) ,其中 n 是坐标点的数量,2 表示 (x, y) 坐标。
# 3.img0_shape :原始图像的形状,格式为 (height, width) 。
# 4.ratio_pad (默认为 None ):一个包含缩放比例和填充的元组,格式为 (gain, (pad_x, pad_y)) 。
# 5.normalize (默认为 False ):是否将坐标归一化到 [0, 1] 范围内。
# 6.padding (默认为 True ):是否应用填充。
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None, normalize=False, padding=True):
    # 将片段坐标 (xy) 从 img1_shape 重新缩放为 img0_shape。
    """
    Rescale segment coordinates (xy) from img1_shape to img0_shape.

    Args:
        img1_shape (tuple): The shape of the image that the coords are from.
        coords (torch.Tensor): the coords to be scaled of shape n,2.
        img0_shape (tuple): the shape of the image that the segmentation is being applied to.
        ratio_pad (tuple): the ratio of the image size to the padded image size.
        normalize (bool): If True, the coordinates will be normalized to the range [0, 1]. Defaults to False.
        padding (bool): If True, assuming the boxes is based on image augmented by yolo style. If False then do regular
            rescaling.

    Returns:
        coords (torch.Tensor): The scaled coordinates.
    """
    #  如果没有提供 ratio_pad ,则根据 img0_shape 和 img1_shape 计算缩放比例和填充:
    if ratio_pad is None:  # calculate from img0_shape
        # 计算缩放比例,取高度和宽度比例的最小值。
        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new
        # 计算水平和垂直方向的填充。
        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2  # wh padding
    # 如果提供了 ratio_pad ,则直接使用提供的缩放比例和填充。
    else:
        gain = ratio_pad[0][0]
        pad = ratio_pad[1]

    # 如果需要填充,则将坐标减去填充值。
    if padding:
        coords[..., 0] -= pad[0]  # x padding
        coords[..., 1] -= pad[1]  # y padding
    # 将坐标按缩放比例进行缩放。
    coords[..., 0] /= gain
    coords[..., 1] /= gain
    # 使用 clip_coords 函数将缩放后的坐标剪辑到原始图像的形状内。
    coords = clip_coords(coords, img0_shape)
    # 如果需要归一化,则将坐标除以图像的宽度和高度,使其归一化到 [0, 1] 范围内。
    if normalize:
        coords[..., 0] /= img0_shape[1]  # width
        coords[..., 1] /= img0_shape[0]  # height
    # 返回缩放和归一化后的坐标。
    return coords
# 这个函数的作用是将坐标从变换后的图像尺寸调整回原始图像尺寸,同时考虑了缩放、填充和归一化。这对于在不同分辨率的图像之间转换坐标非常有用。

28.def regularize_rboxes(rboxes): 

# 这段代码定义了一个名为 regularize_rboxes 的函数,它用于标准化旋转边界框(rboxes)的表示。
# 定义了一个名为 regularize_rboxes 的函数,它接受一个参数。
# 1.rboxes :这是一个包含旋转边界框坐标的张量,格式为 xywhr 。
def regularize_rboxes(rboxes):
    # 在 [0, pi/2] 范围内对旋转框进行正则化。
    """
    Regularize rotated boxes in range [0, pi/2].

    Args:
        rboxes (torch.Tensor): Input boxes of shape(N, 5) in xywhr format.

    Returns:
        (torch.Tensor): The regularized boxes.
    """

    # torch.unbind(input, dim=None) -> Sequence[Tensor]
    # torch.unbind 是 PyTorch 中的一个函数,用于将一个多维张量(tensor)分解为多个张量。这个函数通常用于处理由 torch.cat (张量拼接)产生的结果,或者当你有一个多维张量并希望将其分解为多个子张量时。
    # 参数 :
    # input :要解绑的多维张量。
    # dim :要解绑的维度。默认为 None ,如果不指定, torch.unbind 会将输入张量分解为一维张量。
    # 返回值 :
    # 返回一个张量的序列(sequence),这些张量是输入张量沿 dim 维度解绑后的结果。
    # 功能 :
    # torch.unbind 函数沿着指定的维度将输入张量分解为多个张量。如果输入张量是一维的,那么 dim 参数可以省略, unbind 会将其分解为单个元素的张量。

    # 将 rboxes 张量按最后一个维度拆分为 x 、 y 、 w 、 h  和  t ,分别表示中心点的  x  和  y  坐标、宽度、高度和旋转角度。
    x, y, w, h, t = rboxes.unbind(dim=-1)
    # Swap edge and angle if h >= w
    # 如果宽度 w 大于高度 h ,则 w_ 为 w ,否则为 h 。这一步确保了宽度总是大于或等于高度。
    w_ = torch.where(w > h, w, h)
    # 如果宽度 w 大于高度 h ,则 h_ 为 h ,否则为 w 。这一步确保了高度总是小于或等于宽度。
    h_ = torch.where(w > h, h, w)
    # 如果宽度 w 大于高度 h ,则旋转角度 t 保持不变;否则,旋转角度 t 加上 π/2 ,然后对 π 取模,确保旋转角度在 0 到 π 的范围内。
    t = torch.where(w > h, t, t + math.pi / 2) % math.pi
    # 将标准化后的 x 、 y 、 w_ 、 h_ 和 t 重新组合成一个张量,并返回。
    return torch.stack([x, y, w_, h_, t], dim=-1)  # regularized boxes
# 这个函数的作用是标准化旋转边界框的表示,确保宽度总是大于或等于高度,并且旋转角度在 0 到 π 的范围内。这对于在目标检测和图像处理任务中处理旋转边界框非常有用。

29.def masks2segments(masks, strategy="largest"): 

# 这段代码定义了一个名为 masks2segments 的函数,它将二进制掩码转换为线段(segments)。
# 定义了一个名为 masks2segments 的函数,它接受以下参数。
# 1.masks :一个包含二进制掩码的张量,每个掩码代表一个对象的轮廓。
# 2.strategy (默认为 "largest" ):一个字符串,指定如何处理多个轮廓。可以是 "concat" (连接所有轮廓)或 "largest" (选择最大的轮廓)。
def masks2segments(masks, strategy="largest"):
    # 它接受一个 mask(n,h,w) 列表并返回一个 fragment(n,xy) 列表。
    """
    It takes a list of masks(n,h,w) and returns a list of segments(n,xy).

    Args:
        masks (torch.Tensor): the output of the model, which is a tensor of shape (batch_size, 160, 160)
        strategy (str): 'concat' or 'largest'. Defaults to largest

    Returns:
        segments (List): list of segment masks
    """
    # 初始化一个空列表 segments ,用于存储转换后的线段。
    segments = []
    # 遍历 masks 中的每个掩码。 masks.int() 将掩码转换为整数类型。 cpu() 将掩码移动到 CPU(如果它在 GPU 上)。 numpy() 将掩码转换为 NumPy 数组。 astype("uint8") 将掩码转换为无符号 8 位整数类型。
    for x in masks.int().cpu().numpy().astype("uint8"):

        # cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
        # cv2.findContours 是 OpenCV 中的一个函数,用于在二值图像中查找轮廓。
        # 参数:
        # image :输入图像,通常是一个二值图像。
        # mode :轮廓检索模式。它可以是以下值之一:
        # cv2.RETR_EXTERNAL :只检索最外层的轮廓。
        # cv2.RETR_LIST :检索所有轮廓,并以列表形式返回。
        # cv2.RETR_CCOMP :检索所有轮廓,并以树状结构(contour hierarchy)形式返回。此时,轮廓被分为不同的层级。
        # cv2.RETR_TREE :检索所有轮廓,并以完整的树状结构形式返回。
        # method :轮廓近似方法。它可以是以下值之一:
        # cv2.CHAIN_APPROX_NONE :存储轮廓上的所有点。
        # cv2.CHAIN_APPROX_SIMPLE :压缩水平、垂直和对角方向的轮廓点。
        # cv2.CHAIN_APPROX_TC89_L1 和 cv2.CHAIN_APPROX_TC89_KCOS :使用L1和KOS链逼近算法。
        # contours (可选) :输出参数,返回检测到的轮廓。
        # hierarchy (可选) :输出参数,返回轮廓的层次结构。它是一个多通道多维数组,其中每个轮廓由三个数组组成:[next, previous, first_contour]。其中,“ next ”是下一个轮廓的索引,“ previous ”是上一个轮廓的索引,“ first_contour ”是起始轮廓的索引。
        # offset (可选) :偏移量,指定从哪里开始搜索轮廓。例如,如果指定了(10, 10),则从图像的(10, 10)位置开始搜索轮廓。
        # 返回值 :
        # 如果指定了 contours 参数,则此函数返回被检测到的第一个轮廓的索引;否则,不返回任何内容。

        # 使用 OpenCV 的 findContours 函数找到掩码中的轮廓。 cv2.RETR_EXTERNAL 只检索外部轮廓, cv2.CHAIN_APPROX_SIMPLE 压缩水平、垂直和对角线段,只保留它们的端点。
        c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
        #  如果找到了轮廓。
        if c:
            # 如果 strategy 是 "concat" ,则将所有轮廓连接起来。
            if strategy == "concat":  # concatenate all segments
                c = np.concatenate([x.reshape(-1, 2) for x in c])
            # 如果 strategy 是 "largest" ,则选择最大的轮廓(即点数最多的轮廓)。
            elif strategy == "largest":  # select largest segment

                # np.array(object, dtype=None, copy=True, ndmin=0, order=None, subok=False, view_ok=False)
                # np.array 是 NumPy 库中的一个函数,用于创建一个 NumPy 数组。这个函数非常灵活,可以接受多种类型的输入,包括列表、元组、另一个 NumPy 数组,甚至是其他迭代器,并将它们转换为一个 NumPy 数组。
                # 参数 :
                # object :要转换为数组的对象。可以是列表、元组、另一个 NumPy 数组,或者是任何其他迭代器。
                # dtype :数组中元素的数据类型。如果不指定,NumPy 将自动推断数据类型。
                # copy :布尔值,指示是否需要复制输入数据。如果为 True ,则总是复制输入数据;如果为 False ,则只有在需要时才复制。
                # ndmin :数组的最小维度。这可以用来确保结果至少有这么多的维度。
                # order :'C' 或 'F',指定数组的内存布局。'C' 表示行主序(C-style),'F' 表示列主序(Fortran-style)。如果不指定,由 NumPy 自动决定。
                # subok :布尔值,指示是否允许返回子类数组。
                # view_ok :布尔值,指示是否允许返回输入数据的视图,而不是副本。
                # 返回值 :
                # 返回一个 NumPy 数组。

                # 这行代码是在处理轮廓信息时使用的,它的目的是从多个轮廓中选择最大的一个轮廓。
                # c 是一个列表,其中包含了多个轮廓,每个轮廓都是一个数组,包含一系列点的坐标。
                # np.array([len(x) for x in c]) :这是一个列表推导式,它为列表 c 中的每个轮廓计算长度(即点的数量),并返回一个包含这些长度的 NumPy 数组。
                # argmax() :这是一个 NumPy 函数,它返回数组中最大值的索引。在这里,它返回点数最多的轮廓的索引。
                # c[np.array([len(x) for x in c]).argmax()] :这个表达式使用 argmax() 函数找到点数最多的轮廓,并返回该轮廓。
                # np.array(...) :将轮廓转换为 NumPy 数组。
                # reshape(-1, 2) :将轮廓数组重塑为 (n, 2) 的形状,其中 n 是轮廓中点的数量。 -1 表示自动计算行数, 2 表示每个点有两个坐标(x 和 y)。
                # 综上所述,这行代码的作用是从多个轮廓中选择点数最多的轮廓,并将其重塑为 (n, 2) 的形状,以便于后续处理。
                c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2)
        # 如果没有找到轮廓,则创建一个空的零数组。
        else:
            c = np.zeros((0, 2))  # no segments found
        
        # numpy.ndarray.astype(dtype)
        # astype() 是 NumPy 中的一个方法,它用于将 NumPy 数组的数据类型转换为另一种数据类型。这个方法返回一个新数组,其数据类型被转换为指定的类型。
        # 参数 :
        # dtype :目标数据类型,可以是数据类型的名称(如 'int32' 、 'float64' 等)或数据类型的实际类型(如 np.int32 、 np.float64 )。
        # 返回值 :
        # 返回一个新的数组,其数据类型已经转换为 dtype 指定的类型。
        # 功能 :
        # astype() 方法用于改变数组中元素的数据类型。这在数据处理中非常有用,特别是当你需要确保数据类型一致性或优化内存使用时。

        # 将转换后的轮廓添加到 segments 列表中,并转换为浮点数类型。
        segments.append(c.astype("float32"))
    # 返回包含所有线段的列表。
    return segments
# 这个函数的作用是将二进制掩码转换为线段,这在图像处理和计算机视觉任务中非常有用,尤其是在需要从掩码中提取轮廓信息时。

30.def convert_torch2numpy_batch(batch: torch.Tensor) -> np.ndarray: 

# 这段代码定义了一个名为 convert_torch2numpy_batch 的函数,它将 PyTorch 张量批量转换为 NumPy 数组,通常用于将模型输出的张量(例如图像数据)从 PyTorch 格式转换为 NumPy 格式,以便进行进一步处理或可视化。
# 定义了一个名为 convert_torch2numpy_batch 的函数,它接受一个参数。
# 1.batch :这是一个 PyTorch 张量,返回类型为 NumPy 数组。
def convert_torch2numpy_batch(batch: torch.Tensor) -> np.ndarray:
    # 将一批 FP32 torch 张量 (0.0-1.0) 转换为 NumPy uint8 数组 (0-255),从 BCHW 更改为 BHWC 布局。
    """
    Convert a batch of FP32 torch tensors (0.0-1.0) to a NumPy uint8 array (0-255), changing from BCHW to BHWC layout.

    Args:
        batch (torch.Tensor): Input tensor batch of shape (Batch, Channels, Height, Width) and dtype torch.float32.

    Returns:
        (np.ndarray): Output NumPy array batch of shape (Batch, Height, Width, Channels) and dtype uint8.
    """
    # batch.permute(0, 2, 3, 1) :使用 permute 方法重新排列张量的维度。这通常用于将张量的通道维度(通常是第一个维度)移动到最后,以符合图像数据的常规格式(高度、宽度、通道)。这里的 0, 2, 3, 1 表示批量 维度 、 高度 、 宽度 和 通道 的顺序。
    # .contiguous() :确保张量在内存中是连续的,这对于后续的操作(如复制到 NumPy 数组)是必要的。
    # * 255 :将张量的值乘以 255,这通常用于将归一化的图像数据(值在 [0, 1] 范围内)转换为非归一化的数据(值在 [0, 255] 范围内)。
    # .clamp(0, 255) :使用 clamp 方法将张量的值限制在 [0, 255] 范围内,确保所有像素值都在有效范围内。
    # .to(torch.uint8) :将张量的数据类型转换为 uint8 ,这是图像数据常用的数据类型。
    # .cpu() :如果张量在 GPU 上,使用 cpu() 方法将其移动到 CPU。
    # .numpy() :将 PyTorch 张量转换为 NumPy 数组。
    # 返回转换后的 NumPy 数组。
    return (batch.permute(0, 2, 3, 1).contiguous() * 255).clamp(0, 255).to(torch.uint8).cpu().numpy()
# 这个函数的作用是将 PyTorch 张量批量转换为 NumPy 数组,这在图像处理和计算机视觉任务中非常有用,尤其是在需要将模型输出的数据转换为图像格式时。

31.def clean_str(s): 

# 这段代码定义了一个名为 clean_str 的函数,它用于清理字符串中的特定字符,将它们替换为下划线 _ 。
def clean_str(s):
    # 通过使用“_”字符替换特殊字符来清理字符串。
    """
    Cleans a string by replacing special characters with '_' character.

    Args:
        s (str): a string needing special characters replaced

    Returns:
        (str): a string with special characters replaced by an underscore _
    """
    # 使用 Python 的 re 模块中的 sub 函数来替换字符串中的字符。
    # pattern 参数定义了要被替换的字符集合,这里包括了多种符号和特殊字符。
    # repl 参数定义了替换后的字符,这里是下划线 _ 。
    # string 参数是要被处理的原始字符串。
    # 返回替换后的字符串。
    return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
# 这个函数的作用是去除字符串中的特定字符,这些字符可能在某些应用场景中不被允许或需要被忽略,例如在处理文件名、标签或其他文本数据时。

# Python 的 re 模块提供了正则表达式的支持,用于执行字符串的搜索、替换、分割和匹配等操作。正则表达式是一种强大的文本处理工具,它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。以下是 re 模块的一些常用功能 :
# 搜索(Search):
# re.search(pattern, string) :在字符串中搜索第一个与正则表达式匹配的结果。
# 匹配(Match):
# re.match(pattern, string) :从字符串的开始位置匹配正则表达式,如果字符串开始部分匹配成功则返回匹配对象,否则返回 None 。
# 查找所有匹配(Find All):
# re.findall(pattern, string) :在字符串中查找所有与正则表达式匹配的结果,并返回一个包含所有匹配结果的列表。
# 替换(Substitute):
# re.sub(pattern, repl, string) :在字符串中替换与正则表达式匹配的子串, repl 参数可以是替换字符串或一个函数。
# 分割(Split):
# re.split(pattern, string) :根据正则表达式匹配的结果分割字符串。
# 编译正则表达式(Compile):
# re.compile(pattern) :编译正则表达式,返回一个正则表达式对象,可以用于后续的匹配操作。
# 贪婪与非贪婪匹配:
# 正则表达式默认是贪婪的,即尽可能多地匹配字符。使用 ? 可以使量词变为非贪婪模式,即尽可能少地匹配字符。
# 特殊字符和转义:
# 正则表达式中有特殊含义的字符(如 . 、 * 、 ? 等)需要使用 \ 进行转义,以便匹配字符本身。
# 分组(Group):
# 使用圆括号 () 可以创建捕获组,以便从匹配结果中提取子串。
# 断言(Assertions):
# re 模块支持正向和负向断言,用于指定特定条件的匹配。
# re 模块的应用非常广泛,包括但不限于文本处理、数据清洗、日志分析、网络爬虫等场景。通过正则表达式,可以高效地处理复杂的字符串匹配和提取任务。


http://www.kler.cn/a/419095.html

相关文章:

  • 【Linux】gdb / cgdb 调试 + 进度条
  • Nextjs 前端设置正向代理 解决 跨域问题
  • 「Mac畅玩鸿蒙与硬件35」UI互动应用篇12 - 简易日历
  • 【python】图像、音频、视频等文件数据采集
  • electron-vite_14窗口默认全屏铺满
  • 【力扣】541.反转字符串2
  • Java知识及热点面试题总结(二)
  • 远程桌面协助控制软件 RustDesk v1.3.3 多语言中文版
  • 精准用户获取与私域流量运营:多商户链动 2+1 模式商城小程序的赋能策略
  • Linux内核编译流程(Ubuntu24.04+Linux Kernel 6.8.12)
  • spring boot 调用C#封装的DLL文件中的函数
  • 力扣3372.连接两棵树后最大目标节点数目I
  • 内网使用docker搭建librespeed测速网站
  • 挑战用React封装100个组件【004】
  • UaGateway:实现OPC DA和OPC UA的高效转换
  • FFmpeg一些常用的命令
  • ElasticSearch的学习
  • JAVA中HashMap、TreeMap、LinkedHashMap 的用法与注意事项
  • 简单搭建qiankun的主应用和子应用并且用Docker进行服务器部署
  • AI高中数学教学视频生成技术:利用通义千问、MathGPT、视频多模态大模型,语音大模型,将4个模型融合 ,生成高中数学教学视频,并给出实施方案。
  • MySQL索引与分区:性能优化的关键
  • openbmc dbus架构简析(二)
  • DDR3与MIG IP核详解(一)
  • ESP32-S3模组上跑通ES8388(12)
  • SpringBoot集成swagger3
  • 【Docker】部署nginx