dataset.py

torch.utils.data分为三部分:Dataset、DataLoader、sampler.Sampler

一、Dataset部分

1、先看准备部分

① def get_hash(files): # 输入[list of files_path],返回所有文件的大小总和
  return sum(os.path.getsize(f) for f in files if os.path.isfile(f))

② def cache_labels: 输入[list of txts_path] [list of imgs_path]输出labels的cache.

  values = {'./crop1024/images/1.jpg':[array( [对应1.txt的labels信息] ,dtype=float32), (weights, heights))],……,'hash':int_value}

def cache_labels(self, path='labels.cache'):
  values = {}  
  for (img, label) in zip(self.img_files, self.label_files):
    ls = []
    image = Image.open(img)
    image.verify()  # PIL verify
    shape = img.size  
    assert (shape[0] > 9) & (shape[1] > 9), 'image size <10 pixels'
    if os.path.isfile(label):
    with open(label, 'r') as f:
      ls = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32)  # labels
      if len(ls) == 0:    # 当labels文件中内容为空时也要确保shape一致
        ls = np.zeros((0, 6), dtype=np.float32)
        values[img] = [ls, shape]

    values['hash'] = get_hash(self.label_files + self.img_files)
    torch.save(values, path)  # save for next time
    return values

③ def load_image(self, index): 

 input:self.img_files[index]图片路径;  output:img,hw_original, hw_resized; img是cv2直接读出的.shape=(height, weight, 3), 关于resize:

  cv2.INTER_AREA和cv2.INTER_LINEAR是两种无关的参数,resize后仍然为(w0:h0)的比例,但长边为self.img_size。

def load_image(self, index):

    path = self.img_files[index]
    img = cv2.imread(path)
    assert img is not None, 'Image Not Found ' + path
    h0, w0 = img.shape[:2]  # orig hw
    r = self.img_size / max(h0, w0)  # resize image to img_size
    if r != 1:  # always resize down, only resize up if training with augmentation
        interp = cv2.INTER_AREA if r < 1 and not self.augment else cv2.INTER_LINEAR
        img = cv2.resize(img, (int(w0 * r), int(h0 * r)), interpolation=interp)
    return img, (h0, w0), img.shape[:2]  # img, hw_original, hw_resized

 ④ def load_mosaic(self, index):   mosaic数据增强, 这里只是把index和其他随机的三张图片拼接在一起(同时修改label),数据增强部分由random_perspective实现。

 input:self.labels, self.img_size;  output: img4.size=[resized_height,resized_ width, 3], labels4.shape=(img4中的目标GT数量, [classid ,LT_x,LT_y,RB_x,RB_y,Θ])

 注意这里img4和label4中都是用的上一步load_image中的resized_height和resized_width, label中的xywh格式转换为xyxy格式。

def load_mosaic(self, index):

    labels4 = []
    s = self.img_size
    yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border]  # mosaic center x, y
    indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)]  # 3 additional image indices
    for i, index in enumerate(indices):

        img, _, (h, w) = load_image(self, index)  # img.size = [resized_height,resized_ width, 3]

        # place img in img4
        if i == 0:  # top left
            img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)  # base image with 4 tiles
            # xmin, ymin, xmax, ymax (large image)  用于确定原图片在img4左上角的坐标(左上右下)
            x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc
            # xmin, ymin, xmax, ymax (small image)  用于确定原图片剪裁进img4中的图像内容范围
            x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h
        elif i == 1:  # top right
            x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
            x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
        elif i == 2:  # bottom left
            x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
            x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
        elif i == 3:  # bottom right
            x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
            x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)

        # img4.size = [resized_height,resized_ width, 3]
        img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]  # img4[ymin:ymax, xmin:xmax]
        padw = x1a - x1b      # 原图片未剪裁进img4中的宽度
        padh = y1a - y1b      # 原图片未剪裁进img4中的高度

        x = self.labels[index]
        labels = x.copy()

        if x.size > 0:      # Normalized xywh to pixel xyxy format 
            labels[:, 1] = w * (x[:, 1] - x[:, 3] / 2) + padw  # Left_top_x
            labels[:, 2] = h * (x[:, 2] - x[:, 4] / 2) + padh  # Left_top_y
            labels[:, 3] = w * (x[:, 1] + x[:, 3] / 2) + padw  # right_bottom_x
            labels[:, 4] = h * (x[:, 2] + x[:, 4] / 2) + padh  # right_bottom_y
        labels4.append(labels)

    # Concat/clip labels
    if len(labels4):
        labels4 = np.concatenate(labels4, 0)  # 将第一个维度shape=4取消
        np.clip(labels4[:, 1:5], 0, 2 * s, out=labels4[:, 1:5])  # 限定labels4[:, 1:5]中最小值只能为0,最大值只能为2*self.size

    # Augment
    img4, labels4 = random_perspective(img4, labels4,
                                       degrees=self.hyp['degrees'],
                                       translate=self.hyp['translate'],
                                       scale=self.hyp['scale'],
                                       shear=self.hyp['shear'],
                                       perspective=self.hyp['perspective'],
                                       border=self.mosaic_border)  # border to remove

    return img4, labels4

 2、class LoadImagesAndLabels(Dataset):

