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

YOLOv9-0.1部分代码阅读笔记-augmentations.py

augmentations.py

utils\augmentations.py

目录

augmentations.py

1.所需的库和模块

2.class Albumentations: 

3.def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD, inplace=False): 

4.def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): 

5.def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5): 

6.def hist_equalize(im, clahe=True, bgr=False): 

7.def replicate(im, labels): 

8.def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32): 

9.def random_perspective(im, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)): 

10.def copy_paste(im, labels, segments, p=0.5): 

11.def cutout(im, labels, p=0.5): 

12.def mixup(im, labels, im2, labels2): 

13.def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16): 

14.def classify_albumentations(augment=True, size=224, scale=(0.08, 1.0), ratio=(0.75, 1.0 / 0.75), hflip=0.5, vflip=0.0, jitter=0.4, mean=IMAGENET_MEAN, std=IMAGENET_STD, auto_aug=False): 

15.def classify_transforms(size=224): 

16.class LetterBox: 

17.class CenterCrop: 

18.class ToTensor: 


1.所需的库和模块

import math
import random

import cv2
import numpy as np
import torch
import torchvision.transforms as T
import torchvision.transforms.functional as TF

from utils.general import LOGGER, check_version, colorstr, resample_segments, segment2box, xywhn2xyxy
from utils.metrics import bbox_ioa

IMAGENET_MEAN = 0.485, 0.456, 0.406  # RGB mean
IMAGENET_STD = 0.229, 0.224, 0.225  # RGB standard deviation

2.class Albumentations: 

# 这段代码定义了一个名为 Albumentations 的类, ,它用于图像增强,特别是在目标检测任务中。这个类使用了 albumentations 库来应用一系列的图像变换,以增加数据集的多样性并提高模型的泛化能力。
# 定义了一个名为 Albumentations 的类。
class Albumentations:
    # YOLOv5 Albumentations class (optional, only used if package is installed)    YOLOv5 Albumentations 类(可选,仅在安装了包时使用)。
    # 这段代码是 Albumentations 类的构造函数 __init__ 的实现部分。它负责初始化类实例,并设置图像增强的变换。
    # 这是类的构造函数,它接受一个参数。
    # 1.size :默认值为640。这个参数用于定义图像变换后的目标尺寸。
    def __init__(self, size=640):
        # 初始化一个变量 self.transform ,用于存储图像变换的组合。
        self.transform = None
        # 定义一个前缀字符串,用于日志信息的输出, colorstr 函数用于给字符串添加颜色的自定义函数。
        # def colorstr(*input): -> 构建并返回最终的字符串。它首先通过列表推导式和 join 函数将所有颜色和样式的 ANSI 代码连接起来,然后加上要着色的字符串 string ,最后加上 colors['end'] 来重置样式,确保之后的输出不会受到颜色代码的影响。 -> return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
        prefix = colorstr('albumentations: ')
        # 开始一个try块,用于捕获和处理可能发生的异常。
        try:
            # 尝试导入 albumentations 库,并将其别名为 A 。
            import albumentations as A
            # 检查 albumentations 库的版本是否为1.0.3或更高版本,如果不是,则抛出异常。这个函数是一个自定义函数,用于确保依赖库的版本符合要求。
            # def check_version(current='0.0.0', minimum='0.0.0', name='version ', pinned=False, hard=False, verbose=False): -> 用于检查当前安装的软件包版本是否满足指定的最低版本要求。函数返回 result ,即版本检查的结果。 -> return result
            check_version(A.__version__, '1.0.3', hard=True)  # version requirement

            # 定义一个列表 T ,包含一系列的图像变换操作。这些操作包括随机裁剪、模糊、中值滤波、灰度转换、对比度增强等。
            T = [
                # A.RandomResizedCrop :随机裁剪并调整图像大小。
                A.RandomResizedCrop(height=size, width=size, scale=(0.8, 1.0), ratio=(0.9, 1.11), p=0.0),
                # A.Blur :应用模糊效果。
                A.Blur(p=0.01),
                # A.MedianBlur :应用中值滤波。
                A.MedianBlur(p=0.01),
                # A.ToGray :将图像转换为灰度。
                A.ToGray(p=0.01),
                # A.CLAHE :对比度受限的自适应直方图均衡化。
                A.CLAHE(p=0.01),
                # A.RandomBrightnessContrast :随机调整亮度和对比度。
                A.RandomBrightnessContrast(p=0.0),
                # A.RandomGamma :随机调整图像的伽马值。
                A.RandomGamma(p=0.0),
                # A.ImageCompression :压缩图像。
                A.ImageCompression(quality_lower=75, p=0.0)]  # transforms
            # 使用 A.Compose 将列表 T 中的所有变换操作组合起来,并设置边界框参数,这里使用的是YOLO格式。
            self.transform = A.Compose(T, bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))

            # 使用日志记录器输出当前应用的变换操作。这里使用列表推导式和字符串操作来格式化日志信息。
            LOGGER.info(prefix + ', '.join(f'{x}'.replace('always_apply=False, ', '') for x in T if x.p))
        # 如果 albumentations 库没有安装,捕获 ImportError 异常。
        except ImportError:  # package not installed, skip
            pass
        # 捕获其他所有异常,并使用日志记录器输出异常信息。
        except Exception as e:
            LOGGER.info(f'{prefix}{e}')
    # 这个构造函数的作用是初始化 Albumentations 类的实例,并根据提供的参数设置图像增强的变换。如果 albumentations 库没有安装或者版本不符合要求,它会捕获异常并记录日志信息。这样,即使在库不可用的情况下,程序也可以继续运行,而不会因为缺少依赖库而崩溃。

    # 这段代码是 Albumentations 类中的 __call__ 方法的实现,它是类的实例被调用时执行的方法。
    # 定义了 __call__ 方法,它使得类的实例可以像函数一样被调用,这个方法接受三个参数。
    # 1.im :图像。
    # 2.labels :标签。
    # 3.p :一个概率值,默认为1.0。
    def __call__(self, im, labels, p=1.0):
        # 检查是否已经定义了 self.transform (即是否成功加载了图像增强的变换),并且生成一个随机数与概率 p 比较。如果随机数小于 p ,则执行增强操作。
        if self.transform and random.random() < p:
            # 如果条件满足,将图像 im 和对应的边界框 labels[:, 1:] (假设边界框的坐标存储在 labels 的第2列及以后的列中)以及类别标签 labels[:, 0] (假设类别标签存储在 labels 的第一列)传递给 self.transform 进行变换。
            new = self.transform(image=im, bboxes=labels[:, 1:], class_labels=labels[:, 0])  # transformed
            # 变换完成后,从结果中提取新的图像 new['image']  和新的边界框及类别标签。这里使用列表推导式和 zip 函数将新的类别标签 new['class_labels'] 和新的边界框 new['bboxes'] 组合起来,形成新的 labels 数组。 np.array 用于将这个列表转换为NumPy数组。
            im, labels = new['image'], np.array([[c, *b] for c, b in zip(new['class_labels'], new['bboxes'])])
        # 返回变换后的图像和新的标签。
        return im, labels
    # 这个方法的作用是在调用类的实例时,根据给定的概率  p  随机决定是否对输入的图像和标签进行增强变换。如果进行变换,它会应用之前在构造函数中定义的一系列图像增强操作,并返回变换后的图像和更新后的标签。这种方式使得图像增强操作可以很容易地集成到数据预处理流程中。
# 这个类可以被用来增强图像数据,特别是在训练目标检测模型时。通过随机应用这些变换,可以模拟不同的图像条件,帮助模型学习更加鲁棒的特征。

3.def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD, inplace=False): 

# 这段代码定义了一个名为 normalize 的函数,它用于对图像数据进行标准化处理。标准化是深度学习中常用的数据预处理步骤,目的是将数据的分布调整为均值为0,标准差为1的分布,以提高模型训练的稳定性和收敛速度。
# 这行定义了一个名为 normalize 的函数,它接受三个参数。
# 1.x :要进行标准化处理的图像数据,通常是一个张量(Tensor),格式为BCHW( Batch, Channel, Height, Width )。
# 2.mean :用于标准化的均值,这里默认为 IMAGENET_MEAN 。在ImageNet数据集中,通常使用的均值是 [0.485, 0.456, 0.406] 。
# 3.std :用于标准化的标准差,这里默认为 IMAGENET_STD 。在ImageNet数据集中,通常使用的标准差是 [0.229, 0.224, 0.225] 。
# 4.inplace :一个布尔值,指示是否在原地(in-place)修改输入张量 x 。如果设置为 True ,则不会返回新的张量,而是直接修改输入张量。
def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD, inplace=False):
    # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = (x - mean) / std    根据 BCHW 格式的 ImageNet 统计数据对 RGB 图像 x 进行非规范化,即 = (x - 平均值) / 标准差。

    # torchvision.transforms.functional.normalize(tensor, mean, std, inplace=False)
    # torchvision.transforms.functional.normalize 是 PyTorch 的一个函数,它用于对图像数据进行标准化处理。这个函数是 torchvision.transforms 模块中的一个函数,用于对图像进行数据中心化(data normalization)的操作。
    # 参数说明 :
    # tensor :要进行标准化处理的图像数据,通常是一个 PyTorch 张量(Tensor),格式为 (C x H x W) ,其中 C 是通道数, H 是高度, W 是宽度。
    # mean :用于数据中心化的均值,可以是一个标量或一个长度为图像通道数的列表/元组。如果图像是灰度图像,只需要提供一个标量;如果图像是彩色图像,需要提供每个通道的均值。
    # std :用于数据中心化的标准差,可以是一个标量或一个长度为图像通道数的列表/元组。如果图像是灰度图像,只需要提供一个标量;如果图像是彩色图像,需要提供每个通道的标准差。
    # inplace :一个布尔值,指示是否在原地(in-place)修改输入张量 tensor 。如果设置为 True ,则会直接修改输入张量,否则会返回一个新的张量。
    # 返回值 :
    # 函数返回标准化后的图像数据。如果 inplace 参数为 True ,则不返回任何值,而是直接修改输入张量。
    # 功能说明 :
    # 函数体中,执行标准化操作,即对输入张量 tensor 中的每个像素值减去均值 mean ,然后除以标准差 std 。
    # 这个函数通常与 torchvision.transforms.ToTensor() 一起使用,后者将 PIL 图像或 NumPy 数组转换为 PyTorch 张量,并自动将像素值归一化到 [0, 1] 。 normalize 函数则进一步将数据归一化到均值为0、标准差为1的范围内,这是许多深度学习模型训练中常用的预处理步骤。

    # 调用了 TF.normalize 函数,它执行实际的标准化操作。这个函数将输入张量 x 中的每个像素值减去均值 mean ,然后除以标准差 std 。
    # 函数返回标准化后的图像数据。如果 inplace 参数为 True ,则不返回任何值,而是直接修改输入张量 x 。
    return TF.normalize(x, mean, std, inplace=inplace)
