PythonでOpenCVを使うと簡単なコードで高度な画像処理を行う事が可能です。ここでは画像を使った分析で重宝するテンプレートマッチングをOpenCVのcv2.matchTemplate()で書く方法を紹介します。
こんにちは。wat(@watlablog)です。今回は比較的ライトなTips記事で、OpenCVでテンプレートマッチングをする方法を紹介します!
テンプレートマッチング
テンプレートマッチングとは?
テンプレートマッチングとは、以下の図のようにテンプレート画像が探索画像内のどこにあるか探す処理の事を意味します。
過去に当ブログではGitHubの有名コンテンツであるimoriさんの「画像処理100本ノック」の学習記録を「AI実装検定S級対策!「画像処理100本ノック」学習記録・カンペ」に残しました。このコンテンツではOpenCVを使わないコードを学べますので、より詳しい説明が欲しい方は是非画像処理100本ノックを覗いてみて下さい。
SSD
コンテンツ内にはテンプレートマッチングの種類毎に式が紹介されています。ここではテンプレートマッチングに使用される一般的な式を示します。
SSD(Sum of Squared Difference)は画素値同士の差分の二乗和が最小になる所が最も類似度が高いとする手法です。
SAD
SAD(Sum of Absolute Difference)はSSDと似ていますが、差の絶対値が最小になる所が最も類似度が高いとする手法です。
NCC
NCC(Normalized Cross Correlation)は正規化相互相関を意味し、相関係数として\(-1 \leq NCC \leq 1\)の値をとり、最大値が最も類似度が高いとする手法です。この手法はSSDやSADのように差分を計算する手法に比べ、照明等の変化に強いと言われています。
他にも種類はありますが、代表的な手法はこんなものと思います。
OpenCVについて
先ほどのような式をフルスクラッチとして自分で書いても良いのですが、OpenCVというライブラリを使う事で非常に簡単に画像処理プログラミングをする事ができます。
OpenCVによるテンプレートマッチングに関するチュートリアルは以下の公式ページに掲載されています。基本は当ブログもここを参照して、カスタマイズするというやり方をとっています。
OpenCVのインストールがまだの方は「Pythonで画像処理!OpenCVのメリットとインストール法」も是非ご覧下さい。
本記事の目標
前置きが長くなりましたが、本記事のモチベーションは「Python/PIVの検証用に管内の粒子流れ動画を作ってみた」に記載した通りです。
以下のようなGIF動画に示す粒子流れの画像から速度場を算出してみようという試みです。
1枚目の画像を基準画像とし、基準画像の一部をテンプレート画像として切り抜きます。時刻変化後の2枚目の画像は粒子が流れている最中であり、これを変形画像とした時に、テンプレートマッチングを使って移動量を算出する事を目標とします。
例え変形した後の画像であっても、テンプレートマッチで相関を見れば、他の部分よりは着目粒子付近のパターンの方が相関係数が高いだろうという仮定のもとの内容です。
OpenCVのテンプレートマッチングのブログ記事は、同一の画像の中からパターンマッチングを行う記事がほとんどだったので、こういう記事もあっても良いかなという感覚で書き始めました。
Python/OpenCVのテンプレートマッチングコード
事前準備
本記事のテンプレートマッチングコードを試すためには、まず「Python/PIVの検証用に管内の粒子流れ動画を作ってみた」の記事内コードを一回走らせ、プログラム実行フォルダ内に「out」フォルダを作り、フォルダ内に「00000.png」と「00001.png」の2つの画像を入れておく必要があります。
もしお試しになる場合は上記記事もセットでご覧下さい。
全コード
以下に全コードを示します。本コードはwsizeとウィンドウサイズを設定 (PIVという計測界隈では32がよく使われるそうです)し、ここでは(100, 100)の座標を起点にテンプレート画像をウィンドウサイズ分切り取っています。
テンプレートマッチングの手法は相関をみたいのでNCCを使っています。
resはマッチングの結果として画像の全領域で相関係数が記録された物です。
cv2.minMaxLoc()を使う事で全結果から最大値、最小値とその位置を取得する事が可能です。今回はNCCを使っているので、最大値が目的の数値になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import cv2 from matplotlib import pyplot as plt # グレースケール画像で2枚の画像を読み込み img1 = cv2.imread('out/00000.png', 0) img2 = cv2.imread('out/00001.png', 0) # ウィンドウサイズを設定 wsize = 32 # 1枚目の画像の(100,100)座標からwsize分の画像を抽出しテンプレート画像とする template = img1[100:100+wsize, 100:100+wsize] # テンプレートマッチング method = cv2.TM_CCOEFF_NORMED # Normalized Cross Correlation (NCC) 正規化相互相関係数 res = cv2.matchTemplate(img2,template,method) # テンプレートマッチングの結果 min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 最小値と最大値、その位置を取得 print(max_val, max_loc) # ここからグラフ描画------------------------------------- # フォントの種類とサイズを設定する。 plt.rcParams['font.size'] = 14 plt.rcParams['font.family'] = 'Times New Roman' # 目盛を内側にする。 plt.rcParams['xtick.direction'] = 'in' plt.rcParams['ytick.direction'] = 'in' # グラフの上下左右に目盛線を付ける。 fig = plt.figure(figsize=(10,4)) ax1 = fig.add_subplot(111) ax1.yaxis.set_ticks_position('both') ax1.xaxis.set_ticks_position('both') # 画像を表示(img2に移動前と移動後の矩形を描画しています) img2 = cv2.rectangle(img2,(100, 100), (100+wsize, 100+wsize), (0, 0, 0), 2) img2 = cv2.rectangle(img2,(max_loc[0], max_loc[1]), (max_loc[0]+wsize, max_loc[1]+wsize), (0, 0, 0), 3) ax1.imshow(img2, cmap='gray') fig.tight_layout() plt.show() plt.close() |
以下が結果です。rectangleを使ってテンプレート画像で切り抜いた部分と、2枚目の変形画像でマッチした部分を描画していますが、狙い通りのずれ、つまり移動量を得る事ができました。
コンソールには以下の出力がされます。これは相関係数が約0.57(以外と低い…)、(100, 100)からスタートしたので、(111, 100)と11ピクセル移動している事が結果として得られました。
1 |
0.5683916211128235 (111, 100) |
これだけだとわからないと思いますので、以下に動画を示します。テンプレートマッチングの結果はしっかり粒子の動きに追従しているようです。
まとめ
本記事ではPIVという粒子流れから速度を求める解析のために、テンプレートマッチングの概要を紹介し、OpenCVによる実装を試してみました。
OpenCVを使う事で大変短いコードでテンプレートマッチングをする事ができました。
また、粒子が流れるという現象を持った動画に対して相関をとる事で粒子の移動量を推測する事ができそうという事もわかりました。
実際の計測画像に対してどの程度通用するかはまだわかりませんが、一つずつ学んでいこうと思います。
テンプレート画像を使ったパターンマッチングもPython/OpenCVなら苦もなく使うことができました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!
素晴らしい記事ありがとうございます。非常に参考になります。
質問なのですが、watさんのようにテンプレートの起点座標xyを同じ値(例 (100,100), (200,200)など)にすれば割と上手くいくのですが、(100,150)のようにアレンジすると全くマッチングが上手くいきません。。
これは何故か分かりますでしょうか?NCCの出力値等は確認しているため、描画の仕方がおかしい訳ではなさそうです。
どうぞよろしくお願いいたします。
ご訪問ありがとうございます。
こちらでも試してみましたが、同じようにマッチングがうまくいかないパターンは結構あるようです。
もし当ブログの粒子流れ動画を使っているとしたら、画像を作る時の粒子密度や流れの速度も影響しています。
結局は相関係数を使って移動先を推定しているので、全く異なる位置でより相関が高くなることもありえるということのようです。
ただ、記載頂いたアレンジでうまくいかなくなる原因の説明には一歩足りないかも……。
すみません、ちょっと明快な理由がわかりませんでした。
もしかしたらこちらの記事も参考になるかも知れません。
誤ベクトル除去等。
普通の動画でも動作するので、人工的に作った粒子画像以外ならうまくいきやすい等あるかも知れません。
https://watlab-blog.com/2021/01/31/piv-vector/