1. 程式人生 > 實用技巧 >[PyTorch 學習筆記] 2.3 二十二種 transforms 圖片資料預處理方法

[PyTorch 學習筆記] 2.3 二十二種 transforms 圖片資料預處理方法

本章程式碼:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson2/transforms/

這篇主要分為幾個部分介紹 transforms:

  • 裁剪
  • 旋轉和翻轉
  • 影象變換
  • transforms 方法操作
  • 自定義 transforms 方法

最後是資料增強的實戰:對人民幣二分類實驗進行數增強。

由於圖片經過 transform 操作之後是 tensor,畫素值在 0~1 之間,並且標準差和方差不是正常圖片的。所以定義了transform_invert()方法。功能是對 tensor 進行反標準化操作,並且把 tensor 轉換為 image,方便視覺化。

我們主要修改的是transforms.Compose程式碼塊中的內容,其中transforms.Resize((224, 224))是把圖片縮放到 (224, 224) 大小 (下面的所有操作都是基於縮放之後的圖片進行的),然後再進行其他 transform 操作。

原圖如下:


經過縮放之後,圖片如下:
# 裁剪

transforms.CenterCrop

torchvision.transforms.CenterCrop(size)

功能:從影象中心裁剪圖片

  • size: 所需裁剪的圖片尺寸

transforms.CenterCrop(196)的效果如下:


如果裁剪的 size 比原圖大,那麼會填充值為 0 的畫素。`transforms.CenterCrop(512)`的效果如下:
## transforms.RandomCrop
torchvision.transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant')

功能:從圖片中隨機裁剪出尺寸為 size 的圖片,如果有 padding,那麼先進行 padding,再隨機裁剪 size 大小的圖片。

  • size:
  • padding: 設定填充大小
    • 當為 a 時,上下左右均填充 a 個畫素
    • 當為 (a, b) 時,左右填充 a 個畫素,上下填充 b 個畫素
    • 當為 (a, b, c, d) 時,左上右下分別填充 a,b,c,d
  • pad_if_need: 當圖片小於設定的 size,是否填充
  • padding_mode:
    • constant: 畫素值由 fill 設定
    • edge: 畫素值由影象邊緣畫素設定
    • reflect: 映象填充,最後一個畫素不映象。([1,2,3,4] -> [3,2,1,2,3,4,3,2])
    • symmetric: 映象填充,最後一個畫素也映象。([1,2,3,4] -> [2,1,1,2,3,4,4,4,3])
  • fill: 當 padding_mode 為 constant 時,設定填充的畫素值

transforms.RandomCrop(224, padding=16)的效果如下,這裡的 padding 為 16,所以會先在 4 邊進行 16 的 padding,預設填充 0,然後隨機裁剪出 (224,224) 大小的圖片,這裡裁剪了左上角的區域。


`transforms.RandomCrop(224, padding=(16, 64))`的效果如下,首先在左右進行 16 的 padding,上下進行 64 的 padding,然後隨機裁剪出 (224,224) 大小的圖片。
`transforms.RandomCrop(224, padding=16, fill=(255, 0, 0))`的效果如下,首先在上下左右進行 16 的 padding,填充值為 (255, 0, 0),然後隨機裁剪出 (224,224) 大小的圖片。
`transforms.RandomCrop(512, pad_if_needed=True)`的效果如下,設定`pad_if_needed=True`,圖片小於設定的 size,用 (0,0,0) 填充。
`transforms.RandomCrop(224, padding=64, padding_mode='edge')`的效果如下,首先在上下左右進行 64 的 padding,使用邊緣畫素填充,然後隨機裁剪出 (224,224) 大小的圖片。
`transforms.RandomCrop(224, padding=64, padding_mode='reflect')`的效果如下,首先在上下左右進行 64 的 padding,使用映象填充,然後隨機裁剪出 (224,224) 大小的圖片。
`transforms.RandomCrop(1024, padding=1024, padding_mode='symmetric')`的效果如下,首先在上下左右進行 1024 的 padding,使用映象填充,然後隨機裁剪出 (1024, 1024) 大小的圖片。
## transforms.RandomResizedCrop
torchvision.transforms.RandomResizedCrop(size, scale=(0.08, 1.0), ratio=(0.75, 1.3333333333333333), interpolation=2)

