【Python/Pillow(PIL)】画像データフォーマット(mode)

画像データにはカラー画像の24bit,32bit、モノクロ画像の8bit,16bitなどがありますが、Pillowで対応している画像データフォーマット(mode)の一覧は以下の通りです。

mode 説明
1 1-bit pixels, black and white, stored with one pixel per byte
L 8-bit pixels, black and white
P 8-bit pixels, mapped to any other mode using a color palette
RGB 3×8-bit pixels, true color
RGBA 4×8-bit pixels, true color with transparency mask
CMYK 4×8-bit pixels, color separation
YCbCr 3×8-bit pixels, color video format
LAB 3×8-bit pixels, the L*a*b color space
HSV 3×8-bit pixels, Hue, Saturation, Value color space
I 32-bit signed integer pixels
F 32-bit floating point pixels

また、機能が限定的となりますが、以下のものも用意されています。

LA L with alpha
PA P with alpha
RGBX true color with padding
RGBa true color with premultiplied alpha
La L with premultiplied alpha
I;16 16-bit unsigned integer pixels
I;16L 16-bit little endian unsigned integer pixels
I;16B 16-bit big endian unsigned integer pixels
I;16N 16-bit native endian unsigned integer pixels
BGR;15 15-bit reversed true colour
BGR;16 16-bit reversed true colour
BGR;24 24-bit reversed true colour
BGR;32 32-bit reversed true colour

(参考)

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

一般的に用いられるのは、カラー24bitの RGB とモノクロ(グレースケール)8bitの L が多いと思います。

C言語やC#などでは、モノクロ画像の場合はカラーパレットを参照して表示するインデックスドカラーというのが標準的だったのですが、このインデックスドカラーに相当するのは P となります。

使用例

from PIL import Image

# PIL.Imageで画像を開く
img = Image.open("./Parrots.bmp")
print(img.mode)
img.show()  # 画像の表示

# カラー→モノクロ変換
gray = img.convert("L")
gray.show()  # 画像の表示

(実行結果)

【Python/Pillow(PIL)】画像のヒストグラム取得、表示

Pillowで画像のヒストグラムを取得し、取得した画像データをmatplotlibで表示するには、とても簡単で以下のようにします。

import matplotlib.pyplot as plt # ヒストグラム表示用
from PIL import Image

# PIL.Imageで画像を開く
img = Image.open("./Mandrill.bmp")

# 画像の表示
img.show()

# ヒストグラムの取得
hist = img.histogram()

# ヒストグラムをmatplotlibで表示
plt.plot(range(len(hist)), hist)
plt.show()

(実行結果)

ただし、カラー画像になると、こんな感じ↓になってしまいます。

これは、取得したヒストグラムデータがR,G,Bのデータがつながって768個の一次元のリストに格納されてしまっているためで、具体的にはリストのインデックスで

    0~255:Rのヒストグラム
  256~511:Gのヒストグラム
  512~767:Bのヒストグラム
が格納されています。

以上のことを考慮して、最初のプログラムを変更して、

import matplotlib.pyplot as plt # ヒストグラム表示用
from PIL import Image

# PIL.Imageで画像を開く
img = Image.open("./Mandrill.bmp")

# 画像の表示
img.show()

# ヒストグラムの取得
hist = img.histogram()

# 各色の名前を取得
# カラーのとき:('R', 'G', 'B')
# モノクロのとき:("L")
bands = img.getbands()

# チャンネル数
ch = len(bands)

# グラフの表示色
if (ch == 1):
    colors = ["black"]
else:
    colors = ["red", "green", "blue", "black"]

# ヒストグラムをmatplotlibで表示
x = range(256)
for col in range(ch):
    y = hist[256 * col : 256 * (col + 1)]
    plt.plot(x, y, color = colors[col], label = bands[col])

# 凡例の表示
plt.legend(loc=2)

plt.show()

のようにすると、モノクロ、カラーの両方に対応できます。

【Python/Pillow(PIL)】対応画像ファイルフォーマット

Pillowで画像ファイルを開くときはopen()関数、保存はsave()関数を使って

from PIL import Image

# PIL.Imageで画像を開く
img = Image.open("./Mandrill.bmp")

# OS標準の画像ビューアで表示
img.show()

# 画像のファイル保存
img.save("image.pdf")

と書くだけで、このよう↓に画像ファイルが開き、別のファイルフォーマットで画像を保存できます。

このopen()関数、save()関数で扱う事のできるファイルフォーマットは以下の通りです。

フォーマット open save
BMP
DIB
EPS
GIF
ICNS
ICO
IM
JPEG
JPEG2000
MSP
PCX
PNG
PPM
SGI
SPIDER
TGA
TIFF
WebP
XMB
BLP
CUR
DCX
FLI,FLC
FPX
FREX
GBR
GD
IMT
IPTC/NAA
MCIDAS
MIC
MPO
PCD
PIXAR
PSD
WAL
WMF
WMF
XPM
PALM
PDF
XV Thumbnails

Identify-only formats (認識のみ??)

  BUFR, FITS, GRIB, HDF5, MPEG