# 这个函数可以被用于深度学习模型的数据预处理阶段,以确保输入数据符合模型训练的要求。

4.def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): 

# 这段代码定义了一个名为 denormalize 的函数,它用于对已经标准化(归一化)的图像数据进行反标准化(反归一化)处理。反标准化是将数据从均值为0、标准差为1的分布恢复到原始分布的过程。
# 这行定义了一个名为 denormalize 的函数,它接受三个参数。
# 1.x :要进行反标准化处理的图像数据,通常是一个张量(Tensor),格式为 (B x C x H x W) 或 (C x H x W) ,其中 B 是批次大小, C 是通道数, H 是高度, W 是宽度。
# 2.mean :用于反标准化的均值,可以是一个标量或一个长度为图像通道数的列表/元组。这里默认为 IMAGENET_MEAN ,通常在ImageNet数据集中使用的均值是 [0.485, 0.456, 0.406] 。
# 3.std :用于反标准化的标准差,可以是一个标量或一个长度为图像通道数的列表/元组。这里默认为 IMAGENET_STD ,通常在ImageNet数据集中使用的标准差是 [0.229, 0.224, 0.225] 。
def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD):
    # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = x * std + mean    根据 BCHW 格式的 ImageNet 统计数据对 RGB 图像 x 进行非规范化,即 = x * std + 平均值。
    # 开始一个循环,循环变量 i 从0到2,对应于图像的三个颜色通道(红色、绿色、蓝色)。
    for i in range(3):
        # 在循环体内,对每个通道进行反标准化处理。 x[:, i] 表示选择 x 的第 i 个通道的所有数据。然后,将每个通道的数据乘以对应的标准差 std[i] ,再加上对应的均值 mean[i] ,以恢复到原始的数据范围。
        x[:, i] = x[:, i] * std[i] + mean[i]
    # 回反标准化后的图像数据 x 。
    return x
# 这个 denormalize 函数通过对标准化的图像数据进行反操作,即将每个通道的数据乘以标准差并加上均值,来恢复图像的原始像素值。这对于将模型的输出转换回可解释的图像数据或者进行可视化是非常有用的。

5.def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5): 

# 这段代码定义了一个名为 augment_hsv 的函数,它用于在HSV颜色空间中对图像进行增强。
# 这行定义了一个名为 augment_hsv 的函数,它接受四个参数。
# 1.im :要进行增强的图像。
# 2.hgain :色调增益。
# 3.sgain :饱和度增益。
# 4.vgain :明度增益。这些增益参数控制增强的程度,默认值均为0.5。
def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5):
    # HSV color-space augmentation    HSV 颜色空间增强。
    # 如果任何一个增益参数不为0,则执行增强操作。
    if hgain or sgain or vgain:

        # numpy.random.uniform(low=0.0, high=1.0, size=None)
        # np.random.uniform() 是 NumPy 库中的一个函数,用于生成在指定范围内均匀分布的随机数。
        # 参数说明 :
        # low :随机数生成的下限,默认为0.0。
        # high :随机数生成的上限,默认为1.0。
        # size :输出数组的形状。如果为 None ,则返回一个单一的随机数。如果指定了 size ,则返回一个随机数数组。
        # 返回值 :
        # 返回一个或一组随机数,取决于 size 参数的设置。
        # 功能说明 :
        # np.random.uniform() 函数从指定的区间 [low, high) 中生成均匀分布的随机数。这意味着随机数可以取到 low 的值,但是取不到 high 的值。

        # 生成一个包含三个随机值的数组 r ,这些值在 [-1, 1] 范围内,然后分别乘以对应的增益参数,并加1。这样可以得到每个颜色通道的随机增益因子。
        r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1  # random gains

        # cv2.cvtColor(src, code, dstCn=None)
        # cv2.cvtColor() 是 OpenCV 库中的一个函数,用于转换图像的颜色空间。这个函数可以将图像从一个颜色空间转换到另一个颜色空间,例如从BGR转换到RGB,或者从RGB转换到HSV等。
        # 参数说明 :
        # src :输入图像。
        # code :转换代码,指定从源颜色空间到目标颜色空间的转换类型。这个参数是一个特定的转换标志,例如 cv2.COLOR_BGR2RGB 或 cv2.COLOR_RGB2GRAY 。
        # dstCn :目标图像的通道数。如果为0,则通道数会根据转换代码自动确定。如果提供了这个参数,它必须与转换代码兼容。
        # 功能说明 :
        # cv2.cvtColor() 函数根据提供的转换代码 code 将输入图像 src 从一种颜色空间转换到另一种颜色空间。
        # 返回值 :
        # 返回转换后的图像。
        # 注意事项 :
        # 确保输入图像 src 是有效的,且已经正确加载。
        # 转换代码 code 必须与源图像和目标颜色空间兼容。
        # dstCn 参数通常不需要指定,除非你需要指定目标图像的通道数,或者在某些特定的转换中,如从灰度图像转换到多通道图像时。
        # cv2.cvtColor() 是图像处理中常用的函数之一,它在进行图像分析和计算机视觉任务时非常有用,尤其是在需要不同颜色空间表示的算法中。

        # 将输入图像 im 从BGR颜色空间转换到HSV颜色空间,然后分割成三个通道:色调(hue)、饱和度(sat)和明度(val)。
        hue, sat, val = cv2.split(cv2.cvtColor(im, cv2.COLOR_BGR2HSV))
        # 获取输入图像 im 的数据类型,通常为 uint8 。
        dtype = im.dtype  # uint8

        # 创建一个从0到255的数组 x ,用于构建查找表(LUT)。
        x = np.arange(0, 256, dtype=r.dtype)
        # 计算色调的查找表。色调值乘以增益因子 r[0] 并取模180,以确保值在HSV色调的有效范围内。
        lut_hue = ((x * r[0]) % 180).astype(dtype)
        # 计算饱和度的查找表。饱和度值乘以增益因子 r[1] ,然后使用 np.clip 函数确保值在0到255的范围内。
        lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
        # 计算明度的查找表。明度值乘以增益因子 r[2] ,然后使用 np.clip 函数确保值在0到255的范围内。
        lut_val = np.clip(x * r[2], 0, 255).astype(dtype)

        # cv2.LUT(src, lut[, dst])
        # cv2.LUT() 是 OpenCV 库中的一个函数,用于对图像进行查找表(Look-Up Table,LUT)变换。这个函数将输入图像的每个像素值映射到查找表中对应的新值。
        # 参数说明 :
        # src :输入图像,必须是8位元素的数组(即像素值在0到255之间)。
        # lut :查找表,包含256个元素。对于多通道输入数组,查找表可以是单通道的(在这种情况下,相同的表用于所有通道),或者与输入数组具有相同数量的通道。
        # dst :输出数组,其大小和通道数与 src 相同,深度与 lut 相同。如果未指定,则函数会自动创建一个与 src 大小和通道数相同的输出数组。
        # 功能说明 :
        # cv2.LUT() 函数根据查找表 lut 将输入图像 src 中的每个像素值映射到新值。如果 src 是多通道图像,查找表可以是单通道的,也可以是多通道的。如果是单通道查找表,则对所有通道应用相同的映射;如果是多通道查找表,则每个通道分别应用对应的映射。
        # 返回值 :
        # 返回经过查找表变换后的输出图像。
        # 注意事项 :
        # 输入图像 src 必须是一个8位的数组,即像素值在0到255之间。
        # 查找表 lut 的长度必须是256,因为8位图像的像素值范围是0到255。
        # 如果 src 是多通道图像,确保 lut 的通道数与 src 匹配,或者使用单通道查找表对所有通道应用相同的映射。

        # 使用查找表将HSV通道的值应用到原始的HSV通道上,并将它们合并回一个HSV图像。
        im_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
        # 将增强后的HSV图像转换回BGR颜色空间,并直接存储在输入图像 im 中,因此不需要返回值。
        cv2.cvtColor(im_hsv, cv2.COLOR_HSV2BGR, dst=im)  # no return needed
# 这个 augment_hsv 函数通过对图像的HSV通道应用随机增益来增强图像,增强后的图像直接覆盖原始图像。这种增强可以增加模型训练时的数据多样性,提高模型的泛化能力。

6.def hist_equalize(im, clahe=True, bgr=False): 

