PyTorch 09:transforms 图像变换、方法操作及自定义方法

PyTorch 学习笔记

Posted by YEY on December 13, 2020

Lecture 09 transforms 图像变换、方法操作及自定义方法

上节课中,我们学习了 transforms 中的裁剪、旋转和翻转,本节课我们将继续学习 transforms 中的其他数据增强方法。

1. transforms 图像变换

transforms.Pad

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

1
2
3
4
5
transforms.Pad(
    padding,
    fill=0,
    padding_mode='constant'
)

主要参数

  • padding:设置填充大小。
    • 当为 a 时,上下左右均填充 a 个像素。
    • 当为 (a, b) 时,上下填充 b 个像素,左右填充 a 个像素。
    • 当为 (a, b, c, d) 时,左、上、右、下分别填充 abcd 个像素。
  • padding_mode:填充模式,有 4 种模式:
    • constant
    • edge
    • reflect
    • symmetric
  • fill:当 padding_mode='constant' 时,设置填充的像素值,(R, G, B)(Gray)

transforms.ColorJitter

功能:调整亮度、对比度、饱和度和色相。

1
2
3
4
5
6
transforms.ColorJitter(
    brightness=0,
    contrast=0,
    saturation=0,
    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

transforms.Grayscale

功能:将图片转换为灰度图。

1
transforms.Grayscale(num_output_channels)

主要参数

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

transforms.RandomGrayscale

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

1
2
3
4
transforms.RandomGrayscale(
    num_output_channels,
    p=0.1
)

主要参数

  • num_ouput_channels:输出通道数,只能设为 1 或 3。
  • p:概率值,图像被转换为灰度图的概率。

transforms.RandomAffine

功能:对图像进行仿射变换,仿射变换是二维的线性变换,由五种基本原子变换构成:旋转平移缩放错切翻转

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

主要参数

  • degrees:旋转角度设置。
  • translate:平移区间设置,如 (a, b), a 设置宽 (width),b 设置高 (height)。图像在宽维度平移的区间为 -img_width * a < dx < img_width * a
  • scale:缩放比例 (以面积为单位)。
  • fill_color:填充颜色设置。
  • shear:错切角度设置,有水平错切和垂直错切。
    • 若为 a,则仅在 x 轴错切,错切角度在 (-a, a) 之间。
    • 若为 (a, b),则 a 设置 x 轴角度,b 设置 y 轴角度。
    • 若为 (a, b, c, d),则 ab 设置 x 轴角度,cd 设置 y 轴角度。
  • resample:重采样方式,有 NEARESTBILINEARBICUBIC 三种。

transforms.RandomErasing

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

1
2
3
4
5
6
7
transforms.RandomErasing(
    p=0.5,
    scale=(0.02, 0.33),
    ratio=(0.3, 3,3),
    value=0,
    inplace=False
)

主要参数

  • p:概率值,执行该操作的概率。
  • scale:遮挡区域的面积。
  • ratio:遮挡区域长宽比。
  • value:设置遮挡区域的像素值,(R, G, B) 或者 (Gray)

参考文献Random Erasing Data Augmentation

transforms.Lambda

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

1
transforms.Lambda(lambd)

主要参数

  • lambd:lambda 匿名函数,lambda [arg1 [,arg2, ... , argn]]: expression

代码示例

1
2
transforms.TenCrop(200, vertical_flip=True),
transforms.Lambda(lambda crops: torch.stack([transforms.Totensor()(crop) for crop in crops]))

2. transforms 选择操作

我们已经学习了 transforms 中对图像的各种增强方法,下面我们将介绍对 transforms 方法的三种选择操作,它们可以使 transforms 数据增强方法更加灵活、丰富、多样。

transforms.RandomChoice

功能:从一系列 transforms 方法中随机挑选一个。

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

transforms.RandomApply

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

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

transforms.RandomOrder

功能:对一组 transforms 操作打乱顺序。

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

3. 自定义 transforms

尽管 PyTorch 提供了许多 transforms 方法,然而在实际应用中,可能还需要根据项目需求来自定义一些 transforms 方法。下面我们将学习如何自定义 transforms 方法及其注意事项。

为了自定义 transforms 方法,首先需要了解其运行机制,在之前介绍数据读取机制 DataLoaderDataset 时,我们提到过 transforms 方法是在 Compose 类中的 __call__ 函数中被调用的。我们对一组 transforms 方法进行 for 循环,每次按顺序挑选出我们的 transforms 方法 t 并执行它。可以看到,每个 transforms 方法仅接收一个参数,并返回一个参数。另外注意,由于是通过 for 循环调用,当前 transforms 方法的输出就是下一个 transforms 方法的输入。

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

自定义 transforms 要素

  1. 仅接收一个参数,返回一个参数。
  2. 注意上下游的输出与输入之间的数据类型必须要匹配。

我们在设计 transforms 方法的时候可能需要多个参数,比如设置概率值、信噪比等,这些可以通过类方法实现。

通过类实现多参数传入

1
2
3
4
5
6
class YourTransforms(object):
    def __init__(self, ...):
        ...
    def __call__(self, img):
        ...
        return img

上面是一个自定义 transforms 方法的基本结构。首先是一个初始化 __init__ 方法,在初始化的时候我们可以传入想要的参数,比如概率值、信噪比等等。然后,这个类中还必须有一个 __call__ 函数,即这个类的实例可以被调用,__call__ 函数只接受一个 input 参数,然后执行自定义的一些功能,最后返回一个 output,并且输入与输出的数据类型必须匹配,比如都是 imgtensorlistturple 或者 dict 等。

例子:椒盐噪声

椒盐噪声 (salt pepper noise) 又称为 脉冲噪声,是一种随机出现的白点或者黑点,白点称为 盐噪声,黑点称为 椒噪声

信噪比 (Signal-Noise Rate, SNR) 是衡量噪声的比例,在图像中为图像像素的占比。

下面是对一张小猫图像增加不同信噪比的椒盐噪声的效果图:

从左到右信噪比依次为 0.9、0.7、0.5、0.3。可以看到,随着信噪比的减小,即信号的减少,图片丢失的信息越来越多。当信噪比为 0.9 时,我们还可以清晰地看到这是一张小猫的图像;而当信噪比降低到 0.3 时,我们已经很难辨别图像的真实内容了。

下面,我们通过自定义 transforms 方法对图像添加椒盐噪声:

1
2
3
4
5
6
7
8
9
10
11
class AddPepperNoise(object):

    def __init__(self, snr, p):
        self.snr = snr  # 设置信噪比
        self.p = p  # 设置概率值

    def __call__(self, img):

        ...  # 添加椒盐噪声具体实现过程

        return img

Python 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class AddPepperNoise(object):
    """增加椒盐噪声
    Args:
        snr (float): 信噪比,Signal Noise Rate
        p (float): 概率值,依概率执行该操作
    """

    def __init__(self, snr, p=0.9):
        assert isinstance(snr, float) or (isinstance(p, float))
        self.snr = snr
        self.p = p

    def __call__(self, img):
        """
        Args:
            img (PIL Image): PIL Image
        Returns:
            PIL Image: PIL image.
        """
        if random.uniform(0, 1) < self.p:
            img_ = np.array(img).copy()
            h, w, c = img_.shape
            signal_pct = self.snr
            noise_pct = (1 - self.snr)
            mask = np.random.choice((0, 1, 2), size=(h, w, 1), p=[signal_pct, noise_pct/2., noise_pct/2.])
            mask = np.repeat(mask, c, axis=2)
            img_[mask == 1] = 255   # 盐噪声
            img_[mask == 2] = 0     # 椒噪声
            return Image.fromarray(img_.astype('uint8')).convert('RGB')
        else:
            return img

4. transforms 方法总结

裁剪

  • transforms.CenterCrop
  • transforms.RandomCrop
  • transforms.RandomResizedCrop
  • transforms.FiveCrop
  • transforms.TenCrop

翻转和旋转

  • transforms.RandomHorizontalFlip
  • transforms.RandomVerticalFlip
  • transforms.RandomRotation

图像变换

  • transforms.Pad
  • transforms.ColorJitter
  • transforms.Grayscale
  • transforms.RandomGrayscale
  • transforms.RandomAffine
  • transforms.LinearTransformation
  • transforms.RandomErasing
  • transforms.Lambda
  • transforms.Resize
  • transforms.Totensor
  • transforms.Normalize

transforms 操作

  • transforms.RandomChoice
  • transforms.RandomApply
  • transforms.RandomOrder

5. 数据增强实战应用

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

  • 空间位置:平移
  • 色彩:灰度图,色彩抖动
  • 形状:仿射变换
  • 上下文场景:遮挡, 填充
  • ……

例子

我们看到,在训练集中,猫基本都处于图片的中央位置,而在测试集中的猫处于偏左/右,或者在角落的情况。对于这种情况,我们可以在数据增强时改变训练集中的空间位置,例如平移,来逼近测试集的图片。

例子

我们看到,在训练集中,猫基本都是白色的,而在测试集中的猫是黑色的。对于这种情况,我们可以在数据增强时对训练集中的图片进行色彩抖动或者变换处理,来逼近测试集的图片。有时,训练集和测试集中猫的姿态差异很大,这种情况下,我们可以通过对训练集图片进行仿射变换处理来改变猫的形状。另外,还可以对比看下测试集中有无遮挡情况,可以对训练集进行遮挡、填充等相应处理。

人民币分类

在之前的人民币分类例子中,我们的数据集是面额为 1 元与 100 元的第四套人民币各 100 张,那么基于该数据集训练出的模型是否可以对第五套人民币的 100 元进行正确分类呢?

直观上,第五套人民币的 100 元与第四套人民币的 1 元在颜色上比较相近,而在面额上与第四套人民币的 1 00 元一样。实验证明,如果不进行额外的数据增强,模型会将第五套人民币的 100 元识别为 1 元,这很可能是由于二者在颜色上的相似性导致的。当我们对图像进行灰度处理后,模型将可以对第五套人民币的 100 元进行正确分类。

6. 总结

在本节课中,我们学习了数据预处理 transforms 的图像变换、操作方法,以及自定义 transforms。到目前为止,PyTorch 中的数据模块我们已经学习完毕,在下节课中,我们将会学习 PyTorch 中的模型模块。

下节内容:模型创建步骤与 nn.Module

知识共享许可协议本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 欢迎转载,并请注明来自:YEY 的博客 同时保持文章内容的完整和以上声明信息!