PythonのPillowのImage.getdata()の使い方: 2次元のピクセルデータ列を取得する

75, 2020-10-12

目次

PillowのImage.getdata()の使い方

Pythonには有名なサードパーティー製の画像処理ライブラリ「Pillow」があります。
Pillowには画像を管理するImageモジュールがありますが、このImageモジュールには画像のピクセルデータを2次元のデータ列で取得するImage.getdata()があります。

2次元のデータ列として取得できるので、全てのピクセルの加工を簡単に行うことが出来ます。

Image.getdata()の構造

Image.getdata()は↓のような構造になっています。
Image.getdata()は1つの引数を取り、1つの返り値を返します。

Image.getdata(band=None)

band(第1引数)

第1引数のbandにはImage.getdata()で取得するデータのバンドを指定します。
バンドと言うのは、たとえばRGB画像であれば、R, G, Bの各チャンネルのことです。
バンドR, バンドGということですね。
bandintの整数です。RGBRのバンドを取得したいならband0, Bのバンドを取得したいならband2を指定します。

bandを指定するとImage.getdata()の返り値の構造が変化します。
bandを指定しない場合は返り値はタプルの列ですが、バンドを指定する場合はintの列になります。

返り値

Image.getdata()はシーケンシャルなオブジェクトを返します。
タイプは<class 'ImagingCore'>です。
つまりImage.getdata()の返り値はfor文で回すことも出来るし、list()に渡してリストに変換することも可能ということになります。

使用する画像

今回使用する画像は↓です。
この画像はモードがRGBA, 横幅500px高さ500pxPNG画像です。

火星 500x500 RGBA

画像のピクセル列を取得する

Image.getdata()で画像のピクセル列を取得するには、まずImage.open()に画像のパスを指定して画像(Imageオブジェクト)を開きます。
そしてImage.getdata()を使い、画像のピクセル列を取得し、出力します。

from PIL import Image

im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata()  # ピクセル列を取得
print(type(data))  # 返り値の型を表示
print(list(data))  # ピクセル列を表示

出力結果。

