Pythonで画像をランダムクロップする方法::AIの学習用画像の水増しにおすすめ

2021年9月20日

今日は指定サイズで画像をランダムにクロップする方法を紹介します。

ランダムクロップが何の役に立つかピンとこない方もいると思います。

用途としては、例えば、AIの学習用画像を水増しするときや、画像をランダムに切り出したいときにとても便利です。

一枚の画像から何枚でもクロップ画像が生成できます。

興味のある方は是非参考にしてください。

本プログラムの特徴

  • PySimpleGUIの機能を使ってクロップしたい画像や保存先を指定できる。
  • クロップする回数やサイズを任意に指定できる。
  • クロップした画像は連番で保存される。

プログラム

以下のコードをみてください。

import os
import cv2
import glob
import random
import numpy as np
from tqdm import tqdm
import os.path as osp
import PySimpleGUI as sg
import matplotlib.pyplot as plt


# パラメータ
crop_n = 10             # クロップする回数
crop_size = (600, 600)  # クロップする画像のサイズ(height, width)

# 画像クロップ処理部分を関数化
def crop(image, crop_size):
    
    # 画像サイズ取得
    h, w = image.shape[:2]

    ### 画像サイズをランダムに決定 ###
    # 画像の左上の座標(height 0, width 0)
    h0 = np.random.randint(0, h - crop_size[0])
    w0 = np.random.randint(0, w - crop_size[1])

    # 画像の右下の座標(height 1, width 1)
    h1 = h0 + crop_size[0]
    w1 = w0 + crop_size[1]

    # 画像をスライス[h, w, c]
    image = image[h0:h1, w0:w1, :]

    return image

# 画像選択、保存
fname = sg.popup_get_folder('クロップしたい画像が入ったフォルダを指定してください')
train_dir = sg.popup_get_folder('画像の保存先フォルダを指定してください')

# 画像が入ったフォルダのパス
image_dir = fname + '/' + '*.*'

# オリジナル画像を読み込んでリスト化
files = [i for i in glob.glob(image_dir)]

### 進捗をバー表示しながらオリジナル画像をクロップ処理 ###
for i in tqdm(files, total=len(files)):
    image_name = f"{os.path.basename(i):s}"
    image = cv2.imread(i)
    h, w, _ = image.shape

    # 画像サイズがクロップする画像サイズより小さいときは処理対象外
    if h < crop_size[0] or w < crop_size[1]:
        print(f"{image_name:s} のサイズは h:{h:1d}px, w:{w:1d}px のため分割できません(T_T)")
        continue

    ### クロップ画像を指定回数保存 ###
    for num in range(crop_n):

        # オリジナル画像を指定サイズでクロップ
        crop_img = crop(image, crop_size=crop_size)

        # オリジナル画像のパスから画像名を抜き出す
        title = os.path.splitext(os.path.basename(i))[0]

        # クロップ画像に連番でファイル名を付ける。リテラル文字列補完の機能(Python3.6以降の機能)
        image_save_name = f"{title:s}_{num:03d}.jpg"

        # 保存するファイル名を作成(train_dir のパスに image_save_name のパスをつなげる)
        cropped_image_save_name = osp.join(train_dir, image_save_name)

        # クロップ画像を保存
        cv2.imwrite(cropped_image_save_name, crop_img)

ポイント解説

1.画像選択と保存先指定はPySimpleGUIの機能を使用

以下のコードで簡単に指定できます。コードもシンプルでおすすめです。

fname = sg.popup_get_folder('クロップしたい画像が入ったフォルダを指定してください')
train_dir = sg.popup_get_folder('画像の保存先フォルダを指定してください')

2.画像パスの読み込みは内包表記でリスト化

画像枚数が多いと処理に時間が掛かります。

このため、最速でリスト化できる内包表記(下記コードのような記載方法)がおすすめです。

files = [i for i in glob.glob(image_dir)]

3.クロップ処理の進捗状況をプログレスバーで表示

クロップする画像の枚数や回数が多いと処理に時間がかかります。

進捗状況をプログレスバーで表示させることでイライラせずに済みます。

下記コードの tqdm( ) の部分でプログレスバー表示が可能となります。 なお、tqdm を使用する際は import しておくことを忘れずに!

for i in tqdm(files, total=len(files)):

4.リテラル文字列補完機能を使い文字列作成

以下のコードをみてください。

image_name = f"{os.path.basename(i):s}"

2項で説明した files には画像のパスがリスト化され代入されています。

for 文でリストからパスを1個づつ読み出し、os.path.basename( i ) で読み出したパスからファイル名の部分(例えば、xxx.jpg)だけ分離し、f “{ }" の部分で文字列に変換し image_name に代入しています。

f “{ }" がリテラル文字列補完機能で文字列を作成している部分です。

この機能は、Python3.6以降に使えるようになったものです。

コードをシンプルに記載したい方にはおすすめです。

まとめ

  • 本プログラムはAIの学習用画像の水増しに便利
  • 画像選択と保存先指定はPySimpleGUIの機能を使用すると便利
  • 画像パスの読み込みは内包表記でリスト化した方が処理が早い
  • クロップ処理の進捗状況はプログレスバー表示がおすすめ
  • リテラル文字列補完機能を使って文字列を作成した方がコードがシンプル

参考書籍

本プログラムを作成するにあたり、参考にした書籍を紹介します。

書籍の中でランダムクロップによりAIの学習用画像データを水増ししている部分があります。その考え方に影響を受け、本プログラムを作成しました。

本書籍はランダムクロップについて書かれたものではなく、最近流行りのGANについて最新の手法がサンプルプログラム付きで記載されています。技術的に非常に面白い内容となっています。

特に私が興味を持った内容は、超解像(ESRGAN)と異常検知(EfficientGAN)です。

ESRGANは低解像度の本物画像から超解像した生成画像(高解像度)を得ることができる手法です。また、EfficientGANは正常な画像のみを用いて学習を行い、その後に入力した画像と学習済みモデルから生成した画像との間に差があるかないかをスコア値で示し、異常有無を判定する手法です。

何れの手法も現場で役立つ有用な手法だと思います。

興味のある方は一読することをお薦めします。