# 这段代码定义了一个名为 hist_equalize 的函数,它用于对图像进行直方图均衡化处理。直方图均衡化是一种提高图像对比度的方法,通过调整图像的直方图分布使其更加均匀。
# 这行定义了一个名为 hist_equalize 的函数,它接受三个参数。
# 1.im :要进行直方图均衡化的图像。
# 2.clahe :是否使用对比度受限的自适应直方图均衡化,默认为True。
# 3.bgr :图像通道顺序是否为BGR,默认为False,即RGB。
def hist_equalize(im, clahe=True, bgr=False):
    # Equalize histogram on BGR image 'im' with im.shape(n,m,3) and range 0-255    使用 im.shape(n,m,3) 和范围 0-255 均衡 BGR 图像“im”上的直方图。
    # 将输入图像 im 从BGR或RGB颜色空间转换到YUV颜色空间。如果 bgr 参数为True,则假设图像是BGR格式;否则,假设图像是RGB格式。
    yuv = cv2.cvtColor(im, cv2.COLOR_BGR2YUV if bgr else cv2.COLOR_RGB2YUV)
    # 检查是否使用CLAHE(对比度受限的自适应直方图均衡化)。
    if clahe:

        # cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
        # cv2.createCLAHE() 是 OpenCV 库中的一个函数,用于创建一个对比度受限的自适应直方图均衡化(CLAHE)对象。
        # 参数说明 :
        # clipLimit :(类型:float)控制局部直方图均衡化时的对比度限制。较小的值限制对比度增加,较大的值则允许更多的对比度增强。默认值为2.0。当 clipLimit 设置为0或负值时,表示没有对比度限制。较高的值会增加对比度,但可能导致噪声放大。
        # tileGridSize :(类型:tuple of two ints)每个小网格的大小,以像素为单位(行数,列数)。默认值为 (8, 8) 。图像将被分为多个大小相同的网格块,CLAHE算法分别对每个网格块进行直方图均衡化。
        # 功能说明 :
        # cv2.createCLAHE() 用于创建CLAHE对象,可以通过该对象来调整图像的对比度。与传统的全局直方图均衡化不同,CLAHE适用于局部区域对比度不均匀的情况,能够有效提升图像的局部对比度,同时限制过度增强,避免产生伪影和噪声。
        # 使用步骤 :
        # 创建CLAHE对象。
        # 应用CLAHE算法。
        # 示例代码 :
        # import cv2
        # clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))    # 创建CLAHE对象
        # image = cv2.imread('input.jpg', cv2.IMREAD_GRAYSCALE)    # 读取灰度图像
        # enhanced_image = clahe.apply(image)    # 对图像应用CLAHE

        # 如果使用CLAHE,创建一个 CLAHE 对象,设置剪切限制为2.0,瓷砖网格大小为8x8。
        c = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
        # 对YUV颜色空间中的Y通道(亮度通道)应用CLAHE。
        yuv[:, :, 0] = c.apply(yuv[:, :, 0])
    # 如果选择不使用CLAHE,执行else分支。
    else:

        # cv2.equalizeHist(src)
        # cv2.equalizeHist() 是 OpenCV 库中的一个函数,用于对图像的直方图进行均衡化。直方图均衡化是一种提高图像对比度的方法,它通过调整图像的直方图分布使其更加均匀,从而增强图像的整体对比度。
        # 参数说明 :
        # src :输入图像,必须是单通道的灰度图像(8位深度)。
        # 功能说明 :
        # cv2.equalizeHist() 函数接受一个灰度图像作为输入,并输出该图像的直方图均衡化版本。该函数通过调整像素值的分布,使得输出图像的直方图分布尽可能均匀,从而达到增强图像对比度的效果。
        # 返回值 :
        # 返回直方图均衡化后的图像。
        # 注意事项 :
        # 输入图像必须是单通道的灰度图像,因为直方图均衡化仅适用于灰度图像。
        # 直方图均衡化的效果取决于图像的内容,对于某些图像可能效果不明显,而对于其他图像则可能显著提高对比度。
        # 直方图均衡化可能会增强图像的噪声,特别是在图像的对比度较低时。

        # 对Y通道进行普通的直方图均衡化。
        yuv[:, :, 0] = cv2.equalizeHist(yuv[:, :, 0])  # equalize Y channel histogram
    # 将经过直方图均衡化处理的YUV图像转换回BGR或RGB颜色空间,并返回结果。
    return cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR if bgr else cv2.COLOR_YUV2RGB)  # convert YUV image to RGB
# 这个 hist_equalize 函数提供了两种直方图均衡化方法:普通的直方图均衡化和对比度受限的自适应直方图均衡化(CLAHE)。它首先将图像转换到YUV颜色空间,然后对Y通道进行均衡化处理,最后将图像转换回原始的颜色空间。这种方法特别适用于提高图像的对比度,尤其是在图像的亮度分布不均匀时。

7.def replicate(im, labels): 

# 这段代码定义了一个名为 replicate 的函数,它用于复制图像中的标签(bounding boxes)到新的随机位置。这种操作通常用于数据增强,可以帮助模型学习更加鲁棒的特征。
# 这行定义了一个名为 replicate 的函数,它接受两个参数。
# 1.im :要复制标签的图像。
# 2.labels :图像中的标签,每个标签包含类别和四个边界框坐标。
def replicate(im, labels):
    # Replicate labels    复制标签。
    # 获取输入图像 im 的高度和宽度。
    h, w = im.shape[:2]
    # 提取 labels 数组中除了类别标签之外的边界框坐标,并将其转换为整数类型。
    boxes = labels[:, 1:].astype(int)
    # 将边界框坐标转置并分别赋值给 x1 , y1 , x2 , y2 。
    x1, y1, x2, y2 = boxes.T
    # 计算每个边界框的对角线长度的一半,作为边界框的“大小”。
    s = ((x2 - x1) + (y2 - y1)) / 2  # side length (pixels)
    # 对边 界框的大小 进行排序,并选择最小的一半索引进行复制。
    for i in s.argsort()[:round(s.size * 0.5)]:  # smallest indices
        # 获取当前索引 i 的边界框坐标。
        x1b, y1b, x2b, y2b = boxes[i]
        # 计算边界框的高度和宽度。
        bh, bw = y2b - y1b, x2b - x1b
        # 随机生成一个新的位置 xc , yc ,确保复制的边界框不会超出图像的边界。
        yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw))  # offset x, y
        # 计算新位置的边界框坐标。
        x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh]
        # 将原始边界框中的图像内容复制到新的位置。
        im[y1a:y2a, x1a:x2a] = im[y1b:y2b, x1b:x2b]  # im4[ymin:ymax, xmin:xmax]

        # numpy.append(arr, values, axis=None)
        # np.append() 是 NumPy 库中的一个函数,用于将值添加到数组的末尾。
        # 参数说明 :
        # arr :要添加值的原始数组。
        # values :要添加到数组末尾的值,可以是一个数组或标量。
        # axis :指定沿哪个轴进行添加。如果为 None ,则 arr 和 values 必须具有相同的形状,并且结果数组将是一个一维数组。如果指定了 axis ,则 arr 和 values 必须在其他轴上具有相同的形状。
        # 功能说明 :
        # np.append() 函数将 values 添加到 arr 数组的末尾。如果 axis 参数被指定,函数将沿着指定轴添加值。
        # 返回值 :
        # 返回一个新的数组,其中包含了原始数组 arr 和添加的 values 。
        # 注意事项 :
        # np.append() 函数返回的是新数组,原始数组不会被修改。
        # 如果 axis 参数未指定或为 None ,则 arr 和 values 必须具有相同的形状,或者 values 可以是一个标量。
        # 如果 axis 参数指定了, arr 和 values 必须在其他轴上具有相同的形状。

        # 将新复制的边界框和类别标签添加到 labels 数组中。
        labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0)

    # 返回复制后的 图像 和 更新后的标签数组 。
    return im, labels
# 这个 replicate 函数通过对图像中的边界框进行复制和随机放置,增加了数据集的多样性,有助于模型在训练过程中学习到更多样的特征。这种方法特别适用于目标检测和图像识别任务中的数据增强。

8.def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32): 

# 这段代码定义了一个名为 letterbox 的函数,它用于将输入图像 im 调整大小并填充,以适应新的尺寸 new_shape ,同时保持图像的宽高比,并确保结果图像的尺寸是 stride 的倍数。这个函数通常用于深度学习模型中,特别是在目标检测任务中,以确保输入图像的尺寸符合模型的要求。
# 定义了 letterbox 函数,包含多个参数。
# 1.im :输入图像。
# 2.new_shape :目标尺寸,默认为 (640, 640)。
# 3.color :填充颜色,默认为 (114, 114, 114),即浅灰色。
# 4.auto :是否自动调整填充方式,默认为 True。
# 5.scaleFill :是否拉伸图像以完全填充目标尺寸,默认为 False。
# 6.scaleup :是否允许图像放大,默认为 True。
# 7.stride :结果图像尺寸应该是该值的倍数,默认为 32。
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
    # 这段代码是 letterbox 函数的一部分,它负责调整图像尺寸并添加填充以满足特定的约束条件。
    # Resize and pad image while meeting stride-multiple constraints    调整图像大小并填充,同时满足步长倍数约束。
    # 获取输入图像 im 的当前高度和宽度,并存储在变量 shape 中。 im.shape 返回一个包含图像所有维度尺寸的元组,这里只取前两个元素,即高度和宽度。
    shape = im.shape[:2]  # current shape [height, width]
    # 检查 new_shape 参数是否为整数。如果是,那么将 new_shape 设置为一个元组,其中两个元素都是这个整数,这意味着目标尺寸是正方形的。
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)    计算缩放比例。
    # 计算图像的新尺寸与原始尺寸之间的最小缩放比例。这个比例决定了图像在哪个维度上会被缩放得更多。
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    # 这一行检查 scaleup 参数。如果 scaleup 为 False ,则不允许图像放大,即图像的缩放比例 r 不能超过 1.0。这意味着图像只会被缩小,而不会被放大,这在某些情况下可以提高验证平均精度(val mAP),因为它避免了放大可能带来的图像失真。
    if not scaleup:  # only scale down, do not scale up (for better val mAP)
        r = min(r, 1.0)
    # 这段代码的目的是确保图像在调整尺寸时,既不会超出目标尺寸,也不会因为放大而失真,同时保持图像的宽高比。

    # 这段代码继续处理图像的缩放和填充,以确保图像尺寸符合特定的约束条件。
    # Compute padding
    # 设置宽度和高度的缩放比例为相同的值 r 。
    ratio = r, r  # width, height ratios
    # 根据缩放比例 r 计算缩放后的图像尺寸,并四舍五入到最近的整数。 new_unpad 表示缩放后但未填充的图像尺寸。
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    # 计算水平和垂直方向上需要添加的填充量( dw 和 dh ),以使图像达到目标尺寸 new_shape 。
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    # 如果 auto 参数为 True ,则执行以下操作。
    if auto:  # minimum rectangle

        # numpy.mod(x, y, out=None, where=None, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj])
        # 在 NumPy 中, np.mod 函数用于计算模运算,即对于给定的两个数组,它返回第一个数组中每个元素除以第二个数组中对应元素的余数。
        # 参数说明 :
        # x :被除数数组。
        # y :除数数组。 y 的形状必须与 x 相同,或者能够广播到 x 的形状。
        # out :(可选)用于存放结果的数组。
        # where :(可选)布尔数组,指定在哪些位置执行操作。
        # casting :(可选)控制如何处理不同数据类型之间的转换。
        # order :(可选)指定多维数组中元素的遍历顺序。
        # dtype :(可选)指定输出数组的数据类型。
        # subok :(可选)如果为 True,则返回的数组是 x 和 y 的子类。
        # signature :(可选)指定函数签名。
        # extobj :(可选)用于非常量参数的额外对象。
        # 返回值 :
        # 返回一个数组,包含 x 中每个元素除以 y 中对应元素的余数。

        # 使用 np.mod 函数确保填充后的图像尺寸是 stride 的倍数,这样可以满足某些深度学习模型对输入尺寸的要求。
        dw, dh = np.mod(dw, stride), np.mod(dh, stride)  # wh padding
    # 如果 scaleFill 参数为 True ,则执行以下操作。
    elif scaleFill:  # stretch
        # 不添加任何填充。
        dw, dh = 0.0, 0.0
        # 直接使用目标尺寸作为缩放后的尺寸。
        new_unpad = (new_shape[1], new_shape[0])
        # 重新计算缩放比例,以反映实际的拉伸效果。
        ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratios

    # 将计算出的填充量分成两半,分别应用于图像的两侧。
    dw /= 2  # divide padding into 2 sides
    dh /= 2

    # 如果原始图像尺寸与缩放后的尺寸不同,则需要对图像进行缩放。
    if shape[::-1] != new_unpad:  # resize
        # 使用 OpenCV 的 cv2.resize 函数将图像缩放到 new_unpad 的尺寸,插值方法为线性插值。
        im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    # 计算 顶部 和 底部 的填充量,并进行四舍五入。
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    # 计算 左侧 和 右侧 的填充量,并进行四舍五入。
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))

    # dst = cv2.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value[, mask]]])
    # cv2.copyMakeBorder() 函数是 OpenCV 库中的一个函数,用于在图像周围添加边框(填充)。这个函数非常灵活,可以指定边框的厚度、类型以及边框的颜色。
    # 参数说明 :
    # src :输入图像,可以是灰度图或彩色图。
    # top :图像顶部的边框厚度。
    # bottom :图像底部的边框厚度。
    # left :图像左侧的边框厚度。
    # right :图像右侧的边框厚度。
    # borderType :边框的类型,有以下几种 :
    # cv2.BORDER_CONSTANT :添加一个常数颜色的边框。 value 参数指定了这个颜色。
    # cv2.BORDER_REFLECT :反射图像来填充边框。
    # cv2.BORDER_REFLECT_101 :类似于 BORDER_REFLECT ,但有两个反射。
    # cv2.BORDER_DEFAULT :等同于 BORDER_REFLECT 。
    # cv2.BORDER_REFLECT101 :等同于 BORDER_REFLECT_101 。
    # cv2.BORDER_WRAP :重复图像来填充边框。
    # cv2.BORDER_TRANSPARENT :假设图像是透明的,并进行适当的填充。
    # cv2.BORDER_REPLICATE :复制边缘像素来填充边框。
    # cv2.BORDER_CONSTANT :添加一个常数颜色的边框,与 cv2.BORDER_CONSTANT 相同。
    # dst :(可选)输出图像,如果提供,函数将直接修改这个图像而不是创建一个新的。
    # value :(可选)一个标量或一个三元组或四元组,指定 BORDER_CONSTANT 时边框的颜色。对于彩色图像,它应该是一个三元组或四元组(BGR顺序)。
    # mask :(可选)一个8位单通道图像,与 src 大小相同,用于确定哪些像素需要被复制。在 BORDER_TRANSPARENT 模式下使用。
    # 返回值 :
    # dst :包含原始图像和添加的边框的新图像。

    # 使用 OpenCV 的 cv2.copyMakeBorder 函数在图像周围添加填充,填充颜色为 color 。
    im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    # 返回 填充后的图像 、 缩放比例 和 填充尺寸 。
    return im, ratio, (dw, dh)
    # 这段代码确保了图像在缩放和填充后,其尺寸符合目标尺寸,并且满足特定的对齐要求(如 stride 的倍数)。这样的处理对于准备图像以适应深度学习模型的输入尺寸非常重要。
