2015. 2. 20.
2017.10. 4. OpenCV3.3.0とVisual C++ 2017で動作確認済み、ただし、文末の付記参照
石立 喬

OpenCVとVisual C++による画像処理と認識(12)

----- テンプレートマッチングで道路標識と数字を検出する -----

 テンプレートマッチングは古くから使われてきた探索手法であるが、最近では、学習を利用した、より高性能な探索技法が主流になっている。しかし、幸いに、OpenCVにはテンプレートマッチング用の優れた関数が用意されているので、道路標識や数字の検出を試み、マッチング相関性の計算方法の比較も行った。

テンプレートマッチングとは
 テンプレートとは型紙のことで、見本とも言える。見本と比べて、同じか違うかを判断することである。テンプレートマッチングは、被探索画像の隅から隅まで、順次にテンプレート画像をずらしながら重ねて行き、相関性を求めて、一定値よりも大であれば、そこに一致した図形があると判断する。
 この操作は、テンプレートと大きさが異なったり、傾いていたりすると、一致を判断できない欠点がある。計算量も多いので、実用的には、敬遠されることが多い。ただし、大きさが同じで、方向を揃えることができ、ベルトコンベアなどで被検査物が移動する製品検査などには、有用である。

OpenCVを用いたテンプレートマッチング
 大きく分けて、二つの場合が考えられる。
◎画像の中から最も良く一致した対象物を一個だけ検出する
 たとえば、視野の中に「止まれ」の標識が一個でもあれは、それに反応するようなシステムの場合で、 matchTemplate関数を用いてマッチング結果配列resultを求め、minMaxLoc関数を用いてその最大(相関計算方法によっては、最小)値とその位置を取得する。最大(最小)値が一定の基準を満たしていれば、マッチングしたと判断する。
◎画像の中から、一致した対象物を一個以上検出する
 プリント基板などにある多数の同じパターンで、不良個所が、どことどこにあるかを探して、そのすべてを表示させるような場合に使用する。  matchTemplate関数を用いてマッチング結果配列resultを求め、その配列を二次元的にスキャンして、相関値を順次取得し、一定の基準を満たしていれば、マッチングがあったとして、その座標位置を合致配列に格納する。最終的に、合致配列から読み取って使用する。

カラー画像での簡単なテンプレートマッチング
 テンプレートマッチングは、カラー画像でも使用できる。ただし、特定の色相を抜き出すなどの前処理の後ではグレイスケール画像になることが多く、また処理速度が遅いので、グレイスケール画像でテンプレートマッチングを取るのが一般的である。
 ここでは、カラー画像の例として、被探索画像の一部をそのままテンプレート画像として用いる場合を示す。一番簡単で、探索精度も高い。いわば、うまくいって当たり前の非現実的なケースである。、
 使用したプログラムを図1に示す。このプログラムは、下記から成る。
 1)原画像(被探索画像)src_imageとテンプレート画像temp_imageを読み込み、表示する。
 2)テンプレートマッチングを取る。
 3)結果のマッチング配列(相関値の分布を示す)resultを画像として表示する。
 4)マッチング点(座標)maxPtを取得する。不必要な値に対しては、引数として0またはNULLを入れる。
 5)原画像上に、黄色の矩形でマッチング箇所を表示する。
 図2はテンプレート画像と原画像を表示した結果で、図3は、マッチング結果を画像として表示したものと、マッチング箇所を表示した結果である。マッチング画像は、テンプレートのサイズだけ原画像より小さく、この範囲内でしかマッチングを検出できない。マッチング画像において、最も白い部分がマッチング点で、その位置がテンプレート画像の左上に当たる。

使用したOpenCV関数の説明
◎matchTemplate関数
 引数は

   matchTemplate(被探索図形、テンプレート画像、マッチング結果配列、比較方法);

となり、被探索図形は、カラーでもよい。テンプレート画像は、被探索画像と同タイプとする。マッチング結果配列はCV_32Fである。
 比較方法には下記がある(= で示したのは、代用できる整数値である)。
  TM_SQDIFF = 0 ------------- 差の二乗の合計、小さいほど良くマッチしている
  TM_SQDIFF_NORMED = 1 ---- 同上の正規化
  TM_CCORR = 2 ------------- 乗算したものの合計、大きいほど良くマッチしている
  TM_CCORR_NORMED = 3 ---- 同上の正規化
  TM_CCOEFF = 4 ------------ 相関係数であり、正に大きいほど良くマッチしている
  TM_CCOEFF_NORMED = 5 --- 同上の正規化
 結果配列resultから特定の座標位置の相関値を得るには、
  result.at<float>(y, x)
を用いる。
◎minMaxLoc関数
 下記のように、マッチング結果配列を引数として与え、相関値の最大値、最小値、最小位置、最大位置を参照渡しで受け取る。

   minMaxLoc(マッチング結果配列、&最小値、&最大値 = 0、&最小位置 = 0、&最大位置 = 0、マスク = noArray());

 あらかじめ、double minVal、double maxVal、Point minLoc、Point maxLoc の入れ物を用意しておく(必要あるもののみ)。resultはfloatであるのに、ここでのminMaxとmaxValはdoubleであることに注意されたい。


図1 カラー画像の簡単なプログラム



図2 テンプレート画像と共に原画像を示す



図3 マッチング結果と最大マッチング点の表示


