【OpenCV-Python】円形度

円形度とは、図形の面積と周囲長の関係から、円らしさの値を求めます。

円形度の詳細は以下のページを参照ください。

円形度

 

OpenCV的には、図形の面積はcontourArea()関数で、周囲長はarcLength()関数で求める事ができるので、これを使って円形度を求めます。

 

今回は、下図のように手書きで書いた図形の中から円らしい部分を見つけたいと思います。

 

サンプルプログラム

contourArea()関数と、周囲長はarcLength()関数を使って円らしい部分を抽出するサンプルプログラムを作成しました。
円形度を求める部分は関数(circularity)にしてあります。

import cv2
import numpy as np

def circularity(contour):
    '''
    円形度を求める

    Parameters
    ----------
    contour : ndarray
        輪郭の(x,y)座標の配列

    Returns
    -------
        円形度

    '''
    # 面積
    area = cv2.contourArea(contour)
    # 周囲長
    length = cv2.arcLength(contour, True)

    # 円形度を返す
    return 4*np.pi*area/length/length

# 8ビット1チャンネルのグレースケールとして画像を読み込む
img = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE) 

# 白黒反転して二値化
ret, img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)

# 一番外側の輪郭のみを取得
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) 

# 画像表示用に入力画像をカラーデータに変換する
img_disp = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

# 全ての輪郭を描画
cv2.drawContours(img_disp, contours, -1, (0, 0, 255), 2)

# 輪郭の点の描画
for contour in contours:
    # 円形度の計算
    val = circularity(contour)
    # 輪郭の矩形領域
    x,y,w,h = cv2.boundingRect(contour)
    # 円形度の描画
    cv2.putText(img_disp, f"{val:.3f}", (x, y-10), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 1, cv2.LINE_AA)
    # 円らしい領域(円形度が0.85以上)を囲う
    if val > 0.85:
        cv2.rectangle(img_disp,(x-5,y-5),(x+w+5,y+h+5),(255,0,0),2) # 少し外側を囲う

cv2.imshow("Image", img_disp)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

実行結果

 

実行結果を見ると、だいたい円っぽい部分は円形度を使うと抽出することが出来ますが、真円度的な円の正確さを求めるのには、円形度は不向きな感じがします。

 

関連記事

【OpenCV-Python】findContoursによる輪郭検出

【OpenCV-Python】輪郭(contour)の面積(contourArea)

【OpenCV-Python】輪郭の周囲長(arcLength)

【OpenCV-Python】輪郭の周囲長(arcLength)

findContours()関数などで取得した輪郭の座標から輪郭の長さを求めるにはarcLength()関数を用います。

構文

arcLength(curve, closed) ->retval
curve 輪郭を構成する輪郭のxy座標(x, y)の配列
closed 始点と終点が閉じた周囲長を求めるときはTrue
閉じていない周囲長を求めるときはFalse
(戻り値)retval 周囲長

 

サンプルプログラム

通常はfindContours()関数で取得した輪郭の座標を用いる場合が多いかと思いますが、今回は簡単のため、4点からなる周囲長を求めます。

この輪郭座標の閉じた周囲長(cloased = True)と、閉じていない周囲長(cloased = False)を求めます。

閉じた周囲長(cloased = True)

閉じていない周囲長(cloased = False)

import cv2
import numpy as np

# 輪郭の座標
contour = np.array(
    [
     [1, 2],
     [1, 4],
     [5, 4],
     [5, 2]
    ],
    dtype = np.int32
    )

# 閉じた周囲長
perimeter = cv2.arcLength(contour, True) 
print(perimeter) # 12.0

# 閉じていない周囲長
perimeter = cv2.arcLength(contour, False) 
print(perimeter) # 8.0

関連記事

【OpenCV-Python】findContoursによる輪郭検出

【OpenCV-Python】輪郭(contour)の矩形領域の取得

OpenCVのfindContours関数などで得られた点の座標から、点を囲う矩形領域(四角形の領域)を取得するにはboundingRect関数を用います。

さらに、傾きを考慮した矩形領域を取得するにはminAreaRect関数を用います。

まずは、両方の関数を用いたサンプルを示します。

import cv2
import numpy as np

# 8ビット1チャンネルのグレースケールとして画像を読み込む
img = cv2.imread("sample.bmp", cv2.IMREAD_GRAYSCALE) 

contours, hierarchy = cv2.findContours(
    img, 
    cv2.RETR_EXTERNAL,      # 一番外側の輪郭のみを取得する 
    cv2.CHAIN_APPROX_NONE   # 輪郭座標の省略なし
    ) 

# 画像表示用に入力画像をカラーデータに変換する
img_disp = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

# 輪郭の点の描画
for i, contour in enumerate(contours):
    # 輪郭を描画
    cv2.drawContours(img_disp, contours, i, (255, 0, 0), 2)

    # 傾いていない外接する矩形領域
    x,y,w,h = cv2.boundingRect(contour)
    cv2.rectangle(img_disp,(x,y),(x+w-1,y+h-1),(0,255,0),2)

    # 傾いた外接する矩形領域
    rect = cv2.minAreaRect(contour)
    box = cv2.boxPoints(rect)
    box = np.intp(box)
    cv2.drawContours(img_disp,[box],0,(0,0,255), 2)

# 画像の表示
cv2.imshow("Image", img_disp)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

実行結果

緑の線がboundingRect関数を用いて取得した領域、赤の線がminAreaRect関数を用いた領域となります。

 

矩形領域の取得(boundingRect関数)

boundingRect( array ) -> x, y, width, height
array (x,y)座標の配列 もしくは グレースケールの画像を指定します。
(戻り値) x 取得した矩形領域の左上のx座標を取得します。
(戻り値) y 取得した矩形領域の左上のy座標を取得します。
(戻り値) width 取得した矩形領域の幅を取得します。
(戻り値) height 取得した矩形領域の高さを取得します。

注意点

戻り値で取得した幅(width)および高さ(height)の値は、実際の幅、高さ(2点間の距離)の +1 の値を取得します。

例えば、下図のように、(1, 2), (5, 2), (5, 4), (1, 4)の点を囲う矩形領域を取得すると、

x = 1

y = 2

width = 5

height = 3

となります。

import cv2
import numpy as np

contour = np.array(
    [
     [1, 2],
     [5, 2],
     [5, 4],
     [1, 4]
    ],
    dtype = np.int32
    )

# 傾いていない外接する矩形領域
ret = cv2.boundingRect(contour)

print(ret) # (1, 2, 5, 3)

傾きを考慮した矩形領域の取得(minAreaRect関数)

minAreaRect( points) -> rect
points (x,y)座標の配列
(戻り値) rect 傾いた矩形領域の
中心のxy座標 (x, y)
矩形の幅、高さ(width, height)
矩形の回転角度
のタプルで取得します。

 