功能:隨機大小、隨機寬高比裁剪圖片。首先根據 scale 的比例裁剪原圖,然後根據 ratio 的長寬比再裁剪,最後使用插值法把圖片變換為 size 大小。

  • size: 裁剪的圖片尺寸
  • scale: 隨機縮放面積比例,預設隨機選取 (0.08, 1) 之間的一個數
  • ratio: 隨機長寬比,預設隨機選取 ($\displaystyle\frac{3}{4}$, $\displaystyle\frac{4}{3}$ ) 之間的一個數。因為超過這個比例會有明顯的失真
  • interpolation: 當裁剪出來的圖片小於 size 時,就要使用插值方法 resize
    • PIL.Image.NEAREST
    • PIL.Image.BILINEAR
    • PIL.Image.BICUBIC

transforms.RandomResizedCrop(size=224, scale=(0.08, 1))的效果如下,首先隨機選擇 (0.08, 1) 中 的一個比例縮放,然後隨機裁剪出 (224, 224) 大小的圖片。


`transforms.RandomResizedCrop(size=224, scale=(0.5, 0.5))`的效果如下,首先縮放 0.5 倍,然後隨機裁剪出 (224, 224) 大小的圖片。
## transforms.FiveCrop(TenCrop)
torchvision.transforms.FiveCrop(size)
torchvision.transforms.TenCrop(size, vertical_flip=False)

功能:FiveCrop在影象的上下左右以及中心裁剪出尺寸為 size 的 5 張圖片。Tencrop對這 5 張圖片進行水平或者垂直映象獲得 10 張圖片。

  • size: 最後裁剪的圖片尺寸
  • vertical_flip: 是否垂直翻轉

由於這兩個方法返回的是 tuple,每個元素表示一個圖片,我們還需要把這個 tuple 轉換為一張圖片的tensor。程式碼如下:

transforms.FiveCrop(112),
transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops]))

並且把transforms.Compose中最後兩行註釋:

# transforms.ToTensor(), # toTensor()接收的引數是 Image,由於上面已經進行了 toTensor(), 因此這裡註釋
# transforms.Normalize(norm_mean, norm_std), # 由於是 4 維的 Tensor,因此不能執行 Normalize() 方法
  • transforms.toTensor()接收的引數是 Image,由於上面已經進行了 toTensor()。因此註釋這一行。
  • transforms.Normalize()方法接收的是 3 維的 tensor (在 _is_tensor_image()方法 裡檢查是否滿足這一條件,不滿足則報錯),而經過transforms.FiveCrop返回的是 4 維張量,因此註釋這一行。

最後的 tensor 形狀是 [ncrops, c, h, w],圖片視覺化的程式碼也需要做修改:

## 展示 FiveCrop 和 TenCrop 的圖片
ncrops, c, h, w = img_tensor.shape
columns=2 # 兩列
rows= math.ceil(ncrops/2) # 計算多少行
# 把每個 tensor ([c,h,w]) 轉換為 image
for i in range(ncrops):
    img = transform_invert(img_tensor[i], train_transform)
    plt.subplot(rows, columns, i+1)
    plt.imshow(img)
plt.show()

5 張圖片分別是左上角,右上角,左下角,右下角,中心。圖片如下:


`transforms.TenCrop`的操作同理,只是返回的是 10 張圖片,在`transforms.FiveCrop`的基礎上多了映象。

旋轉和翻轉

transforms.RandomHorizontalFlip(RandomVerticalFlip)

功能:根據概率,在水平或者垂直方向翻轉圖片

  • p: 翻轉概率

transforms.RandomHorizontalFlip(p=0.5),那麼一半的圖片會被水平翻轉。

transforms.RandomHorizontalFlip(p=1),那麼所有圖片會被水平翻轉。

transforms.RandomHorizontalFlip(p=1),水平翻轉的效果如下。


