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
ということですね。
band
はint
の整数です。RGB
のR
のバンドを取得したいならband
に0
, B
のバンドを取得したいならband
に2
を指定します。
band
を指定するとImage.getdata()
の返り値の構造が変化します。
band
を指定しない場合は返り値はタプルの列ですが、バンドを指定する場合はint
の列になります。
返り値
Image.getdata()
はシーケンシャルなオブジェクトを返します。
タイプは<class 'ImagingCore'>
です。
つまりImage.getdata()
の返り値はfor
文で回すことも出来るし、list()
に渡してリストに変換することも可能ということになります。
使用する画像
今回使用する画像は↓です。
この画像はモードがRGBA
, 横幅500px
高さ500px
のPNG
画像です。
画像のピクセル列を取得する
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引数として適当なものを答えよ
- int
- str
- dict
Q2: Image.getdata()
の返り値として適当なものを答えよ
- Image
- ImagingCore
- ImageFilter
Q3: RGB
の画像のImage.getdata()
に整数0
を渡した場合、どのバンドのピクセル列を取得できるか答えよ
- R
- G
- B
問題の正解はこちら↓。
Q1: 1
Q2: 2
Q3: 1