【OpenCV-Python】cvtColorでBayer変換するときの定数

一般的なカラーカメラでは、モノクロのセンサの各画素にBayerと呼ばれるパターンのカラーフィルタが配置された状態で、画像を撮影しています。

このパターンはGRBG(詳細は後述)

 

このフィルタが配置された状態で撮影した画像は、下図のように、市松模様のノイズが入った状態のモノクロ画像となります。

 

 

この画像をRAWデータBayerデータと私は呼んでいます。
(RAWデータというと、圧縮されていないデータを差す場合もあるので、Bayerデータという事が多いです。)

 

最近の工業用のカラーカメラでは、カラーカメラでありながら、このBayerデータのモノクロ画像として取得される場合が多くなりました。

そのため、別途、FPGAやソフトでBayerデータから、カラー画像へ変換する必要があるのですが、OpenCVでは、cvtColor関数を使うと、カラー画像に変換してくれます。

cv.cvtColor(src, code[, dst[, dstCn]]) -> dst

codeの部分には、Bayerデータの画像の左上の2x2画素が、R,G,Bのどの色に対応しているか?で、BGGR, GBRG, GRBG, RGGB のいずれかを指定します。

 

(参考)https://docs.opencv.org/4.9.0/de/d25/imgproc_color_conversions.html

 

しかしながら、ここに、ちょっとした落とし穴がありました。

codeの部分には、最も単純な変換方法では、以下の定数の中から指定します。

(参考)https://docs.opencv.org/4.9.0/d8/d01/group__imgproc__color__conversions.html

 

COLOR_BayerBG2BGR, COLOR_BayerGB2BGR, COLOR_BayerRG2BGR, COLOR_BayerGR2BGR,
COLOR_BayerBG2RGB, COLOR_BayerGB2RGB, COLOR_BayerRG2RGB, COLOR_BayerGR2RGB

は、昔からあった定数で、のちに

COLOR_BayerBGGR2BGR, COLOR_BayerGBRG2BGR,
COLOR_BayerRGGB2BGR, COLOR_BayerGRBG2BGR,
COLOR_BayerBGGR2RGB, COLOR_BayerGBRG2RGB,
COLOR_BayerRGGB2RGB, COLOR_BayerGRBG2RGB

が、あらたに定義されました。

 

例えば COLOR_BayerBGGR2BGR であれば、画像の左上の2 x 2画素のパターンが BGGR となります。

しかし!

COLOR_BayerBGGR2BGR に対応した昔ながらの定数が COLOR_BayerBG2BGR か?というと、そうではありません。

 

ここで、もう一度、この定義を見直します。

 

数学で、 A=B かつ B=C ならば C=A の理屈で、定数を見ると、

 

COLOR_BayerBGGR2BGR = COLOR_BayerRG2BGR

COLOR_BayerRG2BGR = COLOR_BayerBG2RGB

より

COLOR_BayerBGGR2BGR = COLOR_BayerBG2RGB

となります。

 

つまり、画像の左上の2×2画素のパターンが、この↓パターンのとき、

COLOR_BayerBGGR2BGR、COLOR_BayerRG2BGR、COLOR_BayerBG2RGB

のいづれかを指定する必要があります。

 

このパターンで、COLOR_BayerRG2BGRを指定するのは少し無理があるし、COLOR_BayerBG2RGBを指定するのは、気持ち悪い。

※cvtColorで指定する定数は、メモリに配置されている順番で、通常カラー画像の場合、BGRを指定します。

 

全てのパターンを書き出すと

COLOR_BayerRGGB2BGR = COLOR_BayerBG2BGR = COLOR_BayerRG2RGB
COLOR_BayerGRBG2BGR = COLOR_BayerGB2BGR = COLOR_BayerGR2RGB
COLOR_BayerBGGR2BGR = COLOR_BayerRG2BGR = COLOR_BayerBG2RGB
COLOR_BayerGBRG2BGR = COLOR_BayerGR2BGR = COLOR_BayerGB2RGB

となっています。

 

Bayerの4つのパターンを指定するとき、OpenCV以外で一般的には、画像の左上の2x2画素の色のうち、左上、右上の順で画素の色を指定する場合が多いのですが、出力の色の順(BGR)を信じるのであれば、OpenCVでは、2x2画素中の右下、左下の順で色を指定している事になります。

色のパターン(左上、右上の順)を信じるのであれば、出力する色の順の指定はR,G,Bの順となってしまいますが、実際にはB,G,Rの順入っていると思うと辻褄が合います。

 

が、やっぱり気持ち悪いので、画像の左上の2x2画素の色を使った

 

COLOR_BayerBGGR2BGR
COLOR_BayerGBRG2BGR
COLOR_BayerRGGB2BGR
COLOR_BayerGRBG2BGR

 

を使うようにしましょう!

【OpenCV-Python】getTextSize(文字の大きさを調べる)

OpenCVで描画する文字の大きさを調べるにはgetTextSize()関数を用います。

文字を描画するputText()関数では、文字を描画する位置を文字の左下の位置で指定しますが、文字の位置を上寄せ、下寄せ、右寄せ、左寄せ、左寄せなどを行いたい場合は、描画する文字の領域を調べ、描画位置を補正することで、可能になります。

 

getTextSizeの構文

getTextSize( text, fontFace, fontScale, thickness ) -> size, baseLine

引数

text 描画に用いる文字列
fontFace 描画時のフォント
cv2.FONT_HERSHEY_SIMPLEX
cv2.FONT_HERSHEY_PLAIN
cv2.FONT_HERSHEY_DUPLEX
cv2.FONT_HERSHEY_COMPLEX
cv2.FONT_HERSHEY_TRIPLEX
cv2.FONT_HERSHEY_COMPLEX_SMALL
cv2.FONT_HERSHEY_SCRIPT_SIMPLEX
cv2.FONT_HERSHEY_SCRIPT_COMPLEX
cv2.FONT_ITALIC
fontScale 文字の大きさの倍率
thickness 線幅を整数で指定します。

 

戻り値

size 文字の大きさを(幅, 高さ)のタプルで取得します。
baseLine 文字(g, j, p, q, y)の領域から下側にはみ出す量を取得します。

 

サンプルプログラム

import cv2
import numpy as np

img = np.zeros((200, 600, 3), dtype = np.uint8)

text = "abgpqABC123"    # 大きさを測る文字列
fontFace = cv2.FONT_HERSHEY_SIMPLEX # フォント
fontScale = 2           # 文字のサイズ
thickness = 1           # 文字の太さ

bottom_left = (50, 100)

# 文字の大きさを測る
size, baseLine = cv2.getTextSize(text, fontFace, fontScale, thickness)
print(size, baseLine ) # 出力結果 ->  (431, 43) 18

# putTextで指定した左下の位置
cv2.circle(img, bottom_left, 5, (255, 255, 0), -1)
# getTextSizeで取得した領域
cv2.rectangle(img, bottom_left, (bottom_left[0] + size[0], bottom_left[1] - size[1]), (0, 0, 255), 1)
# baseline(g, j, p, q, yの下側にはみ出す量)
cv2.line(img, (bottom_left[0], bottom_left[1] + baseLine), (bottom_left[0] + size[0], bottom_left[1] + baseLine),
         (0, 255, 255), 1)

# 文字の描画
cv2.putText(img, text, bottom_left, fontFace, fontScale, (255, 255, 255), thickness)

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

実行結果

 

関連リンク

https://imagingsolution.net/program/python/opencv-python/opencv-reference/opencv-python-puttext/

参照ページ

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#ga3d2abfcb995fd2db908c8288199dba82

【OpenCV-Python】putText(文字の描画)

OpenCVで画像に文字を描画するにはputText()関数を用います。

ただし、日本語の描画を行うことはできません。

putTextの構文

putText( img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]] ) -> img

引数

