torchvision.transforms是专门用来对数据进行各种的处理。包括如下操作:
- 归一化
- PIL.Image / numpy.ndarray 与Tensor的相互转化
- 对PIL.Image进行裁剪、缩放等操作
通常,在使用torchvision.transforms,我们通常使用transforms.Compose将transforms组合在一起。
1 torchvision.transforms.ToTensor()
对于一个图片img,调用ToTensor转化成张量的形式,发生的不是将图片的RGB三维信道矩阵变成tensor。图片在内存中以bytes的形式存储,转化过程的步骤是:
- img.tobytes() 将图片转化成内存中的存储格式
- torch.BytesStorage.frombuffer(img.tobytes() ) 将字节以流的形式输入,转化成一维的张量
- 对张量进行reshape
- 对张量进行permute(2,0,1)
- 将当前张量的每个元素除以255
- 输出张量
- 把shape=(H,W,C)的像素值范围为[0, 255]的PIL.Image或者numpy.ndarray转换成shape=(C,H,W)的像素值范围为[0, 1.0]的torch.FloatTensor。
- 通道的顺序与你读取图片所用的工具有关:cv2:(B,G,R)、PIL.Image:(R,G,B)
栗子:
import torch
from PIL import Image
import cv2
from torchvision import transforms
import numpy
img_PIL = Image.open("000001.jpg") # PIL的JpegImageFile格式(size=(W,H))
img_cv2 = cv2.imread("000001.jpg") # numpy数组格式(H,W,C=3),通道顺序(B,G,R)
print(img_PIL.size) # (W,H)
print(img_cv2.shape) # (H,W,C)
img_PIL_np = numpy.array(img_PIL) # 转为numpy后,变为HWC
print(img_PIL_np.shape) # (H,W,C)
# 将numpy数组或PIL.Image读的图片转换成(C,H, W)的Tensor格式且/255归一化到[0,1.0]之间
tran = transforms.ToTensor() # 注意用这种写法
img_PIL_tensor = tran(img_PIL)
img_cv2_tensor = tran(img_cv2)
print(img_PIL_tensor.size()) #(C,H,W) 通道顺序(R,G,B)
print(img_cv2_tensor.size()) #(C,H,W) 通道顺序(B,G,R)
输出结果:
(409, 687)
(687, 409, 3)
(687, 409, 3)
torch.Size([3, 687, 409])
torch.Size([3, 687, 409])
注:当使用PIL.Image.open()打开图片后,如果要使用img.shape函数,需要先将image形式转换成array数组。
2 torchvision.transforms.Normalize(mean, std)
对Tensor类型数据进行变换,处理的数据要求是Tensor类型的。给定(R,G,B)均值mean和方差std,将会把Tensor正则化。即:
Normalized_image=(image-mean)/std
在很多代码中,经常会看到:
from torchvision import transforms
normalize = transforms.Normalize(mean = [0.485, 0.456, 0.406],
std = [0.229, 0.224, 0.225])
图片的RGB的范围不是[0,255]吗,那么图片的3个通道的像素值均值不应该是127吗?那么用这样的归一化参数怎么能归一化到[-1,1]呢?
这是由于:
- 在加载数据集的时候就已经将图片的值范围转换为[0,1],如ImageNet数据集,就是在加载ImageNet的数据的时候就转换成[0,1.0]。
- 操作之前就用了torchvision.transforms.ToTensor,其一个作用是将数据的范围转换到[0,1.0]。通常这两个类一起使用。使用
torchvision.transforms.Compose(transforms)
的时候要注意将这个类放在torchvision.transforms.ToTensor()
后面。- [0.485, 0.456, 0.406]这一组平均值是从Imagenet训练集中抽样算出来的。
3 torchvision.transforms.Compose(transforms)
其作用是:将多个transform组合起来使用。
栗子:
from torchvision import transforms
transform = transforms.Compose([
transforms.RandomCrop(32, padding=4), # 先四周填充0,在吧图像随机裁剪成32*32
transforms.RandomHorizontalFlip(), # 图像一半的概率翻转,一半的概率不翻转
transforms.RandomRotation((-45,45)), # 随机旋转
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.229, 0.224, 0.225)), #R,G,B每层的归一化用到的均值和方差
])
执行的时候会按照顺序执行,先执行transforms.RandomCrop(32, padding=4),最后执行transforms.Normalize((0.4914, 0.4822, 0.4465), (0.229, 0.224, 0.225)),所以这里一定要注意类型的问题,这些方法有的使用的是Tensor类型,有些是PIL.Image的类型。
归一化对神经网络的训练是非常重要的,基本都会将数据归一化到[-1.0,1.0],是神经网络中的必须步骤:
from torchvision import transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225])
]
)
注:
- 这两行使用时顺序不可以颠倒,因为归一化需要的是Tensor型的数据,所以要先将数据转化为Tensor型才可以进行归一化。
- 一般情况下我们将对图片的变换操作放到torchvision.transforms.Compose()进行组组合变换。