`transforms.RandomVerticalFlip(p=1)`,垂直翻轉的效果如下。
## transforms.RandomRotation
torchvision.transforms.RandomRotation(degrees, resample=False, expand=False, center=None, fill=None)

功能:隨機旋轉圖片

  • degree: 旋轉角度
    • 當為 a 時,在 (-a, a) 之間隨機選擇旋轉角度
    • 當為 (a, b) 時,在 (a, b) 之間隨機選擇旋轉角度
  • resample: 重取樣方法
  • expand: 是否擴大矩形框,以保持原圖資訊。根據中心旋轉點計算擴大後的圖片。如果旋轉點不是中心,即使設定 expand = True,還是會有部分資訊丟失。
  • center: 旋轉點設定,是座標,預設中心旋轉。如設定左上角為:(0, 0)

transforms.RandomRotation(90)的效果如下,shape 為 (224, 224),原來圖片的 4 個角有部分資訊丟失。


`transforms.RandomRotation((90), expand=True)`的效果如下,,shape 大於 (224, 224),具體的 shape 的大小會根據旋轉角度和原圖大小計算。原來圖片的 4 個角都保留了下來。
但是需要注意的是,如果設定 expand=True, batch size 大於 1,那麼在一個 Batch 中,每張圖片的 shape 都不一樣了,會報錯 `Sizes of tensors must match except in dimension 0`。所以如果 expand=True,那麼還需要進行 resize 操作。

transforms.RandomRotation(30, center=(0, 0)),設定旋轉點為左上角,效果如下。


`transforms.RandomRotation(30, center=(0, 0), expand=True)`的效果如下,如果旋轉點不是中心,即使設定 expand = True,還是會有部分資訊丟失。
# 影象變換

Pad

torchvision.transforms.Pad(padding, fill=0, padding_mode='constant')

功能:對影象邊緣進行填充

  • padding: 設定填充大小
    • 當為 a 時,上下左右均填充 a 個畫素
    • 當為 (a, b) 時,左右填充 a 個畫素,上下填充 b 個畫素
    • 當為 (a, b, c, d) 時,左上右下分別填充 a,b,c,d
    • padding_mode: 填充模式,有 4 種模式,constant、edge、reflect、symmetric
    • fill: 當 padding_mode 為 constant 時,設定填充的畫素值,(R, G, B) 或者 (Gray)

transforms.Pad(padding=32, fill=(255, 0, 0), padding_mode='constant')的效果如下,上下左右的 padding 為 16,填充為 (255, 0, 0)。


`transforms.Pad(padding=(8, 64), fill=(255, 0, 0), padding_mode='constant')`的效果如下,左右的 padding 為 8,上下的 padding 為 64,填充為 (255, 0, 0)。
`transforms.Pad(padding=(8, 16, 32, 64), fill=(255, 0, 0), padding_mode='constant')`的效果如下,左、上、右、下的 padding 分別為 8、16、32、64,填充為 (255, 0, 0)。
`transforms.Pad(padding=(8, 16, 32, 64), fill=(255, 0, 0), padding_mode='symmetric')`的效果如下,映象填充。這時`padding_mode`屬性不是`constant`, fill 屬性不再生效。
## torchvision.transforms.ColorJitter
torchvision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)

功能:調整亮度、對比度、飽和度、色相。在照片的拍照過程中,可能會由於裝置、光線問題,造成色彩上的偏差,因此需要調整這些屬性,抵消這些因素帶來的擾動。

  • brightness: 亮度調整因子

  • contrast: 對比度引數

  • saturation: 飽和度引數

  • brightness、contrast、saturation 引數:當為 a 時,從 [max(0, 1-a), 1+a] 中隨機選擇;當為 (a, b) 時,從 [a, b] 中選擇。

  • hue: 色相引數

    • 當為 a 時,從 [-a, a] 中選擇引數。其中 $0\le a \le 0.5$。
    • 當為 (a, b) 時,從 [a, b] 中選擇引數。其中 $0 \le a \le b \le 0.5$。

transforms.ColorJitter(brightness=0.5)的效果如下。


