画像化のためのグレースケール変換

信号処理の実験をしていると、時には2次元配列を画像化したい場合があります。matplotlibでも2次元配列を表示して画像保存する方法はあるはずですが、汎用性の高い方法としてpgmやppmで出力する方法があります。

はじめに

今回はpgm化です。手順としては、対象の2次元配列の値を正規化し、グレースケール変換したあと、PGMファイルとして出力します。PGMファイルはフォーマットが非常に簡単なので、数値計算、ファイル出力の方法さえ覚えてしまえば、言語や環境をほぼ問わずに応用できます。

ここではpython+numpyでの実装方法をメモしておきます。

正規化

まずは正規化(normalize)です。

def normalize(values, lower, upper):
    return (values - lower) / (upper - lower)

valuesはnumpy.ndarrayの2次元配列を想定しています。lowerとupperは正規化のための下限値と上限値です。正規化なので、本来はvaluesに含まれる最小値よ最大値を使うべきですが、実際にはクリップ処理を行いたい事が多いので、外からクリップ範囲のとして指定できるようにしています。
理想的には、正規化の結果は0.0-1.0の範囲に収まりますが、上記の処理はlowerとupperに与える値によっては0.0-1.0の値の範囲からはみ出します。

正規化の逆処理

目的の値の範囲にスケーリングします。

def denormalize(values, lower=0, upper=1):
    return values * (upper - lower) + lower

normalizeと異なり、lowerとupperにデフォルト値を指定しています。未指定の場合、入力値に1.0をかけるだけの何もしない処理になります。
denormalizeはlowerで指定したオフセットを値に加え、入力値0.0-1.0がupperとlowerの差に収まるようにスケーリングします。

グレースケール化

グレースケール化という表現は正確ではありませんが、グレースケール画像として出力するための前段階の処理として、便宜上、ここでの処理をグレースケール化と呼びます。

def to_grayscale(values, lower, upper):
    normalized = normalize(values, lower, upper)
    denormalized = numpy.clip(denormalize(normalized, 0, 255), 0, 255)
    return denormalized.astype(numpy.uint8)

PGM形式のグレースケールの色深度は256階調なので、入力値を0から255の固定範囲に変換し、numpy.uint8型に変換して値を返します。
ポイントはnumpy.clipで値の範囲を0から255に制限している点です。
実験結果に外れ値が入っている場合、その値を使って正規化してしまうと、本来確認したい値の範囲が相対的に狭まってしまい、グレースケール画像に変換した際に値の変動が潰れて確認できなくなってしまいます。
外れ値は0か255にクリップすることで、確認したい値の範囲の量子化レベルをある程度保って画像化できます。
そのために、一番はじめの正規化処理では正しい正規化ではなく、指定した値の範囲での正規化処理を行っています。

PGMファイルとして出力

あとはPGMファイルとして出力するだけです。PGMのグレースケールかつバイナリフォーマットは非常に簡単です。
“P5(改行)(画像横ピクセル数)(ホワイトスペース)(画像縦ピクセル数)(改行)(画像色深度数)(改行)(ピクセルを表すバイト列)”です。

def write_pgm(pixel_2d_array, filename):
    with open(filename, "wb") as pgm_file:
        pgm_file.write("P5\n".encode())
        width = len(pixel_2d_array[0])
        height = len(pixel_2d_array)
        pgm_file.write(("{:d} {:d}\n".format(width, height)).encode())
        pgm_file.write("255\n".encode())
        pgm_file.write(pixel_2d_array)

numpy.ndarrayの2次元配列は、[Y][X]というメモリレイアウトなので、そのまま画像化するのであれば、Xを横ピクセル数、Yを縦ピクセル数に指定する必要があります。
C言語での2次元配列表現に慣れていれば、特に理解に苦しむことはないはずです。

“P5″はPGMのグレースケールバイナリ形式を示す識別文字列です。”P2″だとグレースケールテキスト形式、”P6″だとRGBカラーバイナリ形式になります。いずれも画像を表現するための最低限のヘッダ情報と、ピクセルの配列という構成です。

おまけ:ラスター配列への変換

スペクトログラム等の、時系列処理した結果の2次元配列データがC言語の2次元配列表現に従っている場合、メモリレイアウトの都合でそのまま画像化すると縦方向が時間の進行方向になります。また、低周波数成分が右側なので、単に転置して時間軸と周波数軸を入れ替えると、画像化の結果、高周波が下、低周波が上という上下逆転した周波数軸になってしまいます。
よって、時系列データについては転置+上下反転処理を施してから画像化したいところです。

numpy.ndarrayで転置したり反転するだけでは、表現上の並びが入れ替わるだけでメモリレイアウトは変化しません。メモリレイアウトを変更するにはresizeを使います。

def to_raster(pixel_2d_array):
    width = len(pixel_2d_array[0])
    height = len(pixel_2d_array)
    raster_presented = pixel_2d_array.transpose()[::-1]
    return numpy.resize(raster_presented, new_shape=(width, height))

Leave a Reply

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