AnyTech Engineer Blog

AnyTech Engineer Blogは、AnyTechのエンジニアたちによる調査や成果、Tipsなどを公開するブログです。

Blenderでセマセグ用の教師データを作成

Blenderでセマセグ用の教師データを作成 こんにちは、AnyTechの岩井です。

最近はBlender等の3DCGソフトや各社GPUの進歩により個人でも手軽に非常にリアルなCG画像を作れるようになりました。リアルなCGは映画やアニメなどの映像作品だけでなく、AIの教師データ作成にも使用されてきています。確かにCGならば様々な状況を作れますし、精巧なアノテーションデータを自動で作成できるので、大規模データを必要とするAIと相性が良さそうですね。 AnyTechでもしばしば僕がBlenderを使ってCGのデータを作成し、簡易検証に用いることがあります。

今回はBlenderでセマンティックセグメンテーションのアノテーションデータを作成する手順を紹介したいと思います。 検証に使用したBlenderのバージョンは3.6.4です。

まずはCG作成

検証用に以下のようなCG画像を作ってみました。

モデルや背景はhttps://polyhaven.com/からお借りしました。 この画像のパンをクラス1、ワインボトルをクラス2として、パンの部分は画素値が1でワインボトルの部分は画素値が2となるようなアノテーションデータを作成してみましょう。

オブジェクトID割り当て

Blenderではオブジェクトにインデックスを割り振ることができます。オブジェクトを選択してオブジェクトプロパティを開くとパスインデックスというものがあるので、それを変更します。 今回はパンのインデックスを1、ワインボトルのインデックスを2に設定します。

パスインデックスの割り当て
この後説明するcompositorでパスインデックスを使用できるようにするため、ビューレイヤーでオブジェクトインデックスにチェックしておきます。
オブジェクトインデックスのチェックボックス

出力設定

Compositorを開いてアノテーションデータを作成します。Compositorはレンダリング後に実行されるポストプロセスのことです。 開いてみると、先ほどビューレイヤーでオブジェクトインデックスにチェックしていたため、レンダーレイヤーからIndexOBというものが出ています。IndexOBからはオブジェクトインデックスが各ピクセルに格納された画像が出ているので、それを直に出力画像へ接続します。

Compositorの処理

最後に、出力プロパティで出力形式をOpenEXRにし、画像をレンダリングして保存します。

exrで保存する設定

出力画像の保存と読み込み

レンダリングすると拡張子が.exrのデータが出力されます。 そのままでは見れないので、pythonで読み取りましょう

import OpenEXR as exr
import numpy as np
import cv2

def read_exr(filename):
    exrfile = exr.InputFile(filename)
    header = exrfile.header()
    dw = header['dataWindow']
    isize = isize = (dw.max.y - dw.min.y + 1, dw.max.x - dw.min.x + 1)
    depth = None

    channelData = dict()
    colorChannel = ["B", "G", "R"]
    for c in header['channels']:
        C = exrfile.channel(c, header['channels'][c].type)
        dtype = np.float16 if 'HALF' == str(header['channels'][c].type) else np.float32
        C = np.frombuffer(C, dtype = dtype)
        C = np.reshape(C, isize)
        if c in ['R', 'G', 'B']:
            channelData[c] = C
        elif c == 'Z':
            depth = C

    img = np.concatenate([channelData[c][..., np.newaxis] for c in colorChannel], axis=2)

    return img, depth


def main():
    img_file = "ann.exr"

    ann_data, _ = read_exr(img_file)
    ann_data = ann_data[...,0].astype(np.uint8) # B G Rのどれか一つを取得

if __name__ == '__main__':
    main()

上記コードのann_dataは白黒画像になっており、それぞれの画素値がオブジェクトインデックスに対応しています。 np.whereを使って画素値が1の部分、画素値が2の部分を抽出してみた結果が以下の通りです。

パン(オブジェクトインデックスが1)のアノテーションデータ
ワインボトル(オブジェクトインデックスが2)のアノテーションデータ

いいかんじですね。

なぜOpenEXR?

ここまで読んでくださった方の中にはなぜわざわざOpenEXRで保存するのか?pngの方がプレビューもしやすいしOpenEXRよりもデータサイズが小さくていいじゃないかと思った方もいるかもしれません。 実際、CompositorでIndexOBを255で徐算し、レンダープロパティのカラーマネージメントでシーケンサーをRawに設定することでpngの各画素値をオブジェクトインデックスに対応付けて出力できます。しかしながら、原因不明なのですがBlenderでpng形式で保存すると小さい謎のノイズがのってしまう現象があります。

こちらがpngで保存した後にnp.whereでオブジェクトインデックスが1の部分を抽出したときの画像です。

png形式で保存したときのアノテーションデータ

この問題を解決できなかったので、僕はOpenEXRを使用しています。

最後に

今回はBlenderでセマンティックセグメンテーション用のアノテーションデータを生成する方法を紹介いたしました。オブジェクトインデックスまで手動で割り振ってしまえば、後はカメラアングルを変えたり背景を変えたりすることで様々な状況の教師データを作成できます。今回は紹介していませんが、Blenderはpythonで動かすこともできるので、Compositor作成からOpenEXRで保存まではpythonで自動化することも可能です。機会があればpythonで自動化する部分も紹介したいと思います。