【Python】画像ビューア(ズーム(拡大/縮小)、移動表示)

Canvasに画像を表示する のページではtkinterでGUIを作り画像ビューアを作りましたが、これに アフィン変換 を追加し、画像の拡大/縮小、移動の出来る画像ビューアを作成しました。

機能は、Fileメニューから画像ファイルを開き、マウスホイールの上下で画像の拡大/縮小を行い、マウスの左ボタンのドラッグで画像を移動します。
左ボタンのダブルクリックで画像全体を表示します。
また、ウィンドウ下にはCanvas上のマウスポインタの座標と、マウスポインタ位置の画像の座標および、その輝度値を表示します。
ウィンドウの右下には画像ファイルの種類、画像サイズ、画像の種類を表示します。

全ソースコード

参考

【Python/tkinter】Canvasに画像を表示する

アフィン変換(平行移動、拡大縮小、回転、スキュー行列)

【Python/NumPy】行列の演算(積、逆行列、転置行列、擬似逆行列など)

【Python/Pillow(PIL)】カラーパレットの設定(インデックスカラー)

PythonのPillowでモノクロ画像ファイルを開くと、Imageクラスの mode は “L” となりますが、これはカラーパレットを持たない画像データとなります。

C言語やC#ではモノクロ画像データを表示するときは、カラーパレットを参照して表示してモニタ上に画像を表示するインデックスカラーという仕組みがありました。

このインデックスカラーですが、モノクロ画像データの場合は 0~255 の輝度値を持ちますが、実際にモニタ上に表示する場合は、 0~255 のインデックスを持つカラーパレットのR,G,Bの値を参照して、モニタに表示されています。

index R G B
0 0 0 0
1 1 1 1
2 2 2 2
3 3 3 3
: : : :
254 254 254 254
255 255 255 255

モノクロ画像の場合、通常、indexの値とR,G,Bの値を同じ値にして表示するのですが、このR,G,Bの値を変更する事で、モノクロ画像に擬似的に色を付けて表示する事が可能になります。

Pillowでモノクロ画像にカラーパレットを使うには putpalette関数を用います。

構文

Image.putpalette(data, rawmode='RGB')

(参考)

https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.putpalette

dataの部分にカラーパレットを指定しますが、これはリストで、

[R0, G0, B0, R1, G1, B1, R2, G2, B2, R3, G3, B3, … R255, G255, B255]

の順で、256×3個(768個)の一次元の配列で指定します。

サンプルプログラム

from PIL import Image

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

palette = []

for i in range(0, 64):
    palette.append(i) # R
    palette.append(i) # G
    palette.append(i) # B

for i in range(64, 128):
    palette.append(0) # R
    palette.append(0) # G
    palette.append(255) # B

for i in range(128, 192):
    palette.append(0) # R
    palette.append(255) # G
    palette.append(0) # B

for i in range(192, 256):
    palette.append(255) # R
    palette.append(0) # G
    palette.append(0) # B

img.putpalette(palette)

# 画像の表示
img.show()

(実行結果)

実際にはカラーパレットは、モノクロカメラに擬似的に色を付けて輝度分布を見やすくする疑似カラー表示を行う場合だったり、二値化を行う際のプレビュー用として私は使っています。

二値化のプレビュー用のプログラムを以下に示します。

from PIL import Image, ImageTk, ImageOps
import tkinter as tk

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

        self.master.title("カラーパレット")     # ウィンドウタイトル

        self.src_img = Image.open("./Mandrill.bmp")

        #---------------------------------------------------------------
        # Canvasの作成
        self.canvas = tk.Canvas(self.master, bg = "#008B8B")
        # Canvasを配置
        self.canvas.pack(expand = True, fill = tk.BOTH)   
        
        #---------------------------------------------------------------
        # Scaleの作成
        self.scale_var = tk.IntVar()
        scaleH = tk.Scale( self.master, 
                    variable = self.scale_var, 
                    command = self.slider_scroll,
                    orient=tk.HORIZONTAL,   # 配置の向き、水平(HORIZONTAL)、垂直(VERTICAL)
                    from_ = 0,            # 最小値(開始の値)
                    to = 256,               # 最大値(終了の値)
                    tickinterval=64         # 目盛りの分解能(初期値0で表示なし)
                    )
        scaleH.pack(fill = tk.X)
        #---------------------------------------------------------------

    def slider_scroll(self, event=None):
        '''スライダーを移動したとき'''
        self.disp_image(self.src_img, self.scale_var.get())

    def disp_image(self, image, threshold):
        '''画像をCanvasに表示する'''

        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        #---------------------------------------------------------------
        # カラーパレットの設定
        palette = []

        for i in range(0, threshold):
            palette.append(i) # R
            palette.append(i) # G
            palette.append(i) # B

        for i in range(threshold, 256):
            palette.append(255) # R
            palette.append(0)   # G
            palette.append(0)   # B
        
        # カラーパレットの設定
        image.putpalette(palette)

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

        # 画像の描画
        self.canvas.create_image(
                canvas_width / 2,       # 画像表示位置(Canvasの中心)
                canvas_height / 2,                   
                image=self.photo_image  # 表示画像データ
                )

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

