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()進行組組合變換。