(参考)

https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html

 

個人的に使うのは、BMP, PNG, TIFF, GIFぐらいですね。
また、用途は少ないですが、画像をPDFファイルに出力できるのは、ちょっと面白い。

【Python/Pillow(PIL)】transformメソッドで射影変換(ホモグラフィ変換)

transform()メソッドでアフィン変換を行う方法はここ↓で紹介しました。

【Python/Pillow(PIL)】transformメソッドでアフィン変換

このページでも説明しているように method に PIL.Image.PERSPECTIVE を指定し、data の部分にホモグラフィ変換行列を指定すれば、射影変換(ホモグラフィ変換)を行う事ができるのですが、ホモグラフィ変換行列を求めるのが少々難しいので、射影変換後の形状が長方形でいいのであれば、method に PIL.Image.QUAD を指定することで、簡単に射影変換を行う事が可能になります。

 

(サンプルプログラム)

from PIL import Image  # 画像データ用

# PIL.Imageで画像を開く
src = Image.open("./buisiness_card.jpg")

quad_data = (
    57, 174,   # 左上
    41, 418,   # 左下
    580, 252,  # 右下
    488, 13    # 右上
    )

# 4点からなる四角形を幅、高さからなる長方形へ変換
dst = src.transform(
            (320, 240),     # 出力サイズ(幅, 高さ)
            Image.QUAD,     # 変換方法
            quad_data,      # 四角を構成する4点の座標
            Image.BICUBIC   # 補間方法
            )

# OS標準の画像ビューアで表示
src.show() # 元画像
dst.show() # 変換画像

(実行結果)

PIL.Image.QUAD では4点の座標からなる四角形を (幅, 高さ)で指定した長方形へ変換します。

4点の座標は下図のように、四角形の角の点を 左上、左下、右下、右上 の順で指定します。

射影変換では、変換後の形状を長方形にする場合が多いかと思いますので、長方形でいいのなら、とても簡単!!

参考記事

【Python/Pillow(PIL)】transformメソッドでアフィン変換

【Python/NumPy】座標からホモグラフィ変換行列を求める方法

【Python/Pillow(PIL)】transformメソッドでアフィン変換

「Python アフィン変換」と検索すると、OpenCVを使った説明が多いような気がしますが、画像を表示するだけなら、Pillowにtransform()メソッドというのがあり、これもなかなか高機能な処理が可能になります。

Pillowならtkinterとの相性もいいので、GUIで画像をウィンドウに表示したいのなら、おススメです。

最終的には、以下のようにウィンドウに画像を表示するのが目標です。

transformメソッドの構文

Image.transform(size, method, data=None, resample=0, fill=1, fillcolor=None)
パラメータ 説明
size (幅, 高さ)のタプル
method 変換方法
  • PIL.Image.EXTENT:元画像の矩形領域を指定して出力先に合わせて変換
  • PIL.Image.AFFINE:アフィン変換
  • PIL.Image.PERSPECTIVE:射影変換(ホモグラフィ変換)
  • PIL.Image.QUAD:元画像の4点で示した領域を出力先に合わせて変換(ホモグラフィ変換の簡易版)
  • PIL.Image.MESH:任意グリッドの変換??
data methodにより内容が異なります。
  • PIL.Image.EXTENT
  • PIL.Image.AFFINE
    アフィン変換行列の2行3列の要素をタプルで指定します。
  • PIL.Image.PERSPECTIVE
    ホモグラフィ変換行列の3行3列の要素をタプルで指定します。
  • PIL.Image.QUAD
  • PIL.Image.MESH
resample 画素補間方法
  • PIL.Image.NEAREST:ニアレストネイバー
  • PIL.Image.BILINEAR:バイリニア
  • PIL.Image.BICUBIC:バイキュービック

(参考)https://imagingsolution.net/imaging/interpolation/

fill ???
fillcolor 出力先の画像の外側の背景色

(参考)
https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.transform

 

アフィン変換のサンプルプログラム

画像ファイルを読み込み、30°回転 → 横2倍、縦0.5倍 → 横+50、縦+70の平行移動 を行うサンプルを示します。

import numpy as np              # アフィン変換行列演算用
import tkinter as tk

