天天看點

Pytorch:torchvision.transforms

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