【Python/Pillow】画像のデータ構成とImageクラス

このページにはプロモーションが含まれています

このページでは、まず画像のデータ構成について説明し、その後 Pillow (PIL) の Image クラスと画像のデータ構成の関係性や、画像の情報を取得する方法について説明していきます。

説明する内容は超基本的なものになりますが、このページで説明する内容は Pillow を利用する上で知っておいた方が良いですし、Pillow だけでなく画像を扱うプログラム・ソフトウェアを開発する上で必須の知識となりますので、是非覚えておいていただければと思います!

画像のデータ構成

では、まずは画像のデータ構成について説明していきます。

今や我々の生活と画像は切っても切れない存在です。この「画像」は “データとして見た時” にどのようなものになっているのでしょうか?この点について説明していきます。

画像は「ピクセルの集合」である

画像とはピクセルの集合になります。

ピクセルとは、画像を構成する点のことを言います。別名「画素」とも呼ばれます。

画像は遠目だと単なる一枚の絵に見えますが、拡大してみると多数の小さな点の集まりであることが確認できます。この1つ1つの点がピクセルです。

ピクセルの説明図

MEMO

このページに掲載している猫の画像は写真ACで公開されているリズム727さんの写真を使用させていただいています

スポンサーリンク

ピクセル値によって画素の見た目が決まる

そして、このピクセルには色がついています。この色がついたピクセルが2次元的に配置され、それを遠目に見ることで画像は1枚の絵として人間に認識されます。

画像がピクセルから構成されることを示す図

より具体的にいえば、各ピクセルの色は基本的に RGB と呼ばれる指標により表現されます。R は赤色の強さ・G は緑色の強さ、B は青色の強さをそれぞれ表す指標であり、これら3つの値から各ピクセルの色が決まることが多いです。RGB はそれぞれ 256 段階の数値、すなわち 0255 の数値で強さが表され、数値が高いほど色の強さが大きいことを表します。

ピクセル値のデータ構成は画像によって異なる

上記で、わざわざ “基本的に” と言っているように、ピクセルの色は別の指標で表現されることもあります。

赤・緑・青ではなくシアン・マゼンタ・イエロー・ブラックの4つの色の強さにより表現されることもありますし、グレースケール画像の場合は1つの色の強さでピクセルの色が表現されることもあります。

ピクセル値の例を示す図

また、ここまで “色” と限定して説明してきましたが、実際にはピクセルには透明度が設定されることもあり、これによっても各ピクセルの見た目が変化することになります。この場合、RGB ではなく RGBα の4つの指標によりピクセルの見た目が表現されることになります。α はアルファチャンネルと呼ばれ、これにより透明度が設定されます。

このような、各ピクセルの色や見た目を決定する指標は「ピクセル値」と呼ばれます。上記の通り、ピクセル値はピクセルの見た目の表現の仕方によって構成が異なります。

例えば、各ピクセルの色が RGB で表現される画像の場合、ピクセル値は RGB の3つの数値によって表現されます。具体的には、(255, 0, 128) のような 3 次元のデータで表示されます。

RGB画像のピクセル値の説明図

それに対し、各ピクセルの見た目が RGBα で表現される画像の場合、ピクセル値は RGBα の4つの数値によって表現されます。具体的には、(255, 0, 128, 128) のような 4 つの数値で表示されます。この場合、各ピクセルでは RGB にプラスして「透明度」が設定され、透明に設定されたピクセルは背景が透けて見えるようになります(この節での説明図では、各画像の後ろ側に黄色背景が存在する例を示しています)。

RGBα画像のピクセル値の説明図

また、複数の指標では無く1つの指標で表される画像もあります。具体的には、グレースケール画像であったり白黒画像等がそれにあたります。

例えばグレースケール画像であれば、ピクセル値は1つの数値によって表現されます。そして、その値は 0255256 段階の値で表現されることが多いです。

グレースケール画像のピクセル値の説明図

また、白黒画像の場合、グレースケール画像同様にピクセル値は1つの数値によって表現されるのですが、その値は 0 or 12 段階の値で表現されることが多いです。

2値画像のピクセル値の説明図

このように、ピクセル値とは画像を構成する点(ピクセル)の色や見た目を表す値となりますが、そのピクセル値の構成は画像によって異なります。

ピクセルは2次元的に配置されて表示される

