【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】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】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変換するときの定数

【OpenCV-Python】filter2D(任意カーネルフィルタ)

OpenCVで任意のカーネルを指定してフィルタ処理を行うには、filter2D()関数を用います。

カーネルそのものは、numpyの二次元配列で指定します。

 

構文

filter2D( src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]] ) -> dst

引数

src 処理を行う画像データを指定します。
ddepth 出力データの1画素1chあたりのビット深度を指定します。
-1 を指定すると、入力データと同じビット深度になります。
設定できる組み合わせは下記を参照ください。
kernel kernelの値を二次元配列で指定します。
カラーなどの複数チャンネルのデータでは、すべて同じカーネルが適用されます。
anchor kernelの基準位置を指定します。
anchorを指定しないか、(-1, -1)を指定すると、基準位置はカーネルの中心位置となります。
delta kernelで処理された後にdeltaの値が加えられます。
borderType ボーダー処理の種類をBorderTypesのenumで指定します。

ddepthに指定可能な組み合わせ

入力データのビット深度 出力データのビット深度
cv2.CV_8U -1, cv2.CV_16S, cv2.CV_32F, cv2.CV_64F
cv2.CV_16U, cv2.CV_16S -1, cv2.CV_16S, cv2.CV_32F, cv2.CV_64F
cv2.CV_32F -1, cv2.CV_32F
cv2.CV64F -1, cv2.CV_64F


戻り値

dst フィルタ処理された画像データ
ビット深度はddpethで指定された値になります。

サンプルプログラム

カーネルの値を自分で指定したガウシアンフィルタ処理を行うサンプルプログラムを以下に示します。

import cv2
import numpy as np

# 処理前画像の読込
src = cv2.imread("Mandrill.bmp", cv2.IMREAD_UNCHANGED)

# 3x3のガウシアンフィルタのカーネル
kernel = np.array([
    [1, 2, 1],
    [2, 4, 2],
    [1, 2, 1]
    ], dtype = np.float32)
kernel /= 16

# 任意カーネルフィルタの実施
dst = cv2.filter2D(src, -1, kernel)

# 任意カーネルフィルタ処理後の画像表示
cv2.imshow("Src Image", src )
cv2.imshow("filter2D", dst)
cv2.waitKey()

実行結果

 

出力先のビット深度に符号なしの値を指定すると、kernelにより計算された値がビット深度の範囲を超える場合、値はクリップされます。

例えば、出力先のビット数が8bitのとき、ビット深度の範囲は0~255なので、計算結果が負の場合は0に、255を超える場合は255に補正されます。

 

評価プログラム

import cv2
import numpy as np

src = np.array([
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0]
    ], dtype = np.uint8)

# ソーベルフィルタのカーネル
kernel = np.array([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]
    ])

# 任意カーネルフィルタ
dst = cv2.filter2D(src, -1, kernel)

print("-----src------------------------------")
print(src)
print("-----dst------------------------------")
print(dst)

実行結果

—–src——————————
[[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]]
—–dst——————————
[[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]]

 

出力先のビット深度を符号ありの16bitにしたときのサンプルは以下の通りです。

import cv2
import numpy as np

src = np.array([
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0]
    ], dtype = np.uint8)

# ソーベルフィルタのカーネル
kernel = np.array([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]
    ])

# 任意カーネルフィルタ
dst = cv2.filter2D(src, cv2.CV_16S, kernel)

print("-----src------------------------------")
print(src)
print("-----dst------------------------------")
print(dst)

実行結果

—–src——————————
[[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]]
—–dst——————————
[[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]]

 

参照ページ

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

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

【OpenCV-Python】medianBlur(メディアンフィルタ)

OpenCVで画像のメディアンフィルタを行うには、medianBlur()関数を用います。

メディアンフィルタは、ごま塩ノイズやスパイクノイズなどと言われる、小さい点々のノイズを除去するのに効果的です。

また、他の平滑化(blur)やガウシアンフィルタ(GaussianBlur)などと比べて、画像のエッジ部分がぼやけることがありません。

