Machine Learning

torchvision.Transforms による画像データのオーグメンテーション

torchvision.Transforms にによるオーグメンテーションについてまとめています。
結果がわかりやすいように極端な値を入れているものが多いです。

torchvision.Transorms の使い方

そのまま学習データに使う

一般的なデータ拡張の方法です。
データを保存せずにそのままデータセットを作り、深層学習へ入っていきます。
データが増えないためすぐに学習を開始できますが、
実際にどういったデータで学習したかを後から検証できません。

from torchvision import transforms
from torchvision import datasets

# 画像データのルートディレクトリを指定
data_path = 'images/train'

# オーグメンテーション
transform = transforms.Compose([

  # ここに処理を追加していく
    transforms.ToTensor()

])

# データセットの作成
dataset = datasets.ImageFolder(data_path, transform)

変換後のデータを保存した上で使用する

別の Python ファイルで別のフォルダに物理的に画像データを増やしていきます。
その後増やしたデータを読み込んで学習に利用します。

こちらの場合はどういった画像データを使用して学習したかを後から検証できます。

検証用データとコード

from PIL import Image
from torchvision import transforms

# オーグメンテーション
transform = transforms.Compose([

    # ここに処理を記載していく

])

# 画像の読み込み
img = Image.open('images/test.png')

# オーグメンテーションの実行
img = transform(img)

# 編集した画像を保存
img.save('images/transformed_test.png')

下記の画像に対して前処理を行っていきます。

Compose | 連続で複数の処理を行う

最もよく使用される Transform の実行コマンドです。
Compose の中で書いた処理を順番に行っていきます。

from PIL import Image
from torchvision import transforms

# 前処理
transform = transforms.Compose([

    # ここで画像を変換していく

])

# 画像の読み込み
img = Image.open('images/test.png')

# 前処理の実行
img = transform(img)

# 編集した画像を保存
img.save('images/transformed_test.png')

コマンドとして使用する

コマンドとして利用するには

上記のtorchvision.transforms.functionalを利用します。
例えば Tensor型への変換であれば

from PIL import Image, ImageFilter
from torchvision import transforms
import torchvision.transforms.functional as TF

img = Image.open('images/test.png')

# Tensor型へ変換
img = TF.to_tensor(img)

print(img.shape)

となります。

Tensor 型へ変換

ToTenor | 0-1 でテンソル型へ変換

from PIL import Image
from torch.utils import data
from torchvision import transforms

# 前処理
transform = transforms.Compose([

    transforms.ToTensor()

])

# 画像の読み込み
img = Image.open('images/test.png')

print(type(img))
print('画像サイズ', img.size)
print('チャンネル数', len(img.getbands()))
print(img.getextrema())


# 前処理の実行
img = transform(img)

print('\n--- 変換後 ---\n')
print(type(img))
print(img.shape)
print('最小値', img.min())
print('最大値', img.max())
<class 'PIL.PngImagePlugin.PngImageFile'>
画像サイズ (1500, 600)
チャンネル数 4
((0, 255), (0, 255), (0, 255), (188, 255))

--- 変換後 ---

<class 'torch.Tensor'>
torch.Size([4, 600, 1500])
最小値 tensor(0.)
最大値 tensor(1.)

最小値と最大値が 0 - 1 で変換されます。

PILToTensor | 0-255 でテンソル変換

from PIL import Image
from torch.utils import data
from torchvision import transforms

# 前処理
transform = transforms.Compose([

    transforms.PILToTensor()

])

# 画像の読み込み
img = Image.open('images/test.png')

print(type(img))
print('画像サイズ', img.size)
print('チャンネル数', len(img.getbands()))
print(img.getextrema())


# 前処理の実行
img = transform(img)

print('\n--- 変換後 ---\n')
print(type(img))
print(img.shape)
print('最小値', img.min())
print('最大値', img.max())
<class 'PIL.PngImagePlugin.PngImageFile'>
画像サイズ (1500, 600)
チャンネル数 4
((0, 255), (0, 255), (0, 255), (188, 255))

--- 変換後 ---

<class 'torch.Tensor'>
torch.Size([4, 600, 1500])
最小値 tensor(0, dtype=torch.uint8)
最大値 tensor(255, dtype=torch.uint8)

最小値と最大値が 0 - 255 のまま変換されます。

リサイズとトリミング

Resize | リサイズ

画像サイズを変更します。

transforms.Resize((縦, 横))
transforms.Resize(サイズ)

ひとつだけ渡した場合はアスペクト比を固定し、最大の長さが指定した値のピクセルになるようにリサイズします。

CenterCrop | 中央を基準にトリミング

中心部から指定したサイズ分だけ切り取ります。

transforms.CenterCrop(サイズ)

FiveCrop | 5カ所をトリミング

4隅と中央の5カ所を切り取ります。

transforms.FiveCrop(サイズ)

TenCrop | 10カ所をトリミング

元画像と反転させた画像に対して4隅と中央の計10カ所を切り取ります。

transforms.TenCrop(サイズ, vertical_flip=上下反転可なら True)

Pad | パディング

pad でパディングを追加します。

transforms.Pad(パディング数, fill=0, padding_mode='constant')

パディングの指定方法

from PIL import Image
import numpy as np
from torchvision import transforms

# 検証用データ
end = 256
arr = np.arange(int(end / 9), end, int(end / 9))
data = arr.reshape((3,3))

# 行列を PIL.Image 型に変換
img = Image.fromarray(data)

print(np.array(img))
[[ 28  56  84]
 [112 140 168]
 [196 224 252]]

3px × 3px のグレースケール画像のデータだと仮定してください。
上記のコードでパディングの指定方法を見ていきます。

パディング数の指定
from PIL import Image
import numpy as np
from torchvision import transforms

# 検証用データ
end = 256
arr = np.arange(int(end / 9), end, int(end / 9))
data = arr.reshape((3,3))
img = Image.fromarray(data)


print('\n---- 整数をひとつ渡す ----\n')
transform = transforms.Pad(padding=1)
print(np.array(transform(img)))

print('\n---- タプルで2つ渡す (横, 縦) ----\n')
transform = transforms.Pad(padding=(3, 1))
print(np.array(transform(img)))

print('\n---- タプルで4つ渡す (左, 上, 右, 下) ----\n')
transform = transforms.Pad(padding=(1, 2, 3, 4))
print(np.array(transform(img)))
---- 整数をひとつ渡す ----

[[  0   0   0   0   0]
 [  0  28  56  84   0]
 [  0 112 140 168   0]
 [  0 196 224 252   0]
 [  0   0   0   0   0]]

---- タプルで2つ渡す (横, 縦) ----

[[  0   0   0   0   0   0   0   0   0]
 [  0   0   0  28  56  84   0   0   0]
 [  0   0   0 112 140 168   0   0   0]
 [  0   0   0 196 224 252   0   0   0]
 [  0   0   0   0   0   0   0   0   0]]

---- タプルで4つ渡す (左, 上, 右, 下) ----

[[  0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0]
 [  0  28  56  84   0   0   0]
 [  0 112 140 168   0   0   0]
 [  0 196 224 252   0   0   0]
 [  0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0]]
パディングモード
from PIL import Image
import numpy as np
from torchvision import transforms

# 検証用データ
end = 256
arr = np.arange(int(end / 9), end, int(end / 9))
data = arr.reshape((3,3))
img = Image.fromarray(data)


print('\n---- constant ----\n')
transform = transforms.Pad(padding=3, padding_mode='constant', fill=0)
print(np.array(transform(img)))

print('\n---- edge ----\n')
transform = transforms.Pad(padding=3, padding_mode='edge', fill=0)
print(np.array(transform(img)))

print('\n---- reflect ----\n')
transform = transforms.Pad(padding=3, padding_mode='reflect', fill=0)
print(np.array(transform(img)))

print('\n---- symmetric ----\n')
transform = transforms.Pad(padding=3, padding_mode='symmetric', fill=0)
print(np.array(transform(img)))
---- constant ----
[[  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0  28  56  84   0   0   0]
 [  0   0   0 112 140 168   0   0   0]
 [  0   0   0 196 224 252   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]]

---- edge ----

[[ 28  28  28  28  56  84  84  84  84]
 [ 28  28  28  28  56  84  84  84  84]
 [ 28  28  28  28  56  84  84  84  84]
 [ 28  28  28  28  56  84  84  84  84]
 [112 112 112 112 140 168 168 168 168]
 [196 196 196 196 224 252 252 252 252]
 [196 196 196 196 224 252 252 252 252]
 [196 196 196 196 224 252 252 252 252]
 [196 196 196 196 224 252 252 252 252]]

---- reflect ----

[[140 168 140 112 140 168 140 112 140]
 [224 252 224 196 224 252 224 196 224]
 [140 168 140 112 140 168 140 112 140]
 [ 56  84  56  28  56  84  56  28  56]
 [140 168 140 112 140 168 140 112 140]
 [224 252 224 196 224 252 224 196 224]
 [140 168 140 112 140 168 140 112 140]
 [ 56  84  56  28  56  84  56  28  56]
 [140 168 140 112 140 168 140 112 140]]

---- symmetric ----

[[252 224 196 196 224 252 252 224 196]
 [168 140 112 112 140 168 168 140 112]
 [ 84  56  28  28  56  84  84  56  28]
 [ 84  56  28  28  56  84  84  56  28]
 [168 140 112 112 140 168 168 140 112]
 [252 224 196 196 224 252 252 224 196]
 [252 224 196 196 224 252 252 224 196]
 [168 140 112 112 140 168 168 140 112]
 [ 84  56  28  28  56  84  84  56  28]]
edge
reflect
symmetric

ランダムなリサイズとトリミング

RandomCrop | ランダムにトリミング

transforms.RandomCrop((縦, 横), padding=None, pad_if_needed=False, fill=0, padding_mode='constant')
フラグ意味
サイズ | 第一引数(int、int) / int(縦, 横) / サイズ(正方形)
paddingNone / int / (int, int) / (int, int, int, int)None 出ない場合は Pad と同じ指定方法
pad_if_neededboolサイズで指定した大きさが元画像より大きい場合にパディングするかどうか
fillint / (int, int, int)constant を指定した場合に使用する色
padding_modestrPad と同じ指定方法

RandomResizedCrop | ランダムなトリミングとスケール

ランダムにトリミングした上でランダムに拡大 / 縮小します。

transforms.RandomResizedCrop(150, scale=(0.08, 1.0), ratio=(3 / 4, 4 / 3))
フラグ意味
サイズ | 第一引数(int、int) / int(縦, 横) / サイズ(正方形)
数値ひとつのみの場合はアスペクト比固定
scaale(float, float)スケールの幅
ratio(int / int, int / int)アスペクト比

ランダムな反転・移動・回転・スケール

RandomHorizontalFlip | 水平に反転

transforms.RandomHorizontalFlip(p=確率)

一定の確率で画像を水平に反転させます。

RandomVerticalFlip | 垂直に反転

transforms.RandomVerticalFlip(p=確率)

一定の確率で画像を垂直に反転させます。

RandomRotation | 回転

transform.RandomRotation(
    角度, expand=すべての画像が収める, 
    center=回転する中心, fill=色)
フラグ意味
角度 | 第一引数int / ( int, int )int | 最小・最大値
(int, int) / [int, int] | (最小値, 最大値)
expand boolすべての画像を画面内に収めるかどうか。
初期値は False
center( int, int )回転する中心
fill int / ( int, int, int ) 背景色

RandomAffine | 回転・移動・スケール

transforms.RandomAffine(
    degrees=[角度の最小, 角度の最大], translate=(水平の移動量, 垂直の移動量), scale=(縮小する最小値, 拡大する最大値))

ランダムに回転・移動・拡大・縮小を行います。画像サイズは変わりません。
設定によって見切れたり、余白が大量にでる場合があります。

RandomPerspective | 遠近法変換

transforms.RandomPerspective(distortion_scale=変形量, p=確率)

変形量は 1 を超えると分割されて出てくるようになるため実用性はないように思えます。

色調補正 / フィルター

Grascale | グレースケール変換

transforms.Grayscale()

カラーパレットの赤色の部分を見るとわかりますが、明度差が無くなっています。
失われる特徴量があります。

3チャンネルのグレースケール

transforms.Grayscale(num_output_channels=3)で変換した場合、
3チャンネルのグレースケール変換になります。

from PIL import Image
from torch.utils import data
from torchvision import transforms
import cv2

# 前処理
transform = transforms.Compose([

    transforms.Grayscale(num_output_channels=3)

])

# 画像の読み込み
img = Image.open('images/test.png')

# 前処理の実行
img = transform(img)

img.save('images/transformed_test.png')


# 配列状況の取得
before_img = cv2.imread ('images/test.png', -1 )
print(before_img.shape)

transformed_img = cv2.imread ('images/transformed_test.png', -1 )
print(transformed_img.shape)
(600, 1500, 4)
(600, 1500, 3)

結果は変わりませんが、次元が削減されません。

GaussianBlur | ブラーをかける

transforms.GaussianBlur(奇数のサイズ, sigma=(標準偏差))

ランダムな色調補正

ColorJitter | 明度・彩度・色相・コントラスト

ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)
フラグ意味
brightnessfloat /
(float, float)
明度の変動値。二つ与えた場合は ( 最小, 最大 )
saturation float /
(float, float)
彩度の変動値 。二つ与えた場合は ( 最小, 最大 )
hue float /
(float, float)
色相の変動値 。二つ与えた場合は ( 最小, 最大 ) | 最小 -0.5, 最大 0.5
contrast float /
(float, float)
コントラストの変動値 。二つ与えた場合は ( 最小, 最大 )

RandomGrayscale | グレースケール

transforms.RandomGrayscale(確率)

ランダムにグレースケール画像に変換します。

RandomInvert | 色調の反転

transforms.RandomInvert(p=確率)

一定の確率で色調を反転させます。

アルファチャンネルが入っている場合はエラーで止まります。
実行前にアルファチャンネルを消しておく必要があります。

# アルファチャンネルを削る
img = img.convert("RGB")

RandomPosterize | 階調の削減

transforms.RandomPosterize(bits=ビット数, p=確率)

一定の確率で色調を反転させます。

アルファチャンネルが入っている場合はエラーで止まります。
実行前にアルファチャンネルを消しておく必要があります。

# アルファチャンネルを削る
img = img.convert("RGB")

RandomSolarize | ソラリゼーション

transforms.RandomSolarize(threshold=しきい値, p=確率)

アルファチャンネルが入っている場合はエラーで止まります。
実行前にアルファチャンネルを消しておく必要があります。

# アルファチャンネルを削る
img = img.convert("RGB")

値が高すぎて白飛びしている場合に利用したりしています。

RandomAdjustSharpness | シャープ

transforms.RandomSolarize(sharpness_factor=変化量, p=確率)
sharpness_factorの値効果
0ぼかしを入れる
1元画像
1以上シャープネス
2シャープネスを2倍
1010倍

RandomAutocontrast | コントラスト

transforms.RandomAutocontrast(p=確率)

ランダムに自動でコントラストを調整します。

アルファチャンネルが入っている場合はエラーで止まります。
実行前にアルファチャンネルを消しておく必要があります。

# アルファチャンネルを削る
img = img.convert("RGB")

調整できるパラメーターは確率のみです。そのため、コントラストに問題がなければ何の変化も起こりません。

RandomEqualize | ヒストグラム平均化

transforms.RandomEqualize(p=確率)

アルファチャンネルが入っている場合はエラーで止まります。
実行前にアルファチャンネルを消しておく必要があります。

# アルファチャンネルを削る
img = img.convert("RGB")

カラーパレットの明度に引っ張られて全体の明度が持ち上がってきています。

Tensor 型に対してのみ行える処理

ToPILImage | PIL image 型へ変換

from PIL import Image
from torch.utils import data
from torchvision import transforms
import torch

# 前処理
transform = transforms.Compose([

    transforms.PILToTensor(),

])

# 画像の読み込み
img = Image.open('images/test.png')

# 読み込んだ直後
print('読み込んだ直後')
print(type(img))

# 前処理の実行
img = transform(img)

print('\ntensor へ変換')
print(type(img))

# PIL 型に変換
toPIL = transforms.ToPILImage()
img = toPIL(img)

# PIL 型の時の情報
print('\nPIL.Image へ変換')
print(type(img))
読み込んだ直後
<class 'PIL.PngImagePlugin.PngImageFile'>

tensor へ変換
<class 'torch.Tensor'>

PIL.Image へ変換
<class 'PIL.Image.Image'>

Normalize | 線形変換

from PIL import Image
from torch.utils import data
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])

])

# 画像の読み込み
img = Image.open('images/test.png')

print(type(img))
print('画像サイズ', img.size)
print('チャンネル数', len(img.getbands()))
print(img.getextrema())


# アルファチャンネルを削る
img = img.convert("RGB")

# 前処理の実行
img = transform(img)

print('\n--- 変換後 ---\n')

print(type(img))
print(img.shape)
print('最小値', img.min())
print('最大値', img.max())

# PIL 型に変換
toPIL = transforms.ToPILImage()
img = toPIL(img)

img.save('images/transformed_test.png')
<class 'PIL.PngImagePlugin.PngImageFile'>
画像サイズ (1500, 600)
チャンネル数 4
((0, 255), (0, 255), (0, 255), (188, 255))

--- 変換後 ---

<class 'torch.Tensor'>
torch.Size([3, 600, 1500])
最小値 tensor(-2.1179)
最大値 tensor(2.6400)

ConvertImageDtype | dtype の変換

from PIL import Image
from torch.utils import data
from torchvision import transforms
import torch

# 前処理
transform = transforms.Compose([

    transforms.PILToTensor(),
    transforms.ConvertImageDtype(dtype=torch.float64)

])

# 画像の読み込み
img = Image.open('images/test.png')

# PIL 型の時の情報
print(type(img))
print('画像サイズ', img.size)
print('チャンネル数', len(img.getbands()))
print(img.getextrema())

# tenspr 型に変換
toTensor = transforms.PILToTensor ()
img_tensor = toTensor(img)

print('\n--- 変換前の tensor ---\n')
print(type(img_tensor))
print(img_tensor.shape)
print('最小値', img_tensor.min())
print('最大値', img_tensor.max())

# 前処理の実行
img = transform(img)

print('\n--- 変換後 ---\n')
print(type(img))
print(img.shape)
print('最小値', img.min())
print('最大値', img.max())

# PIL 型に変換
toPIL = transforms.ToPILImage()
img = toPIL(img)


# PIL 型の時の情報
print('\n--- 変換後の PIL ---\n')
print(type(img))
print('画像サイズ', img.size)
print('チャンネル数', len(img.getbands()))
print(img.getextrema())



img.save('images/transformed_test.png')
<class 'PIL.PngImagePlugin.PngImageFile'>
画像サイズ (1500, 600)
チャンネル数 4
((0, 255), (0, 255), (0, 255), (188, 255))

--- 変換前の tensor ---

<class 'torch.Tensor'>
torch.Size([4, 600, 1500])
最小値 tensor(0, dtype=torch.uint8)
最大値 tensor(255, dtype=torch.uint8)

--- 変換後 ---

<class 'torch.Tensor'>
torch.Size([4, 600, 1500])
最小値 tensor(0., dtype=torch.float64)
最大値 tensor(1., dtype=torch.float64)

--- 変換後の PIL ---

<class 'PIL.Image.Image'>
画像サイズ (1500, 600)
チャンネル数 4
((0, 255), (0, 255), (0, 255), (188, 255))

型が変わっただけで画像に変化は起こりません。
PIL に戻した段階で元画像の情報に戻ります。

RandomErasing | 一部分を消去

transforms.RandomErasing (scale=(消去領域範囲), ratio=(アスペクト比の範囲), value=どのチャンネルを消すか, inplace=インプレーするか, p=確率)
フラグ意味
scale(float, float)入力画像に対する消去領域の比率の範囲
ratio(float, float) 消去された領域のアスペクト比の範囲
value int /
(int, int, int)
デフォルトは0。単一のintの場合、すべてのピクセルを消去するために使用される。
長さが3のタプルの場合、それぞれR、G、Bチャネルを消去するために使用される。
inplaceboolこの変換をインプレースにするブール値。初期値は0。
pfloat処理を行う確率

-Machine Learning