(実行結果)

まとめ

PythonのPillowを使っていると、モノクロ画像のカラーパレットを意識する事は少ない気がしますが、カラーパレットを使わずにモノクロ画像に擬似的に色を付けようとすると、モノクロ8bitのデータをカラーの24bitに変換して、輝度値に合わせてR,G,Bの値を画像の全画素に対して変換を行う必要がありますが、カラーパレットを使うと、たかだか768個のデータを指定するだけなので、簡単に色を付ける事が可能になります。

ただし、このカラーパレットを指定できるのは、8bitのモノクロ画像(インデックスカラーも含む)のみとなります。

参考記事

https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.putpalette

疑似カラー(Pseudo-color)

【C#】Bitmapのカラーパレットの設定

【Python/tkinter】Scale(トラックバー、スライダー)

つまみを動かして値を調整できる、C#でいうところのトラックバーは、tkiterでは Scale といいます。

以下にサンプルプログラムを示します。

import tkinter as tk

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

        self.master.title("Scaleの作成")     # ウィンドウタイトル

        #---------------------------------------------------------------
        # Scaleの作成

        # Scale(デフォルトで作成)
        scaleV = tk.Scale( self.master)
        scaleV.pack(side = tk.RIGHT)

        # Scale(オプションをいくつか設定)
        self.scale_var = tk.DoubleVar()
        scaleH = tk.Scale( self.master, 
                    variable = self.scale_var, 
                    command = self.slider_scroll,
                    orient=tk.HORIZONTAL,   # 配置の向き、水平(HORIZONTAL)、垂直(VERTICAL)
                    length = 300,           # 全体の長さ
                    width = 20,             # 全体の太さ
                    sliderlength = 20,      # スライダー(つまみ)の幅
                    from_ = 100,            # 最小値(開始の値)
                    to = 300,               # 最大値(終了の値)
                    resolution=0.5,         # 変化の分解能(初期値:1)
                    tickinterval=50         # 目盛りの分解能(初期値0で表示なし)
                    )
        scaleH.pack()
        #---------------------------------------------------------------

    def slider_scroll(self, event=None):
        '''スライダーを移動したとき'''
        print(str(self.scale_var.get()))

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

オプション

オプション名 説明
activebackground マウスカーソルがスケール(つまみ)上にあるときのスケールの色を指定します。
background 背景色(値、メモリの文字、スケール(つまみ)の背景色)を指定します。
bd スライダー部の枠線の太さを指定します。
【初期値】2
bg 背景色(値、メモリの文字、スケール(つまみ)の背景色)を指定します。
borderwidth スライダー部の枠線の太さを指定します。
【初期値】2
command 値が変更された時に呼び出されるメソッド名を指定します。
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
digits 値、メモリに表示される値の桁数を指定します。
fg フォントの文字色を設定します。
font フォントを指定します。
foreground フォントの文字色を設定します。
from_ 最小値(開始の値)を指定します。
highlightbackground
highlightcolor
highlightthickness
label スライダーの左上(水平方向に配置のとき)もしくは右上(垂直方向に配置のとき)に表示する文字(ラベル)を指定します。
length 全体の長さを指定します。
orient Scaleの配置の向き(縦(tk.VERTICAL)か横(tk.HORIZONTAL))を指定します。
【初期値】tk.HORIZONTAL
relief Scale全体の枠線のスタイルを設定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
repeatdelay
repeatinterval
resolution つまみを移動した時の変化の分解能を指定します。
【初期値】1
showvalue スライダーの値を表示する(True) /表示しない(False)を指定します。
sliderlength スライダー(つまみ)の幅
sliderrelief
state state = tk.DISABLEDを指定するとScaleが無効(変更できない)になります。
takefocus
tickinterval 目盛りの文字の分解能を指定します。
【初期値】1
to 最大値(終了の値)を指定します。
troughcolor スライダが動く領域(つまみの背景)の色を指定します。
variable Scaleの値をIntVar,DoubleVar,StringVarクラスオブジェクトで指定します。
width 全体の太さを指定します。

【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()  # 画像の表示

(実行結果)