`transforms.ColorJitter(contrast=0.5)`的效果如下。
`transforms.ColorJitter(saturation=0.5)`的效果如下。
`transforms.ColorJitter(hue=0.3)`的效果如下。
## transforms.Grayscale(RandomGrayscale)
torchvision.transforms.Grayscale(num_output_channels=1)

功能:將圖片轉換為灰度圖

  • num_output_channels: 輸出的通道數。只能設定為 1 或者 3 (如果在後面使用了transforms.Normalize,則要設定為 3,因為transforms.Normalize只能接收 3 通道的輸入)
torchvision.transforms.RandomGrayscale(p=0.1, num_output_channels=1)
  • p: 概率值,影象被轉換為灰度圖的概率
  • num_output_channels: 輸出的通道數。只能設定為 1 或者 3

功能:根據一定概率將圖片轉換為灰度圖

transforms.Grayscale(num_output_channels=3)的效果如下。


## transforms.RandomAffine
torchvision.transforms.RandomAffine(degrees, translate=None, scale=None, shear=None, resample=False, fillcolor=0)

功能:對影象進行仿射變換,仿射變換是 2 維的線性變換,由 5 種基本操作組成,分別是旋轉、平移、縮放、錯切和翻轉。

  • degree: 旋轉角度設定
  • translate: 平移區間設定,如 (a, b),a 設定寬 (width),b 設定高 (height)。影象在寬維度平移的區間為 $- img_width \times a < dx < img_width \times a$,高同理
  • scale: 縮放比例,以面積為單位
  • fillcolor: 填充顏色設定
  • shear: 錯切角度設定,有水平錯切和垂直錯切
    • 若為 a,則僅在 x 軸錯切,在 (-a, a) 之間隨機選擇錯切角度
    • 若為 (a, b),x 軸在 (-a, a) 之間隨機選擇錯切角度,y 軸在 (-b, b) 之間隨機選擇錯切角度
    • 若為 (a, b, c, d),x 軸在 (a, b) 之間隨機選擇錯切角度,y 軸在 (c, d) 之間隨機選擇錯切角度
  • resample: 重取樣方式,有 NEAREST、BILINEAR、BICUBIC。

transforms.RandomAffine(degrees=30)的效果如下,中心旋轉 30 度。


`transforms.RandomAffine(degrees=0, translate=(0.2, 0.2)`的效果如下,設定水平和垂直的平移比例都為 0.2。
`transforms.RandomAffine(degrees=0, scale=(0.7, 0.7))`的效果如下,設定寬和高的縮放比例都為 0.7。
`transforms.RandomAffine(degrees=0, shear=(0, 0, 0, 45))`的效果如下,在 x 軸上不錯切,在 y 軸上隨機選擇 (0, 45) 之間的角度進行錯切。
`transforms.RandomAffine(degrees=0, shear=90, fillcolor=(255, 0, 0))`的效果如下。在 x 軸上隨機選擇 (-90, 90) 之間的角度進行錯切,在 y 軸上不錯切。
## transforms.RandomErasing
torchvision.transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)

功能:對影象進行隨機遮擋。這個操作接收的輸入是 tensor。因此在此之前需要先執行transforms.ToTensor()。同時註釋掉後面的transforms.ToTensor()

  • p: 概率值,執行該操作的概率
  • scale: 遮擋區域的面積。如(a, b),則會隨機選擇 (a, b) 中的一個遮擋比例
  • ratio: 遮擋區域長寬比。如(a, b),則會隨機選擇 (a, b) 中的一個長寬比
  • value: 設定遮擋區域的畫素值。(R, G, B) 或者 Gray,或者任意字串。由於之前執行了transforms.ToTensor(),畫素值歸一化到了 0~1 之間,因此這裡設定的 (R, G, B) 要除以 255

transforms.RandomErasing(p=1, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=(254/255, 0, 0))的效果如下,從scale=(0.02, 0.33)中隨機選擇遮擋面積的比例,從ratio=(0.3, 3.3)中隨機選擇一個遮擋區域的長寬比,value 設定的 RGB 值需要歸一化到 0~1 之間。