先看函数调用情况:

def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=False, cache=False, pad=0.0, rect=False,
                      rank=-1, world_size=1, workers=8)
  dataset = LoadImagesAndLabels(path, imgsz, batch_size,
                                      augment=augment,  # augment images
                                      hyp=hyp,  # augmentation hyperparameters
                                      rect=rect,  # rectangular training
                                      cache_images=cache,
                                      single_cls=opt.single_cls,
                                      stride=int(stride),
                                      pad=pad,
                                      rank=rank)

# ---- train.py -----
dataloader, dataset = create_dataloader(train_path, imgsz, batch_size, gs, opt, hyp=hyp, augment=True, cache=opt.cache_images, rect=opt.rect, 
                            rank
=rank, world_size=opt.world_size, workers=opt.workers)

parser.add_argument('--rect', action='store_true', help='rectangular training')

 

self.rect = False if image_weights else rect  # False
self.mosaic = self.augment and not self.rect     # load 4 images at a time into a mosaic (only during training - True)
self.mosaic_border = [-img_size // 2, -img_size // 2]  

 ①def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,

    cache_images=False, single_cls=False, stride=32, pad=0.0, rank=-1): 

  f = []
  for p in path if isinstance(path, list) else [path]:
      if os.path.isdir(p):  # folder
          f += glob.iglob(p + os.sep + '*.*')
  self.img_files = sorted([x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats])
  sa, sb = os.sep + 'images' + os.sep, os.sep + 'labels' + os.sep  # sa=/images/, sb=/labels/
  self.label_files = [x.replace(sa, sb, 1).replace(os.path.splitext(x)[-1], '.txt') for x in self.img_files]
  self.batch = np.floor(np.arange(n) / batch_size).astype(np.int)  

  cache_path = str(Path(self.label_files[0]).parent) + '.cache'  # cached labels
  if os.path.isfile(cache_path):
      cache = torch.load(cache_path)  # load
  if cache['hash'] != get_hash(self.label_files + self.img_files):  # dataset changed
      cache = self.cache_labels(cache_path)  # re-cache
  else:
      cache = self.cache_labels(cache_path)
  labels, shapes = zip(*[cache[x] for x in self.img_files])
  self.shapes = np.array(shapes, dtype=np.float64)
  self.labels = list(labels)

 path:图片所在文件夹路径,可以是一个str,也可以多个路径str list

 self.img_files = ['/**/images/**/1.jpg', '/**/images/**/2.png',…]  self.label_files = ['/**/labels/**/1.txt', '/**/images/**/2.txt',…]

 self.batch记录每一张图片所在的batch_id,ndarray.size=(num_img)

 self.shapes记录每一张图片的(width,height),  ndarray.size=(num_img)

 self.labels记录每一张图片的.txt-label信息,type is list(num_img);其中某张图片的信息self.labels[i].shape=(num_box, 6) [class, xywh_center(归一化),Θ])