前述の通り、画像はピクセルの集まりであり、そのピクセルを2次元的に配置することで画像として構成されることになります。

画像における横方向のピクセルの数は “幅”、画像における縦方向のピクセルの数は “高さ” と呼ばれることが多いです。また、このピクセル数は px という単位で表されます。

画像の幅と高さの説明図

そして、この幅や高さも画像によって異なります。

また、画像の全ピクセルの数は “画素数” と呼ばれます。基本的に画像は四角形なので、この画素数は単純に 幅 x 高さ で計算することができます。したがって、画像とは 幅 x 高さ 個分のピクセルの集まりであると言えます。

画素数の説明図

もしかしたら、将来四角形以外の画像が現れるかもしれな無いですね。その場合は計算式が変わることになります。とりあえず、2024 年 1 月時点では画像の画素数は 幅 x 高さ で計算してやれば良いです。

スポンサーリンク

ピクセルの位置は座標で表現される

前述の通り、画像はピクセルの集まりになります。そして、画像内の各ピクセルの位置は “座標” で管理されるようになっています。座標と聞くと、数学等でよく使用した下の図のような座標を思い浮かべる方も多いかと思います。この座標の場合、(0, 0) 座標が中心に存在し、横軸の正方向は右、縦軸の正方向は上となります。

数学でよく扱う直交座標を示す図

ですが、画像の座標に関しては (0, 0) 座標が画像の左上に存在し、さらに縦軸の正方向は下となります(横軸の正方向は右)。

画像の座標を表す図

また、幅が横方向のピクセル数、高さが縦方向のピクセル数を表すため、画像の一番右下の座標は (幅 - 1, 高さ - 1) となります。つまり、画像内の有効な座標は (0, 0) 〜 (幅 - 1, 高さ - 1) であり、それ以外の座標は画像外の無効な座標となります。

ここで重要な点は、画像内の有効な座標であるかどうかを判断するためには画像の幅や高さが必要になるという点になります。

Image クラス

さて、ここまで画像データの構成について解説してきました。ここからは、ここまで解説してきた画像データの構成を表す各指標を取得するための方法について説明していきます。これらは Pillow の Image クラスのデータ属性 or メソッドにより取得可能です。

まず、画像をプログラムで扱う上で重要になるのが画像の幅と高さです。これらは Pillow の Image クラスの widthheight データ属性によって取得できます。

widthデータ属性で画像の幅、heightデータ属性で画像の高さが取得可能であることを示す図

そして画像では、この幅と高さを持つ四角形の中にピクセルが2次元的に配置されています。各ピクセルはピクセル値によって見た目が異なります。さらに、このピクセル値のデータ構成は画像によって異なります。このピクセル値のデータ構成の情報に関しては、PIillow の Image クラスの mode データ属性から取得可能です。

modeデータ属性でピクセル値のデータ構成の情報を取得することができることを示す図

また、各ピクセル値は PIL の Image クラスの getpixel メソッドによって取得可能です。ピクセル値を取得する際には、ピクセル値を取得したい座標を指定する必要があり、この座標は画像の幅や高さを考慮して指定する必要があります。

getpixelメソッドでピクセル値を取得することができることを示す図

次は、実際に画像ファイルから Image クラスのオブジェクトを生成し、これらの情報の取得を行う例を示していきたいと思います。

ファイル読み込みによる Image オブジェクトの生成

まず、前提知識として Image クラスのオブジェクトの生成について説明しておきます。

Pillow においては、Image クラスのオブジェクトは画像ファイルの読み込みによって生成することが可能です。この画像ファイルの読み込みによるオブジェクトの生成は、Image クラスの open 関数を利用して実現できます。open 関数の第1引数には読み込みたい画像ファイルのパスを指定します。

Imageオブジェクトの生成
from PIL import Image

image = Image.open(ファイルのパス)

この open 関数の実行によって返却値として Image クラスのオブジェクトへの参照が得られます。例えば上記を実行すれば、image 変数が Image クラスのオブジェクトを参照することになります。また、open 関数を利用した場合、Image クラスのオブジェクトは読み込んだ画像、すなわち、open 関数に指定した ファイルのパス に基づいて生成されます。より具体的には、読み込んだ画像の幅・高さ・ピクセル値を持つオブジェクトが生成されます。