$ python sample.py
<class 'ImagingCore'>
[(148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), (148, 55, 55, 255), ...

print(type(data))で大量のピクセルが出力されます。
画像のサイズは横幅500px高さ500pxなので250000ピクセルが出力されることになります。

画像のピクセル列を加工する

mapを使い、Image.getdata()で取得したピクセル列に一括した処理をかけます。
今回の例では画像の全てのピクセルのR成分とG成分を入れ替える処理をしています。

from PIL import Image

im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata()  # ピクセル列を取得

# 全てのピクセルのRとGを入れ替える
data = map(lambda px: (px[1], px[0], px[2]), data)

dst = Image.new('RGBA', im.size, 0)  # 保存用の画像を生成
dst.putdata(list(data))  # 加工したデータを保存用画像にセット
dst.save('dst/out1.png')  # 画像を保存

保存したdst/out1.pngを表示すると↓のようになります。

out1.png

画像のピクセルのRGBそれぞれを2値化する

画像の各ピクセルのR, G, Bの要素をそれぞれ個別に2値化します。

from PIL import Image


def tobin(n):
    """
    2値化する
    """
    if n >= 128:
        return 255
    return 0


def tobinpx(px):
    """
    ピクセルのRGB要素を2値化する
    """
    px = list(px)
    px[0] = tobin(px[0])
    px[1] = tobin(px[1])
    px[2] = tobin(px[2])
    return tuple(px)


im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata()  # ピクセル列を取得

# 全てのピクセルのRGBをそれぞれ2値化する
data = map(lambda px: tobinpx(px), data)

dst = Image.new('RGBA', im.size, 0)  # 保存用の画像を生成
dst.putdata(list(data))  # 加工したデータを保存用画像にセット
dst.save('dst/out2.png')  # 画像を保存

画像の保存結果。

out2.png

バンドRのピクセル列を取得する

Image.getdata()の第1引数に整数0を渡すとバンドRのピクセル列を取得できます。

from PIL import Image
import numpy as np

im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata(0)  # バンド0(R)のピクセル列を取得
print(list(data))  # ピクセル列を出力

出力結果↓。

[148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, ...

ピクセル列を見ると1次元のデータになっています。
なので保存するときはLモードの画像にデータをセットします。

from PIL import Image
import numpy as np

im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata(0)  # バンド0(R)のピクセル列を取得

dst = Image.new('L', im.size, 0)  # 保存用の画像(L = 8bit)を生成
dst.putdata(data)  # 保存用の画像に加工したデータをセットする
dst.save('dst/out4.png')  # 画像を保存する

保存したdst/out4.pngは↓のようになります。

out4.png

画像のピクセル列をnumpyの配列に変換する

Image.getdata()の返すシーケンシャルなオブジェクトはnumpyの配列に変換することが可能です。
numpyは配列の演算のための外部ライブラリで、Pythonによるプログラミングではデファクトスタンダードになっています。
numpyは↓のようにするとpipでインストール可能です。

$ pip install numpy

numpyのインポートは↓のようにnpというエイリアスを付けるのが慣例になっています。

import numpy as np

それではImage.getdata()で取得したピクセル列をnumpyの配列に変換してみます。

from PIL import Image
import numpy as np

im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata()  # ピクセル列を取得
arr = np.array(data)  # numpyの配列に変換
print(arr.shape)  # 形状を出力
print(arr)  # 配列を出力

出力結果↓。

(250000, 4)
[[148  55  55 255]
 [148  55  55 255]
 [148  55  55 255]
 ...
 [198 111  29 255]
 [191 102  25 255]
 [225 134  38 255]]
(^ _ ^)

簡単!

ただ、別にImage.getdata()でピクセル列を取得しなくても、Imageオブジェクトをそのままnumpyに渡せば配列にすることができます。

(^ _ ^)

ええ・・・

from PIL import Image
import numpy as np

im = Image.open('../../img/mars.png')  # 画像を開く
arr = np.array(im)  # numpyの配列に変換
print(arr.shape)  # 形状を出力
print(arr)  # 配列を出力

出力結果。

(500, 500, 4)
[[[148  55  55 255]
  [148  55  55 255]
  [148  55  55 255]
  ...
  [148  55  55 255]
  [148  55  55 255]
  [148  55  55 255]]
  ...

この場合、配列の形状は3次元になります。

画像のピクセル列をnumpyで加工する

Image.getdata()で取得したピクセル列をnumpyの配列に変換し、一括処理をして再び画像に変換します。

from PIL import Image
import numpy as np

im = Image.open('../../img/mars.png')  # 画像を開く
data = im.getdata()  # ピクセル列を取得
arr = np.array(data)  # numpyの配列に変換

arr[:, (0, 1)] = 0  # ピクセルのR, G成分を0にする
data = list(tuple(px) for px in arr)  # 配列をピクセル列に変換

dst = Image.new('RGBA', im.size, 0)  # 保存用の画像を生成
dst.putdata(data)  # 保存用の画像に加工したデータをセットする
dst.save('dst/out3.png')  # 画像を保存する

dst/out3.pngを見ると↓のように出力されました。

out3.png

Image.getdata()で取得した2次元のデータは、Image.fromarray()などで画像に変換することが出来ません。
そのためlist(tuple(px) for px in arr)とやって原始的にnumpyの配列を変換しています。

一見すると色々な変換処理が入っていますが、果たして速度的には遅いです。
numpyに変換するならImage.open()で開いたImageオブジェクトをそのままnumpyに渡し、3次元の配列として処理をしたほうが速いかもしれません。

問題

Q1: Image.getdata()の第1引数として適当なものを答えよ

  1. int

  2. str

  3. dict

Q2: Image.getdata()の返り値として適当なものを答えよ

  1. Image

  2. ImagingCore

  3. ImageFilter

Q3: RGBの画像のImage.getdata()に整数0を渡した場合、どのバンドのピクセル列を取得できるか答えよ

  1. R

  2. G

  3. B


問題の正解はこちら↓。

Q1: 1
Q2: 2
Q3: 1



この記事のアンケートを送信する