【Python】enumerate()関数で配列の要素とインデックス番号を取得

Pythonで配列の各要素をfor文で取得する場合は、以下のようにします。

colors = ['black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']

for c in colors:
    print(c)

(実行結果)

ここに、インデックス番号付きで各要素をする場合は

colors = ['black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']

i = 0
for c in colors:
    print(i, c)
    i += 1

(実行結果)

とも書けますが、Pythonのenumerate()関数を使うと

colors = ['black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']

for i, c in enumerate(colors):
    print(i, c)

(実行結果)

となります。

さらに開始値を0以外にしたい場合は、enumerate()関数の引数に開始値を追加し、

colors = ['black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']

for i, c in enumerate(colors, 11):
    print(i, c)

(実行結果)

とします。

【Python】特殊メソッド

Python以外でクラスを使ったプログラムを作成すると、クラスをインスタンスした時にはコンストラクタが呼ばれ、クラスオブジェクトを解放した時にはデストラクタが呼ばれます。

コンストラクタやデストラクタのように、ある特定の操作をクラスオブジェクトに対して行った時に呼ばれるメソッドをPythonでは特殊メソッド(Special Method)といいます。

Pythonではインスタンスした時や解放した時以外にも様々な特殊メソッドがあります。

(参考)

https://docs.python.org/ja/3/reference/datamodel.html?highlight=__init__#special-method-names

特殊メソッドはアンダーバー2つ(__)で前後が挟まれたメソッド名となります。

 

Deep Learningのプログラムでは比較的この特殊メソッドが使われるので、実際に使った事のある特殊メソッドを紹介します。

__init__(self [, …])

コンストラクタ

クラスがインスタンスされた時に呼ばれます。

__del__(self)

デストラクタ

del object のように、クラスが解放される時に呼ばれます。

ただし、呼ばれない場合もあります。

どちらかというと、挙動はファイナライザに近いです。

__call__(self [, …])

object(a, b, c)のように、クラスオブジェクトに引き数を追加し、クラスオブジェクトをメソッドのように呼び出します。

__len__(self)

len(object)のようにした時に、要素数(int型)を返します。

何の要素数か?は特に規定はなくユーザー次第となります。

__getitem__(self, key)

object[1]のように、クラスオブジェクトに角カッコ付きで配列のように呼び出した時に要素を返します。

__setitem__(self, key, value)

object[1] = 3のように、クラスオブジェクトに角カッコ付きで配列のように、要素を設定します。

__delitem__(self, key)

del object[key] のようにクラスオブジェクトに角カッコ付きで配列の削除のように呼び出した時に要素を削除します。

サンプル

y = a0 + a1*x + a2*x^2+ a3*x^3 +… となるようなn次関数の係数をコンストラクタで設定し、xの値を__call__メソッドで呼び出し、yの値を取得するサンプルです。

class TestClass:
    '''
    y = a0 + a1*x + a2*x^2+ a3*x^3 +・・・の計算
    '''
    def __init__(self, a):
        ''' コンストラクタ '''
        print("__init__")
        self.coeff = a

    def __del__(self):
        ''' デストラクタ '''
        print("__del__")

    def __call__(self, x):
        ''' 呼び出し可能オブジェクト '''
        print("__call__")

        sum = 0
        for i in range(len(self.coeff)):
            sum += self.coeff[i] * x**i

        return sum

    def __len__(self):
        ''' 要素数 '''
        print("__len__")
        return len(self.coeff)

    def __getitem__(self, index):
        ''' 指定した番号の要素取得 '''
        print("__getitem__")
        return self.coeff[index]

    def __setitem__(self, index, value):
        ''' 指定した番号の要素設定 '''
        print("__setitem__")
        self.coeff[index] = value

    def __delitem__(self, index):
        ''' 指定した番号の要素削除 '''
        print("__delitem__")
        del self.coeff[index]

#------------------------------------------------------

# クラスのインスタンス(__init__メソッドの呼び出し)
object = TestClass([2, 1.5, 3]) # y = 2 + 1.5*x + 3*x**2

# __call__メソッドの呼び出し
print(object(3.5))

# 要素数(__len__メソッドの呼び出し)
print(len(object))

# 要素の取得(__getitem__メソッドの呼び出し)
print(object[1])

# 要素の設定(__setitem__メソッドの呼び出し)
object[1] = 0.5
print(object[1])

# 要素の削除(__delitem__メソッドの呼び出し)
del object[2]
print(len(object))

# クラスの解放
del object

(実行結果)

まとめ

個人的には特殊メソッドはコンストラクタ以外は、あまり使う事はないのですが、例えば画像処理で特殊メソッドを駆使するとすると、ガウシアンフィルタのような畳み込みフィルタでは、コンストラクタでカーネルの係数を設定し、__call__メソッドで画像データを引き数で渡して、フィルタ後の画像データを取得するような使い方ができると思います。

特殊メソッドの呼び出され方は決まっていますが、実際にどのようにつかうか?はユーザー次第なので、うまく使えば、キレイなコードを書く事ができると思います。

【Python】変数がNoneかどうか確認する方法

OpenCVで画像ファイルを開く場合では、ファイルがみつからない、日本語パスのためファイルが読み込めないなどのエラーが起きやすいため、画像の変数がNoneかどうかエラーチェックをしたくなります。

そのため、エラーチェックをしたプログラムを書いてみます。

(ダメな例)

import cv2

# OpenCVで画像ファイルを開く
img = cv2.imread("image.bmp", cv2.IMREAD_UNCHANGED)

if img == None:
    print("Load Error")
else:
    cv2.imshow("Image", img)
    cv2.waitKey(0)

このプログラムは一見良さそうで、実際に img == None のときは、正しく動作します。

しかしながら、 img != None のとき(画像ファイルが正しく読み込めたとき)は以下のような例外が発生します。

The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

例外が発生しないようにするには、 == を使うのではなく、 is を使います。

import cv2

# OpenCVで画像ファイルを開く
img = cv2.imread("image.bmp", cv2.IMREAD_UNCHANGED)

if img is None:
    print("Load Error")
else:
    cv2.imshow("Image", img)
    cv2.waitKey(0)

他にも

if img != None:

としたい場合は

if img is not None:

のように != ではなく、 is not を使います。

【Python/tkinter】LabelFrame(ラベルフレーム、グループボックス)

複数のウィジェットをラベル付きの枠線で囲うウィジェットをLabelFrame(ラベルフレーム)と言います。

tkinter以外ではグループボックスなどと言われます。

このラベルフレームは何かの設定値など、共通の目的を持ったウィジェットを一つにまとめるときに用いられます。

基本的な使い方はFrameに似ていますが、Frameに枠線とラベルが追加されています。

(ラベルフレームの実行例)

(サンプルプログラム)