そして、前述の通り、このオブジェクトの各種データ属性やメソッドから、この読み込んだ画像の各種情報を取得することができます。。

また、今回は下記の2つの画像から実際に Image クラスのオブジェクトを生成し、そのオブジェクトから画像の情報を取得する例を示していきたいと思います。このため、事前に2つの画像を同じフォルダ内にダウンロードし、左の画像から順に rgb.pnggray.png と名前を変更しておいてください。

さらに、同じフォルダに Python スクリプトファイル image_test.py を作成し、中身を下記のように変更してください。

2つのImageオブジェクトの生成
from PIL import Image

image1 = Image.open('rgb.png')
image2 = Image.open('gray.png')

このスクリプトを下記のコマンドで実行すれば、同じフォルダ内の rgb.pnggray.png がそれぞれ読み込まれ、これらのファイルに基づいた Image クラスのオブジェクトが生成されることになります。

% python image_test.py

以降もスクリプトの例も示していきますが、スクリプトの実行は全て上記コマンドにより行うことが可能です。

ここからは、これらのオブジェクトから画像の幅や高さ、ピクセルのデータ構成、ピクセル値を取得する例を示していきたいと思います。

スポンサーリンク

width データ属性と height データ属性 (size データ属性)

まずは、各画像の幅と高さを表示してみましょう!

前述の通り、Image クラスのオブジェクトは width データ属性と height データ属性を持っています。そして、これらの widthheight より、オブジェクトの画像の幅や高さを取得することができます。

したがって、先ほど作成した image_test.py を下記のように変更すれば、各画像の幅と高さが標準出力に出力されることになります。

幅と高さの表示
from PIL import Image

image1 = Image.open('rgb.png')
image2 = Image.open('gray.png')

print(image1.width, image1.height)
print(image2.width, image2.height)

先ほどと同様のコマンドで image_test.py を実行すれば下記のような出力結果が得られると思います。これらは上から順に rgb.pnggray.png の画像の幅と高さとなります。

100 135
185 250

したがって、rgb.png の画像内の有効な座標は (0, 0) 〜 (99, 134) であり、gray.png の画像内の有効な座標は (0, 0) 〜 (184, 249) ということになります。

また、これらの幅と高さは size データ属性から一度に取得することも可能です。size データ属性はタプルであり、第 0 要素が幅で第 1 要素が高さとなります。

mode データ属性

続いて mode データ属性を出力して各画像のピクセル値のデータ構成について確認してきましょう!Image クラスのオブジェクトは mode データ属性を持っており、この mode より、そのオブジェクトのピクセル値のデータ構成の情報を取得することができます。

ということで、先程の image_test.py を今度は下記のように変更したいと思います。

モードの表示
from PIL import Image

image1 = Image.open('rgb.png')
image2 = Image.open('gray.png')

print(image1.mode)
print(image2.mode)

先ほどと同様のコマンドで image_test.py を実行すれば下記のような出力結果が得られると思います。これらは上から順に rgb.pnggray.png のピクセル値のデータ構成を示す文字列となります。

RGB
L

RGB は、その画像が RGB 画像であることを示します。この場合、ピクセル値は R (赤)・G (緑)・B (青) の3種類の値から構成されることになります。したがって、rgb.png のピクセル値を取得すると (r, g, b) のような3つの要素のタプルが取得できることになります。

RGB画像のピクセル値が3つの要素から構成されることを示す図

それに対し L は、その画像がグレースケール画像であることを示します。この場合、ピクセル値は1種類の値から構成されることになります。したがって、gray.png のピクセル値を取得するとタプルではなく単なる数値が取得できることになります。

グレースケール画像のピクセル値が単なる値であることを示す図

getpixel メソッド

そして、このピクセル値を取得するためのメソッドが getpixel となります。getpixel は実行した画像オブジェクトのピクセル値を取得するメソッドです。ピクセル値は画像の中に 幅 x 高さ 個存在しますので、その中からどのピクセル値を取得したいのかを座標で指定する必要があります。

例えば rgb.pnggray.png それぞれの中央の座標のピクセル値は下記のようにして取得・出力することが可能です。

ピクセル値の出力
from PIL import Image

image1 = Image.open('rgb.png')
image2 = Image.open('gray.png')