# 这个函数在目标检测和图像分类任务中非常有用,因为它确保了输入图像的尺寸符合模型的要求,同时避免了由于图像尺寸不匹配导致的性能问题。

9.def random_perspective(im, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)): 

# 这段代码定义了一个名为 random_perspective 的函数,它用于对图像进行随机透视变换,同时对图像中的标签(如边界框)进行相应的变换。这种变换可以用于数据增强,以提高模型对不同视角和变形的鲁棒性。
# 这行定义了一个名为 random_perspective 的函数,它接受多个参数。
# 1.im :要进行透视变换的图像。
# 2.targets :图像中的标签,如边界框。
# 3.segments :图像中的线段。
# 4.degrees 、 5.translate 、 6.scale 、 7.shear 、 8.perspective :控制变换的参数。
# 9.border :图像边界。
def random_perspective(im,
                       targets=(),
                       segments=(),
                       degrees=10,
                       translate=.1,
                       scale=.1,
                       shear=10,
                       perspective=0.0,
                       border=(0, 0)):
    # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(0.1, 0.1), scale=(0.9, 1.1), shear=(-10, 10))
    # targets = [cls, xyxy]

    # 计算考虑边界后的图像 高度 和 宽度 。
    height = im.shape[0] + border[0] * 2  # shape(h,w,c)
    width = im.shape[1] + border[1] * 2

    # 创建了一个中心化变换矩阵 C ,将图像中心移动到原点。
    # Center
    C = np.eye(3)
    C[0, 2] = -im.shape[1] / 2  # x translation (pixels)
    C[1, 2] = -im.shape[0] / 2  # y translation (pixels)

    # 创建了一个透视变换矩阵 P ,随机生成透视参数。
    # Perspective
    P = np.eye(3)
    P[2, 0] = random.uniform(-perspective, perspective)  # x perspective (about y)
    P[2, 1] = random.uniform(-perspective, perspective)  # y perspective (about x)

    # 创建了一个旋转和缩放变换矩阵 R ,随机生成旋转角度和缩放比例。
    # Rotation and Scale
    R = np.eye(3)
    a = random.uniform(-degrees, degrees)
    # a += random.choice([-180, -90, 0, 90])  # add 90deg rotations to small rotations
    s = random.uniform(1 - scale, 1 + scale)
    # s = 2 ** random.uniform(-scale, scale)

    # cv2.getRotationMatrix2D(center, angle, scale)
    # cv2.getRotationMatrix2D() 是 OpenCV 库中的一个函数,用于生成二维旋转矩阵。这个函数在图像处理和计算机视觉任务中非常有用,尤其是在需要旋转图像时。
    # 参数 :
    # center :旋转中心点,通常是一个 (x, y) 的二元组,表示图像中旋转轴的中心点。
    # angle :旋转角度,单位是度。正数表示逆时针旋转,负数表示顺时针旋转。
    # scale :缩放因子。当 scale 等于 1 时,表示没有缩放,图像大小不变;大于 1 表示放大;小于 1 表示缩小。
    # 返回值 :
    # 函数返回一个 2x3 的仿射变换矩阵,该矩阵可以用于 cv2.warpAffine() 或 cv2.warpPerspective() 函数来对图像进行旋转。
    # 矩阵形式 :
    # 返回的旋转矩阵 M 的形式如下 :
    # [ cos(θ)  -sin(θ)  tx ]
    # [ sin(θ)   cos(θ)  ty ]
    # 其中, θ 是旋转角度, tx 和 ty 是平移量。
    # 由于 cv2.getRotationMatrix2D() 生成的 tx 和 ty 实际上是为了确保图像围绕指定的中心点旋转而不超出边界,它们并不是固定的,而是根据旋转中心和图像尺寸动态计算的。

    R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)

    # 创建了一个剪切变换矩阵 S ,随机生成剪切角度。
    # Shear
    S = np.eye(3)
    S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180)  # x shear (deg)
    S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180)  # y shear (deg)

    # 创建了一个平移变换矩阵 T ,随机生成平移距离。
    # Translation
    T = np.eye(3)
    T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width  # x translation (pixels)
    T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height  # y translation (pixels)

    # 这段代码是 random_perspective 函数中的一部分,它负责应用一系列变换到图像上,并处理边界情况。
    # Combined rotation matrix    组合旋转矩阵。
    # 将多个变换矩阵相乘,得到最终的变换矩阵 M 。这些矩阵包括 平移矩阵 T 、 剪切矩阵 S 、 旋转和缩放矩阵 R 、 透视矩阵 P 和 中心化矩阵 C 。矩阵乘法的顺序是从右到左,这意味着先进行 中心化变换 C ,然后是 透视变换 P ,接着是 旋转和缩放变换 R , 剪切变换 S ,最后是 平移变换 T 。这个顺序对于最终的变换结果至关重要。
    M = T @ S @ R @ P @ C  # order of operations (right to left) is IMPORTANT
    # 图像变换条件检查。检查是否需要对图像进行变换。如果边界 border 不为 (0, 0) 或最终的变换矩阵 M 不是单位矩阵(即存在变换),则需要对图像进行变换。
    if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any():  # image changed
        # 透视变换。
        # 如果 perspective 参数为真,表示需要进行透视变换。
        if perspective:
            # 使用 cv2.warpPerspective 函数,它接受图像 im 、变换矩阵 M 、输出图像的大小 (width, height) 和边界值 (114, 114, 114) (这里使用的是灰色值)。这个函数会应用透视变换到图像上,并返回变换后的图像。
            im = cv2.warpPerspective(im, M, dsize=(width, height), borderValue=(114, 114, 114))
        # 仿射变换。
        # 如果 perspective 参数为假,表示需要进行仿射变换。
        else:  # affine
            # 这里使用 cv2.warpAffine 函数,它接受图像 im 、变换矩阵 M 的前两行(因为仿射变换是二维的)、输出图像的大小 (width, height) 和边界值 (114, 114, 114) 。这个函数会应用仿射变换到图像上,并返回变换后的图像。
            im = cv2.warpAffine(im, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
    # 这段代码通过组合多个变换矩阵来创建一个最终的变换矩阵 M ,然后根据是否存在透视变换来决定使用 cv2.warpPerspective 还是 cv2.warpAffine 函数。这些变换可以改变图像的视角,增加数据集的多样性,有助于提高模型在不同视角下的泛化能力。

    # Visualize
    # import matplotlib.pyplot as plt
    # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
    # ax[0].imshow(im[:, :, ::-1])  # base
    # ax[1].imshow(im2[:, :, ::-1])  # warped

    # 这段代码是 random_perspective 函数中的一部分,它负责处理图像中的标签(如边界框或线段),并将这些标签应用到变换后的图像上。
    # Transform label coordinates
    # 标签数量检查。
    # 获取标签的数量 n ,并检查是否有标签需要处理。如果 targets 不为空,即 n 大于0,则执行以下操作。
    n = len(targets)
    if n:
        # 线段存在性检查。检查是否存在线段 segments 。如果有至少一个非空线段, use_segments 将被设置为 True 。
        use_segments = any(x.any() for x in segments)
        # 初始化新标签数组。初始化一个形状为 (n, 4) 的新数组 new ,用于存储变换后的标签坐标。
        new = np.zeros((n, 4))
        # 如果存在线段,首先对线段进行上采样(通过调用 resample_segments 函数),然后遍历每个线段。
        if use_segments:  # warp segments
            # def resample_segments(segments, n=1000): -> 用于对线段进行上采样,即在保持线段形状不变的情况下增加线段上的点的数量。返回值。这行代码返回上采样后的线段数组。 -> return segments
            segments = resample_segments(segments)  # upsample
            for i, segment in enumerate(segments):
                # 线段坐标变换。这几行代码对每个线段进行变换。
                # 创建一个形状为 (len(segment), 3) 的数组 xy ,其中 xy[:, :2] 存储线段的坐标, xy[:, 2] 初始化为1。
                xy = np.ones((len(segment), 3))
                xy[:, :2] = segment
                # 应用变换矩阵 M 的转置 M.T 到线段坐标上,进行坐标变换。
                xy = xy @ M.T  # transform
                # 如果是透视变换,需要对坐标进行重新缩放(因为透视变换会改变坐标的深度)。
                xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]  # perspective rescale or affine

                # 裁剪变换后的线段。
                # clip
                # 将变换后的线段坐标裁剪到图像范围内,并转换为边界框格式,存储在 new[i] 中。
                # def segment2box(segment, width=640, height=640): -> 用于将线段标签转换为边界框标签。返回值。返回一个包含边界框坐标的数组,格式为 [x_min, y_min, x_max, y_max] 。 -> return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4))  # xyxy
                new[i] = segment2box(xy, width, height)
    # 这段代码处理图像中的线段标签,将它们应用到变换后的图像上。首先,它检查是否有线段需要处理,然后对每个线段进行上采样和坐标变换,最后将变换后的线段坐标裁剪到图像范围内,并转换为边界框格式。这个过程确保了变换后的图像中的线段标签仍然有效且在图像范围内。

        # 这段代码是 random_perspective 函数中处理边界框变换的部分。如果不存在线段( segments ),则执行以下操作来变换边界框。
        # 边界框变换。如果没有线段需要处理,那么进入处理边界框的分支。
        else:  # warp boxes
            # 创建一个形状为 (n * 4, 3) 的数组 xy ,其中 n 是边界框的数量。每个边界框有4个角点,所以是 n * 4 。数组的第三列初始化为1,用于后续的仿射变换。
            xy = np.ones((n * 4, 3))
            # 将 targets 中的边界框坐标重新排列并赋值给 xy 的前两列。这里假设 targets 中的边界框坐标是以 [x1, y1, x2, y2] 的形式存储的,即左上角和右下角的坐标。
            xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2)  # x1y1, x2y2, x1y2, x2y1
            # 应用变换矩阵 M 的转置 M.T 到边界框的每个角点上,进行坐标变换。
            xy = xy @ M.T  # transform
            # 处理透视变换的情况。如果是透视变换,需要将变换后的坐标除以第三列(即深度信息)进行重新缩放。然后,将结果重塑为 (n, 8) 的形状,每行包含一个边界框的8个坐标。
            xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8)  # perspective rescale or affine

            # 创建新边界框。
            # create new boxes
            # 从变换后的坐标中提取x和y坐标。
            x = xy[:, [0, 2, 4, 6]]
            y = xy[:, [1, 3, 5, 7]]
            # 计算每个边界框的新坐标。 x.min(1) 和 y.min(1) 分别计算每个边界框的最小x和y坐标, x.max(1) 和 y.max(1) 分别计算最大x和y坐标。然后,这些坐标被连接起来形成新的边界框,并重塑为 (n, 4) 的形状。
            new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T

            # 裁剪新边界框。
            # clip
            # 将新边界框的坐标裁剪到图像范围内,确保边界框不会超出图像的边界。
            new[:, [0, 2]] = new[:, [0, 2]].clip(0, width)
            new[:, [1, 3]] = new[:, [1, 3]].clip(0, height)
        # 这段代码处理图像中的边界框标签,将它们应用到变换后的图像上。首先,它对每个边界框的角点进行变换,然后根据变换后的坐标计算新的边界框,并确保这些边界框在图像范围内。这个过程确保了变换后的图像中的边界框标签仍然有效且在图像范围内。

        # 这段代码是 random_perspective 函数中的最后部分,它负责过滤掉那些在变换过程中不再有效的目标(例如,那些与原始目标重叠面积过小的边界框),并更新目标的坐标。
        # filter candidates
        # 调用 box_candidates 函数来过滤目标。 box_candidates 函数比较原始目标( targets )和变换后的目标( new )之间的重叠面积。
        # 参数 box1 是原始目标的坐标,乘以缩放因子 s (因为目标的大小可能会因为缩放而改变), box2 是变换后的目标坐标。 area_thr 是一个阈值,用于确定目标是否足够重叠。如果 use_segments 为真,则使用较小的阈值(0.01),否则使用较大的阈值(0.10)。
        # 函数返回一个索引数组 i ,表示哪些目标被保留。
        # def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16):
        # -> 用于确定哪些边界框在图像增强后仍然是有效的候选框。返回一个布尔数组,表示哪些边界框是有效的候选框。条件包括 : 增强后的边界框宽度和高度大于 wh_thr 。 增强后的边界框面积与增强前边界框面积的比值大于 area_thr 。 增强后的边界框宽高比小于 ar_thr 。 返回值。返回一个布尔数组,表示哪些边界框满足上述条件。
        # -> return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + eps) > area_thr) & (ar < ar_thr)  # candidates
        i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10)
        # 更新目标。使用索引数组 i 来选择被保留的目标,更新 targets 数组。
        targets = targets[i]
        # 将变换后的目标坐标更新到 targets 数组中,只更新坐标部分(即第2至第5列)。
        targets[:, 1:5] = new[i]

    # 返回结果。这行代码返回变换后的图像 im 和更新后的目标 targets 。
    return im, targets
    # 这段代码确保了变换后的图像中的目标(边界框)与原始目标有足够的重叠,并且更新了这些目标的坐标。这样可以确保数据增强过程中目标的一致性和有效性,有助于提高模型在不同视角和变换下的鲁棒性。