import tkinter as tk

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

        self.master.title("ラベルフレームの作成")     # ウィンドウタイトル
        self.master.geometry("300x200")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルフレーム1の作成
        self.labelframe1 = tk.LabelFrame(self.master, text = "フレーム1")
        
        # ラジオボタンに設定する値
        self.radio_value1 = tk.IntVar(value = 0)

        # ラジオボタンの作成
        self.radio1 = tk.Radiobutton(self.labelframe1, text = "項目1", variable = self.radio_value1, value = 0)
        self.radio2 = tk.Radiobutton(self.labelframe1, text = "項目2", variable = self.radio_value1, value = 1)
        self.radio3 = tk.Radiobutton(self.labelframe1, text = "項目3", variable = self.radio_value1, value = 2)
        self.radio1.pack()
        self.radio2.pack()
        self.radio3.pack()

        # ラベルの配置
        self.labelframe1.pack()

        #--------------------------------------------------------
        # ラベルフレーム2の作成
        self.labelframe2 = tk.LabelFrame(self.master, text = "フレーム2", labelanchor = "n", width = 200, height = 100)
        self.labelframe2.propagate(False) # 幅と高さを指定する場合はこの設定が必要

        # ラジオボタンに設定する値
        self.radio_value2 = tk.IntVar(value = 1)

        # ラジオボタンの作成
        self.radio4 = tk.Radiobutton(self.labelframe2, text = "項目1", variable = self.radio_value2, value = 0)
        self.radio5 = tk.Radiobutton(self.labelframe2, text = "項目2", variable = self.radio_value2, value = 1)
        self.radio6 = tk.Radiobutton(self.labelframe2, text = "項目3", variable = self.radio_value2, value = 2)
        self.radio4.pack(anchor = tk.W)
        self.radio5.pack(anchor = tk.W)
        self.radio6.pack(anchor = tk.W)

        # ラベルの配置
        self.labelframe2.pack()
        #--------------------------------------------------------


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

構文