点(1, 1), (0, 2), (4, 4), (5, 3) を囲う傾きを考慮したときの最小となる矩形領域の取得のサンプルを以下に示します。

import cv2
import numpy as np

contour = np.array(
    [
     [1, 1],
     [0, 2],
     [4, 4],
     [5, 3]
    ],
    dtype = np.int32
    )

# 傾いた外接する矩形領域
rect = cv2.minAreaRect(contour) # rectは中心座標(x,y), (width, height), 回転角度
cnter, size, rot = rect

print(rect) # ((2.500000238418579, 2.5), (4.919349670410156, 1.3416407108306885), 26.56505012512207)

関連記事

【OpenCV-Python】findContoursによる輪郭検出

【OpenCV-Python】矩形抽出(矩形度)

【OpenCV-Python】輪郭(contour)の面積(contourArea)

OpenCVで二値化された領域の輪郭座標は、findContours関数を使えば取得することができます。

このときの戻り値である輪郭情報(contours)をcontourArea関数へ渡し面積を求めます。

ただし、求まる面積は、あくまでも輪郭線の内側の面積(画素数ではない)となります。
(下図の例では、面積は 35 となります。)

さらに、白の領域の内側に黒の領域があっても、考慮されません。

構文

contourArea(contour[, oriented]) ->retval
contour 輪郭座標
oriented 輪郭の向きを考慮するか(True)、しないか(False)を指定します。
初期値)False
Trueのとき、輪郭座標の向きが時計周りのときは正、反時計周りのときは負となります。
Falseのとき、向きに関係なく、常に正となります。
下図参照
(戻り値)retval 輪郭の内側の面積

サンプルプログラム

import cv2
import numpy as np

# 画像データ
img = np.array(
    [[0,   0,   0,   0,   0,   0,   0,   0,   0, 0],
     [0, 255, 255, 255, 255, 255, 255, 255, 255, 0],
     [0, 255, 255, 255, 255, 255, 255, 255, 255, 0],
     [0, 255, 255,   0,   0,   0,   0, 255, 255, 0],
     [0, 255, 255,   0,   0,   0,   0, 255, 255, 0],
     [0, 255, 255, 255, 255, 255, 255, 255, 255, 0],
     [0, 255, 255, 255, 255, 255, 255, 255, 255, 0],
     [0,   0,   0,   0,   0,   0,   0,   0,   0, 0],
    ],
    dtype = np.uint8
    )

# 輪郭情報の取得
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) 

# 画像表示用に入力画像をカラーデータに変換する
img_disp = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

# 輪郭ごとの処理
for i, contour in enumerate(contours):
    # 輪郭の面積を求める
    area = cv2.contourArea(contour, True)
    print(f"面積[{i}]: {area}")

    # 輪郭座標
    for point in contour:
        print(point[0])

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img_disp)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

実行結果

このサンプルでは、画像データはnumpyの配列で疑似的に作りましたが、画像にすると下図の用な画像に対して輪郭処理をしています。

この画像では、白の外側の輪郭の面積は 35 となります。

白の内側の輪郭の面積は 13 となります。

内側の輪郭の座標は、黒の画素の上下左右方向(4近傍)に隣接している白の画素の座標となります。

 

関連記事

【OpenCV-Python】findContoursによる輪郭検出

【OpenCV-Python】findContoursによる輪郭検出

OpenCV(Python)で二値化された画像中の白の部分の外側の輪郭のデータを取得するにはfindContours()関数を用います。
黒の部分の輪郭は、白の部分の内側の輪郭という認識になります。

findContours()関数で取得できる情報は、輪郭を構成している点の座標群と輪郭の内側、外側の情報となります。

さらに、これらの情報を用いて、輪郭の長さや、輪郭の内側の面積、重心、輪郭部分の矩形領域などを取得できる関数も別途、用意されています。

処理例

(入力画像)

(処理結果)

※結果は画像データとしては得られず、上図の色付きの線で書いた部分の情報を取得します。

 

構文

findContours(image, mode, method[, contours[, hierarchy[, offset]]]) ->contours, hierarchy
image 輪郭抽出する画像データ
mode 輪郭構造の取得方法
method 輪郭座標の取得方法
(戻り値)contours 輪郭座標
(戻り値)hierarchy 輪郭の階層情報

 

image

輪郭抽出する画像データを指定します。

画像は8bitのグレースケールで、基本的に二値化された画像を指定します。

一部、32bit1チャンネルのデータ(ラベリングデータ)も指定可能です。

二値化されていない場合は、輝度値が0で無い部分に関しての輪郭情報を取得します。

mode

輪郭の階層情報の取得方法をRETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE, RETR_FLOODFILLの中から指定します。

最初に見つける輪郭が白の外側の輪郭となります。

そのため、入力画像は黒色背景の画像を指定します。

白色背景の画像を指定した場合、最初の輪郭は画像全体となります。

modeで指定した輪郭情報は戻り値のhierarchyに格納されます。

●RETR_TREE

輪郭の階層情報をツリー形式で取得します。

この輪郭の階層情報をというのは、下図を用いて説明します。

まず、一番外側の輪郭(白の領域の外側の輪郭)にがあります。

の内側の輪郭にはがあり、の内側にはがあります。

さらに、

の内側にはがあり、の内側には

の内側には

の内側には

の輪郭があります。

この構造をツリーで表現すると、このようになります。

modeRETR_TREEを指定するとhierarchyの中にこの階層構造が格納されます。

 

●RETR_CCOMP

白の輪郭(外側の輪郭)と黒の輪郭(内側の輪郭)の情報だけを取得し、白の輪郭のさらに内側の輪郭かどうか?の情報は失われます。

この構造をツリーで表現すると、このようになります。

●RETR_LIST

白(外側)、黒(内側)の区別なく、すべての輪郭を同じ階層として取得します。

この構造をツリーで表現すると、このようになります。

●RETR_EXTERNAL

一番外側の白の輪郭だけを取得します。

この構造をツリーで表現すると、このようになります。

method

輪郭を構成する点の座標の取得方法をCHAIN_APPROX_NONE, CHAIN_APPROX_SIMPLE, CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOSの中から指定します。

取得した座標は戻り値のcontoursに格納されます。

 

●CHAIN_APPROX_NONE

輪郭上のすべての座標を取得します。

●CHAIN_APPROX_SIMPLE

縦、横、斜め45°方向に完全に直線の部分の輪郭の点を省略します。

 

●CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS

輪郭の座標を直線で近似できる部分の輪郭の点を省略します。

 

(戻り値)contours

輪郭上の座標を取得します。

実際にどのような座標を取得するかは、methodの設定により異なります。

contoursのデータは以下のような配列となります。

contours[輪郭番号][点の番号][0][X座標, Y座標]

 

(戻り値)hierarchy

輪郭の階層情報を取得します。

取得する構造はmodeの設定により異なります。