# 这个 random_perspective 函数通过对图像进行随机透视变换,同时对图像中的标签进行相应的变换,增加了数据集的多样性,有助于模型在训练过程中学习到更多样的特征。这种方法特别适用于目标检测和图像识别任务中的数据增强。

10.def copy_paste(im, labels, segments, p=0.5): 

# 这段代码定义了一个名为 copy_paste 的函数,它用于在图像增强过程中执行复制粘贴操作。这个函数从原始图像中随机选择对象,将其复制到新的位置,并更新相应的标签和线段。
# 这行定义了一个名为 copy_paste 的函数,它接受四个参数。
# 1.im :原始图像。
# 2.abels :图像中的标签,如边界框。
# 3.segments :图像中的线段。
# 4.p :复制粘贴操作的概率,默认为0.5。
def copy_paste(im, labels, segments, p=0.5):
    # Implement Copy-Paste augmentation https://arxiv.org/abs/2012.07177, labels as nx5 np.array(cls, xyxy)    实现复制粘贴增强 https://arxiv.org/abs/2012.07177,标记为 nx5 np.array(cls, xyxy)。
    # 获取线段的数量 n 。
    n = len(segments)
    # 检查是否应该执行复制粘贴操作(即 p 大于0且存在线段)。
    if p and n:
        # 获取图像的 高度 、 宽度 和 通道数 。
        h, w, c = im.shape  # height, width, channels
        # 创建一个与原始图像形状相同、初始化为0的新图像 im_new 。
        im_new = np.zeros(im.shape, np.uint8)

        # calculate ioa first then select indexes randomly    先计算ioa,然后随机选择索引。
        # 根据标签计算边界框的坐标。
        boxes = np.stack([w - labels[:, 3], labels[:, 2], w - labels[:, 1], labels[:, 4]], axis=-1)  # (n, 4)
        # 计算边界框之间的交集面积比(IOA)。
        ioa = bbox_ioa(boxes, labels[:, 1:5])  # intersection over area
        # 选择IOA小于0.30的边界框索引。
        indexes = np.nonzero((ioa < 0.30).all(1))[0]  # (N, )
        # 这段代码是 copy_paste 函数中的核心部分,它负责执行复制粘贴操作。
        # 确定操作次数。
        # 获取满足条件(即IOA小于0.30)的边界框索引的数量。
        n = len(indexes)
        # 从满足条件的索引中随机选择一部分进行复制粘贴操作。 random.sample 函数从 indexes 列表中随机选择 round(p * n) 个不同的索引,其中 p 是复制粘贴操作的概率。
        for j in random.sample(list(indexes), k=round(p * n)):
            # 提取被选中进行复制的 标签 l 、 边界框 box 和 线段 s 。
            l, box, s = labels[j], boxes[j], segments[j]
            # 将新的边界框添加到 labels 数组中。这里 l[0] 是类别标签, *box 是展开的边界框坐标。
            labels = np.concatenate((labels, [[l[0], *box]]), 0)
            # 将新的线段添加到 segments 数组中。这里 w - s[:, 0:1] 是调整线段的 x 坐标,使其适应图像宽度, s[:, 1:2] 是保持线段的 y 坐标不变。
            segments.append(np.concatenate((w - s[:, 0:1], s[:, 1:2]), 1))
            # 在新图像 im_new 上绘制线段。 cv2.drawContours 函数绘制一个或多个轮廓,这里只绘制被选中的线段 segments[j] ,颜色设置为白色( (1, 1, 1) ),并填充轮廓。
            cv2.drawContours(im_new, [segments[j].astype(np.int32)], -1, (1, 1, 1), cv2.FILLED)
        # 这段代码通过随机选择一些边界框,并将其对应的对象复制到新的位置,来增加图像的多样性。这个过程包括更新标签和线段,以及在新图像上绘制线段。这种复制粘贴操作是一种有效的数据增强技术,可以提高模型对不同场景下对象的识别能力。

        # 对原始图像进行左右翻转,作为额外的增强操作。
        result = cv2.flip(im, 1)  # augment segments (flip left-right)
        # 将 im_new 图像左右翻转,然后将其与翻转后的原始图像结合,实现复制粘贴的效果。
        i = cv2.flip(im_new, 1).astype(bool)
        im[i] = result[i]  # cv2.imwrite('debug.jpg', im)  # debug

    # 返回经过复制粘贴操作后的 图像 、更新后的 标签 和 线段 。
    return im, labels, segments