`transforms.RandomErasing(p=1, scale=(0.02, 0.33), ratio=(0.3, 3.3), value='fads43')`的效果如下,value 設定任意的字串,就會使用隨機的值填充遮擋區域。
## transforms.Lambda

自定義 transform 方法。在上面的FiveCrop中就用到了transforms.Lambda

transforms.FiveCrop(112, vertical_flip=False),
transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops]))

transforms.FiveCrop返回的是長度為 5 的 tuple,因此需要使用transforms.Lambda 把 tuple 轉換為 4D 的 tensor。

transforms 的操作

torchvision.transforms.RandomChoice

torchvision.transforms.RandomChoice([transforms1, transforms2, transforms3])

功能:從一系列 transforms 方法中隨機選擇一個

transforms.RandomApply

torchvision.transforms.RandomApply([transforms1, transforms2, transforms3], p=0.5)

功能:根據概率執行一組 transforms 操作,要麼全部執行,要麼全部不執行。

transforms.RandomOrder

transforms.RandomOrder([transforms1, transforms2, transforms3])

對一組 transforms 操作打亂順序

自定義 transforms

自定義 transforms 有兩個要素:僅接受一個引數,返回一個引數;注意上下游的輸入與輸出,上一個 transform 的輸出是下一個 transform 的輸入。

我們這裡通過自定義 transforms 實現椒鹽噪聲。椒鹽噪聲又稱為脈衝噪聲,是一種隨機出現的白點或者黑點,白點稱為鹽噪聲,黑點稱為椒噪聲。信噪比 (Signal-Noise Rate,SNR) 是衡量噪聲的比例,影象中正常畫素佔全部畫素的佔比。

我們定義一個AddPepperNoise類,作為新增椒鹽噪聲的 transform。在建構函式中傳入信噪比和概率,在__call__()函式中執行具體的邏輯,返回的是 image。

import numpy as np
import random
from PIL import Image

# 自定義新增椒鹽噪聲的 transform
class AddPepperNoise(object):
    """增加椒鹽噪聲
    Args:
        snr (float): Signal Noise Rate
        p (float): 概率值,依概率執行該操作
    """

    def __init__(self, snr, p=0.9):
        assert isinstance(snr, float) or (isinstance(p, float))
        self.snr = snr
        self.p = p

    # transform 會呼叫該方法
    def __call__(self, img):
        """
        Args:
            img (PIL Image): PIL Image
        Returns:
            PIL Image: PIL image.
        """
        # 如果隨機概率小於 seld.p,則執行 transform
        if random.uniform(0, 1) < self.p:
            # 把 image 轉為 array
            img_ = np.array(img).copy()
            # 獲得 shape
            h, w, c = img_.shape
            # 信噪比
            signal_pct = self.snr
            # 椒鹽噪聲的比例 = 1 -信噪比
            noise_pct = (1 - self.snr)
            # 選擇的值為 (0, 1, 2),每個取值的概率分別為 [signal_pct, noise_pct/2., noise_pct/2.]
            # 椒噪聲和鹽噪聲分別佔 noise_pct 的一半
            # 1 為鹽噪聲,2 為 椒噪聲
            mask = np.random.choice((0, 1, 2), size=(h, w, 1), p=[signal_pct, noise_pct/2., noise_pct/2.])
            mask = np.repeat(mask, c, axis=2)
            img_[mask == 1] = 255   # 鹽噪聲
            img_[mask == 2] = 0     # 椒噪聲
            # 再轉換為 image
            return Image.fromarray(img_.astype('uint8')).convert('RGB')
        # 如果隨機概率大於 seld.p,則直接返回原圖
        else:
            return img

AddPepperNoise(0.9, p=0.5)的效果如下。


完整程式碼如下:
# -*- coding: utf-8 -*-

import os
import numpy as np
import torch
import random
import math
import torchvision.transforms as transforms
from PIL import Image
from matplotlib import pyplot as plt
from enviroments import rmb_split_dir
from lesson2.transforms.addPepperNoise import AddPepperNoise
def set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)

set_seed(1)  # 設定隨機種子

