🙆

unity.perceptionで作ったデータセットをdeeplab用に変換する

2022/03/11に公開

この記事ではUnity-Technologies/com.unity.perceptionを使って作ったデータセットをtensorflow/modelsにあるdeeplabの学習用にデータセットを作る方法を紹介します。

まずperceptionを2回動かして生成したデータセットをtrainvalと名前を変更して、MyFirstPerceptionというディレクトリに入れます。これをdeeplab用のデータセット(変換前)とします。

$ cd /path/to/MyFirstPerception
$ tree .
.
├── train
│   ├── Datasetd188fe0a-3b9e-4949-909c-0cbb8f0e01b5
│   ├── Logs
│   ├── RGB3f8b2e26-38b9-4486-99a5-510ce22ce75b
│   └── SemanticSegmentation02ceeb36-7c09-40e9-a2d6-e775b593d8c6
└── val
    ├── Dataset9efc96bd-e87a-4abf-a039-e31642f98590
    ├── Logs
    ├── RGBafd20565-3872-45ce-907c-b2a7bc044ca3
    └── SemanticSegmentation8834cfd9-3b67-4abb-b12b-5c0667f5bd7c

これを次のPythonスクリプトで変換します。

transform-dataset.py
from PIL import Image
import itertools
import numpy as np
import json
from glob import glob
import os
import sys


def load_palette(specs):
    colors = [[0, 0, 0]]
    for s in specs:
        v = s['pixel_value']
        colors.append([int(v['r']*0xff), int(v['g']*0xff), int(v['b']*0xff)])
    NUM_ENTRIES_IN_PILLOW_PALETTE = 256
    palette_arr = list(itertools.chain.from_iterable(colors))
    palette_arr.extend(
        [0, 0, 0] * (NUM_ENTRIES_IN_PILLOW_PALETTE - len(colors)))
    palette_img = Image.new('P', (0, 0))
    palette_img.getdata().putpalette('RGB', bytes(palette_arr))
    return palette_img


if len(sys.argv) != 2:
    print(f"{sys.argv[0]} DATASET_DIR", file=sys.stderr)
    sys.exit(1)
dataset_dir = sys.argv[1]
if dataset_dir[-1] == "/":
    dataset_dir = dataset_dir[:-1]
dataset = os.path.basename(dataset_dir)
os.makedirs(os.path.join(dataset, 'ImageSets'), exist_ok=True)
os.makedirs(os.path.join(dataset, 'JPEGImages'), exist_ok=True)
os.makedirs(os.path.join(dataset, 'SegmentationClass'), exist_ok=True)

for path in glob(os.path.join(dataset_dir, '*')):
    variant = os.path.basename(path)
    for path in glob(os.path.join(path, 'Dataset*')):
        with open(os.path.join(path, 'annotation_definitions.json')) as f:
            d = json.load(f)
            assert d['version'] == '0.0.1'
    for definition in d['annotation_definitions']:
        if definition['name'] == 'semantic segmentation':
            palette = load_palette(definition['spec'])
            break
    file_ids = []
    img_size = None
    for path in glob(os.path.join(dataset_dir, variant, 'SemanticSegmentation*')):
        for src in glob(os.path.join(path, '*.png')):
            name, _ = os.path.splitext(os.path.basename(src))
            _, img_id = name.split('_')
            file_id = f"{variant}_{img_id}"
            img = Image.open(src).convert(
                'RGB').quantize(palette=palette, dither=0)
            annotation = Image.fromarray(
                np.array(img, dtype=np.uint8), mode='L')
            if img_size is None:
                img_size = img.size
            annotation.save(os.path.join('MyFirstPerception',
                            'SegmentationClass', f"{file_id}.png"))
    for path in glob(os.path.join(dataset_dir, variant, 'RGB*')):
        for src in glob(os.path.join(path, '*.png')):
            name, _ = os.path.splitext(os.path.basename(src))
            _, img_id = name.split('_')
            file_id = f"{variant}_{img_id}"
            file_ids.append(file_id)
            img = Image.open(src).convert('RGB')
            img = img.resize(img_size)
            img.save(os.path.join('MyFirstPerception',
                                  'JPEGImages', f"{file_id}.jpg"))

    with open(os.path.join('MyFirstPerception', 'ImageSets', f"{variant}.txt"), 'w') as w:
        w.write("\n".join(file_ids))

使い方は引数にデータセットまでのパスを指定するだけです。

$ python transform-dataset.py /path/to/MyFirstPerception

するとカレンとディレクトリにMyFirstPerceptionができます。これをさらにbuild_voc2012_data.pyでTFRecord形式に変換したものを使います。

python ../build_voc2012_data.py \
  --image_folder=./MyFirstPerception/JPEGImages \
  --list_folder=./MyFirstPerception/ImageSets \
  --semantic_segmentation_folder=./MyFirstPerception/SegmentationClass \
  --image_format=jpg

transform-dataset.pyがやっていることは、ほぼファイルをbuild_voc2012_data.pyで使えるように配置し直しているだけですが、アノテーションファイルだけはRGBAから、各ピクセルを0を背景とし、1以降を各ラベルに順に振ったグレースケールの画像に変換しています。Image.fromarray(np.array(img, dtype=np.uint8), mode='L')は遅そうですが、Image.frombytes('L', img.size, bytes(img.getdata()), "raw", "L", 0, 1)より2倍くらい早いことは確認しています。詳しく調べていませんが、numpy.ndarray.tobytesよりもbytesが遅いのかなと思っています。

Discussion