• 22种transforms数据预处理方法


    来源:投稿 作者:阿克西

    编辑:学姐

    建议搭配视频学习↓

    视频链接:https://ai.deepshare.net/detail/p_5df0ad9a09d37_qYqVmt85/6

    1.数据增强(data augmentation)

    数据增强又称为数据增广,数据扩增,它是对训练集进行变换,使训练集更丰富,从而让模型更具泛化能力。

    看一下图片中的数据增强是怎么样的。上图是一张原始图片,对这张图片进行一系列的操作变换得到64张增强样本。64张图片中的第一张图片是对原始图片进行旋转,第二张图片是对原始图片进行颜色变换,第三张图片是进行镜像操作。

    对图片进行一系列操作可以得到大量增强样本提供给模型进行训练,让模型见过更多的样本,从而提升模型的泛化能力,使得模型在验证集上的表现更好。本章学习具体的数据增强方法。

    1.1 导入包和设置参数

    1. import os
    2. BASE_DIR = os.path.dirname(os.path.abspath(__file__))
    3. import numpy as np
    4. import torch
    5. import random
    6. from torch.utils.data import DataLoader
    7. import torchvision.transforms as transforms
    8. from PIL import Image
    9. from matplotlib import pyplot as plt
    10. from tools.my_dataset import RMBDataset
    11. from tools.common_tools import set_seed, transform_invert
    12. def set_seed(seed=1):
    13.     random.seed(seed)
    14.     np.random.seed(seed)
    15.     torch.manual_seed(seed)
    16.     torch.cuda.manual_seed(seed)
    17. set_seed(1)  # 设置随机种子
    18. # 参数设置
    19. MAX_EPOCH = 10
    20. BATCH_SIZE = 1
    21. LR = 0.01
    22. log_interval = 10
    23. val_interval = 1

    1.2 函数transform_invert(),对transform进行逆操作

    transform_invert(),这个函数是用来对transform进行逆操作,使得我们可以观察到模型输入的数据是长什么样的。因为数据经过transfrom,转换为张量的形式,可能是一些浮点的数据,没有办法将这些数据进行可视化,因此需要一个transform_invert()函数,对transform进行逆操作,将张量的数据变换成img,这样就可以进行可视化。

    这个函数接受一个img_和transform_train,返回PIL image,也就是可以直接plot将其可视化。

    1. # 在训练部分被引用
    2. # 传入两个参数img_和train_transform,返回PIL image,也就是可以直接plot将其格式化
    3. def transform_invert(img_, transform_train):
    4.     """
    5.     将data 进行反transfrom操作
    6.     :param img_: tensor
    7.     :param transform_train: torchvision.transforms
    8.     :return: PIL image
    9.     """
    10.     # 对normalize进行反操作
    11.     if 'Normalize' in str(transform_train):
    12.         norm_transform = list(filter(lambda x: isinstance(x, transforms.Normalize),
    13.                                      transform_train.transforms))
    14.         mean = torch.tensor(norm_transform[0].mean,
    15.                             dtype=img_.dtype,
    16.                             device=img_.device)
    17.         std = torch.tensor(norm_transform[0].std,
    18.                            dtype=img_.dtype,
    19.                            device=img_.device)
    20.         # normalize是减去均值除于方差,因此反操作就是乘于方差再加上均值
    21.         img_.mul_(std[:, None, None]).add_(mean[:, None, None])
    22.     # 通道变换,C*H*W --> H*W*C,将channel放到最后面
    23.     img_ = img_.transpose(02).transpose(01)
    24.     
    25.     # 将0-1尺度上的数据转换到0-255
    26.     if 'ToTensor' in str(transform_train) or img_.max() < 1:
    27.         img_ = img_.detach().numpy() * 255
    28.  # 将np_array的形式转换成PIL image
    29.     # 判断channel是3通道还是1通道,分别转换成RGB彩色图像和灰度图像
    30.     if img_.shape[2== 3:
    31.         img_ = Image.fromarray(img_.astype('uint8')).convert('RGB')
    32.     elif img_.shape[2== 1:
    33.         img_ = Image.fromarray(img_.astype('uint8').squeeze())
    34.     else:
    35.         raise Exception("Invalid img shape, expected 1 or 3 in axis 2, but got {}!"
    36.                         .format(img_.shape[2]) )
    37.  # 返回图像就可以对图像进行plot,对图像进行可视化
    38.     return img_

    1.3 Dataset类参数一:从哪读数据,设置硬盘中的路径

    1. ============================ step 1/5 数据 ============================
    2. split_dir = os.path.abspath(os.path.join("rmb_split"))
    3. if not os.path.exists(split_dir):
    4.     raise Exception(r"数据 {} 不存在, 回到lesson-06\1_split_dataset.py生成数据".format(split_dir))
    5. train_dir = os.path.join(split_dir, "train")
    6. valid_dir = os.path.join(split_dir, "valid")

    1.4 🔥Dataset类参数二:数据预处理transform

    1. norm_mean = [0.4850.4560.406]
    2. norm_std = [0.2290.2240.225]
    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.     transforms.ToTensor(),
    4.     transforms.Normalize(norm_mean, norm_std),
    5. ])

    1.5 构建Dataset与DataLoder实例

    1. # 构建MyDataset实例
    2. train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
    3. valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)
    4. # 构建DataLoder
    5. train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
    6. valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)

    1.6 训练

    transforms方法的演示还是采用人民币二分类训练的主代码,这里我们只关心数据模块以及训练模块中取出数据那一部分。

    除2.4 transforms.FiveCrop与transforms.TenCrop外,下述代码均适用。

    1. ============================ step 5/5 训练 ============================
    2. for epoch in range(MAX_EPOCH):
    3.     for i, data in enumerate(train_loader):
    4.         inputs, labels = data   # B C H W
    5.         img_tensor = inputs[0, ...]     # C H W
    6.         img = transform_invert(img_tensor, train_transform)
    7.         plt.imshow(img)
    8.         plt.show()
    9.         plt.pause(0.5)
    10.         plt.close()
    11.         # bs, ncrops, c, h, w = inputs.shape
    12.         # for n in range(ncrops):
    13.         #     img_tensor = inputs[0, n, ...]  # C H W
    14.         #     img = transform_invert(img_tensor, train_transform)
    15.         #     plt.imshow(img)
    16.         #     plt.show()
    17.         #     plt.pause(1)

    以下主要针对1.4进行详细展开。

    2.transforms 裁剪(crop)

    2.1 transforms.CenterCrop中心裁剪

    功能:从图像中心裁剪图片

    • size:所需裁剪图片尺寸

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.     # 1 CenterCrop
    4.     transforms.CenterCrop(196),     # 512
    5.     
    6.     transforms.ToTensor(),
    7.     transforms.Normalize(norm_mean, norm_std),
    8. ])

    在transforms中,为了统一图片的尺寸,一开始会执行transforms.Resize((224,224)),把图片统一地缩放到 224 ∗ 224的尺寸大小。然后执行transforms.CenterCrop(196)操作,裁剪出来一个196大小的图片。假如把代码中的196改为512,大于224。执行debug操作,代码并没有报错,输出图片为(512, 512)大小的图片,对超出224的区域会自动填充为零的像素,也就是全黑的区域。

    断点调试:

    1、下面看代码中的transform.CenterCrop()函数,经过裁剪之后图像会变成什么样。首先设置断点,观察inputs的形状,(BATCH_SIZE, C, H, W):

    2、对程序进行debug,代码停在之前打断点的位置,如下图所示。点击step over功能键,到达代码img_tensor = inputs[0, …] 位置。观察一下代码中data的形式。

    3、点击console就会打开一个命令窗,如下图所示,这个命令窗的环境与当前代码调试的环境是完全一致的,可以在这个命令窗对变量进行更改或者查看。

    现在查看inputs的形状,inputs的形状是一个[1,3,196,196]的形式。第一个维度是BITCH_SIZE,因为在代码开始设置了BATCH_SIZE=1,所以inputs中的第一个维度为1;第二个维度是channel,也就是通道,由于是rgb图像,通道的长度为3;第三维和第四位分别是图像的高和宽。

    由于可视化图片是一个三通道的三维张量,所以需要对inputs进行索引操作,索引出第一块区域,也就是接下来的一句代码“img_tensor = inputs[0, …]”,这段代码的意思是取四维张量中的第一个三维张量,这样就把四维张量变为三维张量了,其顺序为 C ∗ H ∗ W。将得到的三维张量img输入到函数transform_invert()函数中进行逆变换,就返回可以可视化的img,然后将img进行plt操作,就得到上图的裁剪图片。

    在debug之后,可以取消断点,将光标放置在plt.close() 代码处,反复执行run to cursor按键,可以查看不同图像。

    2.2 transforms.RandomCrop随机裁剪

    功能:从图片中随机裁剪出尺寸为size的图片(位置随机裁剪)。

    1. transforms.RandomCrop(size,
    2.                       padding=None,
    3.                       pad_if_needed=False,
    4.                       fill=0,
    5.                       padding_mode='constant')
    • size:所需裁剪图片尺寸

    • padding:设置填充大小(有三种模式)
      • 当为a时,上下左右均填充a个像素

      • 当为(a, b)时,左右填充a个像素,上下填充b个像素

      • 当为(a, b, c, d)时,左,上,右,下分别填充a,b,c,d

    • pad_if_need:若图像小于设定size,则填充

    • padding_mode:填充模式,有4种模式
      • constant:像素值由fill参数设定

      • edge:像素值由图像边缘像素决定

      • reflect:镜像填充,最后一个像素不镜像,eg:对[1,2,3,4]进行2个像素的填充,左侧最后一个像素不镜像,忽略1,从左至右为2,3,镜像填充为[3,2,1,2,3,4]。同理,右侧忽略4,从右至左为3,2,最终填充为[3,2,1,2,3,4,3,2]

      • symmetric:镜像填充,最后一个像素镜像,eg:[1,2,3,4] [2,1,1,2,3,4,4,3],最后一个像素镜像,所以不会跳过1和4,分别从1和4开始进行镜像填充

    • fill:constant时,设置填充的像素值

    下面通过代码观察RandomCrop是怎样对图像进行裁剪的。和前面一样,对图像进行统一的尺寸变换,缩放为(224,224)。

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.     # 2 RandomCrop
    4.     # 1)对上下左右均进行16像素的padding
    5.     transforms.RandomCrop(224, padding=16),
    6.     # 2)分别对左右、上下设置不同的填充,左右填充16,上下填充64
    7.     # transforms.RandomCrop(224, padding=(1664)),
    8.     # 3)设置fill参数,修改填充像素颜色
    9.     # transforms.RandomCrop(224, padding=16, fill=(25500)),
    10.     # 4)当size大于图片原始尺寸的时候,pad_if_needed参数必须打开,否则会报错
    11.     # transforms.RandomCrop(512, pad_if_needed=True),   # pad_if_needed=False
    12.     # 5)采用图片的边界值对图片进行填充
    13.     # transforms.RandomCrop(224, padding=64, padding_mode='edge'),    
    14.     # 6)采用图片的镜像填充
    15.     # transforms.RandomCrop(224, padding=64, padding_mode='reflect'),
    16.     # transforms.RandomCrop(1024, padding=1024, padding_mode='symmetric'),
    17.     
    18.     transforms.ToTensor(),
    19.     transforms.Normalize(norm_mean, norm_std),
    20. ])

    1、对上下左右均进行16像素的padding。裁剪出来的图片左边和上边都有一块黑色的填充区域。为什么左边和上边没有呢?这是因为经过填充之后的图片的尺寸应该是224+16+16,比224大32个像素。在这个大的图片上进行(224,224)的随机选取,由于图像选取右下角的这一部分,所以左边和上边是没有黑色的填充区域的。

    2、padding的第二种模型,分别对左右、上下设置不同的填充,左右填充16,上下填充64。可以看到左右的填充区域相比于上下是更小的。

    3、可以看到填充的区域都是黑色,默认填充的像素是0,如果想设置的填充区域是红色,或者是其它的彩色图,就可以对fill这个参数进行设置,代码中对fill设置一个长度为3的tuple类型,3个元素分别对应的是RGB通道,比如设定(255, 0, 0),可以看一下其padding出来的颜色是红色的。

    4、接下来看一下pad_if_needed参数,当size大于图片原始尺寸的时候,pad_if_needed参数必须打开,否则会报错。可以看到在超出图片的范围全部填充上像素值为0的像素点,也就是黑色的。

    5、观察参数padding_mode的几种模式,padding_mode默认采用constant模式,在采用constant的时候,采用fill参数去设置填充的像素点的像素值。接下里看padding_mode的第二种模式,padding_mode=‘edge’,这种模式是采用图片的边界值对图片进行填充,设置padding的值大一点,padding=64,以便于更好地观察填充的效果。padding_mode='reflect',采用图片的镜像填充。当设置size与padding参数较大时,图像像印钞机填充。

    padding_mode='symmetric’和padding_mode='reflect’功能相差不多,只是相差一个像素值点。

    2.3 transforms.RandomResizedCrop随机裁剪并调整大小

    功能:随机大小、长宽比裁剪图片。

    1. RandomResizedCrop(size,
    2.                   scale=(0.08,1.0),
    3.                   ratio=(3/4,4/3),
    4.                   interpolation)
    • size:所需裁剪图片尺寸;

    • scale:随机裁剪面积比例,默认(0.08,1),0.08-1之间随机选取一个数

    • ratio:随机长宽比,默认(3/4,4/3),3/4到4/3之间随机选取一个数

    • interpolation:插值方法,裁剪出来的图片尺寸可能小于size,所以需要进行插值处理,插值方法有三种
      • PIL.Image.NEAREST:最近邻插值

      • PIL.Image.BILINEAR:双线性插值

      • PIL.Image.BICUBIC:双三次插值

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 3 RandomResizedCrop
    4.     transforms.RandomResizedCrop(size=224, scale=(0.080.1)),
    5.     # transforms.RandomResizedCrop(size=224, scale=(0.50.5)),
    6.     
    7.     transforms.ToTensor(),
    8.     transforms.Normalize(norm_mean, norm_std),
    9. ])

    输出结果如上图所示,所得图片比原始图片小得多,这个比例是在scale=(0.08, 0.1)之间随机选取一个数作为裁剪图片面积得到的,之后再根据ratio长宽比设定图像的长和宽,裁剪得到一个图片。最后将裁剪得到图片resize到设定的size大小尺寸。

    scale=(0.5, 0.5)表示采取一半的面积,然后再进行长宽比的缩放。

    2.4 transforms.FiveCrop与transforms.TenCrop

    transforms.FiveCrop(size)
    

    功能:在图像的左上角、右上角、左下角、右下角以及中心裁剪出尺寸为size的5张图片。

    1. transforms.TenCrop(size,
    2.        vertical_flip=False)

    功能:在图像的上下左右以及中心裁剪出尺寸为size的5张图片,在这五张图片上进行水平或者垂直镜像获得10张图片。

    • size:所需裁剪图片尺寸大小

    • vertical_flip:是否垂直翻转,True为垂直翻转,False为水平翻转

    示例:

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.     # 4 FiveCrop
    4.     transforms.FiveCrop(112), # 直接操作会报错
    5.     transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),
    6.  
    7.  # 5 TenCrop
    8.     # transforms.TenCrop(112, vertical_flip=False),
    9.     # transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),
    10.     
    11.     # transforms.ToTensor(), # 上面进行了ToTensor,这里不再需要,否则会报错
    12.     # transforms.Normalize(norm_mean, norm_std),
    13. ])

    由于FiveCrop()裁剪出来的是五张图片,返回的是一个tuple(元组),不能直接进行操作,当尝试运行代码时,会报错,查看报错信息:

    报错为:pic should be PIL Image or ndarray. Got 。意思是pic这个参数应该是一个PIL Image或者是ndarray的,但是却得到了一个tuple。所以直接使用是不行的,需要对FiveCrop返回的tuple进行一定的操作,将tuple变换为张量的形式或者是PIL Image的形式。

    这里使用到lambda方法,lambda是匿名函数,可以对FiveCrop()的输出进行一系列的变换,使其输出可以变换为代码可以执行的数据格式。看一下lambda匿名函数的功能:

    transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),
    

    lambda匿名函数代码中冒号之前的是函数的输入,冒号之后的整个语句是函数的返回值。

    由于输入是一个tuple格式的数据,需要将tuple中每一张图片,将其拼接为张量的形式,所以代码中采用了torch.stack()的形式,在讲张量的操作的时候,stack是对张量在某一维度上进行拼接,这里采用默认维度,也就是第0个维度。stack()函数中传入的是一个list,代码中采用了python的列表解析式,列表生成器。它的功能是对参数crops进行for循环,每一次提取出一个元素crop,每一次对这个元素crops进行一些操作得到列表的元素。

    crops是FiveCrop()函数输出的一个tuple,然后对tuple的每一个元素进行for循环,每一次取出一个crop,也就是一张图片,对每一张图片进行一个ToTensor()的操作,将其转换为张量的形式,将其变为list的一个元素。通过不断的循环,把五张图片都转为张量的形式,然后得到一个长度为5的list,把这个list放到stack()当中,stack()就把这个长度为5的list拼接成一个张量。这样,通过lambda(),就把tuple转为张量的形式,这样就可以输入到模型中。

    点击运行之后还是会报错,报错如下,注意debug时,设置断点在plt.close():

    由于图片的维度和代码不匹配,不能用原始的方法可视化。因为得到的input不再是一个四维的张量,是一个五维的张量。

    这个五维张量的各个维度分别为(batchsize, ncrops, c, h, w),通过下面这个新的代码对每个crop进行可视化。因此1.6应该修改成如下形式:

    1. ============================ step 5/5 训练 ============================
    2. for epoch in range(MAX_EPOCH):
    3.     for i, data in enumerate(train_loader):
    4.         inputs, labels = data   # B C H W
    5.         # img_tensor = inputs[0, ...]     # C H W
    6.         # img = transform_invert(img_tensor, train_transform)
    7.         # plt.imshow(img)
    8.         # plt.show()
    9.         # plt.pause(0.5)
    10.         # plt.close()
    11.         bs, ncrops, c, h, w = inputs.shape
    12.         for n in range(ncrops):
    13.             img_tensor = inputs[0, n, ...]  # C H W
    14.             img = transform_invert(img_tensor, train_transform)
    15.             plt.imshow(img)
    16.             plt.show()
    17.             plt.pause(1)

    得到一张图片的五维表示,代码要在五维张量中获取每一张图片,每一张图片应该是一个3维的张量,对ncrops进行循环,分别将五张图片进行可视化。通过命令输入窗,可以看到img_tensor的形状(3,112,112),可以直接进行可视化。

    注意因为transforms.ToTensor()与transforms.Normalize(norm_mean, norm_std)被注释,所以应添加如下一行:

    1.  # 判断
    2.     if img_.shape[2== 3:
    3.         img_ = img_.detach().numpy() * 255 # 添加一行
    4.         img_ = Image.fromarray(img_.astype('uint8')).convert('RGB')

    但是只能输出第一张图片五张图和第二张图片一张图,所以可将if 'Normalize' in str(transform_train):与if 'ToTensor' in str(transform_train) or img_.max() < 1:两段代码都注释掉。

    输出五张照片如下所示:

    下面看一下TenCrop()函数的使用方法,它是在FiveCrop()函数的基础上进行翻转得到的十张图片。设置vertical_flip=True,也就是进行垂直的翻转

    3.transforms 翻转和旋转(flip and rotation)

    3.1 transforms Flip 翻转

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 1 Horizontal Flip,水平翻转
    4.     transforms.RandomHorizontalFlip(p=1),
    5.     # 2 Vertical Flip,垂直翻转
    6.     # transforms.RandomVerticalFlip(p=0.5),
    7.     
    8.     transforms.ToTensor(),
    9.     transforms.Normalize(norm_mean, norm_std),
    10. ])

    功能:依概率水平(左右)或垂直(上下)翻转图片

    • p:翻转概率(即有多大的概率将图片进行翻转)

    3.2 transforms Rotation 旋转

    功能:随机旋转图片

    1. RandomRotation(degrees,
    2.                 resample=False,
    3.                 expand=False,
    4.                 center=None)
    • degrees:旋转角度
      • 当为a时,在(-a,a)之间选择旋转角度

      • 当为(a,b)时,在(a,b)之间选择旋转角度

    • resample:重采样方法

    • expand:是否扩大图片,以保持原图信息,图片旋转后,可能会超出矩形框,超出部分可能会丢失,如果expand=True,矩形框会变大

    • center:旋转点设置,默认中心旋转,center=(0, 0)表示左上角旋转

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 3 RandomRotation
    4.     # transforms.RandomRotation(90),
    5.     # transforms.RandomRotation((90), expand=True),
    6.     # transforms.RandomRotation(30, center=(00)),
    7.     # expand only for center rotation
    8.     transforms.RandomRotation(30, center=(00), expand=True),
    9.     
    10.     transforms.ToTensor(),
    11.     transforms.Normalize(norm_mean, norm_std),
    12. ])

    可以看到center=(0, 0),expand=True时,图片也会丢失一部分,因为expand是针对center rotation机制,计算所需扩大的尺度,而在中心和左上角需要计算出的尺度是不一样的,因此expand=True无法找到左上角图像丢失的所有信息。

    当使用expand=True扩大图片时,因为每张图片旋转的角度不同,最后得到的图片的大小是不一样的,如果batchsize 1 最后拼接的时候collate_fn可能出现报错的问题,所以在使用expand的时候,需要注意对图片进行缩放,将所有照片缩放到统一的尺寸。

    4.transforms 图像变换

    4.1 transforms.Pad图片边缘填充

    1. transforms.Pad(padding, 
    2.       fill=0
    3.       padding_mode=constant’)

    功能:对图片边缘进行填充

    • padding:设置填充大小
      • 当为a时,上下左右均填充a个像素

      • 当为(a,b)时,左右填充a个像素,上下填充b个像素

      • 当为(a,b,c,d)时,左,上,右,下分别填充a,b,c,d

    • padding_mode:填充模式,有4种模式,constant、edge、reflect和symmetric

    • fill:constant时,设置填充的像素值,(R,G,B)or(Gray)

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 1 Pad
    4.     transforms.Pad(padding=32, fill=(25500), padding_mode='constant'),
    5.     # transforms.Pad(padding=(864), fill=(25500), padding_mode='constant'),
    6.     # transforms.Pad(padding=(8163264), fill=(25500), padding_mode='constant'),
    7.     # transforms.Pad(padding=(8163264), fill=(25500), padding_mode='symmetric'),
    8.     
    9.     transforms.ToTensor(),
    10.     transforms.Normalize(norm_mean, norm_std),
    11. ])

    4.2 transforms.ColorJitter修改亮度、对比度、饱和度和色相

    1. transforms.ColorJitter(brightness=0,
    2.         contrast=0
    3.         saturation=0
    4.         hue=0)

    功能:修改修改亮度、对比度和饱和度

    • brightness:亮度调整因子
      • 当为a时,从[max(0, 1-a), 1+a]中随机选择一个数

      • 当为(a, b)时,从[a, b]中随机选择一个数

    • contrast:对比度参数,同brightness

    • saturation:饱和度参数,同brightness

    • hue:色相参数
      • 当为a时,从[-a, a]中选择参数,注: 0 ≤ a ≤ 0.5

      • 当为(a, b)时,从[a, b]中选择参数,注:-0.5 ≤ a ≤ b ≤ 0.5

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 2 ColorJitter
    4.     # transforms.ColorJitter(brightness=0.5),
    5.     # transforms.ColorJitter(contrast=0.5),
    6.     # transforms.ColorJitter(saturation=0.5),
    7.     # transforms.ColorJitter(hue=0.3),
    8.     
    9.     transforms.ToTensor(),
    10.     transforms.Normalize(norm_mean, norm_std),
    11. ])

    对比度降低,图像发灰,对比度增高,白色更白,黑色更黑。

    4.3 transforms.Grayscale转灰度图

    1. transforms.RandomGrayscale(num_output_channels,
    2.           p=0.1)  
    3. # 是RandomGrayscale的一个特例,是p=1的情况
    4. transforms.Grayscale(num_output_channels=1)

    功能:依概率将图片转换为灰度图

    • num_ouput_channels:输出通道数,只能设1或3

    • p:概率值,图像被转换为灰度图的概率

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 3 Grayscale,num_output_channels=1会报错,因为这里使用的图像是3维的
    4.     # transforms.Grayscale(num_output_channels=3),
    5.     transforms.ToTensor(),
    6.     transforms.Normalize(norm_mean, norm_std),
    7. ])

    num_output_channels=1报错:因为高斯变换逆变换均值和方差为三维

    4.4 transforms.RandomAffine仿射变换

    空间几何变换

    1. transforms.RandomAffine(degrees,
    2.          translate=None,
    3.          scale=None,
    4.          shear=None,
    5.          resample=False,
    6.          fillcolor=0)

    功能:对图像进行仿射变换,仿射变换是二维的线性变换,由五种基本原子变换构成,分别是旋转、平移、缩放、错切和翻转。经过五种变换的随机组合,即可得到二维线性变换。

    • degrees:旋转角度设置,必设参数

    • translate:平移区间设置,如(a, b),a设置宽(width),b设置高(height) 图像在宽维度平移的区间为 -img_width * a < dx < img_width * a

    • scale:缩放比例(以面积为单位),0到1之间,原始图像占resize后图像的比例

    • fill_color:填充颜色设置,默认黑色填充

    • shear:错切角度设置,有水平错切和垂直错切
      • 若为a,则仅在x轴错切,错切角度在(-a,a)之间,以中心旋转形式

      • 若为(a, b),则仅在x轴错切,错切角度在(a, b)之间

      • 若为(a, h, c, d),则a, b设置x轴角度,c, d设置y轴角度

    • resample:重采样方式,有NEAREST、BILINEAR、BICUBIC

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 4 Affine
    4.     # transforms.RandomAffine(degrees=30),
    5.     # 平移
    6.     # transforms.RandomAffine(degrees=0, translate=(0.20.2), fillcolor=(25500)),
    7.     # transforms.RandomAffine(degrees=0, scale=(0.70.7)),
    8.     
    9.     # 错切
    10.     # 沿着x轴错切
    11.     # transforms.RandomAffine(degrees=0, scale=(0.70.7), shear=45),
    12.     # transforms.RandomAffine(degrees=0, scale=(0.70.7), shear=(4545)),
    13.     # 沿着y轴错切
    14.     # transforms.RandomAffine(degrees=0, scale=(0.70.7), shear=(00045)),
    15.     transforms.RandomAffine(degrees=0, shear=90, fillcolor=(25500)),
    16.     transforms.ToTensor(),
    17.     transforms.Normalize(norm_mean, norm_std),
    18. ])

    下图分别为沿x轴错切,沿x轴错切,沿y轴错切。

    4.5 transforms.RandomErasing随机遮挡

    1. transforms.RandomErasing(p=0.5,
    2.             scale=(0.020.33),
    3.        ratio=(0.33.3), 
    4.        value=0,
    5.        inplace=False)

    功能:对图像进行随机遮挡

    • p:概率值,执行该操作的概率

    • scale:遮挡区域的面积,原论文中推荐数值scale=(0.02, 0.33)

    • ratio:遮挡区域长宽比,设置一个区间,原论文中推荐数值ratio=(0.3, 3.3)

    • value:设置遮挡区域的像素值,(R,G,B)or(Gray),0-1之间,应为操作的是tensor张量。如果为任意字符串,而不是tuple,都默认为random

    参考文献:《Random Erasing Data Augmentation》

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.  # 5 Erasing
    4.     transforms.ToTensor(),
    5.     # 随即遮挡接收的是tensor,在张量上进行操作,之前都是在PIL image上的操作
    6.     # transforms.RandomErasing(p=1, scale=(0.020.33), ratio=(0.33.3), value=(254/25500)),
    7.     transforms.RandomErasing(p=1, scale=(0.020.33), ratio=(0.33.3), value='random'),
    8.     # transforms.RandomErasing(p=1, scale=(0.020.33), ratio=(0.33.3), value='1234'),
    9.     transforms.ToTensor(),
    10.     transforms.Normalize(norm_mean, norm_std),
    11. ])

    4.6 transforms.Lambda

    Lambda匿名函数,常用来简单操作的实现。

    transforms.Lambda(lambd)
    

    功能:用户自定义lambda方法。

    • lambd:lambda匿名函数,语法实现形式,lambda [arg1 [,arg2, … , argn]] : expression,冒号之前是输入参数,之后是表达式,相当于return

    示例:TenCrop返回10张图片的元组形式,而transforms输入输出一般是tensor或PIL image形式,所以需要对TenCrop输入的tuple形式进行变换,拼接成tensor形式,这是可以采用Lambda匿名函数

    1. transforms.TenCrop(200, vertical_flip=True),
    2. # 最后返回一个4维的张量
    3. transforms.Lambda(lambda crops: torch.stack([transforms.Totensor()(crop) for crop in crops])),

    5.transforms的选择操作

    PyTorch不仅可设置对图片的操作,还可以对这些操作进行随机选择、组合,使得数据增强更加灵活多样。

    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.     # 1 RandomChoice
    4.     transforms.RandomChoice([transforms.RandomVerticalFlip(p=1),
    5.                              transforms.RandomHorizontalFlip(p=1)]),
    6.     # 2 RandomApply
    7.     # 有的图像执行错切填充红色后进行灰度变换,有的不执行任何操作
    8.     # transforms.RandomApply([transforms.RandomAffine(degrees=0, shear=45, fillcolor=(25500)),
    9.     #                         transforms.Grayscale(num_output_channels=3)], p=0.5),
    10.     # 3 RandomOrder
    11.     # transforms.RandomOrder([transforms.RandomRotation(15),
    12.     #                         transforms.Pad(padding=32),
    13.     #                         transforms.RandomAffine(degrees=0, translate=(0.010.1), scale=(0.91.1))]),
    14.     transforms.ToTensor(),
    15.     transforms.Normalize(norm_mean, norm_std),
    16. ])

    5.1 transforms.RandomChoice

    transforms.RandomChoice([transforms1, transforms2, transforms3])
    

    功能:从给定的一系列transforms中随机选择一个进行操作,randomly picked from a list。

    5.2 transforms.RandomApply

    transforms.RandomApply([transforms1, transforms2, transforms3], p=0.5)
    

    功能:依据概率执行一组transforms操作

    5.3 transforms.RandomOrder

    transforms.RandomOrder([transforms1, transforms2, transforms3])
    

    功能:将transforms中的操作顺序随机打乱

    6.自定义transforms方法

    1. class Compose(object):
    2.  def __call__(self, img):
    3.   for t in self.transforms:
    4.    img = t(img)
    5.   return img

    在dataset与dataloader机制一节中,我们知道transforms方法是在Compose类中的call函数被调用的。由上述代码可以看出,对一组transforms进行for循环,顺序挑选出transforms方法执行,每次输入一个参数,返回一个参数,因此transforms方法只能接收一个参数,返回一个参数。其次,for循环里,当前transforms方法的输入是上一个方法的输出,方法上下有前后关系,因此输入输出类型要匹配,比如都是PIL image形式或者都是tensor。

    自定义transforms要素:

    1. 仅接收一个参数,返回一个参数,可以是PIL img,可以是tensor,list,tuple或者dict

    2. 注意上下游的输出与输入

    在设计transforms方法时,可能需要多个参数,比如概率取值,信噪比等,这时可以通过类实现多参数传入:

    1. class YourTransforms(object):
    2.  # 初始化,传入想要的参数
    3.  def __init__(self, ...):
    4.   ...
    5.  # 类中必须有call函数,使得类的示例可以被调用,
    6.  # 在这个函数中实现自定义数据增强的具体功能
    7.  # 只能接受一个输入img参数,返回一个img参数
    8.  def __call__(self, img):
    9.   ...
    10.   return img

    椒盐噪声:又称为脉冲噪声,是一种随机出现的白点或者黑点,白点称为盐噪声,黑色为椒噪声。

    信噪比(Signal-Noise Rate, SNR)是衡量噪声的比例,图像像素在整张图象的占比。如下图所示,随着信噪比的减小,图像像素信息丢失逐渐增加。

    椒盐噪声:

    1. class AddPepperNoise(object):
    2.  def __init__(self, snr, p):
    3.   self.snr = snr
    4.   self.p = p
    5.  def __call__(self, img):
    6.   """添加椒盐噪声具体实现过程
    7.   """
    8.   return img
    1. class AddPepperNoise(object):
    2.     """增加椒盐噪声
    3.     Args:
    4.         snr (float): Signal Noise Rate
    5.         p (float): 概率值,依概率执行该操作
    6.     """
    7.     def __init__(self, snr, p=0.9):
    8.         assert isinstance(snr, float) and (isinstance(p, float))    # 2020 07 26 or --> and
    9.         self.snr = snr
    10.         self.p = p
    11.     def __call__(self, img):
    12.         """
    13.         Args:
    14.             img (PIL Image): PIL Image
    15.         Returns:
    16.             PIL Image: PIL image.
    17.         """
    18.         # 概率判断,以一定的概率执行椒盐噪声
    19.         if random.uniform(01< self.p:
    20.             img_ = np.array(img).copy() # 将输入的PIL img形式转变为numpy形式
    21.             h, w, c = img_.shape        # 获取图像的高宽通道数
    22.             signal_pct = self.snr       # 设置信号百分比
    23.             noise_pct = (1 - self.snr)
    24.             # 012分别表示是原始图像,盐噪声,椒噪声
    25.             mask = np.random.choice((012), size=(h, w, 1), p=[signal_pct, noise_pct/2., noise_pct/2.])
    26.             mask = np.repeat(mask, c, axis=2)
    27.             img_[mask == 1= 255   # 盐噪声
    28.             img_[mask == 2= 0     # 椒噪声
    29.             return Image.fromarray(img_.astype('uint8')).convert('RGB')
    30.         else:
    31.             return img
    1. train_transform = transforms.Compose([
    2.     transforms.Resize((224224)),
    3.     AddPepperNoise(0.9, p=0.5),   # 椒盐噪声
    4.     transforms.ToTensor(),
    5.     transforms.Normalize(norm_mean, norm_std),
    6. ])

    以一定概率添加椒盐噪声:

    7.数据增强实战策略

    原则:让训练集与测试集更接近。

    • 空间位置:平移

    • 色彩:灰度图,色彩抖动

    • 形状:仿射变换

    • 上下文场景:遮挡,填充

    • ……

    关注下方【学姐带你玩AI】🚀🚀🚀

    回复“pytorch”了解更多视频详情

    码字不易,欢迎大家点赞评论收藏!

  • 相关阅读:
    数据库云管平台 zCloud v3.5发布,智能化和国产数据库支持能力再增强
    PostgreSQL serial类型
    Linux CentOS 8(磁盘容量配额(Quota))
    基于SSM的校园快递一站式服务系统设计与实现
    网络安全实战,如何利用Python监测漏洞
    新逻辑
    算法 分糖果-(贪心)
    Vue进阶(幺叁幺):父子组件传值实现数据深拷贝_vue3拷贝父组件传来的值
    SpringData、SparkStreaming和Flink集成Elasticsearch
    Google Chrome 任意文件读取 (CVE-2023-4357)漏洞
  • 原文地址:https://blog.csdn.net/weixin_42645636/article/details/130900401