center1 = (image1.width // 2, image1.height // 2)
center2 = (image2.width // 2, image2.height // 2)

print(image1.getpixel(center1))
print(image2.getpixel(center2))

出力結果は下記のようなものになると思います。前述の通り、得られるピクセル値のデータ構成は画像によって異なるところがポイントとなります。

(181, 0, 56)
73

これらはそれぞれ下図のような色となります。

取得されたピクセル値を色で表現した結果を示す図

こんな感じで、ピクセル値のデータは getpixel メソッドにより取得可能で、その際には画像の座標を指定する必要があります。前述の通り、画像内の有効な座標は (0, 0) 〜 (幅 - 1, 高さ - 1) となります。画像内の全ピクセル値を表示したいのであれば、これは下記のようなループによって実現することができます。

全ピクセル値の出力
from PIL import Image

image1 = Image.open('rgb.png')
image2 = Image.open('gray.png')

for y in range(image1.height):
    for x in range(image1.width):
        print(image1.getpixel((x, y)))

for y in range(image2.height):
    for x in range(image2.width):
        print(image2.getpixel((x, y)))

スポンサーリンク

putpixel メソッド

ついでにピクセルのピクセル値を変更するメソッドについても紹介しておきます。

getpixel がピクセル値を取得するメソッドであるのに対し、putpixel はピクセル値を変更するメソッドとなります。例えば下記のような処理を実行すれば、各ピクセル値が反転することになり、その結果が inv_rgb.pnginv_gray.png として保存されることになります。

色の反転
from PIL import Image

image1 = Image.open('rgb.png')
image2 = Image.open('gray.png')

for y in range(image1.height):
    for x in range(image1.width):
        pixel = image1.getpixel((x, y))
        r = 255 - pixel[0]
        g = 255 - pixel[1]
        b = 255 - pixel[2]
        image1.putpixel((x, y), (r, g, b))

for y in range(image2.height):
    for x in range(image2.width):
        pixel = image2.getpixel((x, y))
        v = 255 - pixel
        image2.putpixel((x, y), v)

image1.save('inv_rgb.png')
image2.save('inv_gray.png')

ポイントになるのが putpixel 実行時に第2引数に指定するピクセル値の型が image1image2 とで異なる点になります。これらが異なるのは image1image2 とで mode が異なるため、すなわち image1image2 とで画像を構成するピクセル値のデータの構成が異なるためになります。

つまり、putpixel の第2引数には、実行するオブジェクトの画像の mode に応じたデータを指定する必要があります。ここが適切に指定されないと例外が発生したりするので注意が必要で、適切に指定するためには mode を意識してプログラムを作る必要があります。

また、上記スクリプトを実行することで保存される inv_rgb.pnginv_gray.png はそれぞれ下の図のような画像となります。このように、getpixel で取得したピクセル値を反転し、それを画像のピクセル値に putpixel で置き換えてやれば、簡単にネガポジ反転画像が作成できることになります。

実は、こういったループを組んでピクセル単位で処理を行わなくても、Pillow ではメソッドを1つ実行するだけで全ピクセルに対して同様の処理を実施することが可能です。そして、その方が処理時間が圧倒的に早いです。なので、実現したい画像処理に適したメソッドが存在するのであれば、特に速度が求められる場合はピクセル単位で処理を行うのではなくメソッドを利用する方が良いです。

ただ、ピクセルに慣れることはプログラムで画像を扱う上で重要ですし、ピクセルの知識はどんな画像処理ライブラリやプログラミング言語を扱う上でもほぼ共通になります。汎用性も高く、簡単な幾何学・数学の知識に基づき putpixel メソッドを利用すれば、画像内に様々な図形を描画するようなことも可能です。数学と聞くだけで鳥肌が立つような方もおられるかもしれませんが、こういった画像と一緒に数学を学び直すと苦手だったものも楽しく学ぶことができます!

new 関数による Image オブジェクトの生成

さて、ここまで Image オブジェクトの生成は全て open 関数を利用して行ってきました。この open 関数を利用すれば、Pillow では画像ファイルを読み込んで、その画像ファイルに基づいたオブジェクトをを自動的に生成することができます。

ただし、Pillow では open 関数ではなく new 関数を利用して Image オブジェクトを生成することもできます。new 関数は Pillow の Image クラスに定義されたクラス関数となります。そして、new 関数を利用する場合、画像ファイルを読み込んでオブジェクトを生成するわけではないため、プログラマーが自身で生成するオブジェクトの各種パラメーターを引数を指定して行う必要があります。そして、この引数で指定する必要のあるパラメーターは、ここまでも何度も紹介してきた下記の3つとなります。

    • mode : ピクセル値の構成の情報
    • size : 生成する画像の幅と高さのタプル
    • color : ピクセル値

引数 mode に関しては、データ属性の mode から取得できるものと同様に文字列の形式で指定する必要があります。引数 mode に指定可能な値の代表例は下記の通りです。

  • '1' : 白黒画像(ピクセル値は 0 or 1 の値)
  • 'L' : グレースケール画像(ピクセル値は 0255 の値)
  • 'RGB' : RGB 画像(ピクセル値は 0255 の値を 3 つ持つタプル)
  • 'RGBA' : RGBα 画像(ピクセル値は 0255 の値を 4 つ持つタプル)
    • 4 つ目の値が透明度を表す(0 : 透明・255 : 不透明)

mode に指定可能な値の全リストは下記ページでまとめられていますので、指定可能な値の全てを知りたい方は下記を参照してください。

https://pillow.readthedocs.io/en/stable/handbook/concepts.html

また、引数 size に関しては生成する画像の幅と高さを要素とするタプルを指定します

さらに、引数 color に関してはピクセル値を指定します。このピクセル値を指定する際には mode への指定値を考慮して指定するピクセル値を設定する必要がある点になります。ここまでの解説の中でも説明してきたように mode、すなわちピクセル値の構成によって指定可能なピクセル値が異なります。

例えば mode に 'RGB' を指定した場合は RGB の各色の強さを示す3つの値を要素に持つタプルをピクセル値として color に指定する必要があります。それに対し mode'L' を指定した場合は1つの色の強さを示す値のみを color に指定する必要があります。このように mode に指定する値に応じて color に指定可能なデータの形式が異なることになるので注意してください。

さて、上記のように引数を指定して new 関数を実行した場合は size で指定した幅と高さの画像のオブジェクトが生成されることになります。そして、その画像の全ピクセルのピクセル値は color で指定したものとなります。

例えば下記のように new 関数を実行した場合は幅が 200 px、高さが 100 px の全面黄色の画像のオブジェクトが生成されることになり、そのオブジェクトから save メソッドを実行させているため、このオブジェクトが画像として new_image.png に保存されることになります。

new関数の実行例
from PIL import Image

image = Image.new('RGB', (200, 100), color=(255, 255, 0))
image.save('new_image.png')

実行後の new_image.png は下図のようなものになります。

new関数で生成した画像

こんな感じで、出来上がる画像が一色なので open 関数に比べて実用性低いかもしれませんが new 関数によって画像のオブジェクトが生成可能であることは覚えておくと良いと思います。また、new 関数に指定する引数は画像のデータ構成を決定づける重要なパラメーターばかりで、画像のデータ構成がどのようなものであるかを理解しておけば new 関数に指定が必要な引数は自然と導けるようになると思います。new 関数だけでなく、画像を扱う際にはこのページで説明した画像のデータ構成の知識が必ず役に立ちますので、是非このページで解説した内容は理解しておきましょう!

まとめ

このページでは画像のデータ構成と、それらに関わる Pillow (PIL) の Image クラスのデータ属性、メソッドについて説明しました!

画像とは、幅 x さ 個分のピクセルの集まりになります。そして、各ピクセルはピクセル値によって色(見た目)が設定されています。このピクセル値の構成は画像によって異なる点に注意してください。例えば RGB 画像であればピクセル値は R・G・B それぞれの色の強さを表す3つの値から構成されます。それに対してグレースケール画像の場合は1つの値からピクセル値が構成されます。

そして、Pillow を利用した場合、画像の幅や高さ、ピクセル値の構成の情報やピクセル値そのものは Image クラスのデータ属性やメソッドから取得することが可能です。具体的には、画像ファイルから image クラスのオブジェクトを生成した場合、そのオブジェクトの widthheight データ属性から画像の幅と高さを取得することが可能です。さらに、ピクセル値の構成は mode データ属性から取得可能で、ピクセル値そのものは getpixel から取得することができます。

これらは画像を扱うプログラムやソフトウェアも開発する上で必須級の知識となりますので、是非これらについては覚えておきましょう!

同じカテゴリのページ一覧を表示