from PIL import Image, ImageTk  # 画像データ用

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)
        self.pack()

        self.master.title("画像の表示")       # ウィンドウタイトル
        self.master.geometry("400x300")     # ウィンドウサイズ(幅x高さ)
        
        # Canvasの作成
        back_color = "#008B8B" # 背景色
        canvas = tk.Canvas(self.master, bg = back_color)
        # Canvasを配置
        canvas.pack(expand = True, fill = tk.BOTH)

        # アフィン変換行列(numpy ndarray)
        # 30°回転→横2倍、縦0.5倍→横+50、縦+70の平行移動の例
        # 回転行列(30°回転)
        rad = np.radians(30) # 30°をラジアン単位へ変換
        r = np.array([
            [np.cos(rad), -np.sin(rad), 0],
            [np.sin(rad),  np.cos(rad), 0],
            [0,            0,           1]
            ])
        # 拡大縮小(横2倍、縦0.5倍)
        s = np.array([
            [2,   0, 0],
            [0,   0.5, 0],
            [0,   0, 1]
            ])
        # 平行移動(横+50、縦+70)
        t = np.array([
            [1, 0,  50],
            [0, 1,  70],
            [0, 0,   1]
            ])
        # アフィン変換行列の計算(回転→拡大縮小→平行移動の順)
        affine = np.matmul(t, np.matmul(s, r))
        # 等倍のとき、単位行列
        #affine = np.identity(3)

        # アフィン変換を使った画像の表示
        self.disp_image(canvas, "./Mandrill.bmp", affine, fillcolor = back_color)

    def disp_image(self, canvas, filename, affine, method = Image.AFFINE, resample = Image.NEAREST, fillcolor = None):
        '''画像をCanvasに表示する
        canvas      :表示先のCanvasオブジェクト
        filename    :表示する画像ファイルパス
        affine      :画像データ→表示先へのアフィン変換行列
        method      :アフィン変換(Image.AFFINE)、射影変換(Image.PERSPECTIVE)
        resample    :補間方法:ニアレストネイバー(Image.NEAREST)、バイリニア(Image.BILINEAR)、バイキュービック(Image.BICUBIC)
        fillcolor   :画像の外側の色
        '''

        # キャンバスのサイズを取得(Canvasのサイズに合わせています)
        self.master.update()
        size = (canvas.winfo_width(), canvas.winfo_height())

        # PIL.Imageで開く
        pil_image = Image.open(filename)

        # アフィン変換行列の逆行列の計算
        affine_inv = np.linalg.inv(affine)
        # アフィン変換行列を一次元のタプルへ変換
        affine_tuple = tuple(affine_inv.flatten())

        # アフィン変換で画像を変換
        pil_image = pil_image.transform(
                    size,           # 出力サイズ(幅, 高さ)
                    method,         # 変換方法
                    affine_tuple,   # アフィン変換行列(出力→入力への変換行列)
                    resample,       # 補間方法
                    1,
                    fillcolor       # 画像の外側の背景色
                    )

        #PIL.ImageからPhotoImageへ変換する
        self.photo_image = ImageTk.PhotoImage(image=pil_image)

        # 画像の描画
        canvas.create_image(
                0, 0,                   # 画像表示位置
                anchor = tk.NW,         # 画像表示位置の基準(左上)
                image=self.photo_image  # 表示画像データ
                )

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(実行結果)

アフィン変換(method = PIL.Image.AFFINE)で使う時のポイント

transform()メソッドの第3引数(data)に渡す引数にアフィン変換行列のデータを渡すのですが、transform()メソッドのアフィン変換行列は出力先から元画像へ変換するアフィン変換行列となります。

 

ただし、一般的には元画像から出力先へのアフィン変換で考える場合が多いので、元画像から出力先へのアフィン変換行列(affine)を計算して、最後にアフィン変換行列の逆行列(affine-1)をtransform()メソッドの data へ渡します。

つまり、変換前の座標を(x, y)、変換後の座標を(x’, y’)とすると、アフィン変換の行列は

$$\left(\begin{array}{c}x^{‘}\\ y^{‘}\\ 1\end{array}\right)=
\left(\begin{array}{c}a & b & c\\ d & e & f \\ 0 & 0 & 1\end{array}\right)
\left(\begin{array}{c}x\\ y \\ 1\end{array}\right)$$

となりますが、この逆の変換を計算し、

$$\left(\begin{array}{c}x\\ y\\ 1\end{array}\right)=
\left(\begin{array}{c}a & b & c\\ d & e & f \\ 0 & 0 & 1\end{array}\right)^{-1}
\left(\begin{array}{c}x^{‘}\\ y^{‘} \\ 1\end{array}\right)$$

から、逆行列の部分を

$$\left(\begin{array}{c}a^{‘} & b^{‘} & c^{‘}\\ d^{‘} & e^{‘} & f^{‘} \\ 0 & 0 & 1\end{array}\right)=
\left(\begin{array}{c}a & b & c\\ d & e & f \\ 0 & 0 & 1\end{array}\right)^{-1}$$

と置くと、transform()メソッドの第3引数(data)へは

$$data = (a^{‘}, b^{‘}, c^{‘}, d^{‘}, e^{‘}, f^{‘})$$

となるタプルを渡せばOKです。(サンプルプログラムではタプルの要素数を9個指定してますが、使わない部分を渡しても大丈夫なようです。)

アフィン変換の行列の計算部分については、行列の積、逆行列が計算できるnumpyを使うと便利だと思います。

(参考)
https://imagingsolution.net/program/python/numpy/matrix_operation/

また、アフィン変換の座標系ですが、画像の左上原点(左上の画素のさらに左上が原点)で、右方向が+X方向、下方向が+Y方向、時計回りが+θ方向となります。

ちなみに、C#では左上の画素の中心が原点(0, 0)でした。

(参考)
https://imagingsolution.net/program/csharp/image_coordinate/

参考ページ