contoursのデータは以下のような配列となります。

hierarchy[0][輪郭番号][次の輪郭番号, 前の輪郭番号, 最初子供(内側)の輪郭番号, 親(外側)の輪郭番号]

となります。

下図の例について説明します。

輪郭番号の親はで、最初の子供の番号はとなります。

の次の輪郭番号はで、の前の輪郭番号はとなります。

該当する輪郭番号が無い時は -1 になります。

上図の階層情報の例で言うと、

hierarchy[0][0] は [ 3, -1,  1, -1]
hierarchy[0][1] は [-1, -1,  2,  0]
hierarchy[0][2] は [-1, -1, -1,  1]
hierarchy[0][3] は [-1,  0,  4, -1]
hierarchy[0][4] は [-1, -1,  5,  3]
hierarchy[0][5] は [ 6, -1, -1,  4]
hierarchy[0][6] は [-1,  5,  7,  4]
hierarchy[0][7] は [-1, -1,  8,  6]
hierarchy[0][8] は [ 9, -1, -1,  7]
hierarchy[0][9] は [-1,  8, -1,  7]

となります。

 

輪郭座標(contours)の注意点

contoursで取得できる座標は、白の領域を構成する外側の座標、もしくは内側の座標となります。

例えば、下図のような図形の輪郭座標を取得するとき、

白の輪郭を取得するときは、一番、外側の画素の座標(下図の赤い点)がcontoursに格納されます。

しかし、白の内側の黒の輪郭座標は、黒の画素の座標にはなりません

黒の輪郭座標は、輪郭を構成している黒の画素の4近傍の白の画素の座標となります。
黒の輪郭座標は下図の青い点の座標となります。

輪郭の描画

findContours()関数で取得した輪郭(contours)はdrawContours()関数を用いて描画します。

drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) ->image
image 描画先の画像データ
contours 輪郭座標
contourIdx 描画する輪郭の番号
-1にすると、すべての輪郭を描画
color 描画の色を(b, g, r)のタプルで指定します
thickness 線幅
-1を指定すると塗りつぶしになります。
lineType 線の描画スタイルをFILLED, LINE_4, LINE_8, LINE_AAの中から指定します。
hierarchy 輪郭の階層情報を指定します。
maxLevel 輪郭をどの階層まで表示するかを指定します。
0のとき、最初の輪郭のみ
1以降は数値の階層の輪郭すべてが表示されます。
offset

 

サンプルプログラム

import cv2

# 8ビット1チャンネルのグレースケールとして画像を読み込む
img = cv2.imread("sample.bmp", cv2.IMREAD_GRAYSCALE) 

contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE ) 

# 画像表示用に入力画像をカラーデータに変換する
img_disp = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

# 全ての輪郭を描画
cv2.drawContours(img_disp, contours, -1, (0, 0, 255), 2)

# 輪郭の点の描画
for contour in contours:
    for point in contour:
        cv2.circle(img_disp, point[0], 3, (0, 255, 0), -1)

cv2.imshow("Image", img_disp)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

(実行結果)

 

関連記事

【OpenCV-Python】輪郭(contour)の面積(contourArea)

【OpenCV-Python】輪郭(contour)の矩形領域の取得

【OpenCV-Python】輪郭の周囲長(arcLength)

OneDriveのフォルダ、ファイル容量の確認方法

OneDriveにファイルを大量に配置すると、残容量が不足し、必要の無いファイルを削除したいとき、どのフォルダが容量が多いのか?確認したくなる場合があるかと思います。

 

そんな時、SharePointの機能を使えるプランに入っていれば、下図のように表示することができ便利です。

そもそもSharePointについてですが、Microsoft365には個人プランと法人プランとがあり、法人プラン(会社や学校)であれば、SharePointが使える可能性が高いです。

 

OneDriveをブラウザで開いたときに、アドレスが XXXXXXX.sharepoint.com になっていればSharePointを使えます。

 

ファイル容量の確認方法は以下の手順で行います。

 

OneDriveをブラウザで開き、右上の歯車マーク(設定)をクリックします。

 

表示された OneDriveの設定 をクリックします。

 

 

表示されたウィンドウ左側の その他の設定 をクリックします。

 

 

その他の設定の画面右側の下の方にある 古いサイトの設定ページに戻る をクリックします。

 

 

古いサイトの設定画面の中から、右下の方にある 記憶域メトリックス をクリックします。

 

 

すると最初に示したように、右上にOneDrive全体の空き容量のサイズ、フォルダ、ファイルごとの容量が表示されるので、サイズの大きいフォルダを確認し、不要なファイルを削除すると、比較的簡単に空き容量を増やすことができると思います。

 

 

ここで注意したいのが、ファイルサイズを確認すると、実際のファイルサイズより、合計サイズが大きくなっているファイルが存在している場合があります。

 

 

これは、OneDriveでは、ファイルを上書き保存すると、デフォルトで過去500ファイル分の履歴を残す仕様になっているため、OneDrive上で頻繁に上書き保存したファイルは、ファイルサイズより合計サイズが大きく表示されています。

 

過去の履歴ファイルが必要無い場合は、ファイルの右側に表示されている バージョン履歴 をクリックし、更新日時の部分をクリックすると、メニューが表示され、削除をクリックすると、そのバージョンのファイルが削除されます。

 

全ての履歴が不要の場合は、左上に表示されている すべてのバージョンを削除 をクリックすると、古いバージョンのファイルはすべて削除されます。

 

【Python/tkinter】枠線(relief)の種類一覧

tkinterのウィジェットのButtonやFrame、Labelなどで設定できる枠線(relief)の種類ですが、

  • RAISED
  • GROOVE
  • SUNKEN
  • RIDGE
  • FLAT
  • SOLID

の6種類設定出来るのは、調べれば簡単に出てきますが、どれがどれ?と、いつもなってしまうので、一覧を示します。

サンプルコード

import tkinter as tk

root = tk.Tk()
root.geometry("300x320") 

# ボタンの作成
button1 = tk.Button(
    root, text="tk.RAISED", relief=tk.RAISED, bd=5, width=20)
button2 = tk.Button(
    root, text="tk.GROOVE", relief=tk.GROOVE, bd=5, width=20)
button3 = tk.Button(
    root, text="tk.SUNKEN", relief=tk.SUNKEN, bd=5, width=20)
button4 = tk.Button(
    root, text="tk.RIDGE", relief=tk.RIDGE, bd=5, width=20)
button5 = tk.Button(
    root, text="tk.FLAT", relief=tk.FLAT, bd=5, width=20)
button6 = tk.Button(
    root, text="tk.SOLID", relief=tk.SOLID, bd=5, width=20)

# 配置
button1.pack(pady=10)
button2.pack(pady=10)
button3.pack(pady=10)
button4.pack(pady=10)
button5.pack(pady=10)
button6.pack(pady=10)