元画像 メディアンフィルタ後

メディアンフィルタ処理の構文

medianBlur( src, ksize[, dst] ) -> dst

引数

src メディアンフィルタ処理を行う画像データを指定します。
ksize カーネルのサイズを正の奇数で指定します。1, 3, 5…
カーネルは幅と高さが同じサイズになります。

戻り値

dst メディアン処理された画像データ

サンプルプログラム

メディアンフィルタ処理のサンプルプログラムを以下に示します。

import cv2

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

# メディアンフィルタ
dst = cv2.medianBlur(src, 5)

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

実行結果

参照ページ

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

メディアンフィルタ

【OpenCV-Python】GaussianBlur(ガウシアンフィルタ)

OpenCVで画像のガウシアンフィルタ処理を行うには、GaussianBlur()関数を用います。

ガウシアンフィルタは、「ガウス関数のσの値を変えると、平滑化の効果を変えられる」という説明が多いかと思いますが、ガウシアンフィルタには、それよりも大事な、高周波成分を除去できるという効果があります。

高周波成分を除去するには、σの値は何でもいい訳ではないのですが、OpenCVでは、この高周波成分を除去できるσの値をカーネルのサイズから自動計算で計算させることもできます。

 

ガウシアンフィルタの構文

GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] ) -> dst

引数

src ガウシアンフィルタ処理を行う画像データを指定します。
データタイプはuint8, uint16, int16, float32, float64に対応しています。
ksize ガウシアンフィルタのカーネルのサイズを(幅, 高さ)で指定します。
幅、高さの値は正の奇数の値である必要があります。
また、幅、高さに0を指定すると、σの値(sigmaX, sigmaY)から自動で計算します。
sigmaX X方向のσの値を指定します。
-1などのマイナスの値を指定するとカーネルのサイズから自動でσの値を計算します。
σ = 0.3 * (ksize – 1) * 0.5 – 1) * 0.8
sigmaY Y方向のσの値を指定します。
指定しないか、0を指定すると、sigmaXと同じσの値を用います。
sigmaXとsigmaYの両方が0の場合は、それぞれ、カーネルの幅と高さからσの値を自動で計算します。
borderType ボーダー処理の種類をBorderTypesのenumで指定します。


戻り値

dst ガウシアンフィルタ処理された画像データ

サンプルプログラム

ガウシアンフィルタ処理の一般的に行うシンプルなサンプルプログラムを以下に示します。

import cv2

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

# ガウシアンフィルタ
dst = cv2.GaussianBlur(src, (3, 3), -1)

# ガウシアンフィルタ後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("GaussianBlur", dst)
cv2.waitKey()

実行結果

 

各種パラメータを設定したガウシアンフィルタ処理の例は以下の通りです。

import cv2

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

# ガウシアンフィルタ
dst1 = cv2.GaussianBlur(
    src,    # 入力画像
    (7, 7), # カーネルのサイズ(幅、高さ)
    1.4,    # X方向のσの値(-1など負の値を指定するとカーネルサイズから自動計算)
    0,      # Y方向のσの値(0だとX方向と同じ)
    borderType = cv2.BORDER_DEFAULT  # ボーダー処理の種類
    )
# ガウシアンフィルタ(カーネルサイズを自動計算)
dst2 = cv2.GaussianBlur(
    src,    # 入力画像
    (0, 0), # カーネルのサイズを0にしてσの値からカーネルサイズを自動計算
    2       # X方向のσの値
    )

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

実行結果

 

ガウシアンフィルタのσの値について

高周波成分を除去するためには、σの値を大きくすれば良いという訳ではなく、最適な値があります。

OpenCVでは、sigmaX, sigmaYの値に負の値を指定するとカーネルのサイズ(幅、高さ)から下記の計算式から最適なσの値を計算してくれます。

σ = 0.3 * (ksize – 1) * 0.5 – 1) * 0.8

実際に、ガウシアンフィルタで用いられるカーネルの値は、getGaussianKernel()関数から取得することができます。

いくつか例を示すと

ksize= 3のとき
0.25, 0.5, 0.25

ksize= 5のとき
0.0625, 0.25, 0.375, 0.25, 0.0625

ksize= 7のとき
0.03125, 0.109375, 0.21875, 0.2825, 0.21875, 0.109375, 0.03125

となります。

 

ガウシアンフィルタのσの値と高周波成分の除去効果について

二次元の画像において、もっとも高周波な画像は、下図のような1画素おきの市松模様の画像となります。

この画像において、σの値を0.6, 0.8(自動計算), 3 と変えながらガウシアンフィルタ処理を行うとどのように変化するのか?を見てみたいと思います。

 

サンプルプログラム

import numpy as np
import cv2

def create_test_pattern():
    # テストパターンの作成
    data = np.array([
        [0, 255],
        [255, 0]
        ], dtype= np.uint8)
    img = np.tile(data, (16, 16))
    return img

# テストパターンの作成
src = create_test_pattern()

cv2.namedWindow("Src Image", cv2.WINDOW_NORMAL)
cv2.namedWindow("Gauss 0.3", cv2.WINDOW_NORMAL)
cv2.namedWindow("Gauss -1", cv2.WINDOW_NORMAL)
cv2.namedWindow("Gauss 3", cv2.WINDOW_NORMAL)

# σを変えながらガウシアン処理
gauss1 = cv2.GaussianBlur(src, (3, 3), 0.6) # σ=0.6
gauss2 = cv2.GaussianBlur(src, (3, 3), -1)  # σは自動計算(σ=0.8)
gauss3 = cv2.GaussianBlur(src, (3, 3), 3)   # σ=3

# ガウシアンフィルタ後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("Gauss 0.3", gauss1)
cv2.imshow("Gauss -1", gauss2)
cv2.imshow("Gauss 3", gauss3)
cv2.waitKey()

実行結果

左からσ=0.6,  σ=0.8(自動計算),  σ=3

 

このように、σの値は、単に大きい方が高周波成分を除去できるという訳ではなく、最適なσの値があり、このσの値は、OpenCVが自動で計算してくれる値を用いた方が高周波成分を除去できる事が確認できます。

 

参照ページ

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

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

ガウシアンフィルタの処理アルゴリズムとその効果

移動平均フィルタ VS ガウシアンフィルタ

【OpenCV-Python】blur(平滑化、移動平均)

OpenCVで画像の平滑化を行うには、blur()関数を用います。

ここで言う平滑化は、単純な移動平均フィルタとなります。

移動平均フィルタの処理については、下記ページを参照ください。

平滑化(移動平均)フィルタ

平滑化処理の構文

blur( src, ksize[, dst[, anchor[, borderType]]] ) -> dst

引数

src 平滑化を行う画像データを指定します。
データタイプはuint8, uint16, int16, float32, float64に対応しています。
ksize カーネルのサイズを(幅, 高さ)で指定します。
通常、幅と高さは奇数の値を指定してください。
anchor カーネルの基点となる座標を(x, y)で指定します。
(-1, -1)と指定すると、カーネルの中心座標となります。
指定しないと、初期値でカーネルの中心座標となります。
borderType ボーダー処理の種類をBorderTypesのenumで指定します。

戻り値

dst 平滑化された画像データ

サンプルプログラム

平滑化処理のサンプルプログラムを以下に示します。

import cv2

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

# 平滑化(移動平均)
dst = cv2.blur(src, (5, 5))

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

実行結果

 

anchorやborderTypeについては、ほぼ、指定することは無いかと思いますが、念のため、それぞれを指定したサンプルを示します。

import cv2

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

# 平滑化(移動平均)
dst = cv2.blur(
    src,                # 入力画像
    (3, 21),            # カーネルのサイズ(幅、高さ)
    anchor = (1, 10),   # カーネルの中心座標
    borderType = cv2.BORDER_DEFAULT  # ボーダー処理の種類
    )

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

実行結果

参照ページ

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

平滑化(移動平均)フィルタ

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

外周画像の処理