Albumentations:物体検出(枠)にも対応したデータ拡張ライブラリを解説
データ拡張(Data Augmentation)は、モデルの精度向上に不可欠なテクニックです。この記事では、物体検出の検出枠(BBOX, Bounding Box)やセグメンテーションのマスクデータに対応したデータ拡張ライブラリ「Albumentations」の使い方について、実際の効果を画像で確認しながらクイックリファレンス的な形式で解説します。
データ拡張(Data Augmentation)とは
データ拡張(Data Augmentation)は、訓練データセット内のサンプル数を増やすために、元のデータを加工・変換する手法です。
データ拡張の主要な目的は、モデルの汎化能力を向上させ、過学習を防ぎ、新しいデータに対する性能を向上させることです。これにより、訓練データが限られている場合でも、より強力なモデルを構築できます。
データ拡張をは、ディープラーニングでは非常に有用な手法の1つで、学習時だけでなく、推論時にも利用したりします(TTA、Test Time Augumentation)。
推論時の使い方としては、例えば、データ拡張しない画像と、左右フリップした画像に対してそれぞれ推論を行い、結果をアンサンブルするなどの使い方があります。これにより精度の向上が見込めます
Albumentationsとは
Albumentationsは、コンピュータビジョンタスク用のデータ拡張のためのライブラリです。画像認識(クラス分類)だけでなく、物体検出タスク、セグメンテーションタスクにも対応していいることが特徴です。
この記事では、Albumentationsの使い方について説明します。
Albumentations公式
GitHub: https://github.com/albumentations-team/albumentations
ドキュメント:https://albumentations.ai/
torchvisionのtransformsもv2で「物体検出タスク」、「セグメンテーションタスク」に対応しています。詳しくは、こちらの記事を参照してください。
インストール
Albumentationsはインストールも簡単です。
Google Colabでは以下のコードを実行します。
!pip install -U albumentations
ローカルな環境では、先頭の!を外した以下のコマンドをコマンドラインで実行します。
pip install -U albumentations
基本的な使い方
以下基本的な使い方です。
まず、ライブラリをインポートします。画像の読み込みにOpenCVを利用したいと思いますので、cv2も一緒にインポートしておきます。
import albumentations as A
import cv2
以下、代表的な合成関連のAPIです。他にもいくつかAPIが用意されていますが、Compose
と、OneOf
だけで大体の場合は記述できます。引数などの詳しい情報は、公式ページを参考にしてください。
重複(他で代用できそうなもの)は省略しています。
複数のデータ拡張を順番に呼び出す(Compose
)
Albumentationsでは、「データ拡張Aの次にデータ拡張Bを実行する」といったように、複数のデータ拡張を組みあせて使うことが多いです。
複数のデータ拡張を順番に実行する場合は、以下のようにCompose
を利用します。
以下の例では、RandomCrop
とHorizontalFlip
、RandomBrightnessContrast
の3つの変換を順に処理します。
transform = A.Compose([
A.RandomCrop(width=256, height=256),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
])
このように、Compose
はリストに列挙された処理を順番に処理する関数です。
ほとんどの関数に引数p
が指定できます。これは、処理する確率(確率)を指定するものです。p=1.0にした場合は必ず処理され、p=0.5にした場合は50%の確率で処理されます。
上記で定義したtransform
は、以下のように利用できます。
なお、img = cv2.cv2Color(img, cv2.COLOR_BGR2RGB)
は、読み込んだ画像のフォーマットがBGRでRGBに変換の必要がある場合に挿入します。
img = cv2.imread('/path/to/image.jpg')
img = cv2.cv2Color(img, cv2.COLOR_BGR2RGB)
transformed_img = transform(image = img)
リストに列挙された1つを処理する(OneOf
)
リストに列挙された処理の1つを選択して処理します。複数の処理のうち、1つを選んで処理したい場合に利用します
PerChannel
チャネルごとに変換を行います。
利用例
以下は、Compose
とOneOf
を組み合わせた例です。この例では、RandomCrop
は必ず実行され、HorizontalFlip
とRandomBrightnessContrast
はどちらか一方が50%の確率で適用されます。
transform = A.Compose([
A.RandomCrop(width=256, height=256),
A.OneOf([
A.HorizontalFlip(),
A.RandomBrightnessContrast(),
], p = 0.5)
])
Transforms
Transformsとして分類されている機能の一覧です(ここで紹介した以外の変換もあります)。
ChannelShuffle
RGBをランダムにシャッフルします
result = A.ChannelShuffle(p=1)(image=img)
CLAHE
Contrast Limited Adaptive Histogram Equalization(CLAHE, 適応的ヒストグラム平滑化)を適用します。コントラストが低い映像がはっきりします。
result = A.CLAHE(p=1)(image=img)
ColorJitter
カラージッターを挿入します。brightness
, contrast
, saturation
, hue
それぞれのジッター量を指定できます。
result = A.ColorJitter(brightness=1.0, hue=(-0.5,0.5), p=1)(image=img)
brightness | floatまたは(min, max)で指定 |
contrast | floatまたは(min, max)で指定 |
saturation | floatまたは(min, max)で指 |
hue | floatまたは(min, max)で指定。-0.5~0.5の範囲で指定 |
Downscale
縮小と拡大を行うことで画質劣化させます。
result = A.Downscale(scale_min=0.1, scale_max=0.25, interpolation=cv2.INTER_LINEAR, p=1)(image=img)
scale_min | 縮小の加減 < 1 |
scale_max | 縮小の上限 |
interpolation | 補完方法。デフォルトはCV2.INTER_NEAREST。 OpenCVの補完方法が選択できます。 |
GaussNoise
ガウスノイズをつけます
result = A.GaussNoise(var_limit=(100, 150), p=1)(image=img)
| 変動範囲 (min, max)で指定。floatで指定した場合は、(0, val)までになる |
ImageCompression
圧縮ノイズを付加します。
result = A.ImageCompression(quality_lower=1, quality_upper=10, p=1)(image=img)
| 画質の下限。1~100の範囲で指定する |
quality_upper | 画質の上限。1~100の範囲で指定する |
InvertImg
画素値を反転させます
result = A.InvertImg(p=1)(image=img)
Sharpen
Sharpen
シャープネス処理を施します
result = A.Sharpen(alpha=(0, 1.0), p=1)(image=img)
| (min, max)で設定。0がシャープネスなし、1が最大 |
Normalize
以下の式で正規化します。主に、モデルに入力する前に正規化するための処理です
img = (img - mean * max_pixel_value) / (std * max_pixel_value)
result = A.Normalize(p=1)(image=img)
mean | 平均値。デフォルトは(0.485, 0.456, 0.406) |
std | 偏差。デフォルトは(0.229, 0.224, 0.225) |
max_pixel_value | ピクセルの最大値。デフォルトは255.0 |
ToTensorV2
pytorchのテンソルに変換します。これを利用するには、以下のインポートが必要です。
import albumentations.pytorch
以下のようにpytorchのテンソルに変換できます。
result = A.pytorch.ToTensorV2()(image=img)
result['image'].shape
torch.Size([3, 500, 505])
Blur
Blurとして分類されている機能の一部です
AdvancedBlur
ぼかしフィルタです。詳しくは、https://arxiv.org/abs/2107.10833を参考にしてください。
result = A.AdvancedBlur(
blur_limit=(3, 15),
sigmaX_limit=(0.5, 3),
sigmaY_limit=(0.5, 3), p=1)(image=img)
blur_limit | カーネルサイズ。0または奇数 |
sigmaX_limit | x方向のガウスカーネルの標準偏差 |
sigmaY_limit | y方向のガウスカーネルの標準偏差 |
beta_limit | 1が正規分布。0~の範囲で裾野の形が変わる |
rotate_limit | ガウスカーネルの回転角度 (min,max)で指定。デフォルトは(-90, 90) |
Blur
入力画像をぼかす
result = A.Blur()(image=img)
blur_limit | カーネルサイズ。3以上を設定する必要がある。デフォルトは(3, 7) |
GaussianBlur
ガウスぼかしを行う。ガウスカーネルと偏差が個別に設定できる。
blur_limit | カーネルサイズ。0以上を設定する必要がある。デフォルトは(3, 7) |
sigma_limit | シグマの範囲をfloatか(min, max) で指定する。 0に設定するとカーネルサイズに合わせて自動計算される。 |
Crop
Cropとして分類されている機能の一部です
BBoxSafeRandomCrop
bboxを失わずに、クロップします。主に物体検出タスクのデータ拡張で利用します(物体検出については、後で解説します)
bboxesは、物体検知の枠情報です(albumentationsフォーマット。他のフォーマットも選択可能)。下記コードには、変換後の枠の描画も含まれています。
bboxes = [[0.01, 0.55 , 1.0,0.94]]
def clamp(n, smallest, largest):
return max(smallest, min(n, largest))
result = A.BBoxSafeRandomCrop(erosion_rate=0.3)(image=img, bboxes=bboxes)
img2 = result['image'].copy()
for bbox in result['bboxes']:
x1, y1, x2, y2 = bbox
h, w, _ = img2.shape
x1 = clamp(x1, 0, 1)
y1 = clamp(y1, 0, 1)
x2 = clamp(x2, 0, 1)
y2 = clamp(y2, 0, 1)
cv2.rectangle(img2, (int(x1*w), int(y1*h)), (int(x2*w), int(y2*h)), (0,0,255), 4)
cv2_imshow(img2)
※左図では画像サイズが自動調整されているため、実際の結果とは異なります。
erosion_rate | 枠をどの程度の範囲まで侵食してよいかを指定。 0.0を指定した場合は枠ははみ出させないようにクロップ |
CenterCrop
中央部分をトリミングする
result = A.CenterCrop(width=200, height=200)(image=img)
※左図では画像サイズが自動調整されているため、実際の結果とは異なります。
height | 切り抜く高さを指定 |
width | 切り抜く幅を指定 |
Crop
画像を指定された領域で切り抜きます
result = A.Crop(x_min=0, y_min=0, x_max=256, y_max=100)(image=img)
※左図では画像サイズが自動調整されているため、実際の結果とは異なります。
x_min | (x_min, y_min)-(x_max, y_max)で切り抜き |
y_min | |
x_max | |
y_max |
RandomCrop
ランダムに切り抜きます
result = A.RandomCrop(width=200, height=200)(image=img)
※左図では画像サイズが自動調整されているため、実際の結果とは異なります。
height | 切り抜く高さを指定 |
width | 切り抜く幅を指定 |
Dropout
Dropoutとして分類されている機能の一部です
CoarseDropout
長方形領域をドロップアウトします。
result = A.CoarseDropout(
max_holes= 100,
max_height=16,
max_width =16, p=1)(image=img)
max_holes | 最大の穴の数 |
min_holes | 最小の穴の数 |
max_height | 最大の穴の高さ |
min_height | 最小の穴の高さ |
max_width | 最大の穴の幅 |
min_width | 最小の穴の幅 |
fill_value | 穴を塗りつぶす値。デフォルトは0。floatまたは(0,0,0)形式で指定する |
Cutout
矩形領域をドロップアウトします(CoarseDropout
との差がよくわかりません)
result = A.Cutout (
num_holes=100,
max_h_size=16, max_w_size=16,
fill_value=(255,0,0), p=1)(image=img)
num_holes | 領域の数 |
max_h_size | 最大の穴の高さ |
max_w_size | 最大の穴の幅 |
fill_value | 穴を塗りつぶす値。デフォルトは0。floatまたは(0,0,0)形式で指定する |
幾何変換
Geometricとして分類されている機能の一部です
LongestMaxSize
アスペクト比を維持して、縦横の大きい方がmax_sizeを等しくなるようにスケーリングします
result = A.LongestMaxSize(max_size=256)(image=img)
max_size | 最大サイズ |
interpolation | 補完方法。デフォルトはCV2.INTER_NEAREST。 OpenCVの補完方法が選択できます。 |
RandomScale
サイズをランダムに変更します
result = A.RandomScale(scale_limit=(-1, 1), p=1)(image=img)
scale_limit | 0を1倍として、(min, max)の範囲で倍率を設定します。 デフォルトは(-0,1. 0.1)です |
interpolation | 補完方法。デフォルトはCV2.INTER_NEAREST。 OpenCVの補完方法が選択できます。 |
Resize
指定された高さと幅に変更します。アスペクト比は保存されません。
result = A.Resize(width=100, height=100, p=1)(image=img)
width | 幅を指定します |
height | 高さを指定します |
interpolation | 補完方法。デフォルトはCV2.INTER_NEAREST。 OpenCVの補完方法が選択できます。 |
SmallestMaxSize
アスペクト比を維持して、縦横の小さい方がmax_sizeを等しくなるようにスケーリングします
result = A.SmallestMaxSize(max_size=256)(image=img)
max_size | 最大サイズ |
interpolation | 補完方法。デフォルトはCV2.INTER_NEAREST。 OpenCVの補完方法が選択できます。 |
その他
その他の変換で気になるやつです。
HistogramMatching
ヒストグラムが一致するように、ピクセル値を変換します。下記では、読み込んだ画像を渡して、read_fn
は、画素値そのものを返すように設定しています。
ターゲットドメインが明確な場合のドメイン適応のための機能のようです。
ref = cv2.imread("dark.png")
result = A.HistogramMatching([ref], read_fn=lambda x: x, p=1)(image=img)
reference_images | 参照画像(リスト形式)。 デフォルトではファイル名を [path] とカッコで括って渡す |
read_fn | refrence_images の読み込み関数 |
blend_ratio | ブレンド比率。0でOriginal、1に近いヒストグラムになる。 |
RandomFog
霧をシミュレートして描画します
result = A.RandomFog(fog_coef_lower=0.3, fog_coef_upper=1, alpha_coef=0.08, p=1)(image=img)
fog_coef_lower | 霧強度係数の下限。0~1の範囲 |
fog_coef_upper | 霧強度係数の上限。0~1の範囲 |
alpha_coef | 霧の輪の透明度。0~1の範囲 |
RandomRain
雨のエフェクトを描画します
result = A.RandomRain(p=1)(image=img)
slant_lower | -20~20の範囲で指定 |
slant_upper | -20~20の範囲で指定 |
drop_length | 0~100の範囲で指定 |
drop_width | 1~5の範囲で指定 |
drop_color | (r, g, b)で指定 |
blur_value | ボケの長さ(int) |
brightness_coefficient | 0~1で指定 |
rain_type | [None, “drizzle”, “heavy”, “torrential”]の1つ |
物体検出のデータに関して
Albumentationsの特徴の1つが、物体検知のBBOXやセグメンテーション情報も同時に加工してくれるところです。ここでは、物体検出でAlbumentationsを利用する場合について解説します。
BBOX(Bounding Box)のフォーマット
Albumentationsでは、pascal_voc, albumentationsオリジナル、coco, yoloのBBOXフォーマットをサポートしています。
以下、それぞれのフォーマットです。
フォーマットの指定方法
Compose
のオプションで、どのフォーマットを選択するか設定できます。例えば、yoloフォーマットを利用する場合は、以下のようにbbox_params
に指定します。
transform = A.Compose([
A.RandomCrop(width=256, height=256, p=1),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
], bbox_params=A.BboxParams(format='yolo'))
このとき、min_area
, min_visibility
を設定することができます。
#ex1)
transform = A.Compose([
A.RandomCrop(width=256, height=256, p=1),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
], bbox_params=A.BboxParams(format='yolo', min_area=256))
#ex2)
transform = A.Compose([
A.RandomCrop(width=256, height=256, p=1),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
],bbox_params=A.BboxParams(format='yolo', min_visibility=0.25))
それぞれ、以下の意味になります。
min_area BBOXの面積が指定された面積より小さくなった場合に、そのBBOXを削除する
min_visibility 元の面積に対して、BBOXの面積が指定した比率より小さくなった場合に削除
ex1は面積が256ピクセル以下になった場合、ex2は面積が25%以下になった場合にBBOXが削除されます。
変換へのBBOXの渡し方
変換へのBBOXの渡し方は簡単です。
YOLOフォーマットの場合、BBOXは以下のような形式で格納します。
bboxes = [
[0.1, 0.1, 0.2, 0.2],
[0.3, 0.4, 0.1, 0.2],
:
]
これを画像と一緒に渡します。
image = cv2.imread('/path/to/image')
transform = A.Compose([
A.RandomCrop(width=256, height=256, p=1),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
], bbox_params=A.BboxParams(format='yolo'))
result = transform(imgas=image, bboxes=bboxes)
出力は、result[‘image’]に変換後の画像が、result[‘bboxes’]に変換後のBBOXの情報が格納されます。
クラス名を渡したい場合は、以下のようにすればクラス名を渡すことができます。この場合、戻り値のbboxes
には、5つ目の引数が追加されています。なお、”car”などのラベル名ではなく、101などのIDを渡しても良いです。
def clamp(n, smallest, largest):
return max(smallest, min(n, largest))
bboxes = [[0.01, 0.55 , 1.0,0.94, "car"]]
result = A.BBoxSafeRandomCrop(erosion_rate=0.3)(image=img, bboxes=bboxes)
img2 = result['image'].copy()
for bbox in result['bboxes']:
x1, y1, x2, y2, label = bbox
print(x1, y1, x2, y2, label)
h, w, _ = img2.shape
x1 = clamp(x1, 0, 1)
y1 = clamp(y1, 0, 1)
x2 = clamp(x2, 0, 1)
y2 = clamp(y2, 0, 1)
cv2.rectangle(img2, (int(x1*w), int(y1*h)), (int(x2*w), int(y2*h)), (0,0,255), 4)
cv2_imshow(img2)
実験した感じでは、bboxesの各要素の先頭から4つだけ(上のコードの場合[0.01, 0.55 , 1.0, 0.94, “car”]の赤文字の部分だけ)が変換対象で、残りの部分は変更されずに戻ってくる仕様になっているようです。
まとめ
以上、データ拡張用のライブラリAlbumentationsの簡単な使い方について説明しました。ここに記載している機能以外にもたくさんのデータ拡張が用意されていますので、公式サイトもチェックしてみてください。