この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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