# 这个 copy_paste 函数通过随机选择图像中的对象并将其复制到新的位置,增加了数据集的多样性。这种操作有助于模型学习到更多样的特征,提高模型在不同情况下的泛化能力。

11.def cutout(im, labels, p=0.5): 

# 这段代码定义了一个名为 cutout 的函数,它用于在图像增强过程中执行“cutout”操作,即随机遮盖图像的一部分。这种技术可以提高模型对于图像中遮挡部分的鲁棒性。
# 这行定义了一个名为 cutout 的函数,它接受三个参数。
# 1.im :要进行cutout操作的图像。
# 2.labels :图像中的标签,如边界框。
# 3.p :执行cutout操作的概率,默认为0.5。
def cutout(im, labels, p=0.5):
    # Applies image cutout augmentation https://arxiv.org/abs/1708.04552    应用图像剪切增强 https://arxiv.org/abs/1708.04552 。
    # 随机生成一个数,并检查它是否小于概率 p ,以决定是否执行cutout操作。
    if random.random() < p:
        # 获取图像的高度和宽度。
        h, w = im.shape[:2]
        # 定义了一个列表 scales ,包含了不同的遮盖区域大小比例。这些比例将用于确定遮盖区域的尺寸。
        scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16  # image size fraction
        # 遍历 scales 列表,并为每个比例 s 生成一个随机的遮盖区域高度 mask_h 和宽度 mask_w 。
        for s in scales:
            mask_h = random.randint(1, int(h * s))  # create random masks
            mask_w = random.randint(1, int(w * s))

            # box
            # 计算遮盖区域的左上角坐标 xmin 和 ymin ,以及右下角坐标 xmax 和 ymax ,确保遮盖区域在图像范围内。
            xmin = max(0, random.randint(0, w) - mask_w // 2)
            ymin = max(0, random.randint(0, h) - mask_h // 2)
            xmax = min(w, xmin + mask_w)
            ymax = min(h, ymin + mask_h)

            # apply random color mask
            # 将遮盖区域的像素值设置为随机颜色,这里使用的是介于64到191之间的随机整数,为每个通道生成一个随机值。
            im[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)]

            # 这段代码是 cutout 函数中的一部分,它负责检查和过滤被遮盖区域遮挡过多的标签。
            # return unobscured labels
            # 检查两个条件。是否有标签( labels 数组的长度大于0)以及遮盖区域的比例 s 是否大于0.03(即遮盖区域是否足够小,以至于对标签的影响是显著的)。
            if len(labels) and s > 0.03:
                # 创建一个包含遮盖区域坐标的 NumPy 数组 box ,格式为 [xmin, ymin, xmax, ymax] ,其中 xmin 和 ymin 是遮盖区域的左上角坐标, xmax 和 ymax 是遮盖区域的右下角坐标。
                box = np.array([[xmin, ymin, xmax, ymax]], dtype=np.float32)
                # 计算遮盖区域与每个标签的交集面积比(IOA)。首先, xywhn2xyxy 函数将标签的坐标从 [x, y, w, h] 格式(其中 x, y 是中心点坐标, w, h 是宽度和高度)转换为 [xmin, ymin, xmax, ymax] 格式。然后, bbox_ioa 函数计算遮盖区域 box 与每个转换后的标签坐标的 IOA。
                # def bbox_ioa(box1, box2, eps=1e-7): -> 用于计算两个边界框之间的交集面积比(Intersection over Area, IoA)。返回一个数组,包含每个 box1 中的边界框与对应 box2 中的边界框之间的 IoA 值。 -> return inter_area / box2_area
                # def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0): -> 用于将边界框的坐标从中心点坐标加宽高( x, y, w, h )的格式转换为左上角和右下角坐标( x1, y1, x2, y2 )的格式。返回转换后的边界框坐标数组 y ,格式为 [x1, y1, x2, y2] 。 -> return y
                ioa = bbox_ioa(box, xywhn2xyxy(labels[:, 1:5], w, h))[0]  # intersection over area
                # 过滤标签。过滤掉被遮盖区域遮挡超过60%的标签。 ioa < 0.60 创建一个布尔数组,用于选择 IOA 小于0.60的标签,即那些被遮盖区域遮挡不超过60%的标签。
                labels = labels[ioa < 0.60]  # remove >60% obscured labels
            # 这段代码确保了在执行遮盖操作后,只有那些未被遮盖区域遮挡过多的标签才会被保留。这样可以避免在模型训练时使用那些由于遮盖而可能产生误导的标签,从而提高模型对于遮挡情况的鲁棒性。

    # 返回更新后的标签数组,移除了被遮盖区域遮挡超过60%的标签。
    return labels
# 这个 cutout 函数通过对图像进行随机遮盖,模拟了图像中可能出现的遮挡情况,从而提高了模型对于遮挡部分的鲁棒性。这种技术可以与其他数据增强技术结合使用,提高模型的性能。

12.def mixup(im, labels, im2, labels2): 

# 这段代码定义了一个名为 mixup 的函数,它实现了一种称为 MixUp 的数据增强技术。MixUp 通过在原始训练样本之间进行线性插值来生成新的训练样本,这有助于模型在训练样本之间表现出更平滑的过渡行为,从而提高模型的泛化能力。
# 这行定义了一个名为 mixup 的函数,它接受四个参数。
# 1.im 和 2.labels :是第一张图像及其对应的标签。
# 3.im2 和 4.labels2 :是第二张图像及其对应的标签。
def mixup(im, labels, im2, labels2):
    # Applies MixUp augmentation https://arxiv.org/pdf/1710.09412.pdf    应用 MixUp 增强 https://arxiv.org/pdf/1710.09412.pdf 。
    # 生成了一个随机的MixUp比例 r ,使用 np.random.beta 函数,其中 alpha 和 beta 参数都设置为32.0。这个比例决定了两张图像混合的程度, r 的值在0到1之间,接近0意味着更多地使用 im2 ,接近1意味着更多地使用 im 。
    r = np.random.beta(32.0, 32.0)  # mixup ratio, alpha=beta=32.0
    # 计算了混合后的图像。它将 im 乘以比例 r ,将 im2 乘以 1-r (即 im2 的比例),然后将两者相加得到混合图像。最后,使用 .astype(np.uint8) 确保混合后的图像数据类型为无符号8位整数,这是图像数据常用的数据类型。
    im = (im * r + im2 * (1 - r)).astype(np.uint8)
    # 将两个标签数组 labels 和 labels2 沿着第一个维度(通常是样本维度)连接起来,形成一个新的标签数组。这样,混合图像的标签就是两个原始图像标签的组合。
    labels = np.concatenate((labels, labels2), 0)
    # 返回混合后的图像 im 和新的标签数组 labels 。
    return im, labels
# 这个 mixup 函数通过随机选择一个比例 r 来混合两个图像的特征,并相应地扩展标签数组,从而生成新的训练样本。这种方法可以帮助模型学习到更鲁棒的特征表示,减少过拟合,并提高模型在面对新样本时的泛化能力。MixUp 是一种简单而有效的数据增强技术,特别适用于深度学习和计算机视觉任务。

13.def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16): 

# 这段代码定义了一个名为 box_candidates 的函数,它用于确定哪些边界框在图像增强后仍然是有效的候选框。这个函数考虑了宽度、高度、面积比和宽高比(宽高比)的阈值来过滤候选框。
# 这行定义了一个名为 box_candidates 的函数,它接受六个参数。
# 1.box1 :增强前的边界框坐标,格式为 [x_min, y_min, x_max, y_max] 。
# 2.box2 :增强后的边界框坐标,格式为 [x_min, y_min, x_max, y_max] 。
# 3.wh_thr :宽度和高度的最小阈值(以像素为单位),默认为2。
# 4.ar_thr :宽高比的最大阈值,默认为100。
# 5.area_thr :面积比的最小阈值,默认为0.1。
# 6.eps :用于防止除以零的极小值,默认为1e-16。
def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16):  # box1(4,n), box2(4,n)
    # Compute candidate boxes: box1 before augment, box2 after augment, wh_thr (pixels), aspect_ratio_thr, area_ratio    计算候选框:增强前的 box1、增强后的 box2、wh_thr(像素)、aspect_ratio_thr、area_ratio 。
    # 计算增强前边界框的宽度和高度。
    w1, h1 = box1[2] - box1[0], box1[3] - box1[1]
    # 计算增强后边界框的宽度和高度。
    w2, h2 = box2[2] - box2[0], box2[3] - box2[1]
    # 计算增强后边界框的宽高比。 np.maximum 确保即使在 高度 或 宽度 非常小的情况下也能正确计算宽高比, eps 用于防止除以零。
    ar = np.maximum(w2 / (h2 + eps), h2 / (w2 + eps))  # aspect ratio
    # 返回一个布尔数组,表示哪些边界框是有效的候选框。条件包括 :
    # 增强后的边界框宽度和高度大于 wh_thr 。
    # 增强后的边界框面积与增强前边界框面积的比值大于 area_thr 。
    # 增强后的边界框宽高比小于 ar_thr 。
    # 返回值。返回一个布尔数组,表示哪些边界框满足上述条件。
    return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + eps) > area_thr) & (ar < ar_thr)  # candidates
# 这个 box_candidates 函数通过一系列的几何条件来确定哪些边界框在图像增强后仍然是有效的候选框。这有助于过滤掉那些在增强过程中变得无效或不符合条件的边界框,确保后续处理的边界框是有效和可靠的。

14.def classify_albumentations(augment=True, size=224, scale=(0.08, 1.0), ratio=(0.75, 1.0 / 0.75), hflip=0.5, vflip=0.0, jitter=0.4, mean=IMAGENET_MEAN, std=IMAGENET_STD, auto_aug=False): 

