OpenCVのテンプレートマッチングを使って物体検出する
OpenCVのテンプレートマッチング関数を用いて物体を検出します。
OpenCVが提供する物体検出の種類
OpenCVは6種類のテンプレートマッチング手法を提供します。
大きくは3種類で、それぞれに正規化の有無があり、計6種類となっています。
・二乗差分法 cv::TM_SQDIFF
テンプレート画像と入力画像の画素の差分を二乗したものの総和
(値が0に近いほど似ていると判断できる)
SSD(Sum of Squared Difference)とも呼ばれる
・正規化二乗差分法 cv::TM_SQDIFF_NORMED
二乗差分法を正規化したもの
・相互相関法 cv::TM_CCORR
テンプレート画像と入力画像の画素同士の内積の総和
(値が1に近いほど似ていると判断できる)
・正規化相互相関法 cv::TM_CCORR_NORMED
相互相関法を正規化したもの
NCC(Normalized Cross-Correlation)とも呼ばれる
・相関係数法 cv::TM_CCOEFF
テンプレート画像の平均値と入力画像の平均値画素同士の内積の総和
(値が1に近いほど似ていて、-1に近いほど不一致と判断できる。なお、値が0に近いものは無相関である)
・正規化相関係数法 cv::TM_CCOEFF_NORMED
相関係数法を正規化したもの (ZNCC(Zero-means Normalized Cross-Correlation)とも呼ばれる)
大まかには、
計算量(処理時間)
(正規化)相関係数法 > (正規化)相互相関法 > (正規化)二乗差分法
精密さ
(正規化)相関係数法 > (正規化)相互相関法 > (正規化)二乗差分法
という感じ。
処理速度お求めるなら二乗差分法。精密さを求めるなら正規化相関係数法を選択します。
注意が必要なのは、(正規化)二乗差分法では0に近いほどマッチング率が高く、そのほかの手法は1に近いほどマッチング率が高くなります。
テンプレートマッチングサンプルプログラム(VC++)
すでに、VC++でOpenCVを実行するための準備は済んでいるものとします。
できていない場合は、https://jitaku.work/opencv/vc/opencv/を参照して準備をしてください。
テンプレート画像 | |
入力画像 |
ソースコード
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <math.h>
int main(int argc, char** argv)
{
cv::Mat image = cv::imread("messi5.jpg", cv::IMREAD_COLOR);
cv::Mat templ = cv::imread("messi_face.jpg", cv::IMREAD_COLOR);
if (image.empty() || templ.empty())
{
std::cout << "Can't read one of the images" << std::endl;
return EXIT_FAILURE;
}
int matchMethodEnum[] = { cv::TM_SQDIFF, cv::TM_SQDIFF_NORMED, cv::TM_CCORR, cv::TM_CCORR_NORMED, cv::TM_CCOEFF, cv::TM_CCOEFF_NORMED };
std::string matchMethodName[] = { "01_TM_SQDIFF", "02_TM_SQDIFF_NORMED", "03_TM_CCORR", "04_TM_CCORR_NORMED", "05_TM_CCOEFF", "06_TM_CCOEFF_NORMED" };
for (int i = 0; i < 6; i++) {
int match_method = matchMethodEnum[i];
// 類似度MAP格納領域の確保(結果MAPのサイズは入力画像からテンプレート画像を引いて1足したサイズになる)
int result_cols = image.cols - templ.cols + 1;
int result_rows = image.rows - templ.rows + 1;
cv::Mat result;
result.create(result_rows, result_cols, CV_32FC1);
// マッチング結果の画像を作成する
cv::matchTemplate(image, templ, result, match_method);
// 最後のファイル出力用に類似度MAPのバックアップ取っとく。この行為はテンプレートマッチングには必要ない。結果出力のための処理
cv::Mat resultBackup = result.clone();
// 結果を正規化する
if (match_method == cv::TM_SQDIFF || match_method == cv::TM_CCORR || match_method == cv::TM_CCOEFF || match_method == cv::TM_CCOEFF_NORMED) {
cv::normalize(result, result, 0.0, 1.0, cv::NORM_MINMAX, -1, cv::Mat());
}
// 結果内の最大値、最小値、ポジションを取得する
double minVal;
double maxVal;
cv::Point minLoc;
cv::Point maxLoc;
cv::Point matchLoc;
cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
// 差の二乗和は0に近いほうが一致度が高い。他は逆
if (match_method == cv::TM_SQDIFF || match_method == cv::TM_SQDIFF_NORMED)
{
matchLoc = minLoc; // 小さいほうが正義
}
else
{
matchLoc = maxLoc;
}
// 枠を書く
cv::Mat tmp;
image.copyTo(tmp);
cv::rectangle(tmp, matchLoc, cv::Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows), cv::Scalar::all(255), 2, 8, 0);
// 結果MAPにも枠を書く。ただし、MAPは縦横がそれぞれ入力画像からテンプレート画像を引いて1足したサイズになるので、そのままの座標では当然うまくいかない。その比率分小さくする。
double ratio_w = (double)result_cols / (double)image.cols;
double ratio_h = (double)result_rows / (double)image.rows;
double _x = ratio_w * (double)matchLoc.x;
double _y = ratio_h * (double)matchLoc.y;
cv::rectangle(result, cv::Point((int)_x, (int)_y), cv::Point((int)(_x + (templ.cols * ratio_w)), (int)(_y + (templ.rows * ratio_h))), cv::Scalar::all(255), 2, 8, 0);
// 表示
cv::imshow(matchMethodName[i], tmp);
cv::imshow(matchMethodName[i] + "_result", result);
// ファイルとして保存
cv::imwrite(matchMethodName[i] + ".png", tmp);
// cv::imshow()と異なり、floatの値のままではpng保存したときに真っ黒になる。ので、0 - 255の範囲に正規化してから保存する。なお、すでに0 - 1で正規化したものを正規化してもダメなので注意。大元のresultを正規化する。
cv::normalize(resultBackup, resultBackup, 0, 255.0, cv::NORM_MINMAX, -1, cv::Mat());
cv::rectangle(resultBackup, cv::Point((int)_x, (int)_y), cv::Point((int)(_x + (templ.cols * ratio_w)), (int)(_y + (templ.rows * ratio_h))), cv::Scalar::all(255), 2, 8, 0);
cv::imwrite(matchMethodName[i] + "_result.png", resultBackup);
}
cv::waitKey();
return 0;
}
実行結果
物体検出手法 | 結果画像 | 類似度MAP |
---|---|---|
二乗差分法 TM_SQDIFF | ||
正規化二乗差分法 TM_SQDIFF_NORMED | ||
相互相関法 TM_CCORR | ||
正規化相互相関法 TM_CCORR_NORMED | ||
相関係数法 TM_CCOEFF | ||
正規化相関係数法 TM_CCOEFF_NORMED |
相互相関法(cv::TM_CCORR)のみ外れていますね。
MAPを見ると確かに右下が白いです。
なんでかは知らん。
以上。