[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをOpenCVで増幅してみました

2020.04.22

1 はじめに

CX事業本部の平内(SIN)です。

オブジェクト検出では、大量のデータセットが必要ですが、これを準備する作業量は、結構大変です。そのため、データ(画像)を増幅してデータ数を増加させる手法があります。

今回は、OpenCVによる画像処理で、Amazon SageMaker Ground Truth(以下、Ground Truth)で作成したデータを増幅してみました。

なお、画像の回転や、反転などを行うと、Ground Truthで設定したアノテーションを、やり直す必要が生じるので、ここでは、アノテーションが変化しない変換だけを対象にしました。

2 変換ファンクション

試した変換の種類について列挙します。下記が、サンプルのために使用した、800 × 600の元画像です。

(1) 彩度

彩度は、BGR形式を、一旦、HSV形式に変換して変換しています。パラメータに1.0を与えると無変換となります。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./test.jpg')

# 彩度
def saturation(src, saturation):
    # 一旦、BGRをHSVに変換して彩度を変換する
    img = cv2.cvtColor(src,cv2.COLOR_BGR2HSV)
    img[:,:,(1)] = img[:,:,(1)] * saturation 
    img = cv2.cvtColor(img,cv2.COLOR_HSV2BGR) 
    return img

# 表示
def view(img, func, params):
    w = 5 # 横に5つ並べる
    h = int(len(params)/w)+1
    fig = plt.figure(figsize=(10, 7))
    for i,p in enumerate(params):
        dst = func(img, p)
        ax = fig.add_subplot(h, w, i + 1)
        ax.set_axis_off()
        ax.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB))  # BGR => RGB
        ax.set_title(f"param={p:.1f}")
    plt.show()

view(img, saturation, [0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6,1.8,2.0,3.0])

パラメータは、0.4〜3.0ぐらいが変換として使用できそうです。

下記は、0.2での例です。

3.0での例です。

(2) 明度

明度についても、HSV形式に変換して変換しています。パラメータに1.0を与えると無変換となります。

# 明度
def brightness(src, brightness):
    # 一旦、BGRをHSVに変換して明度を変換する
    img = cv2.cvtColor(src,cv2.COLOR_BGR2HSV) 
    img[:,:,(2)] = img[:,:,(2)] * brightness 
    img = cv2.cvtColor(img,cv2.COLOR_HSV2BGR) 
    return img

パラメータは、0.4〜0.8ぐらいが変換として使用できそうです。

0.6での変換例です。

(3) コントラスト

各色相をalphaで演算しています。パラメータに1.0を与えると無変換となります。

# コントラスト
def contrast(src, alpha):
    # 各色相をalphaで演算する
    img = alpha * src
    return np.clip(img, 0, 255).astype(np.uint8)

パラメータは、0.6〜2.0ぐらいが変換として使用できそうです。

0.8で変換

2.0で変換

(4) モザイク

全体をリサイズして戻すことでモザイクにしています。パラメータに1.0を与えると無変換となります。 実質的に、ピクセル数の小さなデータセットとなるため、小さく映り込む物体の検出用に有効でかも知れません。なお、パラメータは、想定する検出状況と元データの解像度を考慮して決定する必要があります。

# モザイク
def mosaic(src, ratio):
    # ratio倍でリサイズして戻す
    img = cv2.resize(src, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    return cv2.resize(img, src.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

今回の例となっている800 × 600 では、パラメータ0.05ぐらいまでが有効になりそうです。

0.1で変換したものです。

(5) ガウスノイズ

ガウスノイズです。パラメータに1.0を与えると無変換となります。

# ガウスノイズ
def gaussian(src, sigma):
    # ランダム値で生成した画像と合成する
    row,col,ch = src.shape
    mean = 0
    gauss = np.random.normal(mean, sigma, (row, col, ch)).astype('u1')
    gauss = gauss.reshape(row, col, ch)
    return src + gauss

有効なパラメータは、10〜100ぐらいでしょうか。

20での変換例です。

(6) ごま塩ノイズ

ランダム値で生成した画像を合成しています。

# ごま塩ノイズ
def noise(src, amount):
    # ランダム値で生成したノイズと合成する
    img = src.copy()
    num_pepper = np.ceil(amount* src.size * (0.5))
    coords = [np.random.randint(0, i-1 , int(num_pepper)) for i in src.shape]
    img[coords[:-1]] = (0,0,0)
    return img

パラメータ0.1で変換した例です。

3 増幅

ここまでの変換用ファンクションを使用して、Ground Truth のデータを増幅しているコードです。 変換リストに、ファンクション名とパラメータをセットすることで増幅の内容を指定できます。

amplify.py

def main():

    # 変換リスト
    convertList = [
        {"function": saturation, "param": 0.2}, # 彩度
        {"function": saturation, "param": 3.0}, 
        {"function": brightness, "param": 0.6}, # 明度
        {"function": contrast,   "param": 0.8}, # コントラスト
        {"function": mosaic,     "param": 0.05},# モザイク
        {"function": mosaic,     "param": 0.1},
        {"function": mosaic,     "param": 0.2},
        {"function": gaussian,   "param": 20.0},# ガウスノイズ
        {"function": noise,      "param": 0.1}, # ごま塩ノイズ
    ]

    # 元データの情報を取得する
    dataList = getDataList(targetPath, manifest)
    print("全データ: {}件 ".format(len(dataList)))

    outputManifest = ''
    for data in dataList:
        # 元データのエクスポート
        outputManifest += data.dumps() + '\n'
        # 元データの画像
        srcImage = "{}/{}.{}".format(targetPath, data.baseName, data.ext)

        orgBaseName = data.baseName
        # 変換リストに基づく増幅処理
        for i,convert in enumerate(convertList):
            # 元データの名前取得
            baseName = "{}-{}".format(orgBaseName, str(i+1).zfill(3))
            # 出力データの画像ファイル名
            dstImage = "{}/{}.{}".format(targetPath, baseName, data.ext)
            # 元データの名前を変更
            data.baseName = baseName
            # 変換データのエクスポート
            outputManifest += data.dumps() + '\n'
            # 画像の変換処理
            img = cv2.imread(srcImage)
            img = convert["function"](img, convert["param"])
            # 変換後の画像のエクスポート
            cv2.imwrite(dstImage,img)
    # Manifestファイルのエクスポート
    with open("{}/{}".format(targetPath, manifest), mode='w') as f:
        f.write(outputManifest)

    print("増幅後データ: {}件 ".format(len(outputManifest.split('\n'))))

main()

実行すると、下記のように4件のデータが約10倍になっていることを確認できます。

$ python3  amplify.py
全データ: 4件 
増幅後データ: 41件 

4 最後に

今回は、Ground Truthのデータを増幅してみました。この変換では、改めてアノテーションする作業は発生しないので、作業時間は、殆ど必要ないでしょう。 なお、データの増幅は、最終的なオブジェクト検出の条件を考慮して、注意深く行う必要があると思います。

すべてのコードは、下記に起きました。
https://gist.github.com/furuya02/6b10263c77d0b62b492f96f107d38dc7

5 参考にさせて頂いたページ


OpenCV - 画像の明るさやコントラストを変更、ガンマ補正など
機械学習のデータセット画像枚数を増やす方法