ユーニックス総合研究所

  • home
  • archives
  • python-pillow-image-getdata

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

  • 作成日: 2020-10-12
  • 更新日: 2023-12-24
  • カテゴリ: Python

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

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

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を表示すると↓のようになります。

画像のピクセルの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')  # 画像を保存  

画像の保存結果。

バンド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は↓のようになります。

画像のピクセル列を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を見ると↓のように出力されました。

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