img 文字の描画先の画像データ
text 描画する文字列
ただし、日本語には対応していません。
org 文字を描画する位置を文字領域の左下の位置で指定します。
fontFace 文字のフォントの種類を以下の中から指定します。
cv2.FONT_HERSHEY_SIMPLEX
cv2.FONT_HERSHEY_PLAIN
cv2.FONT_HERSHEY_DUPLEX
cv2.FONT_HERSHEY_COMPLEX
cv2.FONT_HERSHEY_TRIPLEX
cv2.FONT_HERSHEY_COMPLEX_SMALL
cv2.FONT_HERSHEY_SCRIPT_SIMPLEX
cv2.FONT_HERSHEY_SCRIPT_COMPLEX
cv2.FONT_ITALIC
fontScale 文字を大きさの倍率を指定します。
color 線色
カラーの場合は、(B, G, R)の順のタプルで指定します。
thickness 線幅を整数で指定します。
lineType cv2.LINE_4     4連結
cv2.LINE_8     8連結(デフォルト)
cv2.LINE_AA   アンチエイリアス
のいずれかを指定します。
bottomLeftOrigin 画像データの始まりの位置が左下の場合、Trueを指定します。
通常は、画像データの始まりは左上なので、False(初期値)で大丈夫です。

 

戻り値

img 画像データに追加で文字が描画された画像データ(引数で渡した画像と同じデータ)

サンプルプログラム

import cv2
import numpy as np

img = np.zeros((400, 600, 3), dtype = np.uint8)

# Textの描画
img = cv2.putText(img, "abcdefg ABC 123", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255))
# Textの表示位置
img = cv2.circle(img, (10, 40), 5, (0, 0, 255),  -1)

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

実行結果

 

フォントの種類について

フォントの種類については、全てのフォントを使ったサンプルを作成しましたので、こちらを参照ください。

import cv2
import numpy as np

img = np.zeros((400, 600, 3), dtype = np.uint8)

# Textの描画(Fontごとの描画)
img = cv2.putText(img, "FONT_HERSHEY_SIMPLEX", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_PLAIN", (10, 80), cv2.FONT_HERSHEY_PLAIN, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_DUPLEX", (10, 120), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_COMPLEX", (10, 160), cv2.FONT_HERSHEY_COMPLEX, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_TRIPLEX", (10, 200), cv2.FONT_HERSHEY_TRIPLEX, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_COMPLEX_SMALL", (10, 240), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_SCRIPT_SIMPLEX", (10, 280), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_HERSHEY_SCRIPT_COMPLEX", (10, 320), cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 1, (255, 255, 255))
img = cv2.putText(img, "FONT_ITALIC", (10, 360), cv2.FONT_ITALIC, 1, (255, 255, 255))

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

実行結果

 

その他のオプション設定

putText()関数では、フォントの種類の他に、文字の表示倍率、線色、色、線幅、アンチエイリアス、データの基準位置に関する設定が可能になっています。

こちらについては、それぞれのオプションを設定したサンプルを作成しましたので、合わせてご参照ください。

import cv2
import numpy as np

img = np.zeros((400, 600, 3), dtype = np.uint8)

# 1倍
img = cv2.putText(img, "abcdefg ABC 123", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255))
# 2倍
img = cv2.putText(img, "abcdefg ABC 123", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255))

# 文字色を赤
img = cv2.putText(img, "abcdefg ABC 123", (10, 140), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255))
# 線幅を3
img = cv2.putText(img, "abcdefg ABC 123", (10, 180), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 3)
# lineTypeをLINE_AA 
img = cv2.putText(img, "abcdefg ABC 123", (10, 220), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv2.LINE_AA)
# bottomLeftOrigin をTrue
img = cv2.putText(img, "abcdefg ABC 123", (10, 260), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv2.LINE_AA, True)

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

実行結果

 

関連リンク

https://imagingsolution.net/program/python/opencv-python/opencv-reference/opencv-python-linetypes/

 

参照ページ

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#ga5126f47f883d730f633d74f07456c576

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#ga0f9314ea6e35f99bb23f29567fc16e11

 

【OpenCV-Python】CvZoomWindow class(ズーム、パン機能付き画像表示ウィンドウ)

OpenCVで画像を表示するには、namedWindow()でウィンドウを作成して、imshow()で画像を表示しますが、画像は等倍で表示するか、cv2.WINDOW_NORMALを指定して、ウィンドウのサイズに合わせて画像の拡大縮小を行う事はできますが、画像の縦横比が狂ってしまうので、あまり使いやすい物にはなっていません。

そこで、namedWindow()のコールバック関数部分で、画像の拡大縮小や移動の表示処理部分を作成し、クラスにまとめた CvZoomWindowクラスを作成しました。

 

インストール方法

pip install cvzoomwindow

 

サンプルコード

import cv2
import cvzoomwindow

# Image loading
img = cv2.imread("image.bmp")

# Instance of CvZoomWindow class
zw = cvzoomwindow.CvZoomWindow(
    "Zoom Window") # Name of the window 

# Displays an image
zw.imshow(img)

# Waits for a pressed key.
cv2.waitKey()

 

ウィンドウ操作方法

マウス操作 表示
左ボタンのダブルクリック 画像全体表示
右ボタンダブルクリック 等倍(1倍)表示
左ボタンのドラッグ 画像の移動
マウスホイールの上回転 画像の拡大
マウスホイールの下回転 画像の縮小

 

CvZoomWindowクラス

 

コンストラクタ

CvZoomWindowクラスの内部で、OpenCVのnamedWindow()関数により画像表示用ウィンドウを作成し、マウス操作によるコールバック処理を追加しています。

def __init__(self, winname : str, back_color = (128, 128, 0), inter = cv2.INTER_NEAREST):
引数 説明
winname 表示するウィンドウのタイトルを指定します。
日本語を指定すると文字化けします。
back_color ウィンドウの背景色を(B, G, R)の値で指定します。
inter 画像表示の補間モードを指定します。
cv2.INTER_NEAREST
cv2.INTER_LINEAR
cv2.INTER_CUBIC
cv2.INTER_AREA
cv2.INTER_LANCZOS4

 

imshowメソッド

画像をウィンドウに表示します。

OpenCVのcv2.imshow()相当です。
実際に画面に画像が表示されるのは、cv2.waitKey()関数が呼ばれたタイミングで表示されます。

def imshow(self, image, zoom_fit : bool = True):
引数 説明
image 表示するOpenCVの画像データを指定します。
zoom_fit imshowメソッド実行後に画像全体を表示するかどうかの設定
True: 画像全体を表示する(初期値)
Flase:画像全体を表意しないカメラ画像など、連続的に画像を表示する場合はFalseに設定してください。
inter 画像表示の補間モードを指定します。
cv2.INTER_NEAREST
cv2.INTER_LINEAR
cv2.INTER_CUBIC
cv2.INTER_AREA
cv2.INTER_LANCZOS4

 

set_mouse_callbackメソッド

マウス操作によるコールバック関数を登録します。

 

コールバック関数は以下のように登録します。

import cvzoomwindow

# CvZoomWindowクラスのインスタンス
zw = cvzoomwindow.CvZoomWindow("Zoom Window" # Name of the window)

# コールバック関数
def mouse_callback(obj, event, w_x, w_y, flags, params, img_x, img_y, scale):
    print(event, w_x, w_y, flags, params, img_x, img_y, scale)
# コールバック関数の登録
zw.set_mouse_callback(mouse_callback)

コールバック関数は、OpenCVのcv2.setMouseCallback()関数で取得できる値に追加して、画像上の座標と画像の表示倍率を取得できます。

def mouse_callback(obj, event, w_x, w_y, flags, params, img_x, img_y, scale):

引数

obj コールバック元のクラスオブジェクト
event 押されたマウスボタンの種類および状態の値。別途、下記参照
w_x マウスポインタのウィンドウ上のX座標
w_y マウスポインタのウィンドウ上のY座標
flags Shift, Ctrl, Altキーの押された種類。別途、下記参照
params ウィンドウのタイトル
img_x マウスポインタの画像上のX座標
img_y マウスポインタの画像上のY標
scale 画像の表示倍率
●eventの値
cv2.EVENT_MOUSEMOVE マウスが動いたとき(ボタンは押されていない)
cv2.EVENT_LBUTTONDOWN マウスの左ボタンが押されたとき
cv2.EVENT_RBUTTONDOWN マウスの右ボタンが押されたとき
cv2.EVENT_MBUTTONDOWN マウスの中央ボタンが押されたとき
cv2.EVENT_LBUTTONUP マウスの左ボタンが離されたとき
cv2.EVENT_RBUTTONUP マウスの右ボタンが離されたとき
EVENT_MBUTTONUP マウスの中央ボタンが離されたとき
EVENT_LBUTTONDBLCLK マウスの左ボタンがダブルクリックされたとき
EVENT_RBUTTONDBLCLK マウスの右ボタンがダブルクリックされたとき
EVENT_MBUTTONDBLCLK マウスの中央ボタンがダブルクリックされたとき
EVENT_MOUSEWHEEL flagの値が正のとき、マウスホイールを上へ回転
EVENT_MOUSEHWHEEL flagの値が負のとき、マウスホイールを下へ回転
●flagsの値
cv2.EVENT_FLAG_LBUTTON マウスの左ボタン
cv2.EVENT_FLAG_RBUTTON マウスの右ボタン
cv2.EVENT_FLAG_MBUTTON マウスの中央ボタン
cv2.EVENT_FLAG_CTRLKEY Ctrlキー
cv2.EVENT_FLAG_SHIFTKEY Shiftキー
cv2.EVENT_FLAG_ALTKEY Altキー

 

プロパティ

Property name Get Set
affine_matrix 画像表示に用いたアフィン変換行列
bright_disp_enabled 輝度値表示の有効(True)、無効(False)設定
displayed_image 拡大縮小されてウィンドウに表示されている画像データ(np.ndarray)
grid_color グリッド線の色を(B,G,R)で設定
grid_disp_enabled グリッド線表示の有効(True)、無効(False)設定
inter 画像表示の補間方法をOpenCVのcv::InterpolationFlags enumで指定します。
max_scale 最大画像表示倍率
min_bright_disp_scale 輝度値を表示する最小倍率
min_grid_disp_scale グリッド線を表示する最小倍率
min_scale 最小画像表示倍率
mouse_event_enabled マウス操作による画像の拡大縮小、移動の操作の有効(True)、無効(False)設定
scale 現在の画像表示倍率
winname ウィンドウのタイトル名
zoom_delta マウスホイール1ノッチあたりの倍率を1以上の値で設定します。

 

関連ページ

https://pypi.org/project/cvzoomwindow/

【OpenCV-Python】Tkinter GUI Sample

 

まとめ

OpenCVのウィンドウで画像の拡大縮小表示をするのは、出来ないと思っていたので、Tkinterで作ってみたのですが、そもそもTkinterでウィンドウを作るのが大変なので、このCvZoomWindowは、とても簡単に使えるのでおススメです。

ライセンス的にはMITとしましたが、本当に自由に使って頂いて構いません。

現状、既知のバグとして、

●マウス操作で画像の拡大/縮小を繰り返した状態で、ウィンドウの×ボタンでウィンドウを閉じるとエラーになります。

●画像を表示した状態で、ウィンドウをリサイズすると、縦横比の崩れた画像が表示されます。

→マウス操作で、画像の拡大、縮小、移動などを行うと、元に戻ります。

 

 

【OpenCV-Python】getWindowImageRect(画像表示領域の取得)

OpenCVのnameWindow()やimshow()で表示されたウィンドウの画像表示領域の情報(位置、幅、高さ)を取得するには、getWindowImageRect()関数を用います。

 

getWindowImageRect()関数で取得する領域の情報は、画像表示領域の位置は、デスクトップのx, y座標となります。

 

getWindowImageRect()関数の構文

getWindowImageRect( winname ) -> retval

引数

winname 画像表示領域を取得するウィンドウのタイトル名

 

戻り値

retval 画像の表示領域の情報
画像表示領域の左上の座標を(x, y)、画像表示領域のサイズを(width, height)とすると、(x, y, width, height) となるタプルで取得します。

 

サンプルプログラム

ウィンドウの画像部分をマウスの左クリックすると、画像の表示領域の情報をコンソールに表示されます。

import cv2
import numpy as np

WINDOW_TITLE = 'Image Window'

def onMouse(event, x, y, flags, params):
    '''マウスのコールバック関数'''

    if event == cv2.EVENT_LBUTTONDOWN:
        # 左ボタンがクリックされたとき、領域情報の表示
        rect = cv2.getWindowImageRect(WINDOW_TITLE)
        print(f"rect:{rect}")

# テスト画像の生成
image = np.zeros((30, 50, 3), dtype = np.uint8)
cv2.circle(image, (25, 15), 10, (0, 255, 0), -1, cv2.LINE_AA)

cv2.namedWindow(WINDOW_TITLE, cv2.WINDOW_NORMAL)
cv2.imshow(WINDOW_TITLE, image)

# コールバック関数の登録
cv2.setMouseCallback(WINDOW_TITLE, onMouse, "Image")

cv2.waitKey() 

 

参照ページ

https://docs.opencv.org/4.x/d7/dfc/group__highgui.html#ga73b55e9851b5691f6a6a4a4e6c57258e

【OpenCV-Python】rectangle(矩形(長方形)の描画)

OpenCVで矩形(長方形)を描画するにはrectangle()関数を用います。

 

rectangle()関数には、長方形の対角の2点の座標を指定する方法と、Rect(長方形の左上のX座標、Y座標、幅、高さ)を指定する方法があります。

 

長方形の対角の2点を指定する方法

rectangle( img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img

引数

img 線の描画先の画像データ
pt1 矩形を構成する座標 (X座標, Y座標)をタプルで指定します。
ただし、座標の値は整数です。(小数は非対応)
pt2 pt1の対角の座標 (X座標, Y座標)をタプルで指定します。
ただし、座標の値は整数です。(小数は非対応)
color 線色
カラーの場合は、(B, G, R)の順のタプルで指定します。
thickness 線幅を整数で指定します。
マイナスの値を指定すると、矩形の内側が塗りつぶされます。
lineType cv2.LINE_4     4連結
cv2.LINE_8     8連結(デフォルト)
cv2.LINE_AA   アンチエイリアス
のいずれかを指定します。
shift 点座標の小数ビット数、デフォルトで0
x, y座標が以下の式に基づいて変換されます。
(x, y) -> (x * 2^(-shift),  y * 2^(-shift))

pt1, pt2の座標は、長方形の対角の座標であれば、組み合わせ、順番は任意に指定できます。

 

戻り値

img 画像データに追加で線が描画された画像データ(引数で渡した画像と同じデータ)

 

サンプルプログラム

import cv2
import numpy as np

img = np.zeros((10, 15, 3), dtype = np.uint8) # 幅15画素、高さ10画素で確保

# Rectで矩形の描画
# Rectは (左上のX座標、Y座標、矩形の幅、高さを指定)
cv2.rectangle(img, (1, 1), (13, 8), (0, 0, 255), 1)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

 

Rect(長方形の左上のX座標、Y座標、幅、高さ)を指定する方法

rectangle( img, rec, color[, thickness[, lineType[, shift]]] ) -> img

引数

img 線の描画先の画像データ
rec 矩形の領域を、矩形の(左上のX座標, 左上のY座標, 矩形の幅, 矩形の高さ)の順のタプルで指定します。
ただし、値は整数です。(小数は非対応)
color 線色
カラーの場合は、(B, G, R)の順のタプルで指定します。
thickness 線幅を整数で指定します。
マイナスの値を指定すると、矩形の内側が塗りつぶされます。
lineType cv2.LINE_4     4連結
cv2.LINE_8     8連結(デフォルト)
cv2.LINE_AA   アンチエイリアス
のいずれかを指定します。
shift 点座標の小数ビット数、デフォルトで0
x, y座標が以下の式に基づいて変換されます。
(x, y) -> (x * 2^(-shift),  y * 2^(-shift))

 

戻り値

img 画像データに追加で線が描画された画像データ(引数で渡した画像と同じデータ)

 

サンプルプログラム

import cv2
import numpy as np

img = np.zeros((10, 15, 3), dtype = np.uint8) # 幅15画素、高さ10画素で確保

# Rectで矩形の描画
# Rectは (左上のX座標、Y座標、矩形の幅、高さを指定)
cv2.rectangle(img, (1, 1, 13, 8), (255, 0, 0), 1)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

 

注意点

矩形の左上のX座標、Y座標、幅、高さで指定したとき、幅方向、高さ方法の座標間の距離は、

 矩形の幅 – 1矩形の高さ – 1

になる事に注意してください。

rec = (1, 1, 13, 8) として、指定した場合の座標は、以下のようになります。

 

関連リンク

https://imagingsolution.net/program/python/opencv-python/opencv-reference/opencv-python-line/

 

参照ページ

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#ga07d2f74cadcf8e305e810ce8eed13bc9

【OpenCV-Python】circle(円の描画)

OpenCVで画像に円を描画するには、circle()関数を用います。

circle()関数の構文

circle( img, center, radius, color[, thickness[, lineType[, shift]]] ) -> img

引数

img 円の描画先の画像データ
center 中心の座標 (X座標, Y座標)のタプルで指定します。
ただし、座標の値は整数です。(小数は非対応)
radius 円の半径を整数で指定します。
color 線色
カラーの場合は、(B, G, R)の順のタプルで指定します。
thickness 線幅を整数で指定します。
負の値を指定すると、大きさに関係なく、円の内側が塗りつぶされます。
lineType cv2.LINE_4     4連結
cv2.LINE_8     8連結(デフォルト)
cv2.LINE_AA   アンチエイリアス
のいずれかを指定します。
shift 点座標の小数ビット数、デフォルトで0
x, y座標が以下の式に基づいて変換されます。
(x, y) -> (x * 2^(-shift),  y * 2^(-shift))

戻り値

img 画像データに追加で円が描画された画像データ(引数で渡した画像と同じデータ)

 

lineType(アンチエイリアス)を変えながら描画したサンプル

import cv2
import numpy as np

img = np.zeros((50, 50, 3), dtype = np.uint8)

cv2.circle(img, (25, 25), 20, (255, 0, 0),  1, cv2.LINE_4)
cv2.circle(img, (25, 25), 15, (0, 255, 0),  1, cv2.LINE_8)
cv2.circle(img, (25, 25), 10, (0, 0, 255),  1, cv2.LINE_AA)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

 

線幅を変えながら描画したサンプル

import cv2
import numpy as np

img = np.zeros((50, 50, 3), dtype = np.uint8)

cv2.circle(img, (25, 25), 22, (255, 0, 0),  3)
cv2.circle(img, (25, 25), 15, (0, 255, 0),  2)
cv2.circle(img, (25, 25), 10, (0, 0, 255),  1)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

 

円の内側を塗りつぶして描画したサンプル

import cv2
import numpy as np

img = np.zeros((50, 50, 3), dtype = np.uint8)

cv2.circle(img, (25, 25), 20, (255, 0, 0),  -3)
cv2.circle(img, (25, 25), 15, (0, 255, 0),  -2)
cv2.circle(img, (25, 25), 10, (0, 0, 255),  -1)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

半径(radius)が負の場合、値の大きさに関係なく、円の内側が塗りつぶされます。

 

参考リンク

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#gaf10604b069374903dbd0f0488cb43670

https://imagingsolution.net/program/python/opencv-python/opencv-reference/opencv-python-linetypes/

 

【OpenCV-Python】setMouseCallback(マウスのコールバック関数登録)

OpenCVのnamedWindow()で作られたウィンドウやimshow()で自動で表示されたウィンドウのマウスイベントのコールバック関数を登録するにはsetMouseCallback()関数を用います。

 

setMouseCallback()関数の構文

cv2.setMouseCallback(winname, onMouse[, userdata])
引数
winname コールバック関数に関連付けるウィンドウのタイトル
onMouse 登録するコールバック関数名
userdata コールバック関数へ渡す値(オプション)
(ウィンドウタイトルを渡すと便利)

登録するコールバック関数の構文

コールバック関数の関数名は何でも構いませんが、引数は以下のようにする必要があります。
def onMouse(event, x, y, flags, params):
引数
event 押されたマウスボタンの種類および状態の値。別途、下記参照
x マウスポインタのX座標(ウィンドウの座標ではなく画像上のX座標)
y マウスポインタのX座標(ウィンドウの座標ではなく画像上のY座標)
flags Shift, Ctrl, Altキーの押された種類。別途、下記参照
params コールバック関数登録時に渡された値
●eventの値
cv2.EVENT_MOUSEMOVE マウスが動いたとき(ボタンは押されていない)
cv2.EVENT_LBUTTONDOWN マウスの左ボタンが押されたとき
cv2.EVENT_RBUTTONDOWN マウスの右ボタンが押されたとき
cv2.EVENT_MBUTTONDOWN マウスの中央ボタンが押されたとき
cv2.EVENT_LBUTTONUP マウスの左ボタンが離されたとき
cv2.EVENT_RBUTTONUP マウスの右ボタンが離されたとき
EVENT_MBUTTONUP マウスの中央ボタンが離されたとき
EVENT_LBUTTONDBLCLK マウスの左ボタンがダブルクリックされたとき
EVENT_RBUTTONDBLCLK マウスの右ボタンがダブルクリックされたとき
EVENT_MBUTTONDBLCLK マウスの中央ボタンがダブルクリックされたとき
EVENT_MOUSEWHEEL flagの値が正のとき、マウスホイールを上へ回転
flagの値が負のとき、マウスホイールを下へ回転
EVENT_MOUSEHWHEEL
●flagsの値
cv2.EVENT_FLAG_LBUTTON マウスの左ボタン
cv2.EVENT_FLAG_RBUTTON マウスの右ボタン
cv2.EVENT_FLAG_MBUTTON マウスの中央ボタン
cv2.EVENT_FLAG_CTRLKEY Ctrlキー
cv2.EVENT_FLAG_SHIFTKEY Shiftキー
cv2.EVENT_FLAG_ALTKEY Altキー

サンプルプログラム

import cv2
import numpy as np

def create_test_image():
    '''テスト画像の作成'''
    img = np.zeros((30, 50, 3), dtype = np.uint8)
    cv2.circle(img, (10, 15), 10, (255, 0, 0), -1, cv2.LINE_AA)
    cv2.circle(img, (25, 15), 10, (0, 255, 0), -1, cv2.LINE_AA)
    cv2.circle(img, (40, 15), 10, (0, 0, 255), -1, cv2.LINE_AA)
    return img

def onMouse(event, x, y, flags, params):
    '''マウスのコールバック関数

    Parameters
    ----------
    event : int
        押されたマウスボタンの種類
    x : int
        マウスポインタの画像上のX座標
    y : int
        マウスポインタの画像上のY座標
    flags : int
        Shift, Ctrl, Altキーの押された種類
    params : 
        コールバック関数登録時に渡された値
    '''
    if disp_image is not None:
        print(f"[{x}, {y}] (R, G, B) = ({disp_image[y, x, 2]}, {disp_image[y, x, 1]}, {disp_image[y, x, 0]}) event = {event} flags = {flags} params = {params}")

# テスト画像の生成
disp_image = create_test_image()

cv2.namedWindow('Image', cv2.WINDOW_NORMAL)
cv2.imshow('Image', disp_image)

# コールバック関数の登録
cv2.setMouseCallback('Image', onMouse, "Image")

cv2.waitKey() 
実行結果

参照ページ

https://docs.opencv.org/4.8.0/d7/dfc/group__highgui.html#ga89e7806b0a616f6f1d502bd8c183ad3e
https://docs.opencv.org/4.8.0/d0/d90/group__highgui__window__flags.html#gaab4dc057947f70058c80626c9f1c25ce
https://docs.opencv.org/4.8.0/d0/d90/group__highgui__window__flags.html#ga927593befdddc7e7013602bca9b079b0

【OpenCV-Python】line(線の描画)

OpenCVで画像に線を描画するには、line()関数を用います。

line()関数の構文

line(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) ->img

引数

img 線の描画先の画像データ
pt1 線の始点の座標 (X座標, Y座標)のタプルで指定します。
ただし、座標の値は整数です。(小数は非対応)
pt2 線の終点の座標 (X座標, Y座標)のタプルで指定します。
ただし、座標の値は整数です。(小数は非対応)
color 線色
カラーの場合は、(B, G, R)の順のタプルで指定します。
thickness 線幅を整数で指定します。
lineType cv2.LINE_4     4連結
cv2.LINE_8     8連結(デフォルト)
cv2.LINE_AA   アンチエイリアス
のいずれかを指定します。
shift 点座標の小数ビット数、デフォルトで0
x, y座標が以下の式に基づいて変換されます。
(x, y) -> (x * 2^(-shift),  y * 2^(-shift))

 

戻り値

img 画像データに追加で線が描画された画像データ(引数で渡した画像と同じデータ)

 

サンプルプログラム

import cv2
import numpy as np

img = np.zeros((10, 10, 3), dtype = np.uint8)

cv2.line(img, (1, 0), (8, 3), (10, 20, 128), 1)
cv2.line(img, (8, 5), (1, 8), (255, 128, 10), 1)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

 

まとめ

線の始点、終点の座標は整数となります。

特に、座標を計算で求めた時に、小数座標を渡してエラーにはまりやすいので、注意してください。

 

参考リンク

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#ga7078a9fae8c7e7d13d24dac2520ae4a2

https://imagingsolution.net/program/python/opencv-python/opencv-reference/opencv-python-linetypes/

https://imagingsolution.net/program/python/opencv-python/create_new_opencv_image_data/

https://docs.opencv.org/3.4/d6/d6e/group__imgproc__draw.html

【OpenCV-Python】カメラからの画像取込を別スレッドにする

カメラからの画像取込を、なぜ、わざわざ別スレッドにしたいか?というと、よくある画像取込のプログラム

import cv2

# カメラを開く
cap = cv2.VideoCapture(0)

while True:
    # 画像をキャプチャする
    _, frame = cap.read()

    # 画像を表示する
    cv2.imshow("Image", frame)

    # キーを押すとループを終了する
    if cv2.waitKey(1) > 0:
        break

# カメラを閉じる
cap.release()
# すべてのウィンドウを閉じる
cv2.destroyAllWindows()

を実行し、画像が表示されたウィンドウのタイトルバーの部分をマウスでクリックすると、while文のループ内で、waitKey(1) の部分で処理が停止します。

 

処理が停止するということは、画像の表示(imshow())や画像の取込(read())も、つられて実行されなくなってしまいます。

 

画像の表示は、最悪、停止してもしょうがないとしても、画像の取込が停止するのは、画像の取込ながら画像処理する場合には、不都合です。

 

そこで、画像の取込部分を別スレッドにしたプログラムがこちら↓

import threading
import queue

import cv2

# カメラを開く
cap = cv2.VideoCapture(0)

# フレーム共用のキュー
q_frames = queue.Queue()

def frame_update():
    '''画像取込'''
    while True:
        # カメラ画像の取得
        _, frame = cap.read()
        # 取得した画像をキューに追加
        q_frames.put(frame)
        print(f"qsize:{q_frames.qsize()}")

# 画像取り込みスレッドの作成
thread = threading.Thread(target=frame_update, daemon=True)
# スレッドの開始
thread.start()

# 画像表示用スレッド
while True:
    frame = q_frames.get() # queueが空の場合、キューに何か入るまで、スレッドをブロックする。
    cv2.imshow("Image", frame)

    # キーを押すとループを終了する
    if cv2.waitKey(1) > 0:
        break

# カメラを閉じる
cap.release()
# すべてのウィンドウを閉じる
cv2.destroyAllWindows()

このようにすることで、画像を表示しているウィンドウのタイトルバーの部分をクリックしても、画像の取込(read())が停止する事は無くなります。

 

ただし、少々、厄介なのが、別スレッドからGUIの表示を行う事が出来ないので、queueを介して画像データをメインスレッド側で画像を表示するようにしています。

 

今回は、waitKeyの部分で処理が停止してしまうのを、スレッドで回避していますが、waitKeyの関数に限らず、処理が停止してしまう関数が他にもあるので、注意が必要です。

【OpenCV-Python】色を指定して新規画像データを初期化

OpenCVで、画像に線や円などを描画する際、ベースとなる画像データを色を指定して新規作成したい時があるかと思います。

 

PythonのOpenCVにはC++のMatクラスのようなものはなく、画像データはnumpyのndarrayなので、numpyの関数を使って画像データを新規作成します。

 

numpyで画像データを作成するには、

 

●すべての画像データを0で初期化する  zeros

●すべての画像データを1で初期化する  ones

●すべての画像データを指定した値で初期化する  full

 

のいずれかの関数を用いる事で、画像データの新規作成が可能になります。

 

zeros を用いる方法

zerosは画像データをすべて0で初期化します。

0で初期化した画像に、値を足すと、足した値一色の画像データとなります。

 

zerosの構文

numpy.zeros(shape, dtype=float, order='C', *, like=None)
shape 配列のサイズを指定します。
モノクロの場合:(高さ, 幅)
カラーの場合:(高さ, 幅, 3)や(高さ, 幅, 4)
dtype データの型を指定します。
画像データなので、
dtype = np.uint8
とします。
order ‘C’ もしくは ‘F’  詳細は不明。。
like 詳細は不明。。

 

サンプルプログラム

import cv2
import numpy as np

# モノクロ(黒(0))で初期化
img_mono_black = np.zeros((100, 400), dtype = np.uint8)
# モノクロ(白(255))で初期化
img_mono_white = np.zeros((100, 400), dtype = np.uint8) + 255
# カラー(黒(0))で初期化
img_color_black = np.zeros((100, 400, 3), dtype = np.uint8) 
# カラー(白(255))で初期化
img_color_white = np.zeros((100, 400, 3), dtype = np.uint8) + 255

cv2.imshow("mono_black", img_mono_black)
cv2.imshow("mono_white", img_mono_white)
cv2.imshow("color_black", img_color_black)
cv2.imshow("color_white", img_color_white)

print(img_color_black)

cv2.waitKey()

実行結果

 

ones を用いる方法

onesは画像データをすべて1で初期化します。

1で初期化した画像に、値を掛け、掛けた値一色の画像データとなります。

 

onesの構文

numpy.ones(shape, dtype=None, order='C', *, like=None)
shape 配列のサイズを指定します。
モノクロの場合:(高さ, 幅)
カラーの場合:(高さ, 幅, 3)や(高さ, 幅, 4)
dtype データの型を指定します。
画像データなので、
dtype = np.uint8
とします。
order ‘C’ もしくは ‘F’  詳細は不明。。
like 詳細は不明。。

 

サンプルプログラム

import cv2
import numpy as np

# モノクロの指定色(128)で初期化
img_mono_128 = np.ones((100, 400), dtype = np.uint8) * 128
# カラーの指定色(グレー200)で初期化
img_color_200 = np.ones((100, 400, 3), dtype = np.uint8) * 200

cv2.imshow("mono_128", img_mono_128)
cv2.imshow("color_200", img_color_200)

cv2.waitKey()

実行結果

 

full を用いる方法

fullは画像データをすべて指定した値で初期化します。

 

fullの構文

numpy.full(shape, fill_value, dtype=None, order='C', *, like=None)
shape 配列のサイズを指定します。
モノクロの場合:(高さ, 幅)
カラーの場合:(高さ, 幅, 3)や(高さ, 幅, 4)
fill_value 指定色
カラーの場合は、 (青, 緑, 赤) の順のタプルで指定します。
dtype データの型を指定します。
画像データなので、
dtype = np.uint8
とします。
order ‘C’ もしくは ‘F’  詳細は不明。。
like 詳細は不明。。

 

サンプルプログラム

import cv2
import numpy as np

# カラーの指定色(青)で初期化
img_color_blue = np.full((100, 400, 3), (255, 0, 0), dtype = np.uint8)
# カラーの指定色(緑)で初期化
img_color_green = np.full((100, 400, 3), (0, 255, 0), dtype = np.uint8)
# カラーの指定色(赤)で初期化
img_color_red = np.full((100, 400, 3), (0, 0, 255), dtype = np.uint8)

cv2.imshow("color_blue", img_color_blue)
cv2.imshow("color_green", img_color_green)
cv2.imshow("color_red", img_color_red)

cv2.waitKey()

実行結果

 

参照ページ

https://numpy.org/doc/stable/reference/generated/numpy.zeros.html#numpy.zeros

https://numpy.org/doc/stable/reference/generated/numpy.ones.html#numpy.ones

https://numpy.org/doc/stable/reference/generated/numpy.full.html

【OpenCV-Python】LineTypes(線描画の種類)

OpenCVで線を描画する際、斜めに線を描画すると、どうしても線がギザギザしてしまうのですが、このギザギザ部分の描画方法をLineTypes enumで指定します。

ほとんどの線や円などの描画では、 cv2.LINE_8 が初期値になっています。

 

LineTypes enum

Filed
cv2.LINE_4 4連結
cv2.LINE_8 8連結
cv2.LINE_AA アンチエイリアス

4連結、8連結という言葉はラベリング処理の時に登場しますが、

4連結は、上下、左右方向につながった線で描画します。

8連結は、上下、左右、斜め方向につながった線で描画します。

アンチエイリアスは、ギザギザの部分をぼかして描画します。

 

具体的には、次のサンプルを参照ください。

 

サンプルプログラム

import cv2
import numpy as np

img = np.zeros((80, 50), dtype = np.uint8)

cv2.line(img, (5, 5), (45, 20), 255, 1, cv2.LINE_4)
cv2.line(img, (5, 15), (45, 30), 255, 1, cv2.LINE_8)
cv2.line(img, (5, 25), (45, 40), 255, 1, cv2.LINE_AA)

cv2.circle(img, (20, 60), 15, 255, 1, cv2.LINE_4)
cv2.circle(img, (20, 60), 10, 255, 1, cv2.LINE_8)
cv2.circle(img, (20, 60), 5, 255, 1, cv2.LINE_AA)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
cv2.imshow("Image", img)
cv2.waitKey()

実行結果

※文字の部分は別途、画像処理ソフトで追加しています。

 

参照ページ

https://docs.opencv.org/4.8.0/d6/d6e/group__imgproc__draw.html#gaf076ef45de481ac96e0ab3dc2c29a777

【OpenCV-Python】matchTemplate(テンプレートマッチング)

OpenCVでテンプレートマッチングを行うにはmatchTemplate()関数を用います。

ただし、OpenCVのテンプレートマッチングは、回転やスケール変動に対応していないため、OpenCVの関数の中では使いずらい関数の一つでもあります。

 

テンプレートマッチングの構文

matchTemplate(image, templ, method[, result[, mask]]) ->result

引数

image テンプレートマッチングを行う、検索対象の画像
templ テンプレート画像
method 相関値(類似度)の計算方法
cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED, cv2.TM_CCORR, cv2.TM_CCORR_NORMED,
cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED
(参考)TemplateMatchModes
mask (オプション)
テンプレート画像のうち、有効な領域をマスク画像で指定します。

methodの設定値のうち、TM_SQDIFFとTM_SQDIFF_NORMEDの値に関しては、検索対象の画像と、テンプレート画像の差分の2乗を用いた値なので、resultで得られた値が小さいほど、似ている場所となります。
それ以外の設定値は、resultで得られた値が大きいほど似ている場所となります。

戻り値

result テンプレートマッチングの結果を32bitのfloatのデータとして格納します。
検索対象の画像と、テンプレートの画像を重ね合わせた時に、テンプレート画像の一番、左上の座標の位置に相関値が格納されます。

 

テンプレートマッチングのサンプルプログラム

import cv2

# 画像を開く
img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
# テンプレート画像を開く
template = cv2.imread("template.jpg", cv2.IMREAD_UNCHANGED)

# テンプレートマッチング
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF)
# 結果が最大、最小の位置を検出
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# 検出位置を描画
cv2.rectangle(
    img,
    max_loc, 
    (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
    (0, 0, 255), 
    3)

# 結果の表示
cv2.imshow("Template", template)
cv2.imshow("Result", img)

cv2.waitKey()

検索対象画像

テンプレート画像

テンプレートマッチングの結果

 

テンプレートマッチングの結果の画像について

テンプレートマッチング結果の画像は32bitのfloatの値が格納された画像となっています。

テンプレートマッチングの結果の画像を表示するサンプルは以下のようになります。

import cv2

# 画像を開く
img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
# テンプレート画像を開く
template = cv2.imread("template.jpg", cv2.IMREAD_UNCHANGED)

# テンプレートマッチング
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF)
# 結果が最大、最小の位置を検出
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# 検出位置を描画
cv2.rectangle(
    img,
    max_loc, 
    (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
    (0, 0, 255), 
    3)

# 結果の表示
cv2.imshow("Template", template)
cv2.imshow("Result", img)
cv2.imshow("Template Result", res / max_val)

cv2.waitKey()

テンプレートマッチングの結果の画像

画像は、結果の画像の最大値で割って、0~1の範囲になるように調整しています。

今回のサンプルでは、相関値の計算にTM_CCOEFFを用いているので、値が大きい場所(画像が明るい場所)が似ている場所となります。

この位置は、テンプレート画像の左上の座標の位置となるので、実際のテンプレートの領域は、この明るい点を左上の座標にした、テンプレート画像の幅と高さの領域が検索結果の領域となります。

 

複数の位置のテンプレートマッチングの結果を取得する方法

これまで示したサンプルではテンプレートマッチングの結果の画像(result)の中から値が最大となる位置を探して、テンプレートマッチングの結果としていました。

しかしながら、この方法だと、値が最大となる1箇所しか結果を得られないため、複数の結果を得たい場合は、得られた結果の画像(result)の最大値となる周辺を値0(ゼロ)で塗りつぶしてから、残りの領域に関して値が最大となる場所を検索し、これを繰り返し行い、相関値がある程度、小さくなったら(大きくなったら)、検索を終了します。

 

複数の検索結果を取得するサンプルプログラム

import cv2

# 画像を開く
img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
# テンプレート画像を開く
template = cv2.imread("template.jpg", cv2.IMREAD_UNCHANGED)

# テンプレートマッチング
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF_NORMED)

while True:

    # 結果が最大、最小の位置を検出
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    print("MaxValue = ", max_val)

    if max_val < 0.3:
        break

    # 検出位置を描画
    cv2.rectangle(
        img,
        max_loc, 
        (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
        (0, 0, 255), 
        3)
    
    # 検出した位置の近辺の値を0にする
    range = 10
    cv2.rectangle(
        res,
        (max_loc[0] - range, max_loc[1] - range), 
        (max_loc[0] + range, max_loc[1] + range), 
        0, 
        -1)
    
# 結果の表示
cv2.imshow("Template", template)
cv2.imshow("Result", img)
cv2.imshow("Template Result", res / max_val)

cv2.waitKey()

検出結果

0で塗りつぶされた結果の画像(result)

 

USBカメラを使ったテンプレートマッチングのサンプルプログラム

サンプルプログラム

import cv2

def TemplateMatching(img, template, threshold, range = 10):
    # テンプレートマッチング
    res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF_NORMED)
    # 結果表示用の画像
    result_roi = img.copy()

    while True:

        # 結果が最大、最小の位置を検出
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        print("MaxValue = ", max_val)

        if max_val < threshold:
            break

        # 検出位置を描画
        cv2.rectangle(
            result_roi,
            max_loc, 
            (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
            (0, 0, 255), 
            3)
        
        # 検出した位置の近辺の値を0にする
        cv2.rectangle(
            res,
            (max_loc[0] - range, max_loc[1] - range), 
            (max_loc[0] + range, max_loc[1] + range), 
            0, 
            -1)
    return result_roi
        
 # カメラを開く
cap = cv2.VideoCapture(0)

# 初期のテンプレート画像の切り出し
ret, frame = cap.read()
top, bottom, left, right = (190, 290, 270, 370)
template = frame[top : bottom, left : right]
cv2.imshow("Template", template)

while True:
    # 画像をキャプチャする
    ret, frame = cap.read()

    image_roi = frame.copy()
    cv2.rectangle(
            image_roi,
            (left, top), 
            (right, bottom), 
            (0, 255, 0), 
            3) 

    result_img = TemplateMatching(frame, template, 0.8)

    # 画像を表示する
    cv2.imshow("Image", image_roi)
    cv2.imshow("Result", result_img)
    
    key = cv2.waitKey(1)
    if key == ord('t'):
        # `t`キーを押すとテンプレート画像を登録する
        template = frame[top : bottom, left : right]
        cv2.imshow("Template", template)
    elif key > 0:
        break

カメラでの撮影画像

USBカメラで撮影中にキーボードの ‘t’ キーを入力すると、中央に表示した緑色の枠の範囲がテンプレート画像となるようにしています。

登録されたテンプレート画像

USBカメラで撮影中に検出されたテンプレート領域

 

まとめ

OpenCVのテンプレートマッチング(matchTemplate)は、回転やスケール変動に対応していないため、実際のカメラで撮影した画像に対して、テンプレートマッチングで何かを位置を検出しようとするのは、困難な場合が多いです。

ぜひ、ここにあるUSBカメラを使ったサンプルを用いて、カメラを遠くしたり、近づけたりして、画像を拡大/縮小し、カメラも回転しながら、いろいろと試してみてください。

さらに、相関値の計算も必ずしも正規化相互相関(TM_CCOEFF_NORMED)が一番、安定して検出するとは限りません。

 

他のマシンビジョン用(工業用の画像処理用)のライブラリでは、テンプレートマッチングは、普通に回転やスケール変動には対応していますし、さらに、被写体の輪郭をベースに、被写体の位置を検出する幾何学サーチなる検出方法も対応しているのがほとんどです。

さらにすごい物になると、ポテトチップスの袋のように、表面がデコボコしていて、画像が不規則に歪んでしまうような画像に対しても、サーチできてしまう物もあります。

 

そのため、OpenCVは一般的な画像処理を行うには、よく出来ているライブライだと思いますが、仕事でテンプレートマッチングを使う必要がある場合、OpenCVのテンプレートマッチングの処理(matchTemplate)を使って検出しようとするのには、十分に注意をしてください。

実際には、使い物にならない場合が多いです。

 

関連記事

テンプレートマッチング(template matching)

【OpenCV-Python】Webカメラから画像をキャプチャして保存する

https://docs.opencv.org/4.8.0/df/dfb/group__imgproc__object.html#ga586ebfb0a7fb604b35a23d85391329be

https://docs.opencv.org/4.8.0/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d

【OpenCV-Python】Canny(Canny edge detection)

Canny edge detectionの処理アルゴリズムついてはこちらのページ

Canny edge detectionの処理アルゴリズム

で紹介していますが、Canny edge detectionで画像のエッジ部分を抽出すると、明確なエッジがなくとも、風景画などの、自然な画像においてもエッジ部分を抽出してくれます。

処理アルゴリズムはやや複雑ですが、OpenCVではCanny()関数を使う事で、簡単にできてしまいます。

 

Canny()関数の構文

Canny( image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]] ) -> edges

引数

image 入力画像(8bitグレースケールのみ)
threshold1 エッジしきい値(小)
threshold2 エッジしきい値(大)
apertureSize Sobelフィルタ処理のカーネルのサイズ(3, 5, 7 のいずれか)
L2gradient X方向とY方向のSobleフィルタから、エッジの大きさを求める方法
Falseのとき:L1ノルム(X方向の絶対値とY方向の絶対値の和)(初期値)
Trueのとき:L2ノルム(X方向の2乗とY方向の2乗の和の平方根)


戻り値

edges Canny edge detectionの処理画像

サンプルプログラム

Canny edge detection処理のサンプルプログラムを示します。

import cv2

# グレースケールで画像を読み込む
src_img = cv2.imread("image.bmp", cv2.IMREAD_GRAYSCALE)
# Canny edge detection
canny_img = cv2.Canny(src_img, 100, 200)

# 入力画像とCanny edge detectionの処理画像の表示
cv2.imshow("Image", src_img)
cv2.imshow("Canny", canny_img)
cv2.waitKey()

処理結果

 

Cannyのしきい値の決め方

Cannyで処理するエッジのしきい値(threshold1, threshold2)はしきい値(小)としきい値(大)説明しましたが、これだけだと少々わかりづらいかと思いますが、このしきい値は、L2gradientの設定を初期状態で行うと、X方向のSobelフィルタとY方向のSobelフィルタ処理の結果をそれぞれの絶対値の和の画像に対して、非極大抑制を行った画像に対するヒステリシスしきい値の小さい方のしきい値と大きい方のしきい値になります。

と、説明すると余計に分からないw

しきい値の意味合い的には

threshold1 エッジの候補としたいエッジ強度のしきい値(エッジが多めに残る)
threshold2 必ずエッジとして残したい部分のエッジ強度のしきい値(エッジが少なめに残る)

Cannyで処理を行った画像は、threshold2で抽出したエッジと、threshold2のエッジとつながっているthreshold1のエッジを残すようになります。

threshold1、threshold2の値は255を超えた値も有効である事に注意してください。

 

現実的には、どの程度のしきい値が最適なのか?わかりづらいので、threshold1とthreshold2の値を同じ値で、しきい値(小)の値と、しきい値(大)の値のそれぞれのCanny処理後の画像を確認し、最適な値を見つけると良いかと思います。

 

処理例のプログラム

/import cv2

# グレースケールで画像を読み込む
src_img = cv2.imread("image.bmp", cv2.IMREAD_GRAYSCALE)

# しきい値(小)の画像
threshold1 = 100
canny_thre1 = cv2.Canny(src_img, threshold1, threshold1)
# しきい値(大)の画像
threshold2 = 500
canny_thre2 = cv2.Canny(src_img, threshold2, threshold2)

# Cannyフィルタの画像
canny_img = cv2.Canny(src_img, threshold1, threshold2)

# 入力画像とCanny edge detectionの処理画像の表示
cv2.imshow("Image", src_img)
cv2.imshow("threshold1", canny_thre1)
cv2.imshow("threshold2", canny_thre2)
cv2.imshow("Canny", canny_img)

cv2.waitKey()

処理結果

thireshold1(しきい値小)のエッジの中から、thireshold2(しきい値大)のエッジに繋がっている部分を残した画像がCanny処理後の画像になります。

 

参考ページ

Canny edge detectionの処理アルゴリズム

【OpenCV-Python】Sobel(エッジ検出)

【OpenCV-Python】Sobel(エッジ検出)

OpenCVでソーベルフィルタ処理を行うには、Sobel()関数を用います。

ソーベルフィルタは、画像のエッジを検出することで、位置決めや寸法計測などに用いられます。

ソーベルフィルタ処理の構文

Sobel( src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]] ) -> dst

引数

src 入力画像
ddepth 出力画像のビット深度
dx x方向に関する微分の次数
dy y方向に関する微分の次数
ksize カーネルのサイズ 1, 3, 5, 7 のいずれか
scale 出力する値の倍率
delta 出力する値に加算する値
borderType 外周処理の種類を指定


戻り値

dst ソーベルフィルタ処理された画像データ

サンプルプログラム

よくあるソーベルフィルタ処理のサンプルプログラムを以下に示します。

ソーベルフィルタはノイズの影響を受けやすい処理のため、最初にノイズ除去のため、メディアンフィルタ処理を施しています。


import cv2

src = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)

# ノイズ除去(メディアンフィルタ)
src_median = cv2.medianBlur(src, 5)
# ソーベルフィルタ
sobel_x = cv2.Sobel(src_median, cv2.CV_32F, 1, 0) # X方向
sobel_y = cv2.Sobel(src_median, cv2.CV_32F, 0, 1) # Y方向

# 立下りエッジ(白から黒へ変化する部分)がマイナスになるため絶対値を取る
# alphaの値は画像表示に合わせて倍率調整
sobel_x = cv2.convertScaleAbs(sobel_x, alpha = 0.5)
sobel_y = cv2.convertScaleAbs(sobel_y, alpha = 0.5)

# X方向とY方向を足し合わせる
sobel_xy = cv2.add(sobel_x, sobel_y)

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)

cv2.imshow("SobelX", sobel_x)
cv2.imshow("SobelY", sobel_y)
cv2.imshow("SobelXY", sobel_xy)
cv2.waitKey()

実行結果

 

実際のSobelフィルタ処理の使い方

Sobleフィルタは最初にも書きましたが、被写体のエッジ部分を検出することで、位置決めや寸法計測に用います。

そのため、上記に示したようなエッジ部分を白く強調した画像は、ほとんど用いる事はありません。

さらに、上記のサンプルでは、Sobelフィルタ処理の絶対値を取得していますが、実際には、正、負の値が重要になります。

例えば、下図のようなバーコードの画像において、線の幅や位置を知りたいときは、黒い線の右側と左側を区別する必要があります。

画像を左側から右側に見た時、白から黒に変化する部分(立下りエッジ)では、X方向のソーベルフィルタ処理の値は負になります。

同様に黒から白に変化する部分(立上りエッジ)では、X方向のソーベルフィルタ処理の値は正になります。

上図の赤矢印の輝度値の一部分をグラフにすると、下図のようになります。

この輝度部分に関して、ソーベルフィルタ処理を行うと、

のようになり、ソーベルフィルタの値の正負を区別することで、立上りエッジと立下りエッジを認識することができるようになります。

この事を利用したサンプルを以下に示します。

import cv2

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

src = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE) # 処理画像
src_disp = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR) # 結果表示用カラー画像

# ソーベルフィルタ
sobel_x = cv2.Sobel(src, cv2.CV_32F, 1, 0) # X方向

# 中心付近の横1ライン分のデータ
sobel_value = sobel_x[112][:]

thre_high = 300 # 立上りエッジのしきい値
thre_low = -300 # 立下りエッジのしきい値

min = 0
max = 0

# 縦方向の中心付近のY座標
y = 112

for i in range(src.shape[1]):

    if sobel_x[y, i] > thre_high:
        if max < sobel_x[y, i]:
            max = sobel_x[y, i]
            max_x = i
    else:
        if max != 0:
            # 立上りエッジの描画
            cv2.line(src_disp, (max_x, 0), (max_x, src.shape[0] - 1), (255, 0, 0), 1)
            max = 0

    if sobel_x[y, i] < thre_low: if min > sobel_x[y, i]:
            min = sobel_x[y, i]
            min_x = i
    else:
        if min != 0:
            # 立下りエッジの描画
            cv2.line(src_disp, (min_x, 0), (min_x, src.shape[0] - 1), (0, 0, 255), 1)
            min = 0

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

実行結果

 

今回用いている画像は、ノイズが少ないため、ノイズ除去を行っていませんが、ノイズが多い画像の場合、エッジをぼかさずにノイズを除去するため、エッジ方向に沿ったノイズ除去フィルタを行います。

例えば、上図の画像では、幅1画素、高さ7画素のカーネルでガウシアンフィルタ処理などを行います。

 

参照ページ

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#gacea54f142e81b6758cb6f375ce782c8d

エッジ抽出(Sobel,Prewittなど)

【OpenCV-Python】BorderTypes(画像の外周処理の設定)

【OpenCV-Python】cvtColor(色変換)

OpenCVでカラー→モノクロ変換、カラーのデータの並びを入れ替えるBGR⇔RGB変換、色抽出の前処理として使われるHSV変換や、Bayer画像をカラー画像に変換するなど、主にカラー画像の変換を行うにはcvtColor()関数を用います。

構文

cvtColor( src, code[, dst[, dstCn]] ) -> dst

引数

src 色変換を行う画像データを指定します。
色変換を行う種類(code)に合わせた画像データを指定します。
code 色変換の種類を指定します。
cv2.COLOR_BGR2GRAYなど。詳細は別途
dstCn 出力画像のチャンネル数
この値が0のバイア、チャンネル数はsrcとcodeから自動的に求められます。
と、あるのですが、詳細は分からず。。

 

戻り値

dst 変換された画像データ

 

codeの設定について

codeの設定により、様々な色変換を指定できます。
コードのフォーマットは

変換前 2 変換後

となっています。

さらに、例えばOpenCVのカラー画像の場合、BGRと指定するように、画像データの並びの順番で色を指定する必要があります。

codeの種類は非常に多くあるのですが、個人的に使った事のある、使いそうなcodeを以下に示します。

 

COLOR_BGR2GRAY カラー画像(8bit3ch)をグレースケールの画像に変換します。
COLOR_BGRA2GRAY カラー画像(8bit4ch)をグレースケールの画像に変換します。
COLOR_GRAY2BGR グレースケールの画像をカラー画像(8bit3ch)に変換します。
B,G,Rの値はすべてGRAYの値と同じ
COLOR_GRAY2BGRA グレースケールの画像をカラー画像(8bit4ch)に変換します。
B,G,Rの値はすべてGRAYの値と同じ
Aの値は255
COLOR_BGR2RGB OpenCVのカラー画像(BGR)のデータの並びをR,G,Bの順に変換します。
他のPythonモジュールとデータの受け渡し時に用います。
COLOR_RGB2BGR データの並びがR,G,B順のカラー画像をOpenCVのカラー画像(BGR)に変換します。
COLOR_BGR2HSV カラー(BGR)からHSVに変換します。
色相(Hue)の値は0~360°で計算されますが、R,G,Bの各データが8bitのとき値が0~255の範囲なので、Hueの値は半分の0~128となります。
COLOR_HSV2BGR HSVからカラー(BGR)に変換します。
Hueの値は0~360°の半分の0~180で指定する必要があります。
COLOR_BGR2HSV_FULL カラー(BGR)からHSVに変換します。
色相(Hue)の値は0~360°で計算されますが、R,G,Bの各データが8bitのとき値が0~360°の値を0~255に均等に割り振られます。
COLOR_HSV2BGR_FULL HSVからカラー(BGR)に変換します。
Hueの値は0~360°の値を0~255に均等に割り振られた値で指定する必要があります。
COLOR_BGR2YUV カラー(BGR)からYUVに変換します。
COLOR_YUV2BGR YUVからカラー(BGR)に変換します。
COLOR_BayerBGGR2BGR Bayerパターンからカラー(BGR)に変換します。
COLOR_BayerGBRG2BGR Bayerパターンからカラー(BGR)に変換します。
COLOR_BayerRGGB2BGR Bayerパターンからカラー(BGR)に変換します。
COLOR_BayerGRBG2BGR Bayerパターンからカラー(BGR)に変換します。

他のコードの種類については、下記ページを参照ください。

https://docs.opencv.org/4.8.0/d8/d01/group__imgproc__color__conversions.html#ga4e0972be5de079fed4e3a10e24ef5ef0

色変換式については下記ページを参照してください。

https://docs.opencv.org/4.8.0/de/d25/imgproc_color_conversions.html

 

サンプルプログラム

カラー(BGR)→モノクロ(GRAY)変換を行った例を以下に示します。

import cv2

# 画像ファイルの読込
src = cv2.imread("Mandrill.bmp", cv2.IMREAD_UNCHANGED)

# カラー(BGR)→モノクロ(GRAY)変換
dst = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)

# 処理前画像の表示
cv2.imshow("Src Image", src)
# カラー→モノクロ変換後の画像表示
cv2.imshow("COLOR_BGR2GRAY", dst)

# キー入力待ち
cv2.waitKey()

実行結果

 

参照ページ

https://docs.opencv.org/4.8.0/d8/d01/group__imgproc__color__conversions.html#ga397ae87e1288a81d2363b61574eb8cab

https://docs.opencv.org/4.8.0/d8/d01/group__imgproc__color__conversions.html#ga4e0972be5de079fed4e3a10e24ef5ef0

https://docs.opencv.org/4.8.0/de/d25/imgproc_color_conversions.html

【OpenCV-Python】デモザイキング(Bayer変換)

【OpenCV-Python】cvtColorでBayer変換するときの定数