code: def __init__(
      self,
      path,  # 指向data/my_train_data.txt路径或data/my_val_data.txt路径
      # 这里设置的是预处理后输出的图片尺寸
      # 当为训练集时，设置的是训练过程中(开启多尺度)的最大尺寸
      # 当为验证集时，设置的是最终使用的网络大小
      img_size=416,
      batch_size=16,
      augment=False,  # 训练集设置为True(augment_hsv)，验证集设置为False
      hyp=None,  # 超参数字典，其中包含图像增强会使用到的超参数
      rect=False,  # 是否使用rectangular training
      cache_images=False,  # 是否缓存图片到内存中
      single_cls=False,
      pad=0.0,
      rank=-1,
  ):
      try:
          path = str(Path(path))
          # parent = str(Path(path).parent) + os.sep
          if os.path.isfile(path):  # file
              # 读取对应my_train/val_data.txt文件，读取每一行的图片路劲信息
              with open(path, "r") as f:
                  f = f.read().splitlines()
          else:
              raise Exception("%s does not exist" % path)
  
          # 检查每张图片后缀格式是否在支持的列表中，保存支持的图像路径
          # img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.dng']
          self.img_files = [
              x
              for x in f
              if os.path.splitext(x)[-1].lower() in img_formats
          ]
          self.img_files.sort()  # 防止不同系统排序不同，导致shape文件出现差异
      except Exception as e:
          raise FileNotFoundError(
              "Error loading data from {}. {}".format(path, e)
          )
  
      # 如果图片列表中没有图片，则报错
      n = len(self.img_files)
      assert n > 0, "No images found in %s. See %s" % (path, help_url)
  
      # batch index
      # 将数据划分到一个个batch中
      bi = np.floor(np.arange(n) / batch_size).astype(np.int)
      # 记录数据集划分后的总batch数
      nb = bi[-1] + 1  # number of batches
  
      self.n = n  # number of images 图像总数目
      self.batch = bi  # batch index of image 记录哪些图片属于哪个batch
      self.img_size = img_size  # 这里设置的是预处理后输出的图片尺寸
      self.augment = augment  # 是否启用augment_hsv
      self.hyp = hyp  # 超参数字典，其中包含图像增强会使用到的超参数
      self.rect = rect  # 是否使用rectangular training
      # 注意: 开启rect后，mosaic就默认关闭
      self.mosaic = (
          self.augment and not self.rect
      )  # load 4 images at a time into a mosaic (only during training)
  
      # Define labels
      # 遍历设置图像对应的label路径
      # (./my_yolo_dataset/train/images/2009_004012.jpg) -> (./my_yolo_dataset/train/labels/2009_004012.txt)
      self.label_files = [
          x.replace("images", "labels").replace(
              os.path.splitext(x)[-1], ".txt"
          )
          for x in self.img_files
      ]
  
      # Read image shapes (wh)
      # 查看data文件下是否缓存有对应数据集的.shapes文件，里面存储了每张图像的width, height
      sp = path.replace(".txt", ".shapes")  # shapefile path
      try:
          with open(sp, "r") as f:  # read existing shapefile
              s = [x.split() for x in f.read().splitlines()]
              # 判断现有的shape文件中的行数(图像个数)是否与当前数据集中图像个数相等
              # 如果不相等则认为是不同的数据集，故重新生成shape文件
              assert len(s) == n, "shapefile out of aync"
      except Exception as e:
          # print("read {} failed [{}], rebuild {}.".format(sp, e, sp))
          # tqdm库会显示处理的进度
          # 读取每张图片的size信息
          if rank in [-1, 0]:
              image_files = tqdm(
                  self.img_files, desc="Reading image shapes"
              )
          else:
              image_files = self.img_files
          s = [exif_size(Image.open(f)) for f in image_files]
          # 将所有图片的shape信息保存在.shape文件中
          np.savetxt(sp, s, fmt="%g")  # overwrite existing (if any)
  
      # 记录每张图像的原始尺寸
      self.shapes = np.array(s, dtype=np.float64)
  
      # Rectangular Training https://github.com/ultralytics/yolov3/issues/232
      # 如果为ture，训练网络时，会使用类似原图像比例的矩形(让最长边为img_size)，而不是img_size x img_size
      # 注意: 开启rect后，mosaic就默认关闭
      if self.rect:
          # Sort by aspect ratio
          s = self.shapes  # wh
          # 计算每个图片的高/宽比
          ar = s[:, 1] / s[:, 0]  # aspect ratio
          # argsort函数返回的是数组值从小到大的索引值
          # 按照高宽比例进行排序，这样后面划分的每个batch中的图像就拥有类似的高宽比
          irect = ar.argsort()
          # 根据排序后的顺序重新设置图像顺序、标签顺序以及shape顺序
          self.img_files = [self.img_files[i] for i in irect]
          self.label_files = [self.label_files[i] for i in irect]
          self.shapes = s[irect]  # wh
          ar = ar[irect]
  
          # set training image shapes
          # 计算每个batch采用的统一尺度
          shapes = [[1, 1]] * nb  # nb: number of batches
          for i in range(nb):
              ari = ar[bi == i]  # bi: batch index
              # 获取第i个batch中，最小和最大高宽比
              mini, maxi = ari.min(), ari.max()
  
              # 如果高/宽小于1(w > h)，将w设为img_size
              if maxi < 1:
                  shapes[i] = [maxi, 1]
              # 如果高/宽大于1(w < h)，将h设置为img_size
              elif mini > 1:
                  shapes[i] = [1, 1 / mini]
          # 计算每个batch输入网络的shape值(向上设置为32的整数倍)
          self.batch_shapes = (
              np.ceil(np.array(shapes) * img_size / 32.0 + pad).astype(
                  np.int
              )
              * 32
          )
  
      # cache labels
      self.imgs = [None] * n  # n为图像总数
      # label: [class, x, y, w, h] 其中的xywh都为相对值
      self.labels = [np.zeros((0, 5), dtype=np.float32)] * n
      extract_bounding_boxes, labels_loaded = False, False
      nm, nf, ne, nd = (
          0,
          0,
          0,
          0,
      )  # number mission, found, empty, duplicate
      # 这里分别命名是为了防止出现rect为False/True时混用导致计算的mAP错误
      # 当rect为True时会对self.images和self.labels进行从新排序
      if rect is True:
          np_labels_path = (
              str(Path(self.label_files[0]).parent) + ".rect.npy"
          )  # saved labels in *.npy file
      else:
          np_labels_path = (
              str(Path(self.label_files[0]).parent) + ".norect.npy"
          )
  
      if os.path.isfile(np_labels_path):
          x = np.load(np_labels_path, allow_pickle=True)
          if len(x) == n:
              # 如果载入的缓存标签个数与当前计算的图像数目相同则认为是同一数据集，直接读缓存
              self.labels = x
              labels_loaded = True
  
      # 处理进度条只在第一个进程中显示
      if rank in [-1, 0]:
          pbar = tqdm(self.label_files)
      else:
          pbar = self.label_files
  
      # 遍历载入标签文件
      for i, file in enumerate(pbar):
          if labels_loaded is True:
              # 如果存在缓存直接从缓存读取
              l = self.labels[i]
          else:
              # 从文件读取标签信息
              try:
                  with open(file, "r") as f:
                      # 读取每一行label，并按空格划分数据
                      l = np.array(
                          [x.split() for x in f.read().splitlines()],
                          dtype=np.float32,
                      )
              except Exception as e:
                  print(
                      "An error occurred while loading the file {}: {}".format(
                          file, e
                      )
                  )
                  nm += 1  # file missing
                  continue
  
          # 如果标注信息不为空的话
          if l.shape[0]:
              # 标签信息每行必须是五个值[class, x, y, w, h]
              assert l.shape[1] == 5, "> 5 label columns: %s" % file
              assert (l >= 0).all(), "negative labels: %s" % file
              assert (l[:, 1:] <= 1).all(), (
                  "non-normalized or out of bounds coordinate labels: %s"
                  % file
              )
  
              # 检查每一行，看是否有重复信息
              if (
                  np.unique(l, axis=0).shape[0] < l.shape[0]
              ):  # duplicate rows
                  nd += 1
              if single_cls:
                  l[:, 0] = 0  # force dataset into single-class mode
  
              self.labels[i] = l
              nf += 1  # file found
  
              # Extract object detection boxes for a second stage classifier
              if extract_bounding_boxes:
                  p = Path(self.img_files[i])
                  img = cv2.imread(str(p))
                  h, w = img.shape[:2]
                  for j, x in enumerate(l):
                      f = "%s%sclassifier%s%g_%g_%s" % (
                          p.parent.parent,
                          os.sep,
                          os.sep,
                          x[0],
                          j,
                          p.name,
                      )
                      if not os.path.exists(Path(f).parent):
                          os.makedirs(
                              Path(f).parent
                          )  # make new output folder
  
                      # 将相对坐标转为绝对坐标
                      # b: x, y, w, h
                      b = x[1:] * [w, h, w, h]  # box
                      # 将宽和高设置为宽和高中的最大值
                      b[2:] = b[2:].max()  # rectangle to square
                      # 放大裁剪目标的宽高
                      b[2:] = b[2:] * 1.3 + 30  # pad
                      # 将坐标格式从 x,y,w,h -> xmin,ymin,xmax,ymax
                      b = (
                          xywh2xyxy(b.reshape(-1, 4))
                          .revel()
                          .astype(np.int)
                      )
  
                      # 裁剪bbox坐标到图片内
                      b[[0, 2]] = np.clip[b[[0, 2]], 0, w]
                      b[[1, 3]] = np.clip[b[[1, 3]], 0, h]
                      assert cv2.imwrite(
                          f, img[b[1] : b[3], b[0] : b[2]]
                      ), "Failure extracting classifier boxes"
          else:
              ne += 1  # file empty
  
          # 处理进度条只在第一个进程中显示
          if rank in [-1, 0]:
              # 更新进度条描述信息
              pbar.desc = (
                  "Caching labels (%g found, %g missing, %g empty, %g duplicate, for %g images)"
                  % (nf, nm, ne, nd, n)
              )
      assert nf > 0, (
          "No labels found in %s."
          % os.path.dirname(self.label_files[0])
          + os.sep
      )
  
      # 如果标签信息没有被保存成numpy的格式，且训练样本数大于1000则将标签信息保存成numpy的格式
      if not labels_loaded and n > 1000:
          print(
              "Saving labels to %s for faster future loading"
              % np_labels_path
          )
          np.save(np_labels_path, self.labels)  # save for next time
  
      # Cache images into memory for faster training (Warning: large datasets may exceed system RAM)
      if cache_images:  # if training
          gb = 0  # Gigabytes of cached images 用于记录缓存图像占用RAM大小
          if rank in [-1, 0]:
              pbar = tqdm(
                  range(len(self.img_files)), desc="Caching images"
              )
          else:
              pbar = range(len(self.img_files))
  
          self.img_hw0, self.img_hw = [None] * n, [None] * n
          for i in pbar:  # max 10k images
              (
                  self.imgs[i],
                  self.img_hw0[i],
                  self.img_hw[i],
              ) = load_image(
                  self, i
              )  # img, hw_original, hw_resized
              gb += self.imgs[i].nbytes  # 用于记录缓存图像占用RAM大小
              if rank in [-1, 0]:
                  pbar.desc = "Caching images (%.1fGB)" % (gb / 1e9)
  
      # Detect corrupted images https://medium.com/joelthchao/programmatically-detect-corrupted-image-8c1b2006c3d3
      detect_corrupted_images = False
      if detect_corrupted_images:
          from skimage import (
              io,
          )  # conda install -c conda-forge scikit-image
  
          for file in tqdm(
              self.img_files, desc="Detecting corrupted images"
          ):
              try:
                  _ = io.imread(file)
              except Exception as e:
                  print(
                      "Corrupted image detected: {}, {}".format(file, e)
                  )
  
imports: import math
  import os
  import random
  import shutil
  from pathlib import Path
  import cv2
  import numpy
  import torch
  from PIL import Image, ExifTags
  from torch.utils.data import Dataset
  from tqdm import tqdm
  from build_utils.utils import xyxy2xywh, xywh2xyxy
label_desc: Use image node_id 1 to create a new LoadImagesAndLabels object from inherited base classes: Dataset with example: obj = LoadImagesAndLabels(path, img_size, batch_size, augment, hyp, rect, cache_images, single_cls, pad, rank)