Pythonで画像(の色)を数値化してリストで扱う方法

【Python】画像のノイズを減らして二値化し、輪郭を抽出する

画像データ(.jpgや.pngなど)をソフトを使わずに加工したいと思ったことはありませんか?または、画像の色の情報を数値を使って定量的に評価したいと思ったことはありませんか?
実は、画像データは数値が格納されたリスト(配列)として取り扱うことができるので、加工や定量評価も簡単にできるんです。
Pythonを使って画像の数値的な取り扱いが可能になると、画像の集合である動画も取り扱えるようになるので、データ解析の幅がぐんと広がります!

この記事では、画像データの取り扱いに必須の画像(の色)を数値化してリストで扱う方法を具体的に解説していきます。

動作環境
macOS Catalina(10.15.5), python3.7.6, Atomエディタ1.44.0

画像データについて

画像データを数値化する前に、画像データについて理解しておく必要があります。
簡単に画像データについて把握していきましょう!

画像データの種類

コンピュータで取り扱える画像データは、ラスターデータとベクターデータの二種類があります。

jpg・png・tiff・gif・icoの拡張子のデータがラスターデータにあたります。
pdf・svg・epsの拡張子のデータはベクターデータです。

ラスター形式の画像データの例

それぞれの画像データの違いは、画像を拡大して表示した時に顕著に表れます。
ラスター画像は拡大するとぼやけてきますが、ベクター画像はどんなに拡大してもぼやけることはありません。
というのも、ラスター画像はピクセル(画素)という単色の小さな正方形がいくつも集まって形成されているからです(上図参照)。

ここで覚えておいて欲しいのは、数値データに変換できるのはラスター画像の方だということです。

ラスター画像の色について

「ラスター画像はピクセル(画素)という単色の小さな正方形がいくつも集まって形成されている」と上で述べましたが、
このピクセルには1色だけ色がついています
例えば、白黒画像(gray_scale画像)の場合だと、白黒の濃淡を0〜255の数値を使って256段階で表現しています。
0に近ければ黒くなり、255に近ければ白くなります。
つまり、色と数値が一対一で対応しているのです。

カラー画像(RGB)の場合は、赤(R)・緑(G)・青(B)の三枚を重ね合わせて複雑な色を表現しています(上図参照)。
赤・緑・青それぞれが白黒画像と同様に0~255の数値を使って表現されています。
例えば、紺色は (R, G, B) = (34, 51, 119) というように数値で表現されます。
ちなみに色の組み合わせは、256×256×256 = 16777216 通りもあります。

色から数値への変換

画像データについて説明してきましたが、ここで少しまとめます。
 ・ラスター画像は色のついたピクセルが集合したもの
 ・ピクセルの色は数値で表現可能 ( 白黒→0~255, カラー→(0~255, 0~255, 0~255) )
つまり、画像データは数値が格納された配列(もしくは行列)と言っても過言ではありません (上図参照)。

 実際に画像データを数値配列に変換できれば、配列内の数値を
①並び替えたり、②必要なところだけ抜き取ったり、③数値を足し合わせたり、④数値を変更したりができます (研究・開発では、②・③あたりがよく使われるんじゃないかなぁと思います。具体例は別記事で紹介します)。
 また、配列内の数値をいじった後に画像データに変換すれば、加工画像が得られます。 

画像を数値の配列に変換する方法

画像⇄数値配列の変換はpythonのPillow(PIL)モジュールで簡単に行えます
また、配列に変換した後の処理はpythonのNumpyモジュールを利用すると便利です。
以下で、具体的に見ていきましょう。

Pillow(PIL)とNumpyを使うための準備

pipコマンドを用いれば簡単にPillow(PIL), Numpyがインストールできます。
次のようにターミナルにコマンドを入力して、returnキーを押してください。

pip install pillow
pip install numpy

最終的にSuccessfullyというワードが書かれていれば、無事にインストール完了です。
すでにインストールされている場合は、already satisfiedというワードが表示されると思います。

画像データの用意

画像データとして次の猫ちゃんの白黒画像(cat_gray.jpg)を用意しました。
右クリックでダウンロードしてください。
(フリー素材で見つけました)

プログラムの実装(サンプルコード)

いきなりですが、猫ちゃんの白黒画像を数値配列に変換するコードは次のようになります。
input_image.pyという名前でDesktop/LabCode/python/data-analysis/input-imageに置きます。

from PIL import Image
import numpy as np

# 画像データの読み込み
img = Image.open('cat_gray.jpg')
# 画像のサイズ(幅[px] x 高さ[px])を取得し、それぞれを変数に代入
width, height = img.size
print(img.size)
print()
# 画像左上の原点(0,0)から横に99、下に99進んだ位置にあるピクセルの色を数値として取得できる
print(img.getpixel((99, 99)))
print()

# 0が格納された配列を画像のサイズと同じサイズで用意
image_array = np.empty((height, width), dtype = int)
print(image_array)
print()

for y in range(height):
    for x in range(width):
        # 順次、ピクセルの色の数値を代入している
        image_array[y][x] = img.getpixel((x, y))
# 画像データが数値の配列になっていることが確認できる
print(image_array)

少し長くなっていますが、print文とかコメント文が多いだけで、本当に必要なコードは8行分だけです。

コードが書けたら、ターミナル上でcwdをDesktop/LabCode/python/data-analysis/input-imageに変更し、$ python input_image.pyとコマンド入力し、プログラムを実行しましょう。

MEMO
pip installではpillowをインストールしましたが、importのところではPILと記述するので注意しましょう。

出力結果

(883, 583)

169

[[0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 ...
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]]

[[176 176 176 ...   5   5   5]
 [176 176 176 ...   5   5   5]
 [176 176 176 ...   5   5   5]
 ...
 [183 183 183 ... 151 151 151]
 [183 183 183 ... 148 148 148]
 [183 183 184 ... 145 145 145]]

プログラムが正しく実行されると、上のようにターミナル上に配列などが表示されると思います。


画像のサイズ(横幅×高さ)が 883px × 583px で、座標(99, 99)のピクセルの色は169 のグレーであることがわかります。
画像のピクセル数と同じ数だけ0が入った配列を用意し、二重forループで順次 色の数値を代入していきます。
最終的に、image_arrayは画像データの数値の配列になっていることがわかります。

二次元配列(リスト)についてよくわかってないって人は
【Python】二次元リストの取り扱いって記事を書いてるのでそちらを参考にしてください!

コードの解説

重要なのは 画像データを読み込むためのコードと、ピクセルの色を数値として取り出すコードの2つです。

img = Image.open('cat_gray.jpg')

Imageモジュールのopenメソッドを使用しています。
openメソッドで猫ちゃんの画像(cat_gray.jpg)をインプットし、その情報をimgに格納しています。

img.getpixel((99, 99))

imgに格納された情報に対して、getpixelメソッドを使用して”あるピクセル”の色を数値として取り出す処理をしています。
“あるピクセル”というのが、ここでは座標(99, 99)の位置にあるピクセルです。この画像のサイズが883×583なので、(0~883, 0~583)の範囲であれば任意の位置の色を取得できます。