こしあん
2019-10-22

Numpyでインデックスカラー画像(VOC2012のマスク)→RGB画像への変換をする方法

Pocket
LINEで送る


Semantic Segmentationのマスク画像には「インデックスカラー」というRGBとは異なったフォーマットを使っていることが多いです。この形式はPILで扱うことができ、RGBに変換できます。しかし、モデルの予測を表示したいときはダイレクトにNumpyで計算できると便利です。VOC2012のマスク画像を例に見ていきます。

インデックスカラーとは

通常のRGBによるカラー画像は、(x, y)の点に対してRGBのピクセル値を格納しています。インデックスカラーは(x, y)座標を使っていることは変わらないのですが、RGBの具体的な値ではなくカラーパレットのIDを格納しています。

例えばカラーパレットが、

  • 0 : RGB=(0, 0, 0) 黒
  • 1 : RGB=(255, 0, 0) 赤
  • 2 : RGB=(0, 255, 0) 緑
  • 3 : RGB=(0, 0, 255) 青

という定義で、2×2の画像の左上から青→緑→赤→黒という画像にしたい場合、インデックスカラー画像では次のようになります。

[[3, 2],
 [1, 0]]

もしこれがRGB画像の場合は次のようになります。

[[[0, 0, 255], [0, 255, 0]],
 [[255, 0, 0], [0, 0, 0]]]

インデックスカラーのほうがぱっと見てわかりやすいですね。Numpyの配列にすると、RGB画像が(Height, Width, 3)という3ランクのテンソルになりますが、インデックスカラー画像は(Height, Width)という1ランクのテンソルになります。

なぜこのインデックスカラーがSemantic Segmentationで使われるのかというと、パレットのインデックスがそのままクラスインデックスに対応できるからです。つまり普段見るような、

のような画像は、インデックスカラーをあたかもRGB画像のように表示しているだけということになります。

コード

VOC2012の最初の画像を使いました。torchvisionから読み込ませます。

import torchvision
from PIL import Image
import numpy as np

# PILのimg(mode=P).convert("RGB")と同じ処理
def convert_to_rgb(index_colored_numpy, palette, n_colors=None):
    assert index_colored_numpy.dtype == np.uint8 and palette.dtype == np.uint8
    assert index_colored_numpy.ndim == 2 and palette.ndim == 2
    assert palette.shape[1] == 3
    if n_colors is None:
        n_colors = palette.shape[0]
    reduced = index_colored_numpy.copy()
    reduced[index_colored_numpy > n_colors] = 0 # 不要なクラスを0とする
    expanded_img = np.eye(n_colors, dtype=np.int32)[reduced]  # [H, W, n_colors] int32
    use_pallete = palette[:n_colors].astype(np.int32)  # [n_colors, 3] int32
    return np.dot(expanded_img, use_pallete).astype(np.uint8)

def main():
    # VOC2012はダウンロード済みとする(未ダウンロードの場合は download=True)
    trainset = torchvision.datasets.VOCSegmentation(root="./data", download=False, image_set="train")
    path = next(iter(trainset.masks))

    with Image.open(path) as img:
        palette = np.array(img.getpalette(), dtype=np.uint8).reshape(-1, 3) # パレットの取得
        p_array = np.asarray(img)  # Numpy配列に変換
        print(p_array.shape)
        converted_rgb = np.asarray(img.convert("RGB")) # PILでコンバート
        rgb_array = convert_to_rgb(p_array, palette)
        print(rgb_array.shape)
        print(np.all(converted_rgb == rgb_array))  # PILでのコンバートと結果が等しいか確認用
    with Image.fromarray(rgb_array) as img:
        img.save("out.png")

if __name__ == "__main__":
    main()

PILでは「img.convert(“RGB”)」とすることで、RGBに変換できます(逆変換はバグなのかうまくいかないので要注意、どうしても逆変換をしたい場合はquantizeを使ったほうが無難

やっていることは、PILのimg.convert(“RGB”)と、Numpyだけで計算したコードの出力が一緒かを確かめています。

Numpyだけの計算はconvert_to_rgbの部分です。言っちゃえばこれは行列の内積を取るだけなので、np.dotで終わります。出力は次のようになります。

(281, 500)
(281, 500, 3)
True

上から、インデックスカラーの画像をNumpy化したときのshape、RGB画像をNumpy化したときのshape、PILのimg.convert(“RGB”)とconvert_to_rgbの出力が全ピクセルで同じかを比較した結果です。

不要なクラスを省いてみる

ちなみにこのconvert_to_rgbは使うパレット数を制限できるので、VOCの仕様に合わせて使うパレットを21(背景+20クラス)に制限してみます。こうすればいいだけです。

convert_to_rgb(p_array, palette, n_colors=21)

境界線(undifinedの領域)が消えました。

Numpy配列からインデックスカラーの画像を作る

逆の方法として、Numpy配列からインデックスカラーの画像を作ってみましょう。

def indexed_image_from_scratch():
    indices = (np.arange(350) // 50).astype(np.uint8).reshape(1, -1)  # [1, 350] px
    indices = np.broadcast_to(indices, (50, 350))  # [50, 350] px

    # 虹色のカラーパレット
    color_palette = [
        255, 0, 0,
        255, 165, 0,
        255, 255, 0,
        0, 128, 0,
        0, 255, 255,
        0, 0, 255,
        128, 0, 128
    ]
    with Image.fromarray(indices, mode="P") as img:
        img.putpalette(color_palette)  # パレットの設定
        img.save("rainbow.png")

虹色を50px置きに描画する関数です。結果はこのようになります。

うまくできました。Image.fromarrayでグレー画像のようにロードしたあと(ただし各値はインデックス)、パレットをputpaletteで設定すればOKです。

Related Posts

PyTorchで複数出力があるモデルの出力の型について... 出力が複数あるモデルの訓練というのは少し複雑なモデルだとよく出てきます。PyTorchでは複数出力のモデルの、出力の型はどうなっているでしょうか。それを見ていきます。中間層の値を取りたい場合も使えます。 サンプルコード import torch from torch import nn cl...
PyTorchでConvolutionフィルターをやる(エッジ検出やアンシャープマスク)... PyTorchでPILのConvolutionフィルター(エッジ検出やアンシャープマスク)をやりたくなったので、どう実装するか考えてみました。 やりたいこと PIL/PillowのConvolutionフィルター(ImageFilterなど)の処理をPyTorchの畳み込み演算で再現したい ...
Numpyの配列のみを操作して四角形を描画する(Numpyの画像処理)... Numpyの画像処理です。Numpyの配列のみを操作して、画面上に四角形を描画してみます。Numpyの画像処理は出力結果の合成のときにたまに使う割には若干独特なので注意が必要です。 Numpy arrayの画像の構造は(y, x, channel) ここだけ覚えておけば大丈夫です。Pillo...
PyTorchでGANの訓練をするときにrequires_grad(trainable)の変更はいる... PyTorchでGANのある実装を見ていたときに、requires_gradの変更している実装を見たことがあります。Kerasだとtrainableの明示的な変更はいるんで、もしかしてPyTorchでもいるんじゃないかな?と疑問になったので、確かめてみました。 requires_gradの変更とは...
スタイル変換のStyle Lossとは何をやっているか... スタイル変換やImage to Imageの損失関数で使われる・Style Lossの実装を詳しく見ていきます。Style Lossの計算で用いているグラム行列の計算方法をTensorFlowで考えます。 Style Lossのやっていること 2つの画像の、VGG16や19(どっちを使うか、ど...
Pocket
LINEで送る
Delicious にシェア

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です