pytorch官方文檔
transform源碼解讀
莫煩pytorch
1 .transform
??torchvision.transforms是pytorch中的圖像預處理包
??有很多圖像預處理方法, 今天從圖像維度出發(fā), 講一些我們經(jīng)常會用到的
- Resize, 把給定的圖片resize到target size
- Normalize, Normalized an tensor image with mean and standard deviation
- ToTensor, convert a PIL image to tensor (H, W, C) in range [0,255] to a torch.Tensor(C, H, W) in the range [0.0, 1.0]
- ToPILImage, convert a tensor to PIL image
- CenterCrop, 在圖片的中間區(qū)域進行crop
- RandomCrop, 在一個隨機位置進行crop
- FiveCrop, 把圖像裁剪為四個角和一個中心
- TenCrop, five crop+flip 1->10
1.1 ToTensor & ToPILImage
??從Numpy到Tensor的轉(zhuǎn)換有兩種方法, 可以用torch.from_numpy(ndarray), 也可以用ToTensor()(ndarray), 這兩種有什么區(qū)別呢?
np_data = np.random.random((260, 194, 3))
# np_data size (height, width, channel)
torch_data = torch.from_numpy(np_data) # numpy -> tensor 1
# torch_data size [260, 194, 3]
torch2array = torch_data.numpy()
# torch2array size[260, 194, 3]
tensor_data = ToTensor()(np_data) # numpy -> tensor 2
#tensor_data size[3, 260, 194]
tensor2array = tensor_data.numpy()
#tensor2array size[3, 260, 194]
??可以看出, 如果用torch.from_numpy()的方法, 轉(zhuǎn)換的tensor和ndarray是相同的shape, (height, width, channel)
的shape進, 仍然變成(height, width, channel)
的shape
??但是, 如果是用ToTensor()的方式, 那就是 (height, width, channel)
的shape進, (channel, height, width)
的shape出
??不同于numpy->tensor有兩種做法, 從tensor->numpy卻只有一種, 就是tensor.numpy(), 而這種做法的shape前后也是不會變化的, 所以我們看到如果按照ToTensor() -> tensor.numpy()的方式將np轉(zhuǎn)成tensor再轉(zhuǎn)為np, 那shape和一開始的np是不同的, 就是(height, width, channel)
會變成(channel, height, width)
??下面我們舉一個實際的讀圖轉(zhuǎn)換的例子
# PIL -> np -> tensor -> PIL
PIL_img = Image.open('test_pic.jpeg')
#PIL_img size [194, 260, 3], range [0,255]
np_img = np.asarray(PIL_img)
# np_img size [260, 194, 3], range [0,255]
tensor_from_np = ToTensor()(np_img)
# tensor_img size [3, 260, 194], range [0,1]
tensor_from_PIL = ToTensor()(PIL_img)
# tensor_img size [3, 260, 194], range [0,1]
PIL_from_tensor = ToPILImage()(tensor_from_np)
# PIL_from_tensor size [194, 260, 3], range[0,255]
PIL_from_tensor.save('img_from_tensor.bmp')
# tensor -> numpy -> PIL
np_from_tensor = tensor_from_np.numpy()
# np_from_tensor size [3, 260, 194], range [0, 1]
np_from_tensor *= 255
# np_from_tensor size [3, 260, 194], range [0, 255]
np_from_tensor = np.transpose(np_from_tensor, (1, 2, 0))
# np_from_tensor size [260, 194, 3], range [0, 255]
PIL_from_np = Image.fromarray(np.uint8(np_from_tensor))
# PIL_from_np size [194, 260, 3], range [0, 255]
PIL_from_np.save('img_from_np.bmp')
??為了說明PIL/np/tensor相互轉(zhuǎn)換之間產(chǎn)生的shape和range的變化, 我們這個例子稍微復雜了一點, 關(guān)鍵就是三點
- 用ToTensor()轉(zhuǎn)換, 不管input的是PIL還是np, 只要是uint8格式的, 都會直接轉(zhuǎn)成[0,1]的float32格式, 所以我們看到的tensor_from_np和tensor_from_PIL是一樣的, 而ToPILImage是將tensor轉(zhuǎn)為PIL Image, 自動又會把float32的[0,1]轉(zhuǎn)回uint8
- 如果是先把tensor轉(zhuǎn)為np, 再轉(zhuǎn)為PIL Image, 那就有上面我們提到的np_img和tensor_from_np.numpy()的shape和range都是不同的, 因為.numpy()的過程就是把tensor格式的數(shù)據(jù)復制到np格式的數(shù)據(jù), 重要的是, np_from_tensor和tensor_from_np共享了同一段內(nèi)存, 所以我們可以看到在更改np_from_tensor的值的時候, tensor_from_np也在變, 所以要進行[0,1]->[0,255] + transpose更改shape + float32->uint8的過程改為和np_img的shape和range都一樣, 這個時候再用Image.fromarray()才能還原成和PIL_img相同的圖像
- ToPILImage除了可以做tensor->PIL, 還可以做np->PIL, 就是將shape為(C,H,W)的Tensor或shape為(H,W,C)的numpy.ndarray轉(zhuǎn)換成PIL.Image, 但注意, 輸入是np的話它不會把[0,1]float32自動轉(zhuǎn)成uint8, 所以用法就變成和Image.fromarray()一樣了
#before np_from_tensor
tensor_from_np2 = tensor_from_np.clone()
...
...
...
np_from_tensor2 = tensor_from_np2.numpy()
np_from_tensor2 *= 255
np_from_tensor2 = np.transpose(np_from_tensor2, (1, 2, 0))
PIL_from_np2 = ToPILImage()(np.uint8(np_from_tensor2))
PIL_from_np2.save('img_from_np2.bmp')
Note:
??這個np.transpose()函數(shù)很有用, opencv的圖像shape和np也不一樣, 也需要用這個函數(shù)來轉(zhuǎn)換維度
??上面提到.numpy()的做法是讓tensor和np.ndarray()共享內(nèi)存, 所以如果我們要用tensor原來的數(shù)據(jù), 那就要在對np_from_tensor處理之前, 先用.clone()的方式復制出一個副本來
1.2 Resize
torchvision.transforms.Resize(size, interpolation=2)
??這個resize的函數(shù)很簡單, 用的就是PIL圖像處理的內(nèi)核, 要注意的是, 這個size的格式是(height, width)
, 也就是說加入要用這個函數(shù)2倍downsample一個輸入PIL Image圖像, 應該是這樣
input_image = Image.open('image path')
Resize((input_image.size[1], input_image.size[0]))(input_image)
#or
Resize((input_image.height, input_image.width))(input_image)
??推薦用第二種形式, 比較清楚地體現(xiàn)PIL Image和tensor的維度差別
1.3 Crop
??torchvision.transforms里有很多crop的方法, 選幾個用的多的說一下
- CenterCrop, 在圖片的中間區(qū)域進行crop
- RandomCrop, 在一個隨機位置進行crop
- FiveCrop, 把圖像裁剪為四個角和一個中心
- TenCrop, 在FiveCrop的基礎(chǔ)上宫峦,再將輸入圖像進行水平或豎直翻轉(zhuǎn),然后再進行FiveCrop操作,這樣一張輸入圖像就能得到10張crop結(jié)果
Crop | Description |
---|---|
CenterCrop(size) | 將給定的PIL.Image 進行中心切割耍群,得到給定的size 伶授,size 可以是tuple ,(target_height, target_width) 擦耀。size 也可以是一個Integer 施符,在這種情況下,切出來的圖片的形狀是正方形慈迈。 |
RandomCrop(szie, padding=0) | 切割中心點的位置隨機選取若贮。size 可以是tuple 也可以是Integer
|
FiveCrop(size) | 把圖像裁剪為四個角和一個中心, 1張圖生成5張圖 |
TenCrop(size, vertical_flip=False) | 在FiveCrop 的基礎(chǔ)上,再將輸入圖像進行水平或豎直翻轉(zhuǎn)痒留,然后再進行FiveCrop 操作谴麦,這樣一張輸入圖像就能得到10張crop結(jié)果 |
RandomSizedCrop(size, interpolation=2) | 先將給定的PIL.Image 隨機切,然后再resize 成給定的size 大小 |
1.4 Normalize
1.5 Compose
??transforms.Compose()就是把以上提到的多個步驟整合到一起, 按順序執(zhí)行
transforms.Compose([
transforms.CenterCrop(10),
transforms.ToTensor(),
])