Pytorch的第一步:(2) transforms 的使用

在我的前一篇文章:Pytorch的第一步:(1) Dataset类的使用 里,不论是使用 torchvision.datasets 还是我们自定义了 Dataset 子类,都有一个形参 transforms 被传入。上篇文章我没有详细讲解,是因为这是一块很大的内容,故专门写本文讲解。
transforms 是图像处理函数,主要用于对索引出来的图片进行 剪切、翻转、平移、仿射等操作,也就是得到我们想要的预处理过程。

pytorch 提供的 torchvision.transforms 模块是专门用来进行图像预处理的,本文按照处理方式的不同,分组介绍和试验这些预处理方法

注意点

  • transforms.Compose() 可以把多类转换操作结合起来
  • 可转换的图像包括 PIL Image, Tensor Imagebatch of Tensor Images. Tensor Image是(C, H, W)形状的tensor, batch of Tensor Images是形状为(B, C, H, W) 的tensor
  • 设置随机数种子要用 torch.manual_seed(n)
  • 本文提到的 transform 方法都是一个类,我们有两种处理方式,一个是实例化这个 transform 类,然后把图片传入,另一种方式是实例化一个 transforms.Compose() 类。然后对 transforms.Compose 的实例传入图像处理。区别是如果直接实例化 transform 类,一次只能对图像做一种 transform 操作。但是 transforms.Compose() 类支持传入多个 transform 类,即一个 transforms.Compose() 包含多个 transform 类,这样 一次性能够实现多个操作:
import torchvision.transforms as transforms
pic = imread('...')
#---------方 法 1---------- 一次一种处理方式
transform = transforms.CenterCrop(720)  # 中心裁剪
picProcessed1 = transform(pic)
transform = transforms.RandomHorizontalFlip(p=0.5)  # 随机水平翻转
picProcessed2 = transform(picProcessed1)

#---------方 法 2----------一步到位
transform = transforms.Compose([
    transforms.CenterCrop(720)
    transforms.RandomHorizontalFlip(p=0.5)
    ])
picProcessed = transform(pic)

0、 Transforms 方法概览

1 裁剪 Crop

1.1 中心裁剪: transforms.CenterCrop()
1.2 随机裁剪: transforms.RandomCrop()
1.3 随机长宽比裁剪: transforms.RandomResizedCrop()
1.4 上下左右中心裁剪: transforms.FiveCrop()
1.5 上下左右中心裁剪后翻转: transforms.TenCrop()

2 翻转和旋转 Flip and Rotation

2.1 依概率 p 水平翻转: transforms.RandomHorizontalFlip(p=0.5)
2.2 依概率 p 垂直翻转: transforms.RandomVerticalFlip(p=0.5)
2.3 随机旋转: transforms.RandomRotation()

3 图像变换

3.1 resize: transforms.Resize
3.2 标准化: transforms.Normalize
3.3 转为 tensor,并归一化至[0-1]: transforms.ToTensor
3.4 填充: transforms.Pad
3.5 修改亮度、对比度和饱和度: transforms.ColorJitter
3.6 转灰度图: transforms.Grayscale
3.7 线性变换: transforms.LinearTransformation()
3.8 仿射变换: transforms.RandomAffine
3.9 依概率 p 转为灰度图: transforms.RandomGrayscale
3.10 将数据转换为 PILImage: transforms.ToPILImage
3.11 transforms.Lambda: Apply a user-defined lambda as a transform.

注意本文将用以下代码为测试代码,逐个说明.
此外,如果没有特殊说明,被处理后的tensor或者图像,其数据类型不变。

import matplotlib.pyplot as plt
import torchvision.transforms as transforms
import torchvision as tv

# 定义转换函数
transform = transforms.Compose([
    transforms.CenterCrop(720) #这是一个例子
    #========================================
    # 此处添加需要放入的变换函数
    #========================================
])

# 读图
picTensor = tv.io.read_image("testpic.jpg")

# 转换图
picTransformed = transform(picTensor)