オブジェクト = tk.LabelFrame(親ウィジェット, オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
bd 枠線の太さを指定します。(borderwidthと同じ)
borderwidth bdと同じ
bg 背景色を指定します。(backgroundと同じ)
background bgと同じ
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
(参考)https://tkdocs.com/shipman/cursors.html
font ラベルのフォントを指定します。
height フレームの高さを画素数で指定します。
highlightbackground
highlightcolor
highlightthickness
labelanchor ラベルの表示位置を指定します。
【設定値】e, en, es, n, ne, nw, s, se, sw, w, wn, ws
labelwidget ラベルの代わりにButtonなどのウィジェットを指定します。
padx フレームの内側に配置するウィジェットまでの横方向の隙間を指定します。
【初期値】0
pady フレームの内側に配置するウィジェットまでの縦方向の隙間を指定します。
【初期値】0
relief フレームのスタイルを指定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
takefocus
text ラベルの文字列を指定します。
width フレームの幅を画素数で指定します。

補足説明

ラベルフレームのサイズ(幅と高さ)指定

widthとheightの値を指定しても、デフォルト状態ではLabelFrameのサイズは変わらず、LabelFrameに配置したウィジェットの大きさに合わせて自動調整されます。

この自動調整を無効にするには ラベルフレームオブジェクト.propagate(False) を実行します。

labelanchorについて

labelanchorでラベルの位置を指定します。

実際にラベルが表示される位置は下図を参照してください。

labelwidgetについて

labelwidgetはラベルの代わりにウィジェットを配置することができます。

下図はラベルの代わりにButtonを配置した例になります。

labelwidget = ウィジェットのオブジェクト

のように指定します。

【Python/tkinter】ウィジェットの配置(place)

ウィジェットを配置するには、pack,grid,placeの3つのメソッドがありますが、ここではplaceについて説明します。

placeではウィジェットの位置を座標を指定して配置します。

初期状態では下記のように親(配置先)の左上を原点として、ウィジェットの左上の座標(初期状態の場合、anchorにより変更可)を指定します。

以下に簡単なサンプルを示します。

import tkinter as tk

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

        self.master.title("ウィジェットの配置(place)")     # ウィンドウタイトル
        self.master.geometry("300x200")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ボタンの作成
        button = tk.Button(self.master, text = "ボタン")
        # 座標を指定して配置
        button.place(x = 100, y = 50)
        #--------------------------------------------------------

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

(実行画面)

構文

ウィジェット.place(オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
x 配置するX座標を指定します。
y 配置するY座標を指定します。
relx 配置先の座標を配置先の幅に対して相対的な位置を0.0~1.0の値で指定します。
0.0:左端、1.0:右端
rely 配置先の座標を配置先の高さに対して相対的な位置を0.0~1.0の値で指定します。
0.0:上端、1.0:下端
anchor ウィジェットを配置する座標の基準位置を指定します。
【設定値】tk.N, tk.NE, tk.E, tk.SE, tk.S, tk.SW, tk.W, tk.NW, tk.CENTER
【初期値】tk.NW
width ウィジェットの幅を画素数で指定します。
height ウィジェットの高さを画素数で指定します。
relwidth ウィジェットの幅を配置先の幅に対して相対的な値(0.0~1.0)で指定します。
relheight ウィジェットの高さを配置先の高さに対して相対的な値(0.0~1.0)で指定します。

anchor

anchorの基準位置はウィジェットに対して以下のようになります。

サンプル

各種オプション設定を使ったサンプルを示します。

import tkinter as tk

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

        self.master.title("ウィジェットの配置(place)")     # ウィンドウタイトル
        self.master.geometry("300x400")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ボタンの作成
        button1 = tk.Button(self.master, text = "ボタン1")
        button1.place(x = 30, y = 20)

        button2 = tk.Button(self.master, text = "ボタン2")
        button2.place(x = 80, y = 70, anchor = tk.CENTER) # 指定座標の基準位置変更

        button3 = tk.Button(self.master, text = "ボタン3")
        button3.place(x = 30, y = 90, width = 100, height = 40) # ウィジェットのサイズ指定

        button4 = tk.Button(self.master, text = "ボタン4")
        button4.place(relx = 0.5, rely = 0.4, anchor = tk.CENTER) # 相対座標指定

        button5 = tk.Button(self.master, text = "ボタン5")
        button5.place(relx = 0.5, relwidth = 0.8, y = 200, anchor = tk.CENTER) # ウィジェットのサイズを相対的に指定
        #--------------------------------------------------------
if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(実行画面)

【Python/tkinter】ウィジェットの配置(grid)

ウィジェットを配置するには、pack,grid,placeの3つのメソッドがありますが、ここではgridについて説明します。

gridでウィジェットを配置するのは、どことなくエクセルのセルにウィジェットを配置するようなイメージに似ています。

こんなイメージ↓

ただし、gridでは行番号(row)、列番号(column)は0(ゼロ)から始まります。

また、エクセルにはセルを結合して中央揃えという機能がありますが、gridにも同様の機能があり、rowspancolumnspanを使ってセルを結合します。

最初のエクセルのイメージで配置したプログラムがこちら↓(ウィジェットの範囲が分かり易いように背景色をつけました)

import tkinter as tk

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

        self.master.title("ウィジェットの配置(grid)")     # ウィンドウタイトル
        self.master.geometry("300x180")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        label1 = tk.Label(self.master, text = "ラベル1", bg = 'cyan1')
        label2 = tk.Label(self.master, text = "ラベル2", bg = 'green1')
        label3 = tk.Label(self.master, text = "ラベル3", bg = 'yellow1')
        label4 = tk.Label(self.master, text = "ラベル4", bg = 'pink1')
        label5 = tk.Label(self.master, text = "ラベル5", bg = 'MediumPurple1')
        label6 = tk.Label(self.master, text = "***ラベル6***", bg = 'LightSteelBlue1')

        #--------------------------------------------------------
        # gridでウィジェットの配置
        label1.grid(row = 0, column = 1, columnspan = 3, sticky = tk.W+tk.E)
        label2.grid(row = 0, column = 0, rowspan = 5, sticky = tk.N+tk.S)
        label3.grid(row = 1, column = 1)
        label4.grid(row = 1, column = 3)
        label5.grid(row = 2, column = 2)
        label6.grid(row = 3, column = 1, columnspan = 3)
        #--------------------------------------------------------

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

構文

ウィジェット.grid(オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
column ウィジェットを配置する列番号(0始まり)を指定します。
columnspan グリッドを横方向に結合する数を指定します。
ipadx ウィジェットの内側の横方向の隙間を設定します。
ipady ウィジェットの内側の縦方向の隙間を設定します。
padx ウィジェットの外側の横方向の隙間を設定します。
pady ウィジェットの外側の縦方向の隙間を設定します。
row ウィジェットを配置する行番号(0始まり)を指定します。
rowspan グリッドを縦方向に結合する数を指定します。
sticky グリッド内のウィジェットを配置する位置
アンカーの機能にも似ていますが、例えば上下(tk.N+ tk.S)を指定すると、ウィジェットが上下方向にグリッド内いっぱいに広がります。【設定値】tk.N, tk.S, tk.W, tk.E, tk.NW, tk.NE, tk.SW, tk.SE, tk.NSEW
および上記組み合わせ(tk.N+ tk.Sなど)

ウィンドウのリサイズに合わせて行、列の幅、高さを調整する

gridでウィジェットを配置すると、行や列方向の最大の高さ、幅に合わせてグリッド状に配置されますが、ウィンドウをリサイズしても幅や高さが変わる事がありません。

↓ウィンドウのリサイズ

そこで、ウィンドウに合わせて行の高さを調整するにはgrid_rowconfigure()メソッドを、列の幅を調整するにはgrid_columnconfigure()メソッドを用います。

 

構文

親ウィジェット.grid_columnconfigure(列番号, オプション)
親ウィジェット.grid_rowconfigure(行番号, オプション)

オプション

オプション名 説明
weight 1以上の整数を指定すると、幅/高さが調整されます。
複数の行もしくは列に対してweightを指定すると、指定したweightの値の比率で幅/高さが調整されます。
minsize 最小の幅/高さを画素単位で指定します。
pad 列/高さの隙間を画素単位で指定します。

 

サンプル

import tkinter as tk

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

        self.master.title("ウィジェットの配置(grid)")     # ウィンドウタイトル
        self.master.geometry("300x180")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        lbl_00 = tk.Label(self.master, text = "row,col")

        lbl_col1 = tk.Label(self.master, text = "col1")
        lbl_col2 = tk.Label(self.master, text = "col2")
        lbl_col3 = tk.Label(self.master, text = "col3")

        lbl_row1 = tk.Label(self.master, text = "row1")
        lbl_row2 = tk.Label(self.master, text = "row2")
        lbl_row3 = tk.Label(self.master, text = "row3")

        #--------------------------------------------------------
        # Entry(テキストボックス)の作成
        entry1 = tk.Entry(self.master, width = 20)
        entry2 = tk.Entry(self.master, width = 20)
        entry3 = tk.Entry(self.master, width = 20)

        #--------------------------------------------------------
        # ボタンの作成
        button1 = tk.Button(self.master, text = "...")
        button2 = tk.Button(self.master, text = "...")
        button3 = tk.Button(self.master, text = "...")

        #--------------------------------------------------------
        # gridでウィジェットの配置
        lbl_00.grid(row = 0, column = 0)

        lbl_col1.grid(row = 0, column = 1)
        lbl_col2.grid(row = 0, column = 2)
        lbl_col2.grid(row = 0, column = 3)

        lbl_row1.grid(row = 1, column = 0)
        lbl_row2.grid(row = 2, column = 0)
        lbl_row3.grid(row = 3, column = 0)

        entry1.grid(row = 1, column = 1, sticky=tk.EW) # 幅に合わせて大きくする
        entry2.grid(row = 2, column = 1, sticky=tk.EW) # 幅に合わせて大きくする
        entry3.grid(row = 3, column = 1, sticky=tk.EW) # 幅に合わせて大きくする

        button1.grid(row = 1, column = 3)
        button2.grid(row = 2, column = 3)
        button3.grid(row = 3, column = 3)

        #--------------------------------------------------------
        # ウィンドウのリサイズに合わせてEntryの幅(column=1)を広げる
        self.master.grid_columnconfigure(1, weight=1) # 列の調整
        #self.master.grid_rowconfigure(1, weight=1) # 行の調整

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

実行結果

↓ウィンドウのリサイズ

gridを使うサンプル

個人的にはウィンドウ内にFrameを配置し、その中にpackでウィジェットを配置する場合が多いのですが、gridはラベルテキストボックス(Entry)を並べて配置する場合に使っています。

以下は、ラベルとテキストボックスを並べたサンプルです。

import tkinter as tk

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

        self.master.title("ウィジェットの配置(grid)")     # ウィンドウタイトル
        self.master.geometry("300x180")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        label0 = tk.Label(self.master, text = "設定値")

        label1 = tk.Label(self.master, text = "項目1")
        label1_1 = tk.Label(self.master, text = "項目1_1")
        label1_2 = tk.Label(self.master, text = "項目1_2")
        label1_3 = tk.Label(self.master, text = "項目1_3")

        label2 = tk.Label(self.master, text = "項目2")
        label2_1 = tk.Label(self.master, text = "項目2_1")
        label2_2 = tk.Label(self.master, text = "項目2_2")
        label2_3 = tk.Label(self.master, text = "項目2_3")

        # テキストボックス(Entry)の作成
        self.entry1_1 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry1_2 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry1_3 = tk.Entry(self.master, justify = tk.RIGHT)

        self.entry2_1 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry2_2 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry2_3 = tk.Entry(self.master, justify = tk.RIGHT)

        #--------------------------------------------------------
        # gridでウィジェットの配置
        label0.grid(row = 0, column = 0, rowspan = 8)

        label1.grid(row = 0, column = 1, columnspan = 2)
        label1_1.grid(row = 1, column = 1); self.entry1_1.grid(row = 1, column = 2)
        label1_2.grid(row = 2, column = 1); self.entry1_2.grid(row = 2, column = 2)
        label1_3.grid(row = 3, column = 1); self.entry1_3.grid(row = 3, column = 2)

        label2.grid(row = 4, column = 1, columnspan = 2)
        label2_1.grid(row = 5, column = 1); self.entry2_1.grid(row = 5, column = 2)
        label2_2.grid(row = 6, column = 1); self.entry2_2.grid(row = 6, column = 2)
        label2_3.grid(row = 7, column = 1); self.entry2_3.grid(row = 7, column = 2)
        #--------------------------------------------------------

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

(実行画面)

【Python/tkinter】Label(ラベル)

tkinterでラベルの作成のサンプルを以下に示します。

import tkinter as tk

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

        self.master.title("ラベルの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        self.label = tk.Label(self.master, text = "ラベルの文字")
        # ラベルの配置
        self.label.pack()
        #--------------------------------------------------------

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

(実行画面)

構文

ラベルオブジェクト = tk.Label(親ウィジェット, オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
activebackground クリックされたときの背景色を指定します。
activeforeground クリックされたときの文字色を指定します。
anchor 文字の配置位置を指定します。
【設定値】tk.N, tk.S, tk.W, tk.E, tk.NW, tk.NE, tk.SW, tk.SE, tk.CENTER
background 背景色を指定します。
bd 枠線の太さを指定します。
ただし、初期状態では枠線が表示されていないため、reliefで枠線のスタイルを指定する必要があります。(borderwidthと同じ)
bg 通常時の背景色を指定します。
bitmap モノクロのBitmapを指定します。
(参考)https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/bitmaps.html
borderwidth bdと同じ
compound 文字と画像の両方を表示する際に、文字に対して画像の表示位置を指定します。
【設定値】tk.LEFT, tk.RIGHT, tk.BOTTOM, tk.TOP, tk.CENTER
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
(参考)https://tkdocs.com/shipman/cursors.html
disabledforeground stateオプションで無効(DISABLED)に設定している際の文字色を設定します。
fg 表示する文字色を指定します。(foregroundと同じ)
font 表示する文字のフォントを指定します。
foreground fgと同じ
height ラベルの高さを文字数で指定します。
画像を配置した時は、画素数の指定になります。
highlightbackground
highlightcolor
highlightthickness
image 表示する画像を指定します。
justify 複数行の文字のときの、文字寄せ方向を指定します。
【設定値】左寄せ(tk.LEFT), 中央寄せ(tk.CENTER), 右寄せ(tk.RIGHT)
padx 文字の両側の隙間を指定します。
pady 文字の上下の隙間を指定します。
relief ラベルの枠線のスタイルを指定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
state
takefocus
text 表示する文字を指定します。
textvariable StringVarクラスオブジェクトを指定し、ラベルの文字列を指定します。
underline 指定した順番(先頭から0始まり)の文字にアンダーラインを付加します。
width ラベルの幅を文字数で指定します。
wraplength 文字の折り返し幅を指定します。

 

ラベルはプログラムの各種情報を表示するのに、プログラムの実行中に文字列を変更する場合が多いかと思います。

ラベルの文字を変更する方法は主に下記の2通りあります。

  • textvariableオプションのStringVarを指定し変更する方法
  • ラベルのtextオプションを直接変更する方法

 

textvariableオプションのStringVarを指定し変更する方法

以下のサンプルでは、LabeltextvariableオプションにStringVarを指定し、ボタンのクリック時にラベルの文字列を変更しています。

import tkinter as tk

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

        self.master.title("ラベルの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        self.label_text = tk.StringVar(value = "ラベルの文字") # valueは初期値
        self.label = tk.Label(self.master, textvariable = self.label_text)
        # ラベルの配置
        self.label.pack()
        #--------------------------------------------------------
        # ボタンの作成
        self.button = tk.Button(self.master, text = "ボタン", command = self.button_click)
        # ボタンの配置
        self.button.pack()
        #--------------------------------------------------------

    def button_click(self):
        '''ボタンがクリックされたとき'''
        # ラベル文字の変更方法1
        # StringVarの値を設定する
        self.label_text.set("ボタンがクリックされた")

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

(プログラム起動時)

(ボタンクリック後)

 

ラベルのtextオプションを直接変更する方法

以下のサンプルでは、ボタンのクリック時にラベルのtextオプションを直接変更しています。

ただし、textオプションとtextvariableオプションを両方指定することは出来ないので注意してください。

import tkinter as tk

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

        self.master.title("ラベルの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        self.label = tk.Label(self.master, text = "ラベルの文字")
        # ラベルの配置
        self.label.pack()
        #--------------------------------------------------------
        # ボタンの作成
        self.button = tk.Button(self.master, text = "ボタン", command = self.button_click)
        # ラベルの配置
        self.button.pack()
        #--------------------------------------------------------

    def button_click(self):
        '''ボタンがクリックされたとき'''
        # ラベル文字の変更方法2
        # ラベルのプロパティを直接設定する。ただし、textvariableプロパティは設定しないこと
        self.label["text"] = "ボタンがクリックされた"

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

(プログラム起動時)

(ボタンクリック後)

【OpenCV/Python】日本語の画像ファイル読込・保存

OpenCVで画像ファイルを開くとき、ファイル名やパスに日本語が含まれていると、画像ファイルを開いてくれません。

試しに以下のようなコードを実行すると、エラーが起き実行できません。

import cv2

# OpenCVで画像ファイルを開く(ファイル名が日本語)
img = cv2.imread("画像ファイル.bmp", cv2.IMREAD_UNCHANGED)

cv2.imshow("Image", img)
cv2.waitKey(0)

エラー情報

Message=OpenCV(4.5.3) C:\Users\runneradmin\AppData\Local\Temp\pip-req-build-sn_xpupm\opencv\modules\imgproc\src\color.cpp:182: 
error: (-215:Assertion failed) !_src.empty() in function 'cv::cvtColor'

エラーそのものはimread関数で画像ファイルを開くのに失敗し、戻り値の画像データ(img)が空(None)になっているため、エラーが発生しています。

日本語の画像ファイルを開くためには、PillowもしくはNumPyで画像ファイルを開いてOpenCVの画像データであるNumPyのndarray形式に変換すれば、OpenCVで日本語の画像ファイルを扱えるようになります。

Pillowで画像ファイルを開き、OpenCV(NumPyのndarray)に変換する

import cv2
import numpy as np
from PIL import Image

# Pillowで画像ファイルを開く
pil_img = Image.open("画像ファイル.bmp")
# PillowからNumPyへ変換
img = np.array(pil_img)
# カラー画像のときは、RGBからBGRへ変換する
if img.ndim == 3:
    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

cv2.imshow("Image", img)
cv2.waitKey(0)

Pillowで画像ファイルを開くと、カラー画像の場合、RGBの順でデータが格納されるので、cvtColorを用いて、OpenCVの形式(BGR)へ変換します。

PillowからNumPyの画像データへ変換する方法は下記ページを参照ください。

【Python】画像データ(NumPy,Pillow(PIL))の相互変換

NumPyで画像ファイルを開き、OpenCV(NumPyのndarray)に変換する

import cv2
import numpy as np
from PIL import Image

import time

start = time.perf_counter()

# NumPyで画像ファイルを開く
buf = np.fromfile("画像ファイル.bmp", np.uint8)
img = cv2.imdecode(buf, cv2.IMREAD_UNCHANGED)

print((time.perf_counter() - start) * 1000)#, "msec")

cv2.imshow("Image", img)
cv2.waitKey(0)

NumPyのfromfileで画像ファイルをバイナリで開き、ファイルの中身をメモリ(buf)に格納します。

OpenCVのimdecodeでメモリ上の画像データをOpenCVの画像データ(NumPyのndarray)に変換します。

OpenCVからPillowへ変換し画像ファイルに保存する

import cv2
from PIL import Image

# OpenCVで画像ファイルを開く(ファイル名に日本語が無い場合)
img = cv2.imread("image_file.bmp", cv2.IMREAD_UNCHANGED)

# カラー画像のときは、BGRからRGBへ変換する
if img.ndim == 3:
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# NumPyからPillowへ変換
pil_image = Image.fromarray(img)
# Pillowで画像ファイルへ保存
pil_image.save("画像ファイル_pillow.bmp")

Pillowで画像ファイルを開いたときの逆の事をすると、日本語のファイル名でも画像ファイルに保存することができます。

OpenCVの画像データ(ndarray)を画像形式に変換しファイルに保存する

import cv2
import numpy as np

# OpenCVで画像ファイルを開く(ファイル名に日本語が無い場合)
img = cv2.imread("image_file.bmp", cv2.IMREAD_UNCHANGED)

# 画像データを画像ファイル形式のメモリ変換する
ret, buf = cv2.imencode(".bmp", img)
# NumPyで画像ファイルへ保存
with open("画像ファイル_numpy.bmp", mode='w+b') as f:
    buf.tofile(f)

OpenCVの画像データ(NumPyのndarray)をimencodeで画像ファイル形式にメモリ上で変換し、NumPyのtofileで画像ファイルに書き込みます。

まとめ

日本語の画像ファイルの読込・保存をするのにPillowおよびNumPyを介して処理を行いましたが、それぞれの処理時間を比較しました。

処理時間は8192×8192画素のカラー画像を5回処理したときの平均時間です。

方法 平均処理時間(msec)
Pillowで画像ファイルを開く 513
NumPyで画像ファイルを開く 210
Pillowで画像ファイルへ保存 337
NumPyで画像ファイルへ保存 1106

これを見ると、日本語ファイル名の画像を開くときは、NumPy、保存するときはPillowを使った方が速い結果になりました。

NumPyで保存するときが極端に遅かったので、関数ごとの処理時間を見てみたところ、imencodeに約100msec、tofileに約1000msecという時間でした。

画像の読込と保存でPillowとNumPyを使い分けるのは、少々面倒ですが、処理時間もそれなりに違うので、画像の読込はNumPy、保存はPillowを使った方がいいかもしれません。

【Python】指定フォルダ内のファイル、フォルダ一覧を取得

フォルダに格納された画像ファイルの一覧を取得する場合など、フォルダのパスを指定してファイルの一覧を取得したい場合があります。

その場合に、Pythonでは主に3通りの方法があります。

  • glob.glob
    検索条件を指定してファイル、フォルダ一覧を取得
  • os.listdir
    フォルダ内のファイル、フォルダ一覧を取得
  • os.scandir
    フォルダ内のファイル、フォルダ一覧をファイルか?フォルダか?の属性付きで取得

 

試しに以下のようなファイル、フォルダ構成の時に、どのようにファイル、フォルダを取得できるのか?をみていきたいと思います。

└─images
    ├─annotation.csv
    ├─0
    │ ├─img0001.bmp
    │ ├─img0001.png
    │ ├─img0002.png
    │ └─img0003.png
    ├─1
    │ ├─img0101.png
    │ ├─img0102.png
    │ └─img0103.png
    └─2
      ├─img0201.png
      ├─img0202.png
      └─img0203.png

glob.glob

指定したフォルダ内において、検索条件を指定してファイル、フォルダ一覧を取得。

最も使いやすいと思います。

(サンプル)

import glob

files = glob.glob("./images/*")
print(files)
# ['./images\\0', './images\\1', './images\\2', './images\\annotation.csv']

files = glob.glob("./images/*.*")
print(files)
# ['./images\\annotation.csv']

files = glob.glob("./images/0/*.png")
print(files)
# ['./images/0\\img0001.png', './images/0\\img0002.png', './images/0\\img0003.png']

files = glob.glob("./images/**", recursive=True)
print("recursive", files)
# ['./images\\', './images\\0', './images\\0\\img0001.bmp', './images\\0\\img0001.png', './images\\0\\img0002.png', './images\\0\\img0003.png', './images\\1', './images\\1\\img0101.png', './images\\1\\img0102.png', './images\\1\\img0103.png', './images\\2', './images\\2\\img0201.png', './images\\2\\img0202.png', './images\\2\\img0203.png', './images\\annotation.csv']# 

検索条件に * を指定すると、フォルダ内のファイル、フォルダ全てを取得します。

検索条件に *.* を指定すると、フォルダ内のファイルを取得します。ただし、フォルダ名に . が含まれる場合は、そのフォルダも取得します。

検索条件に *.png のように指定すると、指定した拡張子のファイルを取得します。

検索条件に ** を指定し、さらに、recursive=True と指定すると、指定したフォルダ以下(子のフォルダ内を含む)のファイル、フォルダ全てを取得します。

詳細はこちら↓のページを参照ください。

https://docs.python.org/ja/3/library/glob.html?highlight=glob#glob.glob

os.listdir

指定したフォルダ内のファイル、フォルダを取得します。

(サンプル)

import os
files = os.listdir("./images")
print(files)
# ['0', '1', '2', 'annotation.csv']

files = os.listdir("./images/0")
print(files)
# ['img0001.bmp', 'img0001.png', 'img0002.png', 'img0003.png']

os.listdirではファイル、フォルダの区別なく、指定したフォルダ内のファイル、フォルダの一覧を取得します。

取得した一覧からファイルか?フォルダか?を判断するにはos.path.isfile()メソッドos.path.isdir()メソッドを使って以下のように行うことも可能です。

import os

files = os.listdir("./images")

for f in files:
    path = os.path.join("./images", f)
    if os.path.isfile(path):
        # ファイルの場合
        print("[file  ]", f)
    if os.path.isdir(path):
        # ファイルの場合
        print("[folder]", f)

# [Dir ] 0
# [Dir ] 1
# [Dir ] 2
# [File] annotation.csv

詳細はこちら↓のページを参照ください。

https://docs.python.org/ja/3/library/os.html?highlight=os%20listdir#os.listdir

os.scandir

指定したフォルダ内のファイル、フォルダをファイルか?フォルダか?の属性付きで取得します。

os.listdirではファイル、フォルダの一覧を取得後にファイルか?フォルダか?を判断しましたが、os.scandirでは、取得時に属性付きで取得します。

(サンプル)

import os

with os.scandir("./images") as it:
    for entry in it:
        if entry.is_file():
            print("[file   ]", entry.name)
        elif entry.is_dir():
            print("[folder ]", entry.name)

# [Dir ] 0
# [Dir ] 1
# [Dir ] 2
# [File] annotation.csv

あまり使用する機会が少ないのですが、フォルダ構成のまま、ファイルを取得した場合など、再帰的に呼び出すとフォルダ構成を取得できると思います。

詳細はこちら↓のページを参照ください。

https://docs.python.org/ja/3/library/os.html?highlight=os%20listdir#os.scandir

【Python】文字列の大文字、小文字変換

文字列を大文字に変換するには upper()メソッド
文字列を小文字に変換するには lower()メソッドを用います。

このupper()、lower()を使ったサンプルは以下の通りです。

# 元の文字列
str = 'Imaging Solution'
print(str)

# ----------------
# 大文字に変換
temp = str.upper()
print(temp)
# IMAGING SOLUTION

# ----------------
# 小文字に変換
temp = str.lower()
print(temp)
# imaging solution

実行結果

【Python】画像データがNumPyかPillowか調べる方法

Pythonで画像処理をしていると、画像データの型(クラス)は、OpenCVを使っているとNumPyだし、Tkinterで画像を表示しようとすると、Pillowを使ったりもするので、どうしても画像データがNumPyとPillowが混在しがちです。

そこで、画像データがNumPyなのか?Pillowなのか?を調べる方法の紹介です。

画像データに限らず、インスタンスしたクラスのオブジェクトが、どのクラスなのかを調べるにはisinstance関数を用います。

ininstance関数の書式は以下の通りです。

ininstance(クラスオブジェクト, クラス)

クラスオブジェクトが指定したクラスと一致している場合はTrueが、異なる場合はFalseが返ります。

このininstance関数を使って、画像データがNumPyなのか?Pillowなのか?を調べる関数の例を以下に示します。

def check_image_data(image):
    '''画像データがNumPyか、Pillowかを調べる'''
    if isinstance(image, np.ndarray):
        print("NumPy Image")
    elif isinstance(image, Image.Image):
        print("Pillow Image")

この関数を使って、実際に画像データがNumPyかPillowかを調べるサンプルは以下の通りです。

from PIL import Image
import numpy as np

def check_image_data(image):
    '''画像データがNumPyか、Pillowかを調べる'''
    if isinstance(image, np.ndarray):
        print("NumPy Image")
    elif isinstance(image, Image.Image):
        print("Pillow Image")

# Pillowの画像データ
pillow_image = Image.open("Mandrill.bmp")
check_image_data(pillow_image)

# NumPyの画像データ
numpy_image = np.asarray(pillow_image)
check_image_data(numpy_image)

実行結果

 

型を調べるだけならtype関数を使うこともできます。

(例)

print(type(numpy_image))
# <class 'numpy.ndarray'>
print(type(pillow_image))
# <class 'PIL.BmpImagePlugin.BmpImageFile'>

上記のコメント部分がtype関数を使って型を表示した結果になりますが、NumPyの型は‘numpy.ndarray’と表示されているので、まだ分かり易いのですが、Pillowの型は、’PIL.Image.Image’と表示されるのを期待しているのですが、‘PIL.BmpImagePlugin.BmpImageFile’と表示されてしまいます。

これは、bmpファイルからPillowの画像データを開いたためで、別のjpegファイルから開くと別の型が表示されます。

そのため、型を調べる、表示するだけなら type関数、型を判断するならisinstance関数という使い分けが良さそうです。

関連記事

【Python】画像データ(NumPy,Pillow(PIL))の相互変換

【Python/tkinter】OpenCVのカメラ動画をCanvasに表示する

USBカメラなどで取得した画像(動画)をOpenCVの cv2.imshow() で表示するには比較的簡単に表示する事ができますが、tkinterを使ってWindow付でCanvasに表示したい場合には、少しコツが必要になります。

そこで、tkinterのCanvasに動画を表示する方法を紹介します。

OpenCVでUSBカメラの動画を表示

USBカメラの動画をOpenCVのの cv2.imshow() で表示するプログラムは、以下のようにすれば表示されます。

import cv2

# カメラをオープンする
capture = cv2.VideoCapture(0)

# カメラがオープン出来たか?
camera_opened = capture.isOpened()

while camera_opened:

    # フレーム画像の取得
    ret, frame = capture.read()
    
    # 画像の表示
    cv2.imshow("Image", frame)

    if cv2.waitKey(1) != -1:
        # キー入力で終了
        break

capture.release()
cv2.destroyAllWindows()

tkinterのCanvasに動画を表示

tkinterのCanvasに動画を表示する場合は、OpenCVの動画表示プログラムの cv2.imshow() の部分を書き換えて表示しようとしても動画が表示されません。

これは、while文中でCanvasへ画像を表示しようとすると、画像の更新スレッドがブロックされた状態となってしまい、画像を更新するには、少しの時間、スレッドを空ける必要があります。

このスレッドを空ける処理には after() 関数を使います。

after() 関数では、指定した時間を待ってから、指定した関数を実行することができます。

処理の意味合い的には sleep() 関数とも似ていますが、sleep() 関数では、指定時間分、スレッドをブロックしてしまいますが、 after() 関数ではスレッドがブロックされません。

それらを考慮し、tkinterのCanvas上にUSBカメラの動画を表示したプログラムが以下になります。

 

以下のサンプルプログラムでは、Camvas上をマウスの左ボタンをクリックすると、画像の取得が開始/停止するようになっています。

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk, ImageOps  # 画像データ用

import cv2

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

        self.master.title("OpenCVの動画表示")       # ウィンドウタイトル
        self.master.geometry("400x300")     # ウィンドウサイズ(幅x高さ)
        
        # Canvasの作成
        self.canvas = tk.Canvas(self.master)
        # Canvasにマウスイベント(左ボタンクリック)の追加
        self.canvas.bind('<Button-1>', self.canvas_click)
        # Canvasを配置
        self.canvas.pack(expand = True, fill = tk.BOTH)

        # カメラをオープンする
        self.capture = cv2.VideoCapture(0)

        self.disp_id = None

    def canvas_click(self, event):
        '''Canvasのマウスクリックイベント'''

        if self.disp_id is None:
            # 動画を表示
            self.disp_image()
        else:
            # 動画を停止
            self.after_cancel(self.disp_id)
            self.disp_id = None

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

        # フレーム画像の取得
        ret, frame = self.capture.read()
    
        # BGR→RGB変換
        cv_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        # NumPyのndarrayからPillowのImageへ変換
        pil_image = Image.fromarray(cv_image)

        # キャンバスのサイズを取得
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        # 画像のアスペクト比(縦横比)を崩さずに指定したサイズ(キャンバスのサイズ)全体に画像をリサイズする
        pil_image = ImageOps.pad(pil_image, (canvas_width, canvas_height))

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

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

        # disp_image()を10msec後に実行する
        self.disp_id = self.after(10, self.disp_image)

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

実行結果

ポイント

  • 動画を表示するときは、while文ではなく、after()関数で繰り返し処理を行う。
  • OpenCVの画像は、カラー画像の場合、BGRのデータの並びをRGBへ並び替えてからOpenCVの画像データ(NumPyのndarray)をPillowのImageへImage.fromarray()関数で変換する。

関連記事

【OpenCV-Python】Tkinter GUI Sample

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

【Python】画像データ(NumPy,Pillow(PIL))の相互変換

【Python/NumPy】カラー画像データをRGBからBGRへ変換

【Python/tkinter】新しいウィンドウを開く(モーダル、モードレスダイアログ)

新しくウィンドウを開く場合、モーダルダイアログモードレスダイアログというものがあります。

モーダルダイアログとは、新しいウィンドウを開いたとき、新しいウィンドウのクリックやテキスト入力などの操作ができるが、元のウィンドウの操作ができない表示方法で、モードレスダイアログとは、新しいウィンドウと元のウィンドウの両方とも操作できる表示方法となります。

PythonのtkinterではToplevel()メソッドを実行すると新しいウィンドウを作成することができますが、デフォルトではモードレスダイアログとして開きます。

モーダルダイアログにするには、Toplevelで作成したウィンドウに対して、grab_set()メソッドを実行することで、モーダルダイアログになります。

 

サンプル実行結果

サンプルプログラム

import tkinter as tk

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

        self.master.title("Main")       # ウィンドウタイトル
        self.master.geometry("300x200") # ウィンドウサイズ(幅x高さ)

        # ボタンの作成
        btn_modeless = tk.Button(
            self.master, 
            text = "Modeless dialog",   # ボタンの表示名
            command = self.create_modeless_dialog    # クリックされたときに呼ばれるメソッド
            )
        btn_modeless.pack()

        btn_modal = tk.Button(
            self.master, 
            text = "Modal dialog",      # ボタンの表示名
            command = self.create_modal_dialog    # クリックされたときに呼ばれるメソッド
            )
        btn_modal.pack()

    def create_modeless_dialog(self):
        '''モードレスダイアログボックスの作成'''
        dlg_modeless = tk.Toplevel(self)
        dlg_modeless.title("Modeless Dialog")   # ウィンドウタイトル
        dlg_modeless.geometry("300x200")        # ウィンドウサイズ(幅x高さ)

    def create_modal_dialog(self):
        '''モーダルダイアログボックスの作成'''
        dlg_modal = tk.Toplevel(self)
        dlg_modal.title("Modal Dialog") # ウィンドウタイトル
        dlg_modal.geometry("300x200")   # ウィンドウサイズ(幅x高さ)

        # モーダルにする設定
        dlg_modal.grab_set()        # モーダルにする
        dlg_modal.focus_set()       # フォーカスを新しいウィンドウをへ移す
        dlg_modal.transient(self.master)   # タスクバーに表示しない

        # ダイアログが閉じられるまで待つ
        app.wait_window(dlg_modal)  
        print("ダイアログが閉じられた")

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

ポイント

  • Toplevelメソッドで新しいウィンドウを作成することができる。
  • Toplevelではデフォルトでモードレスダイアログとなる。
  • モーダルダイアログボックスにするには、grab_set()メソッドを実行する。
  • 新しく作成したウィンドウの表示直後はフォーカスが無いため、フォーカスするにはfocus_set()メソッドを実行する。
  • 新しいウィンドウをタスクバーに表示したくない場合はtransient()メソッドを実行する。
  • モーダルダイアログボックスが閉じられるまで待つには、wait_window(ダイアログ)メソッドで待つ。

【Python/os】パスからファイル名、拡張子、フォルダ名などを取得する

ファイルパスからファイル名、拡張子、フォルダ名などを取得するには、os.pathモジュールを用います。

まずは、サンプルを参照ください。

import os

filepath = r"C:\temp\Image.bmp"

# ファイル名 os.path.basename()
print(os.path.basename(filepath))       # 'Image.bmp'

# フォルダ名 os.path.dirname()
print(os.path.dirname(filepath))        # 'C:\temp'

# 拡張子の取得 os.path.splitext()
print(os.path.splitext(filepath)[1])    # '.bmp'

# 拡張子なしのパス os.path.splitext()
print(os.path.splitext(filepath)[0])    # 'C:\temp\Image'

# 拡張子なしのファイル名 os.path.basename(), os.path.splitext()
print(os.path.splitext(os.path.basename(filepath))[0])    # 'Image'

# ルートディレクトリ os.path.splitdrive()
print(os.path.splitdrive(filepath)[0])  # 'C:'

実行結果

 

使用した関数の説明は以下の通りです。

os.path.basename(path)

パス名(path)から最後の’\’以降のファイル名を返します。

(例)

入力path 戻り値
C:\temp\Images\Image01.bmp Image01.bmp
C:\temp\Images Images

os.path.dirname(path)

パス名(path)のディレクトリ名を返します。

(例)

入力path 戻り値
C:\temp\Images\Image01.bmp C:\temp\Images
C:\temp\Images C:\temp

os.path.splitext(path)

パス名(path)を (拡張子以外, 拡張子)のタプルを返します。

(例)

入力path 戻り値
C:\temp\Images\Image01.bmp (‘C:\\temp\\Images\\Image01’, ‘.bmp’)
C:\temp\Images (‘C:\\temp\\Images’, ”)

os.path.splitdrive(path)

パス名(path)を (ルートディレクトリ, それ以外)のタプルを返します。

(例)

入力path 戻り値
C:\temp\Images\Image01.bmp (‘C:’, ‘\\temp\\Images\\Image01.bmp’)
C:\temp\Images (‘C:’, ‘\\temp\\Images’)

参考

https://docs.python.org/ja/3/library/os.path.html

【Python/NumPy】カラー画像データをRGBからBGRへ変換

カラー画像データは各データが8bit(0~255の256諧調)のR, G, Bの要素からなる24bitカラー画像や、さらに透過率(A)を追加しR, G, B, Aの要素からなる32bitカラー画像があります。

Pythonでは、このカラー画像のデータの並びが使用するモジュールによって異なり、PillowではRGBRGBA の並びとなり、NumPyのndarrayで画像データを管理しているOpenCVでは BGRBGRA の並びとなっています。

つまり、カラー画像をPillowとOpenCV間で変換する場合、画像データの並びも RGB⇔BGRやRGBA⇔BGRA と変換する必要があります。

これを間違うと、RとBが入れ替わった状態となるため、下図のように変な画像になってしまいます。

(正しい画像データの並び)

(間違った画像データの並び)

カラー画像データの事前準備

Pillowでカラー画像を開き、24bitのカラー画像と32bitのカラー画像を用意し、これをNumPyのndarrayへ変換します。

import numpy as np
from PIL import Image
import cv2

# Pillowでカラー画像(RGB)を開く
pillow_rgb24 = Image.open("Mandrill.bmp")
# 24bitカラー(RGB)から32bitカラー(RGBA)へ変換
pillow_rgb32 = pillow_rgb24.convert("RGBA")

###############################
# PillowからNumPyのndarrayへ変換
numpy_rgb24 = np.array(pillow_rgb24) # 24bitカラー(RGB)
numpy_rgb32 = np.array(pillow_rgb32) # 32bitカラー(RGBA)

上記のようにPillowで開いたカラー画像をNumPyへ変換しただけの状態の画像をOpenCVのimshowで表示すると、RとBが入れ替わった画像が表示されます。

cv2.imshow("Image", numpy_rgb24)
cv2.waitKey()

(表示結果)

NumPyでRGB→BGR, RGBA→BGRAへ変換

Pillowのカラー画像をNumPyへ変換した直後のデータは
24bitカラーのとき
[[[R, G, B], [R, G, B], [R, G, B]],
[[R, G, B], [R, G, B], [R, G, B]],
[[R, G, B], [R, G, B], [R, G, B]]]

32bitカラーのとき
[[[R, G, B, A], [R, G, B, A], [R, G, B, A]],
[[R, G, B, A], [R, G, B, A], [R, G, B, A]],
[[R, G, B, A], [R, G, B, A], [R, G, B, A]]]

のように、RGBやRGBAの順で並んでいます。

これをOpenCVで使うときは、データの並びをBGRやBGRAの順へ変換する必要があります。

具体的には

24bitカラーのとき
[[[B, G, R], [B, G, R], [B, G, R]],
[[B, G, R], [B, G, R], [B, G, R]],
[[B, G, R], [B, G, R], [B, G, R]]]

32bitカラーのとき
[[[B, G, R, A], [B, G, R, A], [B, G, R, A]],
[[B, G, R, A], [B, G, R, A], [B, G, R, A]],
[[B, G, R, A], [B, G, R, A], [B, G, R, A]]]

のようにR, G, Bのデータを並び変える必要があります。

このRGB→BGRRGBA→BGRAの変換は以下のように行います。

# NumPyでRGBからBGRへ変換(24bitの場合) その1
numpy_bgr24 = numpy_rgb24[:, :, ::-1]
# NumPyでRGBからBGRへ変換(24bitの場合) その2
numpy_bgr24 = numpy_rgb24[:, :, [2, 1, 0]]

# RGBAからBGRAへ変換(32bitの場合)
numpy_bgr32 = numpy_rgb32[:, :, [2, 1, 0, 3]]

OpenCVでRGB→BGR, RGBA→BGRAへ変換

OpenCVの画像データはNumPyのndarrayなので、PillowからNumPyへ変換した画像データは、そのままOpenCVの関数で処理することができます。

# cvtColorで24bitカラー(RGB)から24bitカラー(BGR)へ変換
numpy_bgr24 = cv2.cvtColor(numpy_rgb24, cv2.COLOR_RGB2BGR)

# cvtColorで32bitカラー(RGBA)から32bitカラー(BGRA)へ変換
numpy_bgr32 = cv2.cvtColor(numpy_rgb32, cv2.COLOR_RGBA2BGRA)

24bitか?、32bitか?を調べる

カラー画像データの並びを入れ替える時は、24bitカラーのときと、32bitカラーのときとで、処理を変える必要があるため、NumPy配列(ndarray)が24bitと32bitのどちらなのか?を調べる必要があります。

それには、NumPyのshapeを取得しshape[2]の値が3であれば24bit、4であれば32bitとなります。

# 24bitか?32bit?かを調べる
print(numpy_rgb24.shape)
print("チャンネル数 = ", numpy_rgb24.shape[2])
print(numpy_rgb32.shape)
print("チャンネル数 = ", numpy_rgb32.shape[2])

(実行結果)

参考

【Python】画像データ(NumPy,Pillow(PIL))の相互変換

【Python/Pillow(PIL)】カラー,モノクロ,HSVなどの変換

PythonのPillowでカラー画像からモノクロ画像などへの変換は convert関数を用います。

 

カラー画像からモノクロ画像への変換は以下のように行います。

from PIL import Image

img_rgb = Image.open("Parrots.bmp")
img_rgb.show()

# グレースケール("L")へ変換
img_gray = img_rgb.convert("L")
img_gray.show()

# モノクロ("1")へ変換
img_mono = img_rgb.convert("1")
img_mono.show()

実行結果

Pillowでは、”モノクロ”と言っても、0~255までの256諧調の輝度値を持ったグレースケール(“L”)と、黒(0)と白(1)の2諧調のモノクロ(“1”)とがあります。
上図の左側がグレースケール(“L”)で、右側がモノクロ(“1”)です。

モノクロ(“1”)の画像は一般にディザ(dither)と呼ばれる処理で、新聞の写真のように1色のインクしか無い印刷で使われます。このディザの処理にもいくつか手法があり、Pillowでは誤差拡散法という処理になっています。(参考書籍:ディジタル画像処理

ただし、このモノクロ(“1”)は、画像処理的には、あまり出番が無いので、私は特に断りの無い限り、グレースケール画像の事を「モノクロ画像」と呼んでいます。

今回は、カラー画像(“RGB”)からグレースケール画像(“L”)へ変換しましたが、convert関数の引数の部分を変えることで、他の色空間への変換も可能です。

どのような色空間へ変換できるか?は以下のページを参照ください。

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

 

このconvert関数はOpenCVでいうところの cvtColor関数に相当しますが、個人的に興味があったのが、色相のように0~360°の値を持つ色空間は、どのように変換されるのか?ということ。

OpenCVでは、0~360°を8bitのデータに収まるように、0~180へ変換する COLOR_BGR2HSV と0~255へ変換する COLOR_BGR2HSV_FULL の2種類があります。

そこで、Pillowではどのように変換されるのか?確認してみました。

 

評価プログラム

from PIL import Image

img_rgb = Image.new("RGB", (3, 1))

# R, G, Bの値を設定
img_rgb.putpixel((0,0), (255, 0, 0))
img_rgb.putpixel((1,0), (0, 255, 0))
img_rgb.putpixel((2,0), (0, 0, 255))

# RGB -> HSV
img_hsv = img_rgb.convert("HSV")

# 各画素の値を取得
print("(0, 0)", img_hsv.getpixel((0, 0)))
print("(1, 0)", img_hsv.getpixel((1, 0)))
print("(2, 0)", img_hsv.getpixel((2, 0)))

実行結果

この結果から、0°は0へ、120°は85へ、240°は170へ変換されていることから、0~360°の角度は0~255へ変換されていることが分かります。

Pillowで行われる色相の角度の変換は、OpenCVの COLOR_BGR2HSV_FULL 相当で変換されている!という事でした。