# 这段代码定义了一个名为 classify_albumentations 的函数,它用于创建一组用于图像分类任务的图像增强变换,这些变换基于 albumentations 库实现。
# 这个函数接受多个参数,用于配置图像增强变换。
# 1.augment :是否应用增强,默认为 True 。
# 2.size :输出图像的大小,默认为 224。
# 3.scale :随机裁剪的规模范围,默认为 (0.08, 1.0) 。
# 4.ratio :随机裁剪的宽高比范围,默认为 (0.75, 1.33) 。
# 5.hflip :水平翻转的概率,默认为 0.5。
# 6.vflip :垂直翻转的概率,默认为 0.0。
# 7.jitter :颜色抖动的强度,默认为 0.4。
# 8.mean 和 9.std :标准化的均值和标准差,默认为 IMAGENET_MEAN 和 IMAGENET_STD 。
# 10.auto_aug :是否应用自动增强,默认为 False 。
def classify_albumentations(
        augment=True,
        size=224,
        scale=(0.08, 1.0),
        ratio=(0.75, 1.0 / 0.75),  # 0.75, 1.33
        hflip=0.5,
        vflip=0.0,
        jitter=0.4,
        mean=IMAGENET_MEAN,
        std=IMAGENET_STD,
        auto_aug=False):
    # YOLOv5 classification Albumentations (optional, only used if package is installed)
    # 定义一个前缀字符串,用于日志输出。
    prefix = colorstr('albumentations: ')
    # 尝试导入 albumentations 库,并检查版本是否符合要求。
    try:
        import albumentations as A
        from albumentations.pytorch import ToTensorV2
        # def check_version(current='0.0.0', minimum='0.0.0', name='version ', pinned=False, hard=False, verbose=False): -> 用于检查当前安装的软件包版本是否满足指定的最低版本要求。函数返回 result ,即版本检查的结果。 -> return result
        check_version(A.__version__, '1.0.3', hard=True)  # version requirement
        # 如果 augment 为 True ,则创建一个包含随机裁剪的变换列表 T 。
        if augment:  # Resize and crop
            T = [A.RandomResizedCrop(height=size, width=size, scale=scale, ratio=ratio)]
            # 自动增强检查。
            # 检查是否设置了 auto_aug 参数为 True 。如果 auto_aug 为 True ,则输出一条日志信息,说明自动增强(如 AugMix, AutoAug 和 RandAug)当前不被支持。这里的 TODO 注释表明这部分功能尚未实现,需要在未来的代码中添加。
            if auto_aug:
                # TODO: implement AugMix, AutoAug & RandAug in albumentation
                LOGGER.info(f'{prefix}auto augmentations are currently not supported')    # {prefix}目前不支持自动增强。
            # 如果 auto_aug 为 False。
            else:
                # 应用水平翻转。则检查 hflip 参数是否大于0,如果是,则将水平翻转变换 A.HorizontalFlip 添加到变换列表 T 中,并设置翻转的概率为 hflip 。
                if hflip > 0:
                    T += [A.HorizontalFlip(p=hflip)]
                # 应用垂直翻转。检查 vflip 参数是否大于0,如果是,则将垂直翻转变换 A.VerticalFlip 添加到变换列表 T 中,并设置翻转的概率为 vflip 。
                if vflip > 0:
                    T += [A.VerticalFlip(p=vflip)]
                # 应用颜色抖动。检查 jitter 参数是否大于0,如果是,则创建一个颜色抖动变换 A.ColorJitter 并添加到变换列表 T 中。颜色抖动的参数是 jitter 值的三倍,分别对应亮度、对比度和饱和度的抖动强度,而色调(hue)的抖动强度设置为0。
                if jitter > 0:
                    color_jitter = (float(jitter),) * 3  # repeat value for brightness, contrast, satuaration, 0 hue
                    T += [A.ColorJitter(*color_jitter, 0)]
        # 如果 augment 为 False ,则使用固定的裁剪变换。
        else:  # Use fixed crop for eval set (reproducibility)
            T = [A.SmallestMaxSize(max_size=size), A.CenterCrop(height=size, width=size)]

        # ToTensorV2(transpose_mask: bool = False, p: float = 1.0, always_apply: bool | None = None)
        # ToTensorV2 是 Albumentations 库中的一个转换函数,它用于将 NumPy 数组转换为 PyTorch 张量。
        # 参数 :
        # transpose_mask :一个布尔值,如果设置为 True ,则将 3D 输入掩码的维度从 [height, width, num_channels] 转换为 [num_channels, height, width] 。
        # p :一个浮点数,表示应用转换的概率,默认值为 1.0,即总是应用转换。
        # always_apply :一个布尔值或 None ,表示是否总是应用转换。
        # 功能 :
        # ToTensorV2 将输入的 NumPy 数组(图像或掩码)转换为 PyTorch 张量。
        # 对于图像,如果输入格式为 HWC (高度、宽度、通道),则转换为 PyTorch 的 CHW 格式。
        # 如果输入格式为 HW (只有高度和宽度),则转换为 PyTorch 的 1HW 格式,并添加通道维度。
        # 使用场景 :
        # ToTensorV2 通常用在数据预处理的最后阶段,尤其是在归一化步骤之后,将处理后的图像数据转换为 PyTorch 张量,以便输入到 PyTorch 模型中。
        # 这个函数是 Albumentations 库与 PyTorch 集成的一部分,使得从 Albumentations 到 PyTorch 的转换变得无缝和高效。
        
        # 将标准化和转换为张量的变换添加到列表 T 中。
        T += [A.Normalize(mean=mean, std=std), ToTensorV2()]  # Normalize and convert to Tensor
        # 输出变换列表的信息。
        LOGGER.info(prefix + ', '.join(f'{x}'.replace('always_apply=False, ', '') for x in T if x.p))
        # 返回一个 A.Compose 对象,该对象包含所有的变换。
        return A.Compose(T)

    # 如果 albumentations 库没有安装或者出现其他异常,将记录相应的日志信息。
    except ImportError:  # package not installed, skip
        LOGGER.warning(f'{prefix}⚠️ not found, install with `pip install albumentations` (recommended)')    # {prefix}⚠️未找到,使用`pip install albumentations`安装(推荐)。
    except Exception as e:
        LOGGER.info(f'{prefix}{e}')    # {prefix}{e}。
# 这个 classify_albumentations 函数提供了一种灵活的方式来配置和应用图像增强变换,这些变换可以用于图像分类任务。通过调整函数参数,用户可以轻松地定制增强策略,以提高模型的鲁棒性和性能。

15.def classify_transforms(size=224): 

# 这段代码定义了一个名为 classify_transforms 的函数,它用于创建一组图像预处理转换操作,这些操作通常用于深度学习模型中,特别是在图像分类任务中。这个函数接受一个参数 size ,它指定了输出图像的尺寸。
# 定义了一个函数 classify_transforms ,它有一个默认参数。
# 1.size :其默认值为 224。这个参数指定了图像的输出尺寸。
def classify_transforms(size=224):
    # Transforms to apply if albumentations not installed
    # 用 assert 语句来确保传入的 size 参数是一个整数。如果不是整数,它将抛出一个错误,并显示一条错误消息。
    assert isinstance(size, int), f'ERROR: classify_transforms size {size} must be integer, not (list, tuple)'    # 错误:classify_transforms 大小 {size} 必须是整数,而不是(列表,元组)。
    # 这是一个被注释掉的代码行,它显示了一个可能的转换序列,包括将图像转换为张量、调整大小、中心裁剪和归一化。这些操作通常由 PyTorch 的 torchvision.transforms 模块提供。
    # T.Compose([T.ToTensor(), T.Resize(size), T.CenterCrop(size), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)])

    # torchvision.transforms.Normalize(mean, std, inplace=False)
    # T.Normalize() 是 PyTorch 的 torchvision.transforms 模块中的一个数据预处理函数,用于对图像数据进行标准化处理,使得数据分布符合标准正态分布(均值为0,标准差为1)。
    # 参数 :
    # mean :一个列表,包含每个通道的均值。这个列表的长度应该与输入数据的通道数相同。
    # std :一个列表,包含每个通道的标准差。这个列表的长度应该与输入数据的通道数相同。
    # inplace :一个布尔值,表示是否在原地修改数据。默认为 False ,意味着会创建一个新的张量来存储标准化后的结果。
    # 功能 :
    # T.Normalize() 函数 将每个通道的数据减去对应的均值,然后除以对应的标准差,使得每个通道的数据均值为0,标准差为1。
    # 用途 :
    # 标准化处理有助于模型更快地收敛,并提高模型的性能。以下是一些具体的好处 :
    # 数据标准化 :使得数据分布符合标准正态分布,有助于模型更快地学习到数据的特征。
    # 提高模型泛化能力 :减少模型对特定数据集的过拟合,提高模型在未见过的数据上的泛化能力。
    # 加速模型训练 :标准化的数据可以使模型在训练过程中更快地学习到数据的特征,从而加速模型的训练速度。

    # 返回一个由 T.Compose 创建的转换序列。这个序列包括 :
    # CenterCrop(size) :中心裁剪图像到指定的 size 。
    # ToTensor() :将 PIL 图像或 NumPy ndarray 转换为 torch.Tensor 。
    # T.Normalize(IMAGENET_MEAN, IMAGENET_STD) :对图像进行归一化,使用 ImageNet 数据集的均值和标准差。
    return T.Compose([CenterCrop(size), ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)])
# 这个函数的目的是创建一个转换序列,用于预处理图像,使其适合用于深度学习模型。这个序列首先将图像中心裁剪到指定尺寸,然后将其转换为张量,最后进行标准化处理。

16.class LetterBox: 