root.mainloop()

【参考文献】OpenCVではじめよう ディープラーニングによる画像認識

4月8日、OpneCVではじめよう ディープラーニングによる画像認識 という本のKindle版が先行して発売されました。(単行本は4月30日発売)

 

この本はOpenCV界隈では有名な二人(実名なので認識しづらいですが)がタッグを組んで書かれた本なので、買わない訳にはいきませんww

この本の対象者としては、PythonのOpenCVで画像処理をしてきて、ディープラーニングの処理もOpenCVで動かしてみたい!という人向けです。

 

Pythonの文法やディープラーニング処理のアルゴリズム的な部分は書かれていません。

逆にディープラーニング処理を行うために必要になるOpenCV処理の説明などは割と細かく書かれています。

おそらく実際に手を動かして確認したであろう内容も含まれているので、その辺はOpenCVをやった事のある人でも参考になると思います。

全体的には、OpenCVに関する内容が半分、残りがディープラーニングに関する内容になっています。

 

サンプルプログラムもソースコードはgithubから、学習済みモデルは出版社のページからダウンロードできるので、比較的簡単にお試しができると思います。

本に書かれているサンプルを使って、こんなプログラムを簡単に動かす事ができました。

ただ、ファイルの読み込み時にフルパス設定になっていたので、パスの中に日本語が入っていてファイルが読み込めないというエラーに少しはまりました。→パスを相対パスにして動作させました。

 

私はKindle版を購入しましたが、参考文献などのリンクは、Kindleからリンクを貼ってあるので便利でした。

Kindle版をPCで見るときは、フォントのサイズやページの幅を調整すると見やすくなります。

 

目次

1章 OpenCVとは
1.1 OpenCVの概要
1.2 主要モジュール
1.3 OSSライセンス
1.4 サポート言語
1.5 サポートプラットフォーム
1.6 高速化
1.7 開発の歴史
1.8 ブランチ管理
1.9 各種サイト

2章 OpenCVインストール
2.1 OpenCVのインストール
2.2 pip
2.3 Miniconda
2.4 Docker
2.5 ソースコード

3章 coreモジュール
3.1 基本処理
3.2 ピクセルごとの操作
3.3 統計情報の取得
3.4 画像の分割、結合
3.5 ユーティリティ

4章 imgprocモジュール
4.1 画像の前処理と後処理
4.2 色の変換
4.3 しきい値処理(2値化)
4.4 画像の幾何変換

5章 imgcodecs、Videoioモジュール
5.1 画像の読み込み
5.2 画像の書き込み

6章 ディープラーニング
6.1 ディープラーニングの概要
6.2 精度評価
6.3 画像処理におけるディープラーニング

7章 dnnモジュール基礎
7.1 dnnモジュールの概要
7.2 顔検出
7.3 オブジェクト検出
7.4 クラス分類
7.5 セグメンテーション
7.6 テキスト検出とテキスト認識
7.7 キーポイント検出

8章 dnnモジュール応用
8.1 dnnモジュールの対応レイヤ
8.2 カスタムレイヤ対応
8.3 学習済みモデルの診断ツール(model-diagnostics)
8.4 外部DNNフレームワーク連携
8.5 学習済みモデルの利用

【Python/tkinter】ウィンドウ(Frame)のxボタンがクリックされたときの終了確認

TkinterのFrameで作成したウィンドウのxボタンをクリックしたときに、終了確認を表示して、プログラムを閉じる/閉じないを選択する方法です。

 

まず、何もしないウィンドウを表示するだけのベースとなるプログラムから手を加えていきます。

 

import tkinter as tk

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

        self.master.geometry("300x300")       # ウィンドウサイズ(幅x高さ)

if __name__ == "__main__":
    root = tk.Tk()

    app = Application(master = root)
    app.mainloop()

(実行結果)

ここに、以下の一文を追加し、xボタンが押されたときに呼び出される関数を指定します。

関数名は何でも構いません。

self.master.protocol("WM_DELETE_WINDOW", self.delete_window)

全、コードはこちら

import tkinter as tk

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

        self.master.geometry("300x300")       # ウィンドウサイズ(幅x高さ)

        # ウィンドウの x ボタンが押された時に呼ばれるメソッドを設定
        self.master.protocol("WM_DELETE_WINDOW", self.delete_window)

    def delete_window(self):
        print("ウィンドウのxボタンが押された")
        pass

if __name__ == "__main__":
    root = tk.Tk()

    app = Application(master = root)
    app.mainloop()

このようにすると、delete_window()メソッドの部分で、printしているだけで特に何もしていないので、xボタンが押されても、ウィンドウが閉じられないプログラムとなります。

xボタンが押されたときにプログラムの終了確認を行うには、このメソッド内で行います。

終了確認を行うプログラムはこちら↓

import tkinter as tk
from tkinter import messagebox

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

        self.master.geometry("300x300")       # ウィンドウサイズ(幅x高さ)

        # ウィンドウの x ボタンが押された時に呼ばれるメソッドを設定
        self.master.protocol("WM_DELETE_WINDOW", self.delete_window)

    def delete_window(self):
        print("ウィンドウのxボタンが押された")

        # 終了確認のメッセージ表示
        ret = messagebox.askyesno(
            title = "終了確認",
            message = "プログラムを終了しますか?")

        if ret == True:
            # 「はい」がクリックされたとき
            self.master.destroy()

if __name__ == "__main__":
    root = tk.Tk()

    app = Application(master = root)
    app.mainloop()

(実行結果)

delete_window()メソッド内で何もしなければ、ウィンドウは閉じられません。

プログラムを終了するには、明示的にdestroy()メソッドを呼び出して、プログラムを終了させる必要があります。

【PyTorch】ドキュメントリンク集

PyTorchのドキュメントを毎回探してしまっているので、よく見るリンク集です。

 

【Python】経過時間をhh:mm:ssフォーマットで表示する

Deep Learningの学習工程のように、処理時間が長い場合に、定期的に下図のようにログを表示したかったのですが、timeモジュールのtime()関数で得られた秒数の差を 時:分:秒hh:mm:ss のフォーマットで表示する方法がみつからない。。

deletetimeモジュールを使うと、近いところまでは出来るのですが、時の部分が1桁になり、細かい調整ができません。。

import datetime
print(datetime.timedelta(seconds=12345))
# 3:25:45

細かい事を気にしなければdatetimeを使うのも良いかと思いますが、結局、秒数を hh:mm:ssフォーマットの文字列を返す関数を作ってみました。

def elapsed_time_str(seconds):
    """秒をhh:mm:ss形式の文字列で返す

    Parameters
    ----------
    seconds : float
        表示する秒数

    Returns
    -------
    str
        hh:mm:ss形式の文字列
    """
    seconds = int(seconds + 0.5)    # 秒数を四捨五入
    h = seconds // 3600             # 時の取得
    m = (seconds - h * 3600) // 60  # 分の取得
    s = seconds - h * 3600 - m * 60 # 秒の取得

    return f"{h:02}:{m:02}:{s:02}"  # hh:mm:ss形式の文字列で返す