# 引數設定
MAX_EPOCH = 10
BATCH_SIZE = 1
LR = 0.01
log_interval = 10
val_interval = 1
rmb_label = {"1": 0, "100": 1}

#對 tensor 進行反標準化操作,並且把 tensor 轉換為 image,方便視覺化。
def transform_invert(img_, transform_train):
    """
    將data 進行反transfrom操作
    :param img_: tensor
    :param transform_train: torchvision.transforms
    :return: PIL image
    """

    # 如果有標準化操作
    if 'Normalize' in str(transform_train):
        # 取出標準化的 transform
        norm_transform = list(filter(lambda x: isinstance(x, transforms.Normalize), transform_train.transforms))
        # 取出均值
        mean = torch.tensor(norm_transform[0].mean, dtype=img_.dtype, device=img_.device)
        # 取出標準差
        std = torch.tensor(norm_transform[0].std, dtype=img_.dtype, device=img_.device)
        # 乘以標準差,加上均值
        img_.mul_(std[:, None, None]).add_(mean[:, None, None])

    # 把 C*H*W 變為 H*W*C
    img_ = img_.transpose(0, 2).transpose(0, 1)  # C*H*W --> H*W*C
    # 把 0~1 的值變為 0~255
    img_ = np.array(img_) * 255

    # 如果是 RGB 圖
    if img_.shape[2] == 3:
        img_ = Image.fromarray(img_.astype('uint8')).convert('RGB')
        # 如果是灰度圖
    elif img_.shape[2] == 1:
        img_ = Image.fromarray(img_.astype('uint8').squeeze())
    else:
        raise Exception("Invalid img shape, expected 1 or 3 in axis 2, but got {}!".format(img_.shape[2]) )

    return img_


norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]