# 显示
picNumpy = picTensor.permute(1, 2, 0).numpy()
plt.imshow(picNumpy)
plt.show()
picNumpy = picTransformed.permute(1, 2, 0).numpy()
plt.imshow(picNumpy)
plt.show()

本文使用的样例图片为:torch.Size([3, 2100, 3174])


示例图片,图片被分为24格方便观察变化,图片尺寸是 torch.Size([3, 2100, 3174])

1 裁剪 Crop

1.1 中心裁剪: transforms.CenterCrop( size )

*size* 可以是 int、list、turple, 功能是把图片剪裁,
只保留以图片中心点为中心,*size* 为尺寸的那部分。代码如下:

transforms.CenterCrop(size=1080)
输出:
CenterCrop(size=(1080, 1080))
转换后图片形状: torch.Size([3, 2100, 3174])
转换后图片形状: torch.uint8 

转换后图片形状: torch.Size([3, 1080, 1080])
转换后图片形状: torch.uint8
中心裁剪


1.2 随机裁剪: transforms.RandomCrop()

transforms.RandomCrop(size=1600, padding=300,
                      pad_if_needed=False, fill=0, padding_mode='constant')
这里的pad是先把四周填充 pad=300 像素,然后再剪裁,
相当于先给图片扩大一圈边框,再随机找位置剪裁,有可能会把填充的边框裁剪进去,也可能不会。
输出:
RandomCrop(size=(1600, 1600), padding=300)
转换后图片形状: torch.Size([3, 2100, 3174])
转换后图片形状: torch.uint8 

转换后图片形状: torch.Size([3, 1600, 1600])
转换后图片形状: torch.uint8
随机剪裁,此图把填充的边框也裁剪进去了


1.3 随机长宽比裁剪: transforms.RandomResizedCrop()

transforms.RandomResizedCrop(size=900, scale=(0.03, 1.8),
                             ratio=(0.9999, 1.0), interpolation=2)
带缩放的裁剪。
scale表示 size 的缩放倍数,ratio 表示 size 的宽高比倍数,
所以对于我们想要的 size ,首先会被缩放和改变宽高比,然后那得到的size去裁剪原图,
最后把我们裁剪的图片再还原回 输入的 size 大小。
这样就实现了我们带缩放和宽高比变换的裁剪。
只设置了缩放,未设置宽高比的裁剪。 可以看出原图被缩小了

同时设置了缩放、宽高比的裁剪。 可以看出原图被缩小了,并且宽度被压缩


1.4 上下左右中心裁剪: transforms.FiveCrop(size:int)

返回一个 `tuple`,(左上裁剪,右上裁剪,左下裁剪,右下裁剪,中间裁剪),
这个`tuple`包含5个tensor ,每个tensor代表的是对原图进行的截图,
截图位置就是左上,右上,左下,右下,中间。大小是传入的参数 size
代码:
transforms.FiveCrop(1000)
输出:(以左上为例)
FiveCrop(size=(1000, 1000))
转换后图片形状: torch.Size([3, 2100, 3174])
转换后图片形状: torch.uint8 

转换后图片形状: torch.Size([3, 1000, 1000])
转换后图片形状: torch.uint8

FiveCrop左上角图片


1.5 上下左右中心裁剪后翻转: transforms.TenCrop()

与 FiveCrop() 类似,只不过它返回的是10元元组,这多出来的后 5 个元素是前五个的水平翻转罢了。
带水平翻转

2 翻转和旋转 Flip and Rotation


2.1 依概率 p 水平翻转: transforms.RandomHorizontalFlip(p=0.5)
平平无奇的水平翻转,并非一定会翻转,而是按照 p=0.5 的概率进行,可以设置为1 ,让其一定翻转。(下同)

翻转了


2.2 依概率 p 垂直翻转: transforms.RandomVerticalFlip(p=0.5)

翻转了


2.3 随机旋转: transforms.RandomRotation(degrees=90, resample=False, expand=False, center=None)

平平无奇的图片旋转操作,这里需要注意一下,
比如我们想旋转完,图片尺寸不变,就需要把 expand 设置为 False,
这会自动截掉那些超出边框的部分;
如果设置为True ,就会自动扩展图像边框,让图像完整地保留下来。
degrees 不是设置的旋转角度,
而是会在 [-degrees, +degrees] 间随机选角度。
这个直接就是角度值,不是弧度制。
center 是旋转中心
未 expand