使い方は、こんな感じです。

start = time.time()
time.sleep(3)
print(elapsed_time_str(time.time() - start))
# 00:00:03

print(elapsed_time_str(12345))
# 03:25:45

【PyTorch】MNISTのサンプルプログラム

ここでは、ある程度Deep Learningの概要やPythonについて勉強し、実際にPyTorchを使ってプログラムを組みたい人向けを想定しています。(ほぼ自分用、備忘録です)

MNISTの0~9の手書き文字画像の分類は、DeepLearningにおけるHellow World的な存在ですが、DeepLearningの画像データや正解ラベルも簡単に入手できるので、最初にプログラムを行うのには、とても便利です。

DeepLearningのプログラムを1つ作っておけば、他に流用できる部分も多いので、まずは、このMNISTをやってみたいと思います。

ここでは、Deep Learning処理の概念的な理解を重視して、ニューラルネットワーク部分はシンプルにしました。

サンプルプログラム

まずは、いきなりMNISTの0~9の手書き文字画像分類のサンプルプログラムです。

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms

#----------------------------------------------------------
# ハイパーパラメータなどの設定値
num_epochs = 10         # 学習を繰り返す回数
num_batch = 100         # 一度に処理する画像の枚数
learning_rate = 0.001   # 学習率
image_size = 28*28      # 画像の画素数(幅x高さ)

# GPU(CUDA)が使えるかどうか?
device = 'cuda' if torch.cuda.is_available() else 'cpu'

#----------------------------------------------------------
# 学習用/評価用のデータセットの作成

# 変換方法の指定
transform = transforms.Compose([
    transforms.ToTensor()
    ])

# MNISTデータの取得
# https://pytorch.org/vision/stable/generated/torchvision.datasets.MNIST.html#torchvision.datasets.MNIST
# 学習用
train_dataset = datasets.MNIST(
    './data',               # データの保存先
    train = True,           # 学習用データを取得する
    download = True,        # データが無い時にダウンロードする
    transform = transform   # テンソルへの変換など
    )
# 評価用
test_dataset = datasets.MNIST(
    './data', 
    train = False,
    transform = transform
    )

# データローダー
train_dataloader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size = num_batch,
    shuffle = True)
test_dataloader = torch.utils.data.DataLoader(
    test_dataset,     
    batch_size = num_batch,
    shuffle = True)

#----------------------------------------------------------
# ニューラルネットワークモデルの定義
class Net(nn.Module):
    def __init__(self, input_size, output_size):
        super(Net, self).__init__()

        # 各クラスのインスタンス(入出力サイズなどの設定)
        self.fc1 = nn.Linear(input_size, 100)
        self.fc2 = nn.Linear(100, output_size)

    def forward(self, x):
        # 順伝播の設定(インスタンスしたクラスの特殊メソッド(__call__)を実行)
        x = self.fc1(x)
        x = torch.sigmoid(x)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

#----------------------------------------------------------
# ニューラルネットワークの生成
model = Net(image_size, 10).to(device)

#----------------------------------------------------------
# 損失関数の設定
criterion = nn.CrossEntropyLoss() 

#----------------------------------------------------------
# 最適化手法の設定
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate) 

#----------------------------------------------------------
# 学習
model.train()  # モデルを訓練モードにする

for epoch in range(num_epochs): # 学習を繰り返し行う
    loss_sum = 0

    for inputs, labels in train_dataloader:

        # GPUが使えるならGPUにデータを送る
        inputs = inputs.to(device)
        labels = labels.to(device)

        # optimizerを初期化
        optimizer.zero_grad()

        # ニューラルネットワークの処理を行う
        inputs = inputs.view(-1, image_size) # 画像データ部分を一次元へ並び変える
        outputs = model(inputs)

        # 損失(出力とラベルとの誤差)の計算
        loss = criterion(outputs, labels)
        loss_sum += loss

        # 勾配の計算
        loss.backward()

        # 重みの更新
        optimizer.step()

    # 学習状況の表示
    print(f"Epoch: {epoch+1}/{num_epochs}, Loss: {loss_sum.item() / len(train_dataloader)}")

    # モデルの重みの保存
    torch.save(model.state_dict(), 'model_weights.pth')

#----------------------------------------------------------
# 評価
model.eval()  # モデルを評価モードにする

loss_sum = 0
correct = 0

with torch.no_grad():
    for inputs, labels in test_dataloader:

        # GPUが使えるならGPUにデータを送る
        inputs = inputs.to(device)
        labels = labels.to(device)

        # ニューラルネットワークの処理を行う
        inputs = inputs.view(-1, image_size) # 画像データ部分を一次元へ並び変える
        outputs = model(inputs)

        # 損失(出力とラベルとの誤差)の計算
        loss_sum += criterion(outputs, labels)

        # 正解の値を取得
        pred = outputs.argmax(1)
        # 正解数をカウント
        correct += pred.eq(labels.view_as(pred)).sum().item()

print(f"Loss: {loss_sum.item() / len(test_dataloader)}, Accuracy: {100*correct/len(test_dataset)}% ({correct}/{len(test_dataset)})")

実行結果

 

使用モジュールの定義

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms

torchは、もちろんPyTorch用のモジュールです。

torchvisionはPyTorchの画像を使ったDeepLearning処理のための補助モジュールで、データセットの作成や、画像データからテンソルへの変換、画像の水増しなどを行います。

パラメータの設定

#----------------------------------------------------------
# ハイパーパラメータなどの設定値
num_epochs = 10         # 学習を繰り返す回数
num_batch = 100         # 一度に処理する画像の枚数
learning_rate = 0.001   # 学習率
image_size = 28*28      # 画像の画素数(幅x高さ)

# GPU(CUDA)が使えるかどうか?
device = 'cuda' if torch.cuda.is_available() else 'cpu'

エポック数(繰り返し回数)、学習率など、設定を変更するパラメータは、一か所にまとめておくと変更しやすいので便利です。

データセット、データローダーの作成

#----------------------------------------------------------
# 学習用/評価用のデータセットの作成

# 変換方法の指定
transform = transforms.Compose([
    transforms.ToTensor()
    ])

# MNISTデータの取得
# https://pytorch.org/vision/stable/generated/torchvision.datasets.MNIST.html#torchvision.datasets.MNIST
# 学習用
train_dataset = datasets.MNIST(
    './data',               # データの保存先
    train = True,           # 学習用データを取得する
    download = True,        # データが無い時にダウンロードする
    transform = transform   # テンソルへの変換など
    )
# 評価用
test_dataset = datasets.MNIST(
    './data', 
    train = False,
    transform = transform
    )