train_transform = transforms.Compose([
    # 縮放到 (224, 224) 大小,會拉伸
    transforms.Resize((224, 224)),

    # 1 CenterCrop 中心裁剪
    # transforms.CenterCrop(512),     # 512
    # transforms.CenterCrop(196),     # 512

    # 2 RandomCrop
    # transforms.RandomCrop(224, padding=16),
    # transforms.RandomCrop(224, padding=(16, 64)),
    # transforms.RandomCrop(224, padding=16, fill=(255, 0, 0)),
    # transforms.RandomCrop(512, pad_if_needed=True),   # pad_if_needed=True
    # transforms.RandomCrop(224, padding=64, padding_mode='edge'),
    # transforms.RandomCrop(224, padding=64, padding_mode='reflect'),
    # transforms.RandomCrop(1024, padding=1024, padding_mode='symmetric'),

    # 3 RandomResizedCrop
    # transforms.RandomResizedCrop(size=224, scale=(0.08, 1)),
    # transforms.RandomResizedCrop(size=224, scale=(0.5, 0.5)),

    # 4 FiveCrop
    # transforms.FiveCrop(112),
    # 返回的是 tuple,因此需要轉換為 tensor
    # transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),

    # 5 TenCrop
    # transforms.TenCrop(112, vertical_flip=False),
    # transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),

    # 1 Horizontal Flip
    # transforms.RandomHorizontalFlip(p=1),

    # 2 Vertical Flip
    # transforms.RandomVerticalFlip(p=1),

    # 3 RandomRotation
    # transforms.RandomRotation(90),
    # transforms.RandomRotation((90), expand=True),
    # transforms.RandomRotation(30, center=(0, 0)),
    # transforms.RandomRotation(30, center=(0, 0), expand=True),   # expand only for center rotation


    # 1 Pad
    # transforms.Pad(padding=32, fill=(255, 0, 0), padding_mode='constant'),
    # transforms.Pad(padding=(8, 64), fill=(255, 0, 0), padding_mode='constant'),
    # transforms.Pad(padding=(8, 16, 32, 64), fill=(255, 0, 0), padding_mode='constant'),
    # transforms.Pad(padding=(8, 16, 32, 64), fill=(255, 0, 0), padding_mode='symmetric'),

    # 2 ColorJitter
    # transforms.ColorJitter(brightness=0.5),
    # transforms.ColorJitter(contrast=0.5),
    # transforms.ColorJitter(saturation=0.5),
    # transforms.ColorJitter(hue=0.3),

    # 3 Grayscale
    # transforms.Grayscale(num_output_channels=3),

    # 4 Affine
    # transforms.RandomAffine(degrees=30),
    # transforms.RandomAffine(degrees=0, translate=(0.2, 0.2), fillcolor=(255, 0, 0)),
    # transforms.RandomAffine(degrees=0, scale=(0.7, 0.7)),
    # transforms.RandomAffine(degrees=0, shear=(0, 0, 0, 45)),
    # transforms.RandomAffine(degrees=0, shear=90, fillcolor=(255, 0, 0)),

    # 5 Erasing
    # transforms.ToTensor(),
    # transforms.RandomErasing(p=1, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=(254/255, 0, 0)),
    # transforms.RandomErasing(p=1, scale=(0.02, 0.33), ratio=(0.3, 3.3), value='fads43'),

    # 1 RandomChoice
    # transforms.RandomChoice([transforms.RandomVerticalFlip(p=1), transforms.RandomHorizontalFlip(p=1)]),

    # 2 RandomApply
    # transforms.RandomApply([transforms.RandomAffine(degrees=0, shear=45, fillcolor=(255, 0, 0)),
    #                         transforms.Grayscale(num_output_channels=3)], p=0.5),
    # 3 RandomOrder
    # transforms.RandomOrder([transforms.RandomRotation(15),
    #                         transforms.Pad(padding=32),
    #                         transforms.RandomAffine(degrees=0, translate=(0.01, 0.1), scale=(0.9, 1.1))]),

    AddPepperNoise(0.9, p=0.5),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

path_img=os.path.join(rmb_split_dir, "train", "100","0A4DSPGE.jpg")
img = Image.open(path_img).convert('RGB')  # 0~255
img=transforms.Resize((224, 224))(img)
img_tensor = train_transform(img)



## 展示單張圖片
# 這裡把轉換後的 tensor 再轉換為圖片
convert_img=transform_invert(img_tensor, train_transform)
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.subplot(1, 2, 2)
plt.imshow(convert_img)
plt.show()
plt.pause(0.5)
plt.close()


## 展示 FiveCrop 和 TenCrop 的圖片
# ncrops, c, h, w = img_tensor.shape
# columns=2 # 兩列
# rows= math.ceil(ncrops/2) # 計算多少行
# # 把每個 tensor ([c,h,w]) 轉換為 image
# for i in range(ncrops):
#     img = transform_invert(img_tensor[i], train_transform)
#     plt.subplot(rows, columns, i+1)
#     plt.imshow(img)
# plt.show()

資料增強實戰應用

資料增強的原則是需要我們觀察訓練集和測試集之間的差異,然後應用有效的數增強,使得訓練集和測試集更加接近。

比如下圖中的資料集,訓練集中的貓是居中,而測試集中的貓可能是偏左、偏上等位置的。這時可以使用平移來做訓練集資料增強。


在下圖的資料集中,訓練集中白貓比較多,測試集中黑貓比較多,這時可以對訓練集的數做色彩方面的增強。而貓的姿態各異,所以可從仿射變換上做資料增強。還可以採用遮擋、填充等資料增強。
我們在上個人民幣二分類實驗中,使用的是第四套人民幣。但是在這個資料集上訓練的模型不能夠很準確地對第五套人民幣進行分類。下圖是 3 種圖片的對比,第四套 1 元人民幣和第五套 100 元人民幣都比較偏紅,因此容易把第五套 100 元人民幣分類成 1 元人民幣。
而實際測試中,訓練完的模型在第五套 100 元人民幣上錯誤率高,第五套 100 元人民幣分類成 1 元人民幣。
在 `transforms`中添加了灰度變換`transforms.RandomGrayscale(p=0.9)`,把所有圖片變灰,減少整體顏色帶來的偏差,準確率有所上升。

參考資料


如果你覺得這篇文章對你有幫助,不妨點個贊,讓我有更多動力寫出好文章。