nb = self.batch[-1] + 1
if self.rect:
# Sort by aspect ratio  按纵横比的数值从小到大重新进行排序,矩形训练通常以成批处理
    s = self.shapes  # wh
    ar = s[:, 1] / s[:, 0]  # aspect ratio, h/w
    irect = ar.argsort()
    self.img_files = [self.img_files[i] for i in irect]
    self.label_files = [self.label_files[i] for i in irect]
    self.labels = [self.labels[i] for i in irect]
    self.shapes = s[irect]  # wh
    ar = ar[irect]

    # Set training image shapes
    shapes = [[1, 1]] * nb
    for i in range(nb):
        ari = ar[bi == i]
        mini, maxi = ari.min(), ari.max()
        if maxi < 1:
            shapes[i] = [maxi, 1]
        elif mini > 1:
            shapes[i] = [1, 1 / mini]

    self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(np.int) * stride

 这步为Rectangular Training作准备,默认是不执行的。

 self.batch_shapes.shape=(num_batch,2),这一步是为batch中的图片计算出一个统一的shape,要能代表batch内图片的总体情况。

 ② def __getitem__(self, index) 

  整个过程是对self.img_files[index]和self.labels[index]的变换,其中也用到hyp等数据增强的参数!

    def __getitem__(self, index):
        if self.image_weights:  # False, 取参数初始化默认值
            index = self.indices[index]
        hyp = self.hyp
        mosaic = self.mosaic and random.random() < hyp['mosaic']  # 1 ,True and True

        if mosaic:  # True
            img, labels = load_mosaic(self, index)
            shapes = None

        else:
            img, (h0, w0), (h, w) = load_image(self, index)

            # Letterbox, 如果进行矩形训练,则获取每个batch的输入图片的shape
            shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size  # final letterboxed shape
            img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
            shapes = (h0, w0), ((h / h0, w / w0), pad)

            labels = []
            x = self.labels[index]
            if x.size > 0:
                # Normalized format xywh to xyxy
                labels = x.copy()
                labels[:, 1] = ratio[0] * w * (x[:, 1] - x[:, 3] / 2) + pad[0]  # pad width
                labels[:, 2] = ratio[1] * h * (x[:, 2] - x[:, 4] / 2) + pad[1]  # pad height
                labels[:, 3] = ratio[0] * w * (x[:, 1] + x[:, 3] / 2) + pad[0]
                labels[:, 4] = ratio[1] * h * (x[:, 2] + x[:, 4] / 2) + pad[1]

        if self.augment:
            if not mosaic:        # Augment imagespace # 随机对图片进行旋转,平移,缩放,裁剪 -- train中不执行
                img, labels = random_perspective(img, labels,
                                                 degrees=hyp['degrees'],
                                                 translate=hyp['translate'],
                                                 scale=hyp['scale'],
                                                 shear=hyp['shear'],
                                                 perspective=hyp['perspective'])
            # Augment colorspace  随机改变图片的色调(H),饱和度(S),亮度(V)
            augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
            
        # labels.size = (目标数量, [class, xyxy, Θ]),  convert xyxy to xywh , 归一化标签 [0.0~1.0]
        labels[:, 1:5] = xyxy2xywh(labels[:, 1:5])  
        labels[:, [2, 4]] /= img.shape[0]  
        labels[:, [1, 3]] /= img.shape[1]
            
        if self.augment:
            # flip up-down 上下翻转  沿x轴翻转 (y变x不变), θ根据左右偏转也进行改变, 原θ=0时,情况特殊不做改变
            if random.random() < hyp['flipud']:  # 配置文件默认值为0.5
                img = np.flipud(img)
                labels[:, 2] = 1 - labels[:, 2]
                labels[:, -1] = 180 - labels[:, -1]  
                labels[labels[:, -1] == 180, -1] = 0  

            # flip left-right 左右翻转  沿y轴翻转( x变y不变),θ根据左右偏转也进行改变, 原θ=0时,情况特殊不做改变
            if random.random() < hyp['fliplr']:  # 配置文件默认值为0.5
                img = np.fliplr(img)
                labels[:, 1] = 1 - labels[:, 1]
                labels[:, -1] = 180 - labels[:, -1]   
                labels[labels[:, -1] == 180, -1] = 0  

        # img.size=[resized_height,resized_width,3] -> [3, resized_height, resized_width]
        img = img[:, :, ::-1].transpose(2, 0, 1)
        img = np.ascontiguousarray(img)
        labels_out = torch.zeros((len(labels), 7))
        labels_out[:, 1:] = torch.from_numpy(labels)

        return torch.from_numpy(img), labels_out, self.img_files[index], shapes  # train -> mosaic==True -> shape=None

 load_mosaic:经过load_image取出cv2的图片并resize成(resized_height, resized_weight), 这在load_mosaic中没有改变 并且把label由[0~1]转成[r_h,r_w]。

进行了4张图片和label的拼接,xywh格式改成了xyxy格式,然后是random_perspective做数据增强。

 augment:augment_hsv(img, 数据增强调整色调、亮度、饱和度; 转格式xyxy -> xywh, 并label归一化(resized_height, resized_weight); np.flipud(img), np.fliplr(img)

两种翻转按一定概率执行!

 最有由cv2的(height,width,3) -> (3,height,width)。

 ③ def __len__(self): 

       return len(self.img_files)

@staticmethod
def collate_fn(batch):

    img, label, path, shapes = zip(*batch) 
    for i, l in enumerate(label):  
        l[:, 0] = i
    return torch.stack(img, 0), torch.cat(label, 0), path, shapes

 ④ def conllate_fn(batch):

@staticmethod
def collate_fn(batch):

    img, label, path, shapes = zip(*batch) 
    for i, l in enumerate(label):  
        l[:, 0] = i
    return torch.stack(img, 0), torch.cat(label, 0), path, shapes

 返回img.size = (batch_size, 3 , resized_height, resized_width) 没有归一化;

   labels.size=(batch中的目标数量, [图片在当前batch中的索引,classid,归一化后的xywh, Θ])

   path.size=batch_size, 该batch中所有image的路径 

 

posted @ 2021-11-18 17:30  shines87  阅读(162)  评论(0)    收藏  举报