# データローダー
train_dataloader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size = num_batch,
    shuffle = True)
test_dataloader = torch.utils.data.DataLoader(
    test_dataset,     
    batch_size = num_batch,
    shuffle = True)

データセットとは、推論を行うためのデータとラベル(正解データ)のセットの集まりです。

学習時に用いる画像と評価時に用いる画像を別の画像にするため、学習用のデータセットは train = True
評価用のデータセットは train = False に設定します。

データローダーは、学習時にデータセットの中からbatch_size個分だけ、まとめてデータを取り出します。

後述する学習時のfor分の部分で、以下のように書いている部分が、データローダーの中からデータとラベルをbatch_size個ずつ取り出している部分となります。

for inputs, labels in train_dataloader:
       :

このデータを取り出す際に、データを拡大、縮小、移動などを行い、見かけ上のデータ数を増やす水増しと言われる処理や、データを並び替えて値を0~1の値に収まるように変換し、テンソルと呼ばれるデータ形式に変更します。

”テンソル”という言葉が出てくると、いきなり難しくも感じるのですが、プログラム的に言うと、ただの多次元配列となります。

画像データの場合、幅(W)、高さ(H)、色(C)と置くと、モノクロ画像の場合[H、W]の2次元配列となり、カラーの場合は[H, W, C]の3次元配列となりますが、これにバッチ数のNを追加し、
[N, C, H, W]の順番の4次元配列に格納されたデータがテンソルとなります。

今回は、手書き文字のデータセットを用いましたが、他にも既存のデータセットがいろいろあるので、こちらを参照ください。

https://pytorch.org/vision/stable/datasets.html

また、既存のデータセットではなく、自作のデータセットを作成することも可能ですが、これについては、別途記事にしたいと思います。

ニューラルネットワークの作成

#----------------------------------------------------------
# ニューラルネットワークモデルの定義
class Net(nn.Module):
    def __init__(self, input_size, output_size):
        super(Net, self).__init__()

        # 各クラスのインスタンス(入出力サイズなどの設定)
        self.fc1 = nn.Linear(input_size, 100)
        self.fc2 = nn.Linear(100, output_size)

    def forward(self, x):
        # 順伝播の設定(インスタンスしたクラスの特殊メソッド(__call__)を実行)
        x = self.fc1(x)
        x = torch.sigmoid(x)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

#----------------------------------------------------------
# ニューラルネットワークの生成
model = Net(image_size, 10).to(device)

今回は入力に画像データ全画素数(28×28)の784個入力で、全結合(出力100個)→シグモイド→全結合(出力10個)→ログソフトマックス というとてもシンプルな物にしました。

ニューラルネットワークを作成するには、nn.Moduleクラスを継承し、ニューラルネットワークのクラスを作成します。ここではクラス名を Net としましたが、クラス名についての制限はありません。

通常、作成するクラスのコンストラクタ(__init__)の部分で、各層で使われる処理(ここでは全結合のLinear)のクラスのインスタンスにより、入力サイズや出力サイズ、各種オプション設定を行います。

順伝播される処理の流れは forwardメソッド(メソッド名はforwardであること)により定義します。

例えば

x = self.fc1(x)

の部分では、コンストラクタの部分で定義したLinearクラスのインスタンス(self.fc1)にカッコ() を付けて、入力データである x を引数のように渡していますが、この書き方はPython特有で、実際には Linearクラスの
__call__メソッドが呼ばれます。

このような書き方を特殊メソッドというのですが、詳細は下記ページを参照ください。

(参考)

【Python】特殊メソッド

 

損失関数の定義

#----------------------------------------------------------
# 損失関数の設定
criterion = nn.CrossEntropyLoss() 

#----------------------------------------------------------
# 学習中にて
# 損失(出力とラベルとの誤差)の計算
loss = criterion(outputs, labels)

損失関数は、ニューラルネットワークの出力と、ラベルとの誤差を計算します。

今回、損失関数に用いているCrossEntropyLossは損失関数の中では少し特殊で、複数の出力の値とラベル1つの値との誤差を計算しています。

よくあるのは、出力の数とラベルの数は同じ数で、下図のようなイメージとなります。

学習の工程では、この誤差が小さくなるよう、各層の重みの調整を繰り返し行います。

(参考)

https://pytorch.org/docs/stable/nn.html#loss-functions

 

最適化手法の定義

#----------------------------------------------------------
# 最適化手法の設定
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate) 

最適化関数は、損失関数から求めた勾配(損失関数を求める重みで偏微分し、現時点の重みを代入した値)を元に、最適値へ近づけるための手法です。

下図は、重みの値が最適値に更新されるときのイメージですが、現時点の重みが最適値から離れている場合は勾配(接線の傾き)は大きくなり、より大きく最適値に近づくように重みの値が更新されます。

逆に、重みの値が最適値に近づくと、勾配が小さくなり、重みの更新量も少なくなります。

学習率(lr)は、値が大きいと早く最適値へ近づくようになりますが、大きすぎると逆に最適値から離れてしまうので、注意が必要です。

とりあえずは lr = 0.001 ぐらいから始めてみるといいかも?しれません。

(参考)

https://pytorch.org/docs/stable/optim.html

 

学習

#----------------------------------------------------------
# 学習
model.train()  # モデルを訓練モードにする

for epoch in range(num_epochs): # 学習を繰り返し行う
    loss_sum = 0

    for inputs, labels in train_dataloader:

        # GPUが使えるならGPUにデータを送る
        inputs = inputs.to(device)
        labels = labels.to(device)

        # optimizerを初期化
        optimizer.zero_grad()

        # ニューラルネットワークの処理を行う
        inputs = inputs.view(-1, image_size) # 画像データ部分を一次元へ並び変える
        outputs = model(inputs)

        # 損失(出力とラベルとの誤差)の計算
        loss = criterion(outputs, labels)
        loss_sum += loss

        # 勾配の計算
        loss.backward()

        # 重みの更新
        optimizer.step()

    # 学習状況の表示
    print(f"Epoch: {epoch+1}/{num_epochs}, Loss: {loss_sum.item() / len(train_dataloader)}")

    # モデルの重みの保存
    torch.save(model.state_dict(), 'model_weights.pth')

学習の工程は、各重みを最適値へ近づけるため、for分で繰り返し処理を行うのですが、学習の部分で重要な部分を抜き出すと

# optimizerを初期化
optimizer.zero_grad()

# 損失(出力とラベルとの誤差)の計算
loss = criterion(outputs, labels)

# 勾配の計算
loss.backward()

# 重みの更新
optimizer.step()

の繰り返しとなっています。

この繰り返しのイメージは、こんな感じ↓です。

 

評価

#----------------------------------------------------------
# 評価
model.eval()  # モデルを評価モードにする

loss_sum = 0
correct = 0

