YOLOv10-1.1部分代码阅读笔记-base.py

base.py

ultralytics\data\base.py

目录

base.py

1.所需的库和模块

2.class BaseDataset(Dataset): 


1.所需的库和模块

# Ultralytics YOLO , AGPL-3.0 license

import glob
import math
import os
import random
from copy import deepcopy
from multiprocessing.pool import ThreadPool
from pathlib import Path
from typing import Optional

import cv2
import numpy as np
import psutil
from torch.utils.data import Dataset

from ultralytics.utils import DEFAULT_CFG, LOCAL_RANK, LOGGER, NUM_THREADS, TQDM
from .utils import HELP_URL, IMG_FORMATS

2.class BaseDataset(Dataset): 

# 这段代码定义了一个名为 BaseDataset 的类,它是用于图像处理和数据增强的基础数据集类。这个类继承自 PyTorch 的 Dataset 类,并提供了一系列方法来加载、处理和转换图像数据。
# 定义了一个名为 BaseDataset 的类,继承自 PyTorch 的 Dataset 类。
class BaseDataset(Dataset):
    # 用于加载和处理图像数据的基本数据集类。
    """
    Base dataset class for loading and processing image data.

    Args:
        img_path (str): Path to the folder containing images.
        imgsz (int, optional): Image size. Defaults to 640.
        cache (bool, optional): Cache images to RAM or disk during training. Defaults to False.
        augment (bool, optional): If True, data augmentation is applied. Defaults to True.
        hyp (dict, optional): Hyperparameters to apply data augmentation. Defaults to None.
        prefix (str, optional): Prefix to print in log messages. Defaults to ''.
        rect (bool, optional): If True, rectangular training is used. Defaults to False.
        batch_size (int, optional): Size of batches. Defaults to None.
        stride (int, optional): Stride. Defaults to 32.
        pad (float, optional): Padding. Defaults to 0.0.
        single_cls (bool, optional): If True, single class training is used. Defaults to False.
        classes (list): List of included classes. Default is None.
        fraction (float): Fraction of dataset to utilize. Default is 1.0 (use all data).

    Attributes:
        im_files (list): List of image file paths.
        labels (list): List of label data dictionaries.
        ni (int): Number of images in the dataset.
        ims (list): List of loaded images.
        npy_files (list): List of numpy file paths.
        transforms (callable): Image transformation function.
    """

    # 这段代码定义了 BaseDataset 类的构造函数 __init__ ,用于初始化一个用于图像处理和数据增强的基础数据集类。
    # 定义了类的构造函数,接受多个参数,用于配置数据集的行为。
    # 1.img_path :图像文件路径。
    # 2.imgsz :目标图像尺寸,默认为 640。
    # 3.cache :是否缓存图像,可以是 "ram" 或 "disk"。
    # 4.augment :是否进行数据增强,默认为 True 。
    # 5.hyp :超参数配置,默认为 DEFAULT_CFG 。
    # 6.prefix :日志前缀,默认为空字符串。
    # 7.rect :是否使用矩形训练,默认为 False 。
    # 8.batch_size :批量大小,默认为 16。
    # 9.stride :模型的步长,默认为 32。
    # 10.pad :填充比例,默认为 0.5。
    # 11.single_cls :是否为单类别训练,默认为 False 。
    # 12.classes :包含的类别列表,默认为 None 。
    # 13.fraction :数据集的使用比例,默认为 1.0。
    def __init__(
        self,
        img_path,
        imgsz=640,
        cache=False,
        augment=True,
        hyp=DEFAULT_CFG,
        prefix="",
        rect=False,
        batch_size=16,
        stride=32,
        pad=0.5,
        single_cls=False,
        classes=None,
        fraction=1.0,
    ):
        # 使用给定的配置和选项初始化 BaseDataset。
        """Initialize BaseDataset with given configuration and options."""
        # 调用父类 Dataset 的构造函数,确保父类的初始化方法被正确调用。
        super().__init__()
        # 将传入的 img_path 参数值赋给类的实例变量 self.img_path ,用于 存储图像文件的路径 。
        self.img_path = img_path
        # 将传入的 imgsz 参数值赋给类的实例变量 self.imgsz ,用于 指定目标图像的尺寸 。
        self.imgsz = imgsz
        # 将传入的 augment 参数值赋给类的实例变量 self.augment ,用于控制 是否进行数据增强 。
        self.augment = augment
        # 将传入的 single_cls 参数值赋给类的实例变量 self.single_cls ,用于控制 是否为单类别训练 。
        self.single_cls = single_cls
        # 将传入的 prefix 参数值赋给类的实例变量 self.prefix ,用于 存储日志前缀 。
        self.prefix = prefix
        # 将传入的 fraction 参数值赋给类的实例变量 self.fraction ,用于 指定数据集的使用比例 。
        self.fraction = fraction
        # 调用 get_img_files 方法,传入 self.img_path ,获取 图像文件的路径列表 ,并赋值给 self.im_files 。
        self.im_files = self.get_img_files(self.img_path)
        # 调用 get_labels 方法,获取 标签数据 ,并赋值给 self.labels 。
        self.labels = self.get_labels()
        # 调用 update_labels 方法,传入 classes 参数,更新标签数据。如果 classes 不为 None ,则 只保留指定的类别 。如果 single_cls 为 True ,则将 所有类别的标签设置为 0 。
        self.update_labels(include_class=classes)  # single_cls and include_class
        # 计算 标签数据的长度 ,即 图像的数量 ,并赋值给 self.ni 。
        self.ni = len(self.labels)  # number of images
        # 将传入的 rect 参数值赋给类的实例变量 self.rect ,用于控制 是否使用矩形训练 。
        self.rect = rect
        # 将传入的 batch_size 参数值赋给类的实例变量 self.batch_size ,用于 指定批量大小 。
        self.batch_size = batch_size
        # 将传入的 stride 参数值赋给类的实例变量 self.stride ,用于 指定模型的步长 。
        self.stride = stride
        # 将传入的 pad 参数值赋给类的实例变量 self.pad ,用于 指定填充比例 。
        self.pad = pad
        # 如果 self.rect 为 True ,则。
        if self.rect:
            #  断言 self.batch_size 不为 None ,确保批量大小已设置。
            assert self.batch_size is not None
            # 调用 set_rectangle 方法,设置矩形训练的图像形状。
            self.set_rectangle()

        # Buffer thread for mosaic images    马赛克图像的缓冲线程。
        # 初始化一个空列表,用于存储 mosaic 图像的索引 。缓冲区的大小等于批量大小。
        self.buffer = []  # buffer size = batch size
        # 计算 最大缓冲区长度 。如果 self.augment 为 True ,则最大缓冲区长度为 self.ni 、 self.batch_size * 8 和 1000 中的最小值。如果 self.augment 为 False ,则最大缓冲区长度为 0 。
        self.max_buffer_length = min((self.ni, self.batch_size * 8, 1000)) if self.augment else 0

        # Cache images    缓存图片。
        # 检查是否将图像缓存到 RAM 中。如果 cache 为 "ram" 且 check_cache_ram 返回 False ,则将 cache 设置为 False 。
        if cache == "ram" and not self.check_cache_ram():
            cache = False
        # self.ims :初始化一个列表,用于 存储缓存的图像 ,长度为 self.ni ,初始值为 None 。
        # self.im_hw0 :初始化一个列表,用于 存储图像的原始高度和宽度 ,长度为 self.ni ,初始值为 None 。
        # self.im_hw :初始化一个列表,用于 存储图像调整后的高度和宽度 ,长度为 self.ni ,初始值为 None 。
        self.ims, self.im_hw0, self.im_hw = [None] * self.ni, [None] * self.ni, [None] * self.ni

        # path.with_suffix(suffix)
        # with_suffix 是 Python pathlib 模块中 Path 类的一个方法,它用于修改路径对象的后缀(扩展名)。
        # path : Path 类的实例。
        # suffix :要设置的新后缀。如果为空字符串,则移除路径的当前后缀。
        # 返回值 :
        # 返回一个新的 Path 对象,其后缀被修改为指定的 suffix 。
        # 方法功能 :
        # 如果原始路径没有后缀, with_suffix 方法会将指定的后缀追加到路径的末尾。
        # 如果原始路径已经有后缀, with_suffix 方法会替换为指定的新后缀。
        # 如果指定的 suffix 是空字符串, with_suffix 方法会移除路径的当前后缀。
        # 注意事项 :
        # with_suffix 方法不会修改原始的 Path 对象,而是返回一个新的 Path 对象。
        # 这个方法在处理文件扩展名时非常有用,尤其是在你需要动态更改文件类型或处理不同格式的文件时。

        # 生成一个列表,包含 每个图像文件对应的 .npy 文件路径 。
        self.npy_files = [Path(f).with_suffix(".npy") for f in self.im_files]
        # 如果 cache 为 True ,则调用 cache_images 方法缓存图像。
        if cache:
            self.cache_images(cache)

        # Transforms    变换。
        # 调用 build_transforms 方法,传入超参数 hyp ,初始化数据增强转换。这个方法应该返回一个数据增强转换的组合,用于在数据加载时应用。
        self.transforms = self.build_transforms(hyp=hyp)
    # BaseDataset 类的构造函数 __init__ 用于初始化一个用于图像处理和数据增强的基础数据集类。它接受多个参数来配置数据集的行为,包括图像路径、目标尺寸、缓存设置、数据增强、超参数配置、日志前缀、矩形训练、批量大小、步长、填充比例、单类别训练、类别列表和数据集使用比例。构造函数中初始化了多个实例变量,并调用了多个方法来加载图像文件、标签,更新标签,设置矩形训练,初始化缓冲区和缓存,以及初始化数据增强转换。这个类为图像分类、目标检测等任务提供了一个灵活且强大的数据处理框架。

    # 这段代码定义了 BaseDataset 类中的 get_img_files 方法,用于读取图像文件路径,支持从目录和文件中读取路径,并进行筛选和排序。
    # 定义了 get_img_files 方法,传入 1.img_path 参数,用于读取图像文件路径。
    def get_img_files(self, img_path):
        # 读取图像文件。
        """Read image files."""
        try:
            # 初始化一个空列表 f ,用于 存储图像文件路径 。
            f = []  # image files
            # 如果 img_path 是一个列表,则遍历列表中的每个路径。 如果 img_path 是一个单一路径,则将其转换为列表并遍历。
            for p in img_path if isinstance(img_path, list) else [img_path]:
                # 使用 Path 类将路径转换为操作系统无关的路径对象。
                p = Path(p)  # os-agnostic
                # 如果 p 是一个目录,则使用 glob.glob 递归地查找目录下的所有文件,并将路径添加到列表 f 中。
                if p.is_dir():  # dir
                    f += glob.glob(str(p / "**" / "*.*"), recursive=True)
                    # F = list(p.rglob('*.*'))  # pathlib
                # 检查路径 p 是否是一个文件。如果是文件,则进入这个分支。
                elif p.is_file():  # file
                    # 使用 with open(p) as t 打开文件 p ,并将其内容读取到变量 t 中。
                    with open(p) as t:
                        # 读取文件内容,去除首尾空白字符,然后按行分割,返回一个 包含每行内容的列表 。
                        t = t.read().strip().splitlines()
                        # 转换为全局路径。 p.parent 获取文件 p 的父目录路径。 str(p.parent) + os.sep 将父目录路径转换为字符串,并添加操作系统的路径分隔符,确保路径的正确性。
                        parent = str(p.parent) + os.sep
                        # 处理每行路径。使用列表推导式处理文件 t 中的每一行路径 x 。如果路径 x 以 ./ 开头,则将其替换为父目录路径 parent ,将相对路径转换为绝对路径。 如果路径 x 不以 ./ 开头,则保持不变。 将处理后的路径列表添加到 f 中。
                        f += [x.replace("./", parent) if x.startswith("./") else x for x in t]  # local to global path
                        # 注释掉的 pathlib 版本。这行代码是注释掉的,使用 pathlib 库的版本。它使用 p.parent / x.lstrip(os.sep) 将相对路径 x 转换为绝对路径。 x.lstrip(os.sep) 去除路径 x 开头的路径分隔符,确保路径的正确性。 p.parent / x.lstrip(os.sep) 将相对路径 x 转换为绝对路径,并添加到列表 F 中。
                        # F += [p.parent / x.lstrip(os.sep) for x in t]  # local to global path (pathlib)
                # 如果 p 既不是目录也不是文件,则抛出 FileNotFoundError 异常。
                else:
                    raise FileNotFoundError(f"{self.prefix}{p} does not exist")    # {self.prefix}{p} 不存在。
            # 使用列表推导式筛选出扩展名在 IMG_FORMATS 中的文件路径,并将路径中的斜杠统一为操作系统的默认斜杠。 对筛选后的文件路径进行排序。
            im_files = sorted(x.replace("/", os.sep) for x in f if x.split(".")[-1].lower() in IMG_FORMATS)
            # self.img_files = sorted([x for x in f if x.suffix[1:].lower() in IMG_FORMATS])  # pathlib
            # 使用 assert 语句确保至少有一个图像文件被找到,否则抛出异常。
            assert im_files, f"{self.prefix}No images found in {img_path}"    # {self.prefix}在{img_path}中未找到任何图像。
        # 捕获并处理任何异常,如果发生异常则抛出 FileNotFoundError 。
        except Exception as e:
            raise FileNotFoundError(f"{self.prefix}Error loading data from {img_path}\n{HELP_URL}") from e    # {self.prefix}从 {img_path} 加载数据时出错\n{HELP_URL}。
        # 检查 self.fraction 是否小于 1。如果小于 1,则表示只使用数据集的一部分图像。
        if self.fraction < 1:
            # im_files = im_files[: round(len(im_files) * self.fraction)]
            # 计算 要选择的图像数量 num_elements_to_select ,通过将图像文件列表 im_files 的长度乘以 self.fraction ,然后四舍五入到最近的整数。 len(im_files) 获取图像文件列表的长度。 self.fraction 是数据集的使用比例,范围在 0 到 1 之间。
            num_elements_to_select = round(len(im_files) * self.fraction)

            # random.sample(population, k)
            # random.sample 是 Python 标准库 random 模块中的一个函数,它用于从一个序列中随机选择指定数量的不重复元素,并返回一个新列表。
            # population :一个序列,表示可供选择的元素集合。
            # k :一个整数,表示需要随机选择的元素数量。
            # 返回值 :
            # 返回一个新列表,包含从 population 中随机选择的 k 个不重复元素。
            # 功能 :
            # random.sample 函数可以确保从 population 中选择的 k 个元素是唯一的,不会有重复。如果 population 中的元素数量小于 k ,则抛出 ValueError 。
            # 注意事项 :
            # population 必须是一个序列,例如列表、元组或字符串。
            # k 的值不能大于 population 中元素的数量,否则会抛出 ValueError 。
            # 每次调用 random.sample 都会生成一个新的随机选择的列表,因为随机数生成器的状态在每次调用时都会改变。

            # 使用 random.sample 方法从 im_files 中随机选择 num_elements_to_select 个图像文件。 random.sample 方法返回一个新列表,包含从原始列表中随机选择的指定数量的元素,且不重复。
            im_files = random.sample(im_files, num_elements_to_select)
        # 返回筛选和处理后的图像文件路径列表。
        return im_files
    # get_img_files 方法用于读取图像文件路径,支持从目录和文件中读取路径,并进行筛选和排序。如果指定了数据集使用比例 self.fraction ,则从图像文件列表中随机选择指定比例的文件路径。这个方法为数据加载和处理提供了灵活性,确保只有有效的图像文件被加载。

    # 这段代码定义了 BaseDataset 类中的 update_labels 方法,用于更新数据集的标签信息,以仅包含指定的类别。
    # 定义了 update_labels 方法,接受一个可选的参数。
    # 1.include_class :该参数是一个列表,包含需要保留的类别。
    def update_labels(self, include_class: Optional[list]):
        # 更新标签以仅包含这些类(可选)。
        """Update labels to include only these classes (optional)."""
        # 初始化类别数组。将 include_class 转换为 NumPy 数组,并将其形状重塑为 (1, -1) ,即一个行向量。这有助于后续的比较操作。
        include_class_array = np.array(include_class).reshape(1, -1)
        # 遍历标签。遍历 self.labels 列表中的每个标签字典。
        for i in range(len(self.labels)):
            # 检查是否需要过滤类别。如果 include_class 不为 None ,则进行类别过滤。
            if include_class is not None:
                # 获取标签信息。从当前标签字典中提取 类别 、 边界框 、 分割掩码 和 关键点 信息。
                cls = self.labels[i]["cls"]
                bboxes = self.labels[i]["bboxes"]
                segments = self.labels[i]["segments"]
                keypoints = self.labels[i]["keypoints"]
                # 过滤类别。使用 == 操作符比较 cls 和 include_class_array ,生成一个布尔数组。 使用 any(1) 沿着行方向(即每个类别)进行逻辑或操作,生成一个布尔数组 j ,表示每个边界框是否属于指定的类别。
                j = (cls == include_class_array).any(1)
                # 更新标签信息。
                # 使用布尔数组 j 过滤 cls 和 bboxes ,只保留 属于指定类别的 边界框 和 类别标签 。
                self.labels[i]["cls"] = cls[j]
                self.labels[i]["bboxes"] = bboxes[j]
                # 如果存在分割掩码 segments ,则使用布尔数组 j 过滤 segments ,只保留 属于指定类别的 分割掩码 。
                if segments:
                    # 这行代码是 update_labels 方法中的一部分,用于更新标签字典中的 segments 字段,以仅包含属于指定类别的分割掩码。
                    # enumerate(j) :遍历布尔数组 j ,返回每个元素的索引 si 和值 idx 。
                    # if idx :仅选择 idx 为 True 的索引 si 。
                    # segments[si] :根据索引 si 从 segments 列表中选择对应的分割掩码。
                    # 最终,列表推导式返回一个新列表,包含所有属于指定类别的分割掩码。
                    # 这行代码通过列表推导式和布尔数组 j ,从 segments 列表中筛选出属于指定类别的分割掩码,并更新标签字典中的 segments 字段。这样可以确保标签信息与训练需求一致,特别是在多类别数据集中只保留特定类别的分割掩码时非常有用。
                    self.labels[i]["segments"] = [segments[si] for si, idx in enumerate(j) if idx]
                # 如果存在关键点 keypoints ,则使用布尔数组 j 过滤 keypoints ,只保留属于指定类别的关键点。
                if keypoints is not None:
                    self.labels[i]["keypoints"] = keypoints[j]
            # 处理单类别训练。如果 self.single_cls 为 True ,则将所有类别标签设置为 0,表示单类别训练。
            if self.single_cls:
                self.labels[i]["cls"][:, 0] = 0
    # update_labels 方法用于更新数据集的标签信息,以仅包含指定的类别。如果 include_class 不为 None ,则过滤掉不属于指定类别的标签信息。如果 self.single_cls 为 True ,则将所有类别标签设置为 0,适用于单类别训练。这个方法确保数据集的标签信息与训练需求一致,提高了数据处理的灵活性和适应性。

    # 这段代码定义了 BaseDataset 类中的 load_image 方法,用于从数据集索引 i 加载一张图像,并返回图像及其调整后的尺寸。
    # 定义了 load_image 方法,接受两个参数。
    # 1.i :图像的索引。
    # 2.rect_mode :布尔值,表示是否保持图像的宽高比进行调整,默认为 True 。
    def load_image(self, i, rect_mode=True):
        # 从数据集索引“i”加载 1 个图像,返回(im,调整大小的 hw)。
        """Loads 1 image from dataset index 'i', returns (im, resized hw)."""
        # 初始化变量。
        # im :从 self.ims 列表中获取第 i 个 图像的缓存数据 。
        # f :从 self.im_files 列表中获取第 i 个 图像文件的路径 。
        # fn :从 self.npy_files 列表中获取第 i 个图像的 .npy 文件路径。
        im, f, fn = self.ims[i], self.im_files[i], self.npy_files[i]
        # 这段代码是 load_image 方法中的一部分,用于处理图像加载逻辑。具体来说,它检查图像是否已缓存到 RAM 中,如果没有缓存,则尝试从 .npy 文件或原始图像文件中加载图像。
        # 检查图像 im 是否为 None ,表示图像未缓存到 RAM 中。如果是 None ,则需要从文件中加载图像。
        if im is None:  # not cached in RAM
            # 从 .npy 文件加载图像。
            # 检查 .npy 文件是否存在。
            if fn.exists():  # load npy
                # 尝试从 .npy 文件中加载图像。
                try:
                    im = np.load(fn)
                # 捕获加载过程中可能发生的任何异常。
                except Exception as e:
                    # 记录警告信息,提示 .npy 文件损坏,并记录错误原因。
                    LOGGER.warning(f"{self.prefix}WARNING ⚠️ Removing corrupt *.npy image file {fn} due to: {e}")    # {self.prefix}警告 ⚠️ 删除损坏的 *.npy 图像文件 {fn},原因是:{e}。
                    # 删除损坏的 .npy 文件, missing_ok=True 表示如果文件不存在也不抛出异常。
                    Path(fn).unlink(missing_ok=True)
                    # 使用 OpenCV 从原始图像文件 f 读取图像。
                    im = cv2.imread(f)  # BGR
            # 从原始图像文件加载图像。
            # 如果 .npy 文件不存在,则直接使用 OpenCV 从原始图像文件 f 读取图像。
            else:  # read image
                im = cv2.imread(f)  # BGR
            # 检查图像是否加载成功。
            # 检查图像是否成功加载。如果 im 为 None ,表示图像文件未找到或无法读取。
            if im is None:
                # 抛出 FileNotFoundError 异常,提示图像文件未找到。
                raise FileNotFoundError(f"Image Not Found {f}")    # 未找到图片 {f} 。
        # 这段代码处理了图像加载的逻辑,确保图像可以从 .npy 文件或原始图像文件中正确加载。如果 .npy 文件损坏或不存在,会尝试从原始图像文件中加载图像。如果图像文件也不存在或无法读取,会抛出 FileNotFoundError 异常。通过这种方式,代码确保了图像加载的健壮性,避免了因文件损坏或缺失导致的程序错误。

            # 这段代码是 load_image 方法中的一部分,用于调整图像尺寸。具体来说,它根据 rect_mode 参数的值,决定是保持图像的宽高比进行调整,还是将图像拉伸到目标尺寸。
            # 从图像 im 的形状中提取原始高度 h0 和宽度 w0 。
            h0, w0 = im.shape[:2]  # orig hw
            # 保持宽高比调整图像尺寸。
            # rect_mode :布尔值,表示是否保持图像的宽高比进行调整。
            if rect_mode:  # resize long side to imgsz while maintaining aspect ratio
                # 计算调整比例 r ,即目标尺寸 self.imgsz 与图像的长边(高度或宽度)的比值。
                r = self.imgsz / max(h0, w0)  # ratio
                # 如果调整比例 r 不等于 1,表示需要调整图像尺寸。
                if r != 1:  # if sizes are not equal
                    # 计算调整后的宽度 w 和高度 h ,确保调整后的尺寸不超过目标尺寸 self.imgsz 。
                    w, h = (min(math.ceil(w0 * r), self.imgsz), min(math.ceil(h0 * r), self.imgsz))
                    # 使用 OpenCV 的 resize 方法调整图像尺寸,插值方法为 cv2.INTER_LINEAR ,适用于双线性插值。
                    im = cv2.resize(im, (w, h), interpolation=cv2.INTER_LINEAR)
            # 拉伸图像到目标尺寸。
            # 如果图像的原始尺寸不是目标尺寸 self.imgsz x self.imgsz ,则将图像拉伸到目标尺寸。
            elif not (h0 == w0 == self.imgsz):  # resize by stretching image to square imgsz
                # 使用 OpenCV 的 resize 方法将图像拉伸到目标尺寸 self.imgsz x self.imgsz ,插值方法为 cv2.INTER_LINEAR 。
                im = cv2.resize(im, (self.imgsz, self.imgsz), interpolation=cv2.INTER_LINEAR)
            # 这段代码根据 rect_mode 参数的值,决定如何调整图像尺寸。如果 rect_mode 为 True ,则保持图像的宽高比,将长边调整到目标尺寸 self.imgsz 。 如果 rect_mode 为 False ,则将图像拉伸到目标尺寸 self.imgsz x self.imgsz 。通过这种方式,可以灵活地处理不同尺寸的图像,确保图像在训练或推理时具有统一的输入尺寸。这对于许多计算机视觉任务(如目标检测和图像分类)是非常重要的。

            # 这段代码是 load_image 方法中的一部分,用于处理图像缓存和缓冲区管理。具体来说,它在启用数据增强时将图像及其相关信息缓存到内存中,并管理一个缓冲区以限制缓存的图像数量。
            # Add to buffer if training with augmentations
            # 检查是否启用数据增强。如果 self.augment 为 True ,则执行缓存操作。
            if self.augment:
                # 缓存图像及其相关信息。
                # self.ims[i] :缓存 加载的图像 im 。
                # self.im_hw0[i] :缓存 图像的原始尺寸 (h0, w0) 。
                # self.im_hw[i] :缓存 图像调整后的尺寸 im.shape[:2] 。
                self.ims[i], self.im_hw0[i], self.im_hw[i] = im, (h0, w0), im.shape[:2]  # im, hw_original, hw_resized
                # 将图像索引添加到缓冲区。将图像索引 i 添加到缓冲区 self.buffer 中。
                self.buffer.append(i)
                # 管理缓冲区长度。
                # 如果缓冲区长度达到最大值 self.max_buffer_length ,则移除最早添加的图像索引及其缓存数据。
                if len(self.buffer) >= self.max_buffer_length:
                    # 从缓冲区中移除最早添加的图像索引 j 。
                    j = self.buffer.pop(0)
                    # 将移除的图像索引 j 对应的缓存数据设置为 None ,释放内存。
                    self.ims[j], self.im_hw0[j], self.im_hw[j] = None, None, None

            # 返回图像和尺寸信息。返回 加载的图像 im 、 原始尺寸 (h0, w0) 和 调整后的尺寸 im.shape[:2] 。
            return im, (h0, w0), im.shape[:2]

        # 返回缓存的图像和尺寸信息。如果图像已缓存到 RAM 中,直接返回缓存的图像和尺寸信息。
        return self.ims[i], self.im_hw0[i], self.im_hw[i]
        # 这段代码在启用数据增强时,将图像及其相关信息缓存到内存中,并管理一个缓冲区以限制缓存的图像数量。通过这种方式,可以确保在数据增强训练过程中,图像可以快速加载和访问,同时避免内存溢出。缓冲区的管理确保了缓存的图像数量不会超过预设的最大值,从而平衡了内存使用和加载速度。
    # load_image 方法用于从数据集索引 i 加载一张图像,并根据 rect_mode 参数调整图像尺寸。如果图像未缓存到 RAM 中,方法会从 .npy 文件或原始图像文件中加载图像,并进行必要的尺寸调整。加载的图像和尺寸信息会被缓存到 RAM 中,以便后续快速访问。这个方法确保图像可以被快速加载,特别是在使用数据增强时,可以显著提高训练速度。

    # 这段代码定义了 BaseDataset 类中的 cache_images 方法,用于将图像缓存到内存(RAM)或磁盘(disk)。
    # 定义了 cache_images 方法,接受一个参数。
    # 1.cache :用于指定缓存方式,可以是 "ram" 或 "disk"。
    def cache_images(self, cache):
        # 将图像缓存到内存或磁盘。
        """Cache images to memory or disk."""
        # 初始化变量。
        # b 初始化为 0,用于 累计缓存图像所需的字节数 。
        # gb 初始化为 1 << 30 ,即 1 GB 的字节数。
        b, gb = 0, 1 << 30  # bytes of cached images, bytes per gigabytes
        # 选择缓存函数。如果 cache 为 "disk",则选择 self.cache_images_to_disk 方法。 否则,选择 self.load_image 方法。
        fcn = self.cache_images_to_disk if cache == "disk" else self.load_image
        # 使用线程池缓存图像。
        #  使用 ThreadPool 创建一个线程池,线程数为 NUM_THREADS 。
        with ThreadPool(NUM_THREADS) as pool:

            # pool.imap(func, iterable, chunksize=None)
            # imap() 方法用于将一个可迭代的输入序列分块分配到线程池中的线程进行处理,并将结果返回一个迭代器。这个方法特别适用于需要顺序处理输入和输出的场景。
            # 参数 :
            # func :一个函数,它将被调用并传入 iterable 中的每个项目。
            # iterable :一个可迭代对象,其元素将被传递给 func 函数。
            # chunksize :(可选)一个整数,指定了每个任务传递给 func 的项目数量。默认值为 1,意味着每个任务只包含一个项目。如果设置为大于 1 的值,那么 func 将接收到一个包含多个项目的列表。
            # 返回值 :
            # 返回一个 Iterator ,它生成每个输入元素经过 func 处理后的结果。
            # 特点 :
            # 结果的顺序与输入序列的顺序相同。
            # 如果任何一个任务因为异常而终止, imap() 会立即抛出异常。
            # 它允许主线程在子线程完成工作之前继续执行,而不是等待所有任务完成。
            # ThreadPool.imap() 是处理 I/O 密集型任务或者需要顺序处理结果的并发任务的有用工具。与之相对的是 imap_unordered() ,它同样返回一个迭代器,但是结果的顺序可能与输入序列不同,适用于不在乎结果顺序的场景。

            # 使用 pool.imap 并行调用 fcn 方法,传入图像索引 range(self.ni) 。
            results = pool.imap(fcn, range(self.ni))
            # 使用 TQDM 创建一个进度条,显示缓存进度。
            # class TQDM(tqdm_original):
            # -> TQDM 类用于创建一个进度条,可以显示任务的进度。
            # -> def __init__(self, *args, **kwargs):
            pbar = TQDM(enumerate(results), total=self.ni, disable=LOCAL_RANK > 0)
            # 处理缓存结果。
            # 遍历进度条中的每个结果 i, x 。
            # 在 cache_images 方法中的 for i, x in pbar: 循环中, x 是从 results 迭代器中获取的每个元素,这个迭代器是由 pool.imap(fcn, range(self.ni)) 生成的。 fcn 是根据 cache 参数选择的函数,可以是 self.cache_images_to_disk 或 self.load_image 。因此, x 的内容取决于 fcn 的返回值。
            # 当 cache == "disk" 时如果 cache 为 "disk",则 fcn 是 self.cache_images_to_disk 。这个方法通常会将图像保存为 .npy 文件,并返回一个表示操作成功与否的值(例如 True 或 None )。在这种情况下, x 通常不包含有用的信息,因为主要操作是将图像保存到磁盘。
            # 当 cache == "ram" 时如果 cache 为 "ram",则 fcn 是 self.load_image 。这个方法会加载图像并返回一个包含图像数据、原始尺寸和调整后尺寸的元组。在这种情况下, x 包含以下内容 :
            # 图像数据 :加载的图像,通常是一个 NumPy 数组。
            # 原始尺寸 :图像的原始高度和宽度,通常是一个元组 (h0, w0) 。
            # 调整后尺寸 :图像调整后的高度和宽度,通常是一个元组 (h, w) 。
            for i, x in pbar:
                # 如果 cache 为 "disk",则累加 .npy 文件的大小。
                if cache == "disk":
                    b += self.npy_files[i].stat().st_size
                # 如果 cache 为 "ram",则将 图像 、 原始尺寸 和 调整后的尺寸 存储到 self.ims 、 self.im_hw0 和 self.im_hw 中,并累加图像的字节数。
                else:  # 'ram'
                    self.ims[i], self.im_hw0[i], self.im_hw[i] = x  # im, hw_orig, hw_resized = load_image(self, i)
                    b += self.ims[i].nbytes
                # 更新进度条的描述,显示当前已缓存的图像大小。
                pbar.desc = f"{self.prefix}Caching images ({b / gb:.1f}GB {cache})"    # {self.prefix}缓存图像({b/gb:.1f}GB{cache})。
            # 关闭进度条。
            pbar.close()
    # cache_images 方法通过并行处理将图像缓存到内存或磁盘。根据 cache 参数的不同,选择不同的缓存方法。使用 ThreadPool 和 TQDM 提高缓存效率并显示进度。这个方法确保图像可以被快速加载,特别是在使用数据增强时,可以显著提高训练速度。通过累加缓存图像的字节数,可以实时显示缓存进度和已使用的内存大小。

    # 这段代码定义了 BaseDataset 类中的 cache_images_to_disk 方法,用于将图像保存为 .npy 文件,以便更快地加载。
    # 定义了 cache_images_to_disk 方法,接受一个参数。
    # 1.i :表示图像的索引。
    def cache_images_to_disk(self, i):
        # 将图像保存为 *.npy 文件以便更快地加载。
        """Saves an image as an *.npy file for faster loading."""
        # 获取 .npy 文件路径。
        # 从 self.npy_files 列表中获取第 i 个图像的 .npy 文件路径。 self.npy_files 是在构造函数中生成的,包含每个图像文件对应的 .npy 文件路径。
        f = self.npy_files[i]
        # 检查文件是否存在。检查 .npy 文件是否已存在。如果文件已存在,则不进行任何操作,避免重复保存。
        if not f.exists():
            # 保存图像为 .npy 文件。
            # f.as_posix() :将 Path 对象转换为 POSIX 风格的字符串路径。
            # cv2.imread(self.im_files[i]) :使用 OpenCV 读取第 i 个图像文件。
            # np.save(f.as_posix(), ...) :将读取的图像数据保存为 .npy 文件。
            # allow_pickle=False :禁用 pickle 序列化,提高文件的兼容性和安全性。
            np.save(f.as_posix(), cv2.imread(self.im_files[i]), allow_pickle=False)
    # cache_images_to_disk 方法用于将图像保存为 .npy 文件,以便更快地加载。这个方法检查目标 .npy 文件是否存在,如果不存在,则从原始图像文件中读取图像数据并保存为 .npy 文件。通过这种方式,可以显著提高图像加载速度,特别是在频繁读取图像数据时。 .npy 文件格式是 NumPy 的二进制文件格式,专门用于存储数组数据,读写速度比常规图像文件格式更快。

    # 这段代码定义了 BaseDataset 类中的 check_cache_ram 方法,用于检查将图像缓存到 RAM 中所需的内存是否足够。
    # 定义了 check_cache_ram 方法,接受一个可选参数。
    # 1.safety_margin :默认值为 0.5,表示安全边际,用于确保有足够的内存余量。
    def check_cache_ram(self, safety_margin=0.5):
        # 检查图像缓存要求与可用内存。
        """Check image caching requirements vs available memory."""
        # 初始化变量。 b 初始化为 0,用于 累计缓存图像所需的字节数 。 gb 初始化为 1 << 30 ,即 1 GB 的字节数。
        b, gb = 0, 1 << 30  # bytes of cached images, bytes per gigabytes
        # 采样图像。 n 设置为 self.ni 和 30 中的较小值,表示从数据集中随机选择 30 张图像进行采样,以估计缓存所有图像所需的内存。
        n = min(self.ni, 30)  # extrapolate from 30 random images
        # 计算所需内存。
        # 遍历 n 次,每次随机选择一张图像 im 。
        for _ in range(n):
            im = cv2.imread(random.choice(self.im_files))  # sample image
            # im.shape[0] 和 im.shape[1] 分别表示图像的高度和宽度。 ratio 计算 目标尺寸 self.imgsz 与图像的最大边长的比值。
            ratio = self.imgsz / max(im.shape[0], im.shape[1])  # max(h, w)  # ratio
            # im.nbytes 计算图像的字节数。
            # b += im.nbytes * ratio**2 累加调整尺寸后的图像所需的字节数。
            b += im.nbytes * ratio**2
        # 计算总所需内存。 mem_required 计算缓存整个数据集所需的总内存(以 GB 为单位),考虑了安全边际 safety_margin 。
        mem_required = b * self.ni / n * (1 + safety_margin)  # GB required to cache dataset into RAM

        # psutil.virtual_memory()
        # psutil.virtual_memory() 是一个函数,属于 psutil 库,用于获取系统虚拟内存(RAM)的使用情况。
        # 参数 :无参数。
        # 返回值 :
        # 该函数返回一个命名元组( psutil._common.smem ),其中包含了以下属性 :
        # total :总物理内存大小,单位为字节。
        # available :可供分配的内存大小,单位为字节,这个值是系统认为可用的内存,包括缓存和缓冲区占用的内存。
        # percent :已使用内存的百分比。
        # used :已使用的内存大小,单位为字节。
        # free :空闲的内存大小,单位为字节。
        # active :当前正在使用或最近使用的内存,单位为字节。
        # inactive :标记为未使用的内存,单位为字节。
        # buffers :缓存数据,如文件系统元数据,单位为字节。
        # cached :缓存数据,单位为字节。
        # shared :可由多个进程共享的内存,单位为字节。
        # slab :用于内核数据结构的内存,单位为字节。

        # 获取系统内存信息。使用 psutil.virtual_memory() 获取 系统的虚拟内存信息 。
        mem = psutil.virtual_memory()
        # 检查内存是否足够。 cache 设置为 True 如果 mem_required 小于可用内存 mem.available ,否则设置为 False 。
        cache = mem_required < mem.available  # to cache or not to cache, that is the question
        # 日志输出。
        # 如果 cache 为 False ,则输出日志信息,显示所需内存、可用内存和总内存,以及是否缓存图像的决定。
        if not cache:
            LOGGER.info(
                f'{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images '    # {self.prefix}{mem_required / gb:.1f}GB RAM 需要缓存图像,
                f'with {int(safety_margin * 100)}% safety margin but only '    # 安全裕度为 {int(safety_margin * 100)}%,
                f'{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, '    # 但只有 {mem.available / gb:.1f}/{mem.total / gb:.1f}GB 可用,
                f"{'caching images ✅' if cache else 'not caching images ⚠️'}"    # {'caching images ✅' if cache else 'not caching images ⚠️'}。
            )
        # 返回 cache ,表示是否可以将图像缓存到 RAM 中。
        return cache
    # check_cache_ram 方法通过采样数据集中的图像,估计将整个数据集缓存到 RAM 中所需的内存,并与系统可用内存进行比较。如果可用内存足够,则返回 True ,表示可以缓存图像;否则返回 False ,表示不缓存图像。这个方法确保在缓存图像时不会超出系统内存限制,从而避免内存不足的问题。

    # 这段代码定义了 BaseDataset 类中的 set_rectangle 方法,用于设置 YOLO 检测中边界框的形状为矩形。这种方法通过调整图像的尺寸,使得每个批次中的图像具有相同的宽高比,从而提高训练效率。
    # 定义了 set_rectangle 方法,用于设置 YOLO 检测中边界框的形状为矩形。
    def set_rectangle(self):
        # 将 YOLO 检测的边界框形状设置为矩形。
        """Sets the shape of bounding boxes for YOLO detections as rectangles."""
        # 计算批次索引。
        # np.arange(self.ni) 生成一个从 0 到 self.ni - 1 的数组。 np.floor(np.arange(self.ni) / self.batch_size) 计算 每个图像所属的批次索引 。
        bi = np.floor(np.arange(self.ni) / self.batch_size).astype(int)  # batch index
        # 计算 总的批次数量 nb 。
        nb = bi[-1] + 1  # number of batches

        # 计算宽高比。
        # 从每个标签字典中提取 图像的形状 ( 高度 和 宽度 ),并存储在数组 s 中。
        s = np.array([x.pop("shape") for x in self.labels])  # hw
        # 计算每个图像的宽高比。
        ar = s[:, 0] / s[:, 1]  # aspect ratio
        # 获取 按宽高比排序的索引 。
        irect = ar.argsort()
        # 根据排序索引重新排列 图像文件路径 和 标签 。
        self.im_files = [self.im_files[i] for i in irect]
        self.labels = [self.labels[i] for i in irect]
        # 重新排列 宽高比数组 。
        ar = ar[irect]

        # 设置训练图像形状。
        # Set training image shapes
        # 初始化 shapes 列表,每个元素为 [1, 1] ,表示每个批次的图像形状。
        shapes = [[1, 1]] * nb
        # 遍历每个批次, nb 是总的批次数量。
        for i in range(nb):
            # 获取当前批次的宽高比。
            # bi == i 生成一个布尔数组,表示哪些图像属于当前批次 i 。
            # ar[bi == i] 从宽高比数组 ar 中提取 当前批次的宽高比 ,存储在 ari 中。
            ari = ar[bi == i]
            # 计算最小和最大宽高比。
            # ari.min() 计算当前批次中 图像的最小宽高比 ,存储在 mini 中。
            # ari.max() 计算当前批次中 图像的最大宽高比 ,存储在 maxi 中。
            mini, maxi = ari.min(), ari.max()
            # 调整图像形状。
            # 如果最大宽高比 maxi 小于 1。
            if maxi < 1:
                # 表示当前批次中的所有图像都比目标尺寸宽,因此将图像的高度设置为 maxi ,宽度设置为 1。这样可以确保图像在高度方向上被适当缩放,而宽度方向上保持不变。
                shapes[i] = [maxi, 1]
            # 如果最小宽高比 mini 大于 1。
            elif mini > 1:
                # 表示当前批次中的所有图像都比目标尺寸高,因此将图像的宽度设置为 1 / mini ,高度设置为 1。这样可以确保图像在宽度方向上被适当缩放,而高度方向上保持不变。
                shapes[i] = [1, 1 / mini]

        # 计算批次形状。
        # np.array(shapes) * self.imgsz / self.stride + self.pad 计算 每个批次的图像形状 ,考虑目标尺寸、步长和填充。
        # np.ceil(...).astype(int) 将计算结果向上取整并转换为整数。
        # * self.stride 确保图像形状是步长的倍数。
        # self.batch_shapes 存储 每个批次的图像形状 。
        self.batch_shapes = np.ceil(np.array(shapes) * self.imgsz / self.stride + self.pad).astype(int) * self.stride
        # 存储 每个图像所属的批次索引 。
        self.batch = bi  # batch index of image
    # set_rectangle 方法通过调整图像的尺寸,使得每个批次中的图像具有相同的宽高比,从而提高训练效率。这种方法特别适用于 YOLO 检测任务,因为它可以减少图像尺寸不一致带来的计算开销。通过按宽高比排序和调整图像形状,可以确保每个批次的图像在训练时具有相同的输入尺寸,从而提高训练速度和效率。

    # 这段代码定义了 BaseDataset 类中的 __getitem__ 方法,该方法是 PyTorch Dataset 类的一个特殊方法,用于在数据加载器(DataLoader)中索引数据集时返回单个数据项。
    # 定义了 __getitem__ 方法,接受一个参数。
    # 1.index :表示数据集中的索引位置。
    def __getitem__(self, index):
        # 返回给定索引的转换标签信息。
        """Returns transformed label information for given index."""
        # 返回转换后的图像和标签。
        # self.get_image_and_label(index) :调用 get_image_and_label 方法,传入索引 index ,获取图像和标签信息。
        # self.transforms(...) :将获取的图像和标签信息传递给 self.transforms ,这是一个数据增强和预处理的转换流程,返回转换后的图像和标签。
        # return ... :返回转换后的图像和标签信息。
        return self.transforms(self.get_image_and_label(index))
    # __getitem__ 方法在数据加载器中索引数据集时被调用,返回经过数据增强和预处理的图像和标签信息。这个方法确保每次索引数据集时,都能获取到经过适当处理的数据,适用于训练和验证过程。通过调用 get_image_and_label 方法获取原始图像和标签,再通过 self.transforms 进行转换,可以灵活地应用各种数据增强和预处理策略。

    # 这段代码定义了 BaseDataset 类中的 get_image_and_label 方法,用于获取并处理指定索引 index 处的图像和标签信息。
    # 定义了 get_image_and_label 方法,接受一个参数。
    # 1.index :表示数据集中的索引位置。
    def get_image_and_label(self, index):
        # 从数据集获取并返回标签信息。
        """Get and return label information from the dataset."""
        # 深拷贝标签信息。使用 deepcopy 从 self.labels 列表中获取第 index 个标签的深拷贝。深拷贝确保返回的标签信息不会受到原始数据的修改影响。
        label = deepcopy(self.labels[index])  # requires deepcopy() https://github.com/ultralytics/ultralytics/pull/1948
        # 移除不必要的字段。从标签字典中移除 shape 字段,该字段在矩形训练模式下使用,但在返回标签信息时不需要。
        label.pop("shape", None)  # shape is for rect, remove it
        # 加载图像和尺寸信息。
        # 调用 self.load_image(index) 方法,加载第 index 个图像及其原始尺寸和调整后的尺寸。
        # 将 加载的图像 、 原始尺寸 和 调整后的尺寸 分别赋值给标签字典中的 img 、 ori_shape 和 resized_shape 字段。
        label["img"], label["ori_shape"], label["resized_shape"] = self.load_image(index)
        # 计算缩放比例。计算图像调整后的高度和宽度与原始高度和宽度的比值,存储在 ratio_pad 字段中。这在评估时用于将预测结果转换回原始图像尺寸。
        label["ratio_pad"] = (
            label["resized_shape"][0] / label["ori_shape"][0],
            label["resized_shape"][1] / label["ori_shape"][1],
        )  # for evaluation
        # 处理矩形训练模式。
        # 如果启用了矩形训练模式 ( self.rect 为 True ),则从 self.batch_shapes 中获取第 index 个图像的矩形形状,并存储在 rect_shape 字段中。
        if self.rect:
            label["rect_shape"] = self.batch_shapes[self.batch[index]]
        # 更新标签信息。调用 self.update_labels_info(label) 方法,更新标签信息。这个方法可以由子类重写,以进行特定的标签处理。
        # 返回更新后的标签信息。
        return self.update_labels_info(label)
    # get_image_and_label 方法用于获取并处理指定索引 index 处的图像和标签信息。它加载图像,计算必要的尺寸信息和缩放比例,并根据矩形训练模式进行处理。最后,它调用 update_labels_info 方法更新标签信息,确保返回的标签信息适用于后续的训练或评估过程。通过这种方式,可以灵活地处理不同格式的标签信息,提高数据处理的通用性和灵活性。

    # 这段代码定义了 BaseDataset 类中的 __len__ 方法,该方法是 Python 的特殊方法,用于返回数据集的长度。
    # 定义了 __len__ 方法,这个方法在调用 len(dataset) 时被自动调用,其中 dataset 是 BaseDataset 类的实例。
    def __len__(self):
        # 返回数据集的标签列表的长度。
        """Returns the length of the labels list for the dataset."""
        # 返回数据集长度。
        # len(self.labels) :计算 self.labels 列表的长度,即数据集中的标签数量。
        # return ... :返回计算得到的长度。
        return len(self.labels)
    # __len__ 方法返回数据集的长度,即数据集中的图像和标签对的数量。这个方法在使用 PyTorch 的 DataLoader 时非常有用,因为 DataLoader 会调用 __len__ 方法来确定数据集的大小,从而正确地分批加载数据。通过返回 self.labels 的长度,可以确保数据加载器知道数据集中有多少个样本。

    # 这段代码定义了 BaseDataset 类中的 update_labels_info 方法,该方法用于更新和自定义标签信息的格式。这个方法是一个占位符,具体实现需要在子类中根据实际需求进行定制。
    # 定义了 update_labels_info 方法,接受一个参数。
    # 1.label :表示当前的标签信息字典。
    def update_labels_info(self, label):
        # 在此自定义您的标签格式。
        """Custom your label format here."""
        # 返回标签信息。直接返回传入的 label 字典,不进行任何修改。 这个方法的目的是提供一个钩子(hook),允许子类在需要时重写该方法,以自定义标签信息的格式。
        return label
    # update_labels_info 方法是一个抽象方法,用于更新和自定义标签信息的格式。默认实现中,它直接返回传入的标签信息字典,不进行任何修改。子类可以根据具体需求重写这个方法,以实现特定的标签处理逻辑。例如,可能需要将标签信息从一种格式转换为另一种格式,或者添加额外的字段。通过这种方式, BaseDataset 类提供了灵活性,允许用户根据不同的任务需求自定义数据处理流程。

    # 这段代码定义了 BaseDataset 类中的 build_transforms 方法,但该方法的具体实现被留空,并抛出了 NotImplementedError 异常。这表明 build_transforms 方法是一个抽象方法,需要在子类中具体实现。
    # 定义了 build_transforms 方法,接受一个可选参数。
    # hyp :该参数通常是一个包含超参数的字典,用于配置数据增强的参数。
    def build_transforms(self, hyp=None):
        # 用户可以在此处自定义增强。
        """
        Users can customize augmentations here.

        Example:
            ```python
            if self.augment:
                # Training transforms
                return Compose([])
            else:
                # Val transforms
                return Compose([])
            ```
        """
        # 抛出 NotImplementedError 异常,表示该方法在当前类中没有具体实现。子类必须实现这个方法,以提供具体的转换逻辑。
        raise NotImplementedError
    # build_transforms 方法是一个抽象方法,用于构建和返回数据增强的转换流程。子类需要根据具体的任务需求实现这个方法,以生成适用于训练或验证的数据增强转换。例如,不同的任务可能需要不同的数据增强策略,如随机裁剪、旋转、颜色变换等。通过在子类中实现 build_transforms 方法,可以灵活地配置和应用这些数据增强策略。

    # 这段代码定义了 BaseDataset 类中的 get_labels 方法,但该方法的具体实现被留空,并抛出了 NotImplementedError 异常。这表明 get_labels 方法是一个抽象方法,需要在子类中具体实现。
    # 定义了 get_labels 方法,该方法用于加载和返回数据集的标签信息。
    def get_labels(self):
        # 用户可以在此处自定义自己的格式。
        """
        Users can customize their own format here.

        Note:
            Ensure output is a dictionary with the following keys:
            ```python
            dict(
                im_file=im_file,
                shape=shape,  # format: (height, width)
                cls=cls,
                bboxes=bboxes, # xywh
                segments=segments,  # xy
                keypoints=keypoints, # xy
                normalized=True, # or False
                bbox_format="xyxy",  # or xywh, ltwh
            )
            ```
        """
        # 抛出 NotImplementedError 异常,表示该方法在当前类中没有具体实现。子类必须实现这个方法,以提供具体的标签加载逻辑。
        raise NotImplementedError
    # get_labels 方法是一个抽象方法,用于加载数据集的标签信息。子类必须实现这个方法,以确保数据集的标签可以被正确加载和处理。这种设计模式允许 BaseDataset 类提供一个通用的框架,而具体的实现细节则由子类根据不同的数据集格式和需求来完成。例如,不同的数据集可能有不同的标签文件格式(如 JSON、XML、TXT 等),子类可以通过实现 get_labels 方法来处理这些不同的格式。
# BaseDataset 类是一个基础的数据集类,用于图像处理和数据增强。它继承自 PyTorch 的 Dataset 类,提供了加载、处理和转换图像数据的功能。该类支持多种配置选项,包括图像路径、目标尺寸、缓存设置、数据增强、超参数配置、日志前缀、矩形训练、批量大小、步长、填充比例、单类别训练、类别列表和数据集使用比例。通过灵活配置这些参数, BaseDataset 类可以适应不同的训练需求,包括单类别训练、数据增强、矩形训练等。它还提供了方法来加载图像文件、更新标签信息、缓存图像到内存或磁盘,并构建数据增强转换流程。这个类为图像分类、目标检测等任务提供了一个灵活且强大的数据处理框架。

 

你可能感兴趣的:(YOLO,笔记,深度学习)