# 这段代码定义了一个名为 LetterBox 的类,它用于图像预处理,特别是在需要将图像调整到特定尺寸的同时保持其宽高比的场景中。这种预处理方式常用于目标检测模型,如 YOLO。
class LetterBox:
    # YOLOv5 LetterBox class for image preprocessing, i.e. T.Compose([LetterBox(size), ToTensor()])    YOLOv5 LetterBox 类用于图像预处理,即 T.Compose([LetterBox(size), ToTensor()])。
    # __init__ 方法是 LetterBox 类的构造函数,它用于初始化类的实例。这个方法接收三个参数。
    # 1.size :这是一个可以是整数或元组的参数,用于指定输出图像的尺寸。如果 size 是一个整数,那么图像的高和宽都将被设置为这个值。如果 size 是一个元组,那么它将分别被用作图像的高和宽。
    # 2.auto :这是一个布尔值参数,用于指示是否自动计算短边尺寸。如果设置为 True ,并且提供了最大尺寸的整数, LetterBox 将自动解决短边的尺寸问题,使用 stride 参数来确保输出尺寸是 stride 的倍数。
    # 3.stride :这是一个整数参数,当 auto 参数为 True 时使用。它指定了输出图像尺寸应该是 stride 的倍数。
    def __init__(self, size=(640, 640), auto=False, stride=32):
        super().__init__()
        # 如果 size 是一个整数( isinstance(size, int) 返回 True ),那么 self.h (高度)和 self.w (宽度)都将被设置为这个整数值。这意味着输出图像的高和宽将相等。
        # 如果 size 不是一个整数(即是一个元组),那么 size 将直接解包为 self.h 和 self.w 。这意味着 size 元组中的第一个元素将被设置为 self.h ,第二个元素将被设置为 self.w 。
        self.h, self.w = (size, size) if isinstance(size, int) else size
        # 将 auto 参数的值赋给 self.auto 属性。 auto 是一个布尔值,用于指示是否自动计算短边尺寸。如果 auto 为 True ,则 LetterBox 类将根据 stride 参数自动调整图像尺寸以适应特定的步长要求。
        self.auto = auto  # pass max size integer, automatically solve for short side using stride
        # 将 stride 参数的值赋给 self.stride 属性。 stride 是一个整数,用于定义在自动模式下调整图像尺寸时的步长。步长通常用于卷积神经网络中,以确保输入图像的尺寸是网络期望的尺寸。
        self.stride = stride  # used with auto
    # 这段代码的目的是确保 LetterBox 类的实例能够根据提供的参数正确地初始化,以便后续可以对图像进行适当的预处理。这个类的实例化和使用通常与图像预处理流程一起,特别是在需要将图像调整到特定尺寸以适应神经网络输入要求时。

    # 这段代码是 LetterBox 类的 __call__ 方法,它定义了如何对输入图像 im 进行处理。这个方法的作用是将输入图像调整到类实例初始化时指定的尺寸,同时保持图像的宽高比,并在必要时添加填充以达到所需的尺寸。
    # 1.im :输入图像,一个 NumPy 数组,格式为 HWC(高度、宽度、通道数)。
    def __call__(self, im):  # im = np.array HWC
        # 获取输入图像 im 的高度和宽度。
        imh, imw = im.shape[:2]
        # 计算图像缩放的比例 r ,这是原始图像尺寸和目标尺寸之间的最小比例,以保持图像的宽高比。
        r = min(self.h / imh, self.w / imw)  # ratio of new/old
        # 根据比例 r 计算调整后的图像尺寸。
        h, w = round(imh * r), round(imw * r)  # resized image
        # 如果 self.auto 为 True ,则使用 stride 来调整 h 和 w ,确保它们是 stride 的整数倍。 如果 self.auto 为 False ,则直接使用 self.h 和 self.w 作为目标尺寸。
        hs, ws = (math.ceil(x / self.stride) * self.stride for x in (h, w)) if self.auto else self.h, self.w
        # 计算填充的起始位置,以便调整后的图像居中显示。
        top, left = round((hs - h) / 2 - 0.1), round((ws - w) / 2 - 0.1)
        # 创建一个新的填充图像 im_out ,尺寸为 (self.h, self.w, 3) ,填充值为 114(通常用于 YOLO 模型的背景色)。
        im_out = np.full((self.h, self.w, 3), 114, dtype=im.dtype)
        # 将原始图像 im 缩放到调整后的尺寸 (w, h) ,并将结果放置在填充图像 im_out 的中心位置。
        im_out[top:top + h, left:left + w] = cv2.resize(im, (w, h), interpolation=cv2.INTER_LINEAR)
        # 返回处理后的填充图像 im_out 。
        return im_out
    # 这个方法的目的是将输入图像调整到指定的尺寸,同时保持图像的宽高比,并在必要时添加填充以确保图像居中。这是许多计算机视觉任务中常见的预处理步骤,特别是在目标检测模型中。

17.class CenterCrop: 

# 这段代码定义了一个名为 CenterCrop 的类,它用于对图像进行中心裁剪。
class CenterCrop:
    # YOLOv5 CenterCrop class for image preprocessing, i.e. T.Compose([CenterCrop(size), ToTensor()])    YOLOv5 CenterCrop 类用于图像预处理,即 T.Compose([CenterCrop(size), ToTensor()])。
    # 定义了 CenterCrop 类的构造函数,它接受一个参数。
    # 1.size :用于指定裁剪后的图像尺寸。
    def __init__(self, size=640):
        # 调用父类的构造函数。由于 CenterCrop 类没有明确继承自其他类,这行代码可能不是必需的,除非它是设计为继承自某个基类。
        super().__init__()
        # 如果 size 是一个整数,那么裁剪后的图像的 高度 和 宽度 都将被设置为这个值。如果 size 是一个元组,那么它将分别被用作裁剪后的图像的高度和宽度。
        self.h, self.w = (size, size) if isinstance(size, int) else size

    # 这段代码是 CenterCrop 类的 __call__ 方法的实现,它用于对输入的图像进行中心裁剪并调整到指定的尺寸。
    # 定义 __call__ 方法,使得 CenterCrop 类的实例可以像函数一样被调用。
    # 1.im :是一个 NumPy 数组,表示输入的图像,格式为 HWC(高度、宽度、通道数)。
    def __call__(self, im):  # im = np.array HWC
        # 从输入图像 im 中提取高度 imh 和宽度 imw ,并分别存储在变量 imh 和 imw 中。
        imh, imw = im.shape[:2]
        # 计算图像的最小维度 m ,即高度和宽度中的较小值。
        m = min(imh, imw)  # min dimension
        # 计算裁剪的起始点。 top 是从图像顶部开始的垂直位置, left 是从图像左侧开始的水平位置。这两个值确保裁剪区域位于图像的中心。
        top, left = (imh - m) // 2, (imw - m) // 2
        # 从输入图像中裁剪出中心区域,裁剪区域的尺寸为 m x m 。然后使用 OpenCV 的 cv2.resize 函数将裁剪出的区域调整到 (self.w, self.h) 的尺寸。 interpolation=cv2.INTER_LINEAR 指定了线性插值方法,这是一种常用的图像缩放技术。
        return cv2.resize(im[top:top + m, left:left + m], (self.w, self.h), interpolation=cv2.INTER_LINEAR)
    # 方法返回调整尺寸后的图像。这个中心裁剪操作在图像预处理中很常见,尤其是在需要将图像输入到神经网络之前,确保输入尺寸一致性的情况下。
# 这个类的实例化和使用通常与图像预处理流程一起,特别是在需要从图像中心裁剪出特定尺寸的区域时。

18.class ToTensor: 

# 这段代码定义了一个名为 ToTensor 的类,它用于将 NumPy 数组格式的图像转换为 PyTorch 张量。
class ToTensor:
    # YOLOv5 ToTensor class for image preprocessing, i.e. T.Compose([LetterBox(size), ToTensor()])    YOLOv5 ToTensor 类用于图像预处理,即 T.Compose([LetterBox(size), ToTensor()])。
    # 定义了 ToTensor 类的构造函数,它接受一个参数。
    # half :一个布尔值,用于指定是否将张量转换为半精度浮点数(float16)。
    def __init__(self, half=False):
        # 调用父类的构造函数。由于 ToTensor 类没有明确继承自其他类,这行代码可能不是必需的,除非它是设计为继承自某个基类。
        super().__init__()
        # 将传入的 half 参数值赋给类的实例变量 self.half 。
        self.half = half

    # 这段代码是 ToTensor 类的 __call__ 方法的实现,它负责将一个以 HWC(高度、宽度、通道)格式存储的 NumPy 数组图像转换为 PyTorch 张量。
    # 定义 __call__ 方法,使得 ToTensor 类的实例可以像函数一样被调用。
    # 1.im :是一个 NumPy 数组,表示输入的图像,格式为 HWC,并且通道顺序是 BGR。
    def __call__(self, im):  # im = np.array HWC in BGR order
        # im.transpose((2, 0, 1)) :将图像的维度从 HWC 转换为 CHW(通道、高度、宽度)。
        # [::-1] :将通道顺序从 BGR 转换为 RGB。
        # np.ascontiguousarray :确保 NumPy 数组在内存中是连续存储的,这对于后续转换为 PyTorch 张量是必要的。
        im = np.ascontiguousarray(im.transpose((2, 0, 1))[::-1])  # HWC to CHW -> BGR to RGB -> contiguous
        # 使用 PyTorch 的 torch.from_numpy 方法将 NumPy 数组转换为 PyTorch 张量。
        im = torch.from_numpy(im)  # to torch
        # 如果 self.half 属性为 True ,则将张量的数据类型转换为半精度浮点数(float16)。否则,转换为单精度浮点数(float32)。这一步将图像的像素值从无符号整型(uint8)转换为浮点数。
        im = im.half() if self.half else im.float()  # uint8 to fp16/32
        # 将图像的像素值从 [0, 255] 范围归一化到 [0.0, 1.0] 范围,这是深度学习模型中常见的预处理步骤。
        im /= 255.0  # 0-255 to 0.0-1.0
        # 返回转换后的 PyTorch 张量。
        return im
    # 这个方法的目的是将图像数据转换为 PyTorch 张量,并进行必要的预处理,以便可以直接用于深度学习模型的训练和推理。这种转换和预处理是图像处理和计算机视觉任务中常见的步骤,尤其是在使用 PyTorch 框架时。
# 这个类的实例化和使用通常与图像预处理流程一起,特别是在需要将图像数据输入到 PyTorch 模型之前。


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

相关文章:

  • 如何通过高防服务隐藏服务器源IP
  • C语言的语法糖
  • 51.WPF应用加图标指南 C#例子 WPF例子
  • Linux命令行工具-使用方法
  • AWTK fscript 中的 输入/出流 扩展函数
  • nginx 修改内置 404 页面、点击劫持攻击。
  • List接口
  • HTML5 MathML
  • 【mysql】如何解决主从架构从库延迟问题
  • MybatisPlus介绍与应用
  • 泷羽sec学习打卡-brupsuite8伪造IP和爬虫审计
  • CTF_1
  • STM32-笔记4-按键点亮led
  • Jsckson @JsonValue 注解
  • springcloud打成jar包运行在centos7
  • 使用html2canvas库对可滚动的dom节点导出全量的图片
  • 【机器人】Graspness 端到端 抓取点估计 | 论文解读
  • Linux 磁盘空间不足之排查方法(Troubleshooting Method for Linux Disk Space Shortage)
  • 上传图片的预览
  • 【从零开始入门unity游戏开发之——C#篇16】C#什么是面向对象编程?
  • Kioptix Level 4(0基础教学)
  • 机器学习经典算法(scikit-learn)
  • 深入探索Vue.js中的v-show指令:动态控制DOM元素的高级技巧
  • Pandas教程之二十九: 使用 Pandas 处理日期和时间
  • 键盘常见键的keyCode和对应的键名
  • VMware OpenWrt怎么桥接模式联网