with torch.no_grad():
    for inputs, labels in test_dataloader:

        # GPUが使えるならGPUにデータを送る
        inputs = inputs.to(device)
        labels = labels.to(device)

        # ニューラルネットワークの処理を行う
        inputs = inputs.view(-1, image_size) # 画像データ部分を一次元へ並び変える
        outputs = model(inputs)

        # 損失(出力とラベルとの誤差)の計算
        loss_sum += criterion(outputs, labels)

        # 正解の値を取得
        pred = outputs.argmax(1)
        # 正解数をカウント
        correct += pred.eq(labels.view_as(pred)).sum().item()

print(f"Loss: {loss_sum.item() / len(test_dataloader)}, Accuracy: {100*correct/len(test_dataset)}% ({correct}/{len(test_dataset)})")

今回は、0~9までの画像分類を行っているので、出力(output)は10個のテンソルデータとなり、この10個のデータの配列中、値が最大となる値のインデックス番号が、推測された数字の値(0~9)と一致するようになっています。

 

まとめ

ここで紹介しているサンプルは、Deep Learningをするはしめの一歩として、もろもろシンプルにしています。

ニューラルネットワークはシンプルだし、学習中の過学習の評価用に検証データも欲しいし、画像も表示したい。

と、いろいろ不足しているかと思いますが、おいおい記事にしたいと思います。

 

PyTorchそのものは、画像を使ったDeepLearningで行われる、画像の分類、認識、領域分割専用に作られている訳では無いので、最初のハードルが少々高く感じます。

さらに、画像データをニューラルネットワークに流すには、画像データをテンソルに変換して。。

とかいうと、なぜか難しく感じてしまいます。

テンソルも、ただの配列なのに、numpyのndarrayは、比較的容易に受け入れていたのに、なぜ、PyTorchのテンソルは受け入れ難く感じるのか??

 

本記事では、その辺のハードルが少しでも下がって感じてもらえると幸いです。

【Microsoft Edge】お気に入りバーを常に表示する

Microsoft Edgeのお気に入りバーは、デフォルトでは、新しいタブを表示したときのみ表示され、ページ内のリンクをクリックするとお気に入りバーが非表示になってしまいます。

 

 

私はこれまで、Google Chromeを使っていてChromeに慣れているとEdgeでもお気に入りバーは常に表示しておきたい!

ということで、お気に入りバーを常に表示する方法は以下の通りです。

 

  1. (お気に入り)をクリック
  2. (その他のオプション)をクリック
  3. お気に入りバーの表示 にマウスポインタを合わせる
  4. 常に をクリック

 

これで、お気に入りバーは常に表示されるようになります。

 

【Windows11】ウィンドウ外側の影をなくす方法

Windows11ではウィンドウの外側に影が付いて表示されるようになりました。

これは、なんとなく雰囲気はよくなったものの、マニュアルやブログなどの作成時に、ウィンドウのスクリーンショットを撮るときは、邪魔となるケースもあります。

そこで、この影を表示させない方法を紹介します。

ウィンドウ外側の影を消す設定方法

ます、ウィンドウのスタートボタンを右クリックし、表示されたメニューの中からシステムを選択します。

 

表示されたウィンドウの右側にあるシステムの詳細設定をクリックします。

 

次にパフォーマンスの部分にある設定ボタンをクリックします。

 

すると、ウィンドウの下に影を表示するという項目があるので、ここのチェックを外し、適用ボタンをクリックします。

 

これで、設定は完了です。

影があるときと、影が無いときを比較すると、

影あり

影なし

 

個人的にはブログを書くときに影があると邪魔な場合が多いので、影はない方がいいですね。

また、スクリーンショットをウィンドウに合わせて撮影できるアプリなどもありますが、Windows11ではウィンドウの角が丸くなっているので、影があるとスクリーンショット画像の角が少し黒くなってしまうため、やっぱり影は邪魔です。

 

ちなみに、スクリーンショットを撮影するプログラムに Screenpressoというプログラムがあります。
私の中ではマニュアルやブログを書くときには必須のアプリになっていて、おススメです。

無料の画面キャプチャソフト『Screenpresso』

【OpenCV/Python】画像ファイルの読み込み、表示

OpenCVでBmpやJpegなどの画像ファイルを開くには、imread関数を用います。

開いた画像データはimshow関数でウィンドウ付きで画像を表示します。

実際に画像が表示されるのは、waitKey関数が呼ばれたタイミングとなります。

以下に最もシンプルなサンプルを示します。

import cv2

# 画像ファイルの読み込み(カラー画像(3チャンネル)として読み込まれる)
img = cv2.imread("Mandrill.bmp")

# 画像の表示
cv2.imshow("Image", img)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

(実行結果)

ここに示したサンプルでは、画像データは必ず8bit x 3ch のデータとなり、表示されたウィンドウはリサイズすることができません。

そのようにしないためには、各関数のオプションを設定します。

詳細は、各関数ごとに説明します。

imread()

bmp, jpeg, png, tiff などの画像ファイルを開きます。

cv2.imread( filename[, flags] ) -> retval
引数 説明
filename 画像のファイル名を指定します。
ただし、日本語は使えません。
flags ファイルを開く際に、カラーデータとして開くか?モノクロデータとして開くか?などを指定します。
指定しない場合、カラーデータ(BGRの3チャンネル)として画像ファイルを開きます。
cv2.IMREAD_UNCHANGED 画像ファイルのフォーマットのまま、画像ファイルを開きます。
cv2.IMREAD_GRAYSCALE   グレースケールに変換して画像ファイルを開きます。
cv2.IMREAD_COLOR          カラーデータ(BGRの3チャンネル)に変換して画像ファイルを開きます。
cv2.IMREAD_ANYDEPTH    画像ファイルのビット深度のまま画像ファイルを開きます。
cv2.IMREAD_ANYCOLOR    画像ファイルの色(チャンネル数)のまま画像ファイルを開きます。(参考)ImreadModes
戻り値 画像の輝度値がnumpyのndarray配列に格納されて返されます。
画像データは画像の左上から格納されています。
グレースケールの場合、[高さ, 幅]の二次元配列になります。
カラーの場合、[高さ, 幅, 色]の三次元配列になります。色の順番は、B,G,R,B,G,R・・・の順で格納されています。

画像ファイル名に日本語ファイルを使いたい場合は、NumPyやPillowを使って日本語ファイルを読み込む事も可能です。詳細は以下のページを参照ください。

日本語の画像ファイル読込・保存

また、imread()関数では、画像ファイルが見つからない場合など、画像ファイル読み込み時にエラーが発生しがちです。その場合の対応は以下のページを参照ください。

imreadで画像ファイルが読み込めないときの対応

 

(参考)

https://docs.opencv.org/4.5.5/d4/da8/group__imgcodecs.html#ga288b8b3da0892bd651fce07b3bbd3a56

imshow()