道路標識に対するテンプレートマッチング(同一種類に対して一個のみ検出の例)
 道路標識を例に、簡単なテンプレートマッチングを行う。図4は、そのプログラムで、原画像の読込み部分は省略してある。
 ここでは、下記の処理を行う。原画像bin_imageは、「画像処理と認識(10)」で既に示した赤色抽出によって、道路標識の部分だけを二値化したものである。
 1)二種類のテンプレート画像temp_stopとtemp_noentryをグレイスケールとして読み込む。
 2)テンプレートtemp_stopで原画像bin_imageとマッチングをとり、相関値の最大値maxValとその位置maxPtを取得する。
 3)相関値の最大がしきい値thresholdより大きければ、その位置を黄色の矩形で原画像に描き、表示する。
 4)テンプレートtemp_noentryで原画像とマッチングをとり、相関値の最大値とその位置を取得する。
 5)相関値の最大がしきい値より大きければ、その位置をシアンの矩形で原画像に描き、表示する。
 図5は正しく検出された例を、図6は、二個の標識がある合成した原画像で、二種類の道路標識を同時に検出できることを示す。


図4 「止まれ」と「進入禁止」を検出するプログラム



図5 正しく検出された例



図6 実際にはない合成画像で、異なる二つの道路標識を同時に検出した


 図7はその他の結果の一部を紹介したもので、左二つは、少し斜めになっていても検出できた例、三番目は、大きい「止まれ」を下の部分だけで「止まれ」と判断した例、右端は小さい「進入禁止」を「止まれ」と間違えた例である。


図7 少し斜めでも検出できたが、「進入禁止」を「止まれ」と間違えた例も


数字の二値画像に対するテンプレートマッチング(複数個を同時に検出の例)
 被検索画像から複数の図形を検出するためには、最大値のみを取得するminMaxLoc関数は使えない。マッチング配列resultから、自分で探し出す必要がある。図8はそのプログラムで、下記を行う。
 1)原画像をカラー画像src_imageとして読み込む(後に、マッチング結果を赤色で描くため)。
 2)原画像をグレイスケール化してbin_imageとする。
 3)数字「3」のテンプレートtemp_threeと原画像bin_imageとでテンプレートマッチングを行う。
 4)マッチング結果配列resultをスキャンして、値がしきい値thresholdより大きければ、その座標位置を検出点配列detected_pointに入れる。
 5)配列detected_pointを順次読み取って、原画像上に赤枠でマッチング位置を表示する。
 図9は得られた結果で、5個の「3」がすべて検出されている。しきい値thresholdは試行錯誤で決定したが、0.94fまで下げると、「8」も検出されるようになったので、余裕を見て、0.96fとした。


図8 複数個を検出するプログラム



図9 数字を検出した結果


マッチング評価法(相関性の計算方法)の比較
 テンプレートと被検索画像の間での演算方法に下記のタイプ(各方法に割り当てられた整数で示す)がある。
  タイプ0 ---- TM_SQDIFF
  タイプ1 ---- TM_SQDIFF_NORMED
  タイプ2 ---- TM_CCORR
  タイプ3 ---- TM_CCORR_NORMED
  タイプ4 ---- TM_CCOEFF
  タイプ5 ---- TM_CCOEFF_NORMED
◎所要時間
 テンプレートマッチングの実施に当って、その所要時間が気になるので、数字「3」を検出するプログラムを使って、上記各タイプの比較を行った。そのプログラムを図10に、結果を図11に示す。プログラムでは、TM_SQDIFF等の文字列で指定する代わりに、対応する整数値を用いた。
 インクルードファイルの追加、名前空間の宣言など、作表方法については「Visual Studio Express 2013(Visual C++)の新しい使い方(3)」を、時間測定方法については「・・・の新しい使い方(5)」を参照されたい。


図10 所要時間を測定するためのプログラム



図11 タイプ2(TM_CCORR)がやや早いが、他は大差がない


◎出力データ
 結果として得られる最小値、最大値の値は、タイプによって異なる。そこで、これを確認した。そのプログラムを図12に、結果を図13に示す。タイプ1と2は、数値が小さい方がマッチングの度合いが高い。タイプ1のminValが0.000704になっているのはかなり良好な一致を示している。タイプ2〜5は、数値が大きい方が相関性が高い。タイプ3のmaxValが0.999652であるのも素晴らしい。プログラムで設定したthreshold = 0.96fは甘すぎたかもしれない。


図12 最小値と最大値を具体的に知るためのプログラム



図13 タイプ3(TM_CCORR_NORMED)の最大値が1に一番近い


結 論
 実用的には、あまり使われなくなったテンプレートマッチングであるが、被検索画像から抜き取ったままのテンプレート画像に対しては、非常に良い成績を示した。道路標識検出の場合も、テンプレート画像に近いサイズの標識には十分な性能が得られた。
 マッチングの度合いを評価する計算方法が6種類、OpenCVに用意されているので、これらで得られる数値と所要時間を確認した。ここで用いた簡単な例に限っての評価であるが、最大値、最小値などの数値は、いずれの方法も十分な精度を持っていた。所要時間では、TM_CCORRが36msecと早く、他は40msec〜50msecであった。ただし、これはDebugモードでビルドしたので、Releaseモードでは、もっと早いかもしれない。

付 記
 OpenCV3.3.0とVisual C++ 2017を使用するにあたり、下記を参照されたい。それ以外は、古いプログラムをそのまま使用できる。
 1)従来のインクルードファイルをそのまま使用しても良いが、#include <opencv2/opencv.hpp>と変更しても問題はない。
 2)従来のint _tmain(int argc, _TCHAR* argv[])をそのまま使用しても良いが、int main()にしても差支えない。
 3)vectorを使用するのに、using namespace std;の追加が必要である(従来は無くても良かったような気がする)。




「Visual C++の勉強部屋」(目次)へ