こしあん
2019-10-22

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

Pocket
LINEで送る
Delicious にシェア

3.6k{icon} {views}



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です。



Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

【新刊】インフィニティNumPy――配列の初期化から、ゲームの戦闘、静止画や動画作成までの221問

「本当の実装力を身につける」ための221本ノック――
機械学習(ML)で避けて通れない数値計算ライブラリ・NumPyを、自在に活用できるようになろう。「できる」ための体系的な理解を目指します。基礎から丁寧に解説し、ディープラーニング(DL)の難しいモデルで遭遇する、NumPyの黒魔術もカバー。初心者から経験者・上級者まで楽しめる一冊です。問題を解き終わったとき、MLやDLなどの発展分野にスムーズに入っていけるでしょう。

本書の大きな特徴として、Pythonの本でありがちな「NumPyとML・DLの結合を外した」点があります。NumPyを理解するのに、MLまで理解するのは負担が大きいです。本書ではあえてこれらの内容を書いていません。行列やテンソルの理解に役立つ「従来の画像処理」をNumPyベースで深く解説・実装していきます。

しかし、問題の多くは、DLの実装で頻出の関数・処理を重点的に取り上げています。経験者なら思わず「あー」となるでしょう。関数丸暗記では自分で実装できません。「覚える関数は最小限、できる内容は無限大」の世界をぜひ体験してみてください。画像編集ソフトの処理をNumPyベースで実装する楽しさがわかるでしょう。※紙の本は電子版の特典つき

モザイク除去から学ぶ 最先端のディープラーニング

「誰もが夢見るモザイク除去」を起点として、機械学習・ディープラーニングの基本をはじめ、GAN(敵対的生成ネットワーク)の基本や発展型、ICCV, CVPR, ECCVといった国際学会の最新論文をカバーしていく本です。
ディープラーニングの研究は発展が目覚ましく、特にGANの発展型は市販の本でほとんどカバーされていない内容です。英語の原著論文を著者がコードに落とし込み、実装を踏まえながら丁寧に解説していきます。
また、本コードは全てTensorFlow2.0(Keras)に対応し、Googleの開発した新しい機械学習向け計算デバイス・TPU(Tensor Processing Unit)をフル活用しています。Google Colaboratoryを用いた環境構築不要の演習問題もあるため、読者自ら手を動かしながら理解を深めていくことができます。

AI、機械学習、ディープラーニングの最新事情、奥深いGANの世界を知りたい方にとってぜひ手にとっていただきたい一冊となっています。持ち運びに便利な電子書籍のDLコードが付属しています。

「おもしろ同人誌バザールオンライン」で紹介されました!(14:03~) https://youtu.be/gaXkTj7T79Y?t=843

まとめURL:https://github.com/koshian2/MosaicDeeplearningBook
A4 全195ページ、カラー12ページ / 2020年3月発行

Shikoan's ML Blog -Vol.1/2-

累計100万PV超の人気ブログが待望の電子化! このブログが電子書籍になって読みやすくなりました!

・1章完結のオムニバス形式
・機械学習の基本からマニアックなネタまで
・どこから読んでもOK
・何巻から読んでもOK

・短いものは2ページ、長いものは20ページ超のものも…
・通勤・通学の短い時間でもすぐ読める!
・読むのに便利な「しおり」機能つき

・全巻はA5サイズでたっぷりの「200ページオーバー」
・1冊にたっぷり30本収録。1本あたり18.3円の圧倒的コストパフォーマンス!
・文庫本感覚でお楽しみください

北海道の駅巡りコーナー

日高本線 車なし全駅巡り

ローカル線や秘境駅、マニアックな駅に興味のある方におすすめ! 2021年に大半区間が廃線になる、北海道の日高本線の全区間・全29駅(苫小牧~様似)を記録した本です。マイカーを使わずに、公共交通機関(バス)と徒歩のみで全駅訪問を行いました。日高本線が延伸する計画のあった、襟裳岬まで様似から足を伸ばしています。代行バスと路線バスの織り成す極限の時刻表ゲームと、絶海の太平洋と馬に囲まれた日高路、日高の隠れたグルメを是非たっぷり堪能してください。A4・フルカラー・192ページのたっぷりのボリュームで、あなたも旅行気分を漫喫できること待ったなし!

見どころ:日高本線被災区間(大狩部、慶能舞川橋梁、清畠~豊郷) / 牧場に囲まれた絵笛駅 / 窓口のあっただるま駅・荻伏駅 / 汐見の戦争遺跡のトーチカ / 新冠温泉、三石温泉 / 襟裳岬

A4 全192ページフルカラー / 2020年11月発行


Pocket
Delicious にシェア

Add a Comment

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