画像をウィンドウ付きで表示します。
実際に画面に画像が表示されるのは、cv2.waitKey()関数が呼ばれたタイミングで表示されます。

cv2.imshow( winname, mat ) -> None
引数 説明
winname 表示するウィンドウのタイトルを指定します。
namedWindow()関数で作成したウィンドウと同じwinnameを指定することで、画像を表示するウィンドウを切り替えることもできます。
winnameで指定したウィンドウが無い場合、新しいウィンドウで画像を表示します。
日本語を指定すると文字化けします。
mat 表示する画像データを指定します。
戻り値 なし

 

(参考)

https://docs.opencv.org/4.5.5/d7/dfc/group__highgui.html#ga453d42fe4cb60e5723281a89973ee563

waitKey()

指定時間(mSec)分、キー入力を待ちます。

OpenCVで生成したウィンドウのキー入力イベント待ちとなります。

引数 説明
delay キー入力までの待ち時間をmSec単位で指定します。
値を省略、もしくは 0以下 を指定した場合、無限待ちとなります。
戻り値 入力したキーコードが戻されます。

(参考)

https://docs.opencv.org/4.5.5/d7/dfc/group__highgui.html#ga5628525ad33f52eab17feebcfba38bd7

namedWindow()

imshow()関数で表示するためのウィンドウを生成します。

cv2.namedWindow( winname[, flags] ) -> None
引数 説明
winname 表示するウィンドウのタイトルを指定します。
日本語を指定すると文字化けします。
flags ウィンドウのリサイズに関する設定を指定します。
cv2.WINDOW_NORMAL リサイズ可能なウィンドウを生成します。
表示されている画像はウィンドウサイズに合わせてリサイズされます。
cv2.WINDOW_AUTOSIZE 画像のサイズに合わせてウィンドウサイズを調整します。
ウィンドウのリサイズはできません。
他に cv2.WINDOW_FREERATIO, cv2.WINDOW_KEEPRATIO, cv2.WINDOW_GUI_NORMAL, cv2.WINDOW_GUI_EXPANDED の設定がありますが、少なくともWindows環境ではcv2.WINDOW_NORMALと同じ
(参考)WindowFlags
戻り値 なし

生成したウィンドウはdestroyWindow()関数で指定したウィンドウを削除するか、destroyAllWindows()関数で全てのウィンドウを削除するようにします。

(参考)

https://docs.opencv.org/4.5.5/d7/dfc/group__highgui.html#ga5afdf8410934fd099df85c75b2e0888b

destroyWindow()

ウィンドウタイトル名を指定してウィンドウを閉じます。

cv2.destroyWindow( winname ) -> None
引数 説明
winname 閉じるウィンドウのタイトルを指定します。
戻り値 なし

(参考)

https://docs.opencv.org/4.5.5/d7/dfc/group__highgui.html#ga851ccdd6961022d1d5b4c4f255dbab34

destroyAllWindows()

表示されているウィンドウ全てを閉じます。

cv2.destroyAllWindows() -> None

(参考)

https://docs.opencv.org/4.5.5/d7/dfc/group__highgui.html#ga6b7fc1c1a8960438156912027b38f481

各種設定を行ったサンプルプログラム

import cv2

# 画像ファイルの読み込み(ファイルフォーマットのまま読み込み)
img1 = cv2.imread("Mandrill.bmp", cv2.IMREAD_UNCHANGED)
img2 = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)

# リサイズ可能なウィンドウの作成
cv2.namedWindow("Image1", cv2.WINDOW_NORMAL)
cv2.namedWindow("Image2", cv2.WINDOW_NORMAL)

# 画像の表示
cv2.imshow("Image1", img1)
cv2.imshow("Image2", img2)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

# すべて全てのウィンドウを削除
cv2.destroyAllWindows()

(実行結果)

【C#】各種メモリの最大値(2GB制限)

最近のPCではOSは64ビットで、搭載メモリも8GBぐらいは普通にあるので、C#のプログラムでもメモリを4GBぐらいは普通に確保できそうですが、実際には2BGぐらいで頭打ちになります。

おそらくメモリサイズ(要素数)を計算するときにint型で計算していてint型の最大値(2,147,483,647)を超える事でエラーになる場合が多そうです。

そこで、試しにメモリ確保の処理(配列、Bitmapクラス、Marshal.AllocCoTaskMem)の最大値が、どの程度まで確保できるのか?を確認してみました。

評価環境

  • Windows11 Home 64bit
  • 搭載メモリ 32GB
  • Visual Studio 2019
  • C#(.NET Core 3.1)

 

配列の最大値

よく使うbyte, int, float, doubleの型で試してみたところ、以下の要素数が最大となりました。

var byteArr = new byte[2147483591];
var intArr= new int[2146435071];
var floatArr= new float[2146435071];
var doubleArr= new double[2146435071];

配列の最大値に関しては、メモリのサイズというよりも、要素数のint型の最大値制限になっているようです。

.NET Frameworkの場合、デフォルトでは最大サイズが異なるようです。
詳細は、下記ページを参照ください。

https://docs.microsoft.com/ja-jp/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element?redirectedfrom=MSDN

Bitmapクラスの最大値

Bitmapクラスの確保には、幅、高さ、PixelFormatの組み合わせが、いろいろできてしまうため、幅の値は固定して、高さを変えながらエラーが出るまで最大の高さを確認しました。

以下が、PixelFormatをFormat8bppIndexed, Format24bppRgb, Format32bppArgbで確認したときの最大のサイズとなります。

var bmp8 = new Bitmap(1024 * 3, 699049, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var bmp24 = new Bitmap(1024, 699049, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
var bmp32 = new Bitmap(1024, 524287, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

Bitmapクラスに関しては、 幅 x 高さ x 画素のバイト数(1, 3, 4) の値がint型の最大値に引っかかっているようです。

 

AllocCoTaskMemの最大値

あまり使う機会はありませんが、メモリ確保で使われるAllocCoTaskMemについても調べてみました。

var ptr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(2147483647);

こちらは、AllocCoTaskMemの引数がint型のため、最大値を超えるとビルド時にエラーとなります。

 

まとめ

C#のメモリの最大値について調べてみましたが、おおむね2GBの制限があります。

メモリのサイズというより、要素数のint型の最大値(2,147,483,647)を超える事によりエラーになっているようです。

今時のPCで、2GBしかメモリを確保できないだなんて。。

1つのプロセス(実行しているプログラム)で2GB以下のメモリを複数確保する事は可能です。

どうしても2GB以上のメモリを確保したい場合は、メモリを分割して処理するか、C言語ライブラリを作成して、メモリ管理をライブラリ側で行う事で可能になります。
ただし、この時にも、うっかりint型で width * height * ch みたいな計算をしてしまうと、int型の最大値を超えてしまうので、要素数やメモリサイズを計算するときの型には注意が必要です。

 

参考

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/integral-numeric-types