【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(エッジ検出)