设置了 expand

3 图像变换


3.1 resize: transforms.Resize(size=900)

如果 size 只是一个 int ,那就会在保持长宽比的情况下,把短边缩放到指定 size ,
长边按照长宽比缩放
缩放效果


3.2 标准化: transforms.Normalize(mean=[], var=[])

mean、var 都是三元序列,分别对应三个通道的均值方差
注意:此时输入的图像应是 0-1 之间的 float,而不是0-255的 int8 类型
标准化的图片


3.3 转为 tensor,并归一化至[0-1]: transforms.ToTensor

这个函数的输入对象主要是 numpy.ndarray 以及 PIL.image 类型的图片。
这方法是为了把其他库读入的图片转换成 pytorch 统一的图片 Tensor 形式。即:
1. shape 为 [B, C, H, W]
2. dtype 为 torch.float


3.4 填充: transforms.Pad(padding, fill=0, padding_mode='constant')

padding_mode='constant'模式下:
padding:在哪些边填充多少像素。三种情况:
1.单独一个 int ,表示图像四边都要填充 int 个像素值为 fill 的像素。
2.一个长度为 2 的元组或数组 list。
那么 list[0] 表示在图像左右填充list[0]个像素值为 fill 或者 fill[0] 的像素。
3.一个长度为 4 的元组或数组 list。
那么 list[0] 表示在图像左填充list[0]个像素值为 fill 或者 fill[0] 的像素; 
list[1] 表示在图像左填充list[1]个像素值为 fill 或者 fill[1] 的像素;
list[2] 表示在图像左填充list[2]个像素值为 fill 或者 fill[2] 的像素; 
list[4] 表示在图像左填充list[4]个像素值为 fill 或者 fill[4] 的像素。
padding_mode 还可以选:
edge: pads with the last value at the edge of the image
reflect: pads with reflection of image without repeating the last value on the edge。
For example, padding [1, 2, 3, 4] with 2 elements on both sides 
in reflect mode will result in [3, 2, 1, 2, 3, 4, 3, 2]
以上两种模式下不需要设置fill。
*注意:文档中说 fill 可以是个 3元 tuple ,
然后就能分别给 RGB 三个通道添加不同灰度的边框,实现彩色边框,
可我试了不行。*
用 reflect 模式做的 pad


3.5 修改亮度、对比度和饱和度

transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)
随机修改亮度、对比度、饱和度和色调,
其随机范围是 【max(0,1-brightness), 1+brightness】,
brightness,contrast,saturation都是可以【0,∞】
hue比较特殊,只能在 【0,0.5】之间取值
各类都修改了一下


3.6 转灰度图: transforms.Grayscale(num_output_channels=1)

简单地变换成灰度图,输出通道数可以是1或者3
输出通道选择3的话,R=G=B
灰度图


3.7 线性变换: transforms.LinearTransformation()
这个好麻烦,我不会,自己看文档

3.8 仿射变换: transforms.RandomAffine

transforms.RandomAffine(degrees=10, translate=None, scale=None, shear=(20, 30), resample=0, fillcolor=0)
这个函数的 shear 就是剪切,比较重要
translate是平移
degrees是旋转
这是个综合函数,可以实现很多功能,可以让其他为默认值,只用其中一种
仿射变换


3.9 随机透视: transforms.RandomPerspective()

transforms.RandomPerspective(distortion_scale=0.5, p=0.5, interpolation=2, fill=0)
随机按照概率 p 进行程度为 distortion_scale 的透视变换
透视变换


3.10 依概率 p 转为灰度图: transforms.RandomGrayscale
就是不一定会转变成灰度图,按照某种概率搞

3.11 将数据转换为 PILImage: transforms.ToPILImage
转成 PILImage ,我用不到先不写了

3.12 transforms.Lambda: Apply a user-defined lambda as a transform.
这个不会哦。

推荐阅读更多精彩内容