物体検出等の領域とその領域が何かを識別するための学習をするためにテストデータ、学習データを用意したい時があります。そしてデータの用意は許される範囲でなるべく楽にしたいです。例えば画像を切り抜いたり回転させることで1枚の画像が多様な画像になります。これをするために画像をランダムに回転させて切り抜く手法が取れます。
実際のコードが次です。
from typing import Tuple
import PIL
import random
from PIL import Image
import numpy as np
def random_rotate_image(image: PIL.Image) -> Tuple[PIL.Image, float]:
"""
画像をランダムな角度で回転させる関数。
Args:
image (PIL.Image): 回転させる画像。
Returns:
PIL.Image: 回転後の画像。
float: 回転角度(度単位)。
"""
# ランダムな角度(0〜360度)で
angle = random.uniform(0, 360)
# 画像を回転
rotated_image = image.rotate(angle, expand=True)
return rotated_image, angle
def get_random_crop(image: PIL.Image, crop_size: Tuple[int, int]) -> PIL.Image:
"""
画像からランダムに指定したサイズの領域を切り出す関数。
Args:
image (PIL.Image): 切り出す元の画像。
crop_size (tuple): 切り出す領域のサイズ(幅, 高さ)。
Returns:
PIL.Image: 切り出した画像。
"""
# 座標指定がはみ出さないようにランダムに切り出し領域を決定
width, height = image.size
x = random.randint(0, width - crop_size[0])
y = random.randint(0, height - crop_size[1])
crop_box = (x, y, x + crop_size[0], y + crop_size[1])
# 画像を切り出し
return image.crop(crop_box)
def is_mono_color_image(image: Image.Image, color: int, threshold: float = 0.95) -> bool:
"""
画像内のピクセルの一定割合が特定の色かどうかをチェックします。
Args:
image (Image.Image): PIL画像オブジェクト。
color (int): チェックする色(グレースケール値)。
threshold (float): 色の割合がこの値を超えたらTrueを返します(0.0〜1.0の範囲)。
Returns:
bool: 画像のピクセルの割合が指定した色の割合を超えているならばTrue。
"""
# 画像をNumPy配列に変換
image_np = np.array(image)
# 画像の全ピクセル数
total_pixels = image_np.size
# 指定した色のピクセル数
color_pixels = np.sum(image_np == color)
# 指定した色の割合が閾値を超えているかどうか
return color_pixels / total_pixels >= threshold
def crop_image(image_path: str, crop_size: Tuple[int, int]) -> PIL.Image:
"""
画像をランダムに回転させ、ランダムに切り出す関数
Args:
image_path (str): 画像ファイルのパス。
crop_size (tuple): 切り出し領域のサイズ(幅, 高さ)。
Returns:
PIL.Image: 切り出した画像。
"""
image = Image.open(image_path)
# ランダムに回転
rotated_image, angle = random_rotate_image(image)
# ランダムに切り出し
cropped_image, crop_box = get_random_crop(rotated_image, crop_size)
# 画像が黒一色の場合は再度切り出し。回転後の端の部分を切り抜くと黒一色の場合があるため。
while is_mono_color_image(cropped_image, color=0):
rotated_image, angle = random_rotate_image(image)
cropped_image, crop_box = get_random_crop(rotated_image, crop_size)
return cropped_image
if __name__ == '__main__':
# 使用例
image_path = './tmp.png' # 元画像
crop_size = (400, 400) # 切り出し領域のサイズを指定(幅, 高さ)
cropped_image = crop_image(image_path, crop_size)
cropped_image.show() # 切り出した画像を表示
# 繰り返し切り抜いて保存するなら↓
num_crops = 5
for i in range(num_crops):
cropped_image = crop_image(image_path, crop_size)
cropped_image.save(f'./tmp_{i}.png')
注意点として画像を回転させてから切り出す都合上、元画像の回転後にできる余白が切り出した画像に含まれます。
この画像を増やす手法が特に有効なのは1枚の画像になりうる複数の画像が学習セットであり、最終的な処理したい対象である時です。この時に複数の画像を1枚の巨大な画像にし、そこからランダムな切り出しを行いテストデータとすることで実際の形式でありうる画像を多数生成できます。これは例えばカメラを動かしながら連写するような撮り方をしてできた画像です。また画像を切り抜いた時に座標も抜き出しすような改造を加えるとセグメントのアノテーションなどもまとめて増やせて便利です。