OpenCVで画像の特徴抽出・マッチングを行う

概要

OpenCVでは特徴点抽出,特徴記述,特徴点のマッチングついて様々なアルゴリズムが実装されているが,それぞれ共通のインターフェースが用意されている.共通インターフェースを使えば,違うアルゴリズムであっても同じ書き方で使うことができる.

特徴点抽出はFeatureDetectorインターフェース,特徴記述はDescriptorExtractorインターフェース,マッチングはDescriptorMatcherインターフェースである.いずれのインターフェースも,オブジェクト生成時に文字列によってアルゴリズムを指定することができる.

以下に使い方を簡単にまとめる.(OpenCV2.4.7の情報.バージョンが古いと使えないアルゴリズムがあるので注意.OpenCV3.0.0-alphaでの変更点は「OpenCV3.0.0-alphaの特徴抽出・マッチングまとめ - whoopsidaisies's diary」にまとめた.)

また,説明で様々なアルゴリズム名が出てくるが,その内容に関しては以下のスライド等を参照.
MIRU2013チュートリアル:SIFTとそれ以降のアプローチ

インクルードするヘッダ

以下のソースコードを使うために必要なヘッダファイル.

#include <opencv2/opencv.hpp>
#include <opencv2/nonfree/nonfree.hpp> // SIFTまたはSURFを使う場合は必要

FeatureDetectorインターフェース

特徴点抽出のインターフェース.

オブジェクトの生成

FeatureDetector::createによってオブジェクトを生成する.その際,特徴点抽出のアルゴリズムを示すdetectorTypeを文字列で指定する.指定可能なdetectorTypeは以下.

  • FAST
  • FASTX
  • STAR
  • SIFT
  • SURF
  • ORB
  • BRISK
  • MSER
  • GFTT
  • HARRIS
  • Dense
  • SimpleBlob

SIFTまたはSURFを使う場合は,事前にcv::initModule_nonfree()を呼び出さないといけない.

実行

特徴点抽出の計算を実行をする際は,FeatureDetector::detectを呼び出す.第1引数に画像,第2引数に特徴点の情報を格納するKeyPointクラスの配列を渡す.第3引数で探索範囲の指定も出来るが,ここでは割愛.

サンプルコード

以下に,画像から特徴点抽出を行うコードを示す.(STARアルゴリズムを使う例)

// 画像の読み込み
cv::Mat img = cv::imread("test.jpg");

// SIFTまたはSURFを使う場合はこれを呼び出す.
//cv::initModule_nonfree(); 
// FeatureDetectorオブジェクトの生成
cv::Ptr<cv::FeatureDetector> detector = FeatureDetector::create("STAR");
// 特徴点情報を格納するための変数
std::vector<cv::KeyPoint> keypoint;
// 特徴点抽出の実行
detector->detect(img, keypoint);

DescriptorExtractorインターフェース

特徴記述のインターフェース.

オブジェクトの生成

DescriptorExtractor::createによってオブジェクトを生成する.その際,アルゴリズムを示すdescriptorExtractorTypeを文字列で指定する.指定可能なdescriptorExtractorTypeは以下.

  • SIFT
  • SURF
  • BRIEF
  • BRISK
  • ORB
  • FREAK

SIFTまたはSURFを使う場合は,事前にcv::initModule_nonfree()を呼び出さないといけない.

実行

特徴記述の計算を実行をする際は,DescriptorExtractor::computeを呼び出す.第1引数に画像,第2引数に特徴点の配列,第3引数に画像の特徴情報を格納するための行列を指定する.

サンプルコード

以下に,画像から特徴点抽出を行うコードを示す.(FREAKアルゴリズムを使う例)

// SIFTまたはSURFを使う場合はこれを呼び出す.
//cv::initModule_nonfree(); 
// DescriptorExtractorオブジェクトの生成
cv::Ptr<DescriptorExtractor> extractor = cv::DescriptorExtractor::create("FREAK");
// 画像の特徴情報を格納するための変数
cv::Mat descriptor;
// 特徴記述の計算を実行
extractor->compute(img, keypoint, descriptor);

DescriptorMatcherインターフェース

特徴点マッチングのインターフェース.

オブジェクトの生成

DescriptorMatcher::createによってオブジェクトを生成する.その際,アルゴリズムを示すdescriptorMatcherTypeを文字列で指定する.指定可能なdescriptorMatcherTypeは以下.

  • BruteForce
  • BruteForce-L1
  • BruteForce-SL2
  • BruteForce-Hamming
  • BruteForce-Hamming(2)
  • FlannBased

ただし,descriptorMatcherTypeは選択した”特徴記述アルゴリズムの表現が実数ベクトルかバイナリコードかによって使える使えないがある.組み合わせを以下の表にまとめる.

実数ベクトル
SIFT, SURF
バイナリコード
BRIEF, BRISK, ORB, FREAK
BruteForce
BruteForce-L1
BruteForce-SL2
BruteForce-Hamming
BruteForce-Hamming(2)
×
FlannBased ×

実行

特徴点マッチングの計算を実行する際は,DescriptorMatcher::matchを呼び出す.第1引数に第1画像の特徴情報が格納された行列,第2引数に第2画像の特徴情報が格納された行列,第3引数に,特徴点のマッチング情報が格納される構造体DMatchの配列を指定する.

マッチング結果画像の作成

drawMatchesによって,特徴点のマッチング結果を画像に書き込める.第1引数が第1画像,第2引数が第1画像の特徴点,第3引数が第2画像,第4引数が第2画像の特徴点,第5引数が特徴点のマッチング情報,第6引数が結果画像を格納する行列.第7引数以降で細かい指定ができるがここでは割愛.

サンプルコード

以下に,特徴点マッチングのコードを示す.(BruteForce-Hammingアルゴリズムを使う例)

// DescriptorMatcherオブジェクトの生成
cv::Ptr<DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
// 特徴点のマッチング情報を格納する変数
std::vector<cv::DMatch> dmatch;
// 特徴点マッチングの実行
matcher->match(descriptor1, descriptor2, dmatch);
// マッチング結果画像の作成
cv::Mat result;
cv::drawMatches(img1, keypoint1, img2, keypoint2, dmatch, result);
cv::imshow("matching", result);

クロスチェック

上記のコードでは,画像1→画像2のマッチングのみ行っている.画像2→画像1のマッチングも行い,双方の結果で一致するもののみをマッチング結果とする方法をクロスチェックという.これを行うと,誤対応を減らせる可能性がある.

// クロスチェックする場合
std::vector<cv::DMatch> dmatch, match12, match21;
matcher->match(descriptor1, descriptor2, match12);
matcher->match(descriptor2, descriptor1, match21);
for (size_t i = 0; i < match12.size(); i++)
{
	cv::DMatch forward = match12[i];
	cv::DMatch backward = match21[forward.trainIdx];
	if (backward.trainIdx == forward.queryIdx)
		dmatch.push_back(forward);
}

使えるアルゴリズムまとめ

FeatureDetectorインターフェース,DescriptorExtractorインターフェース,DescriptorMatcherインターフェースについて使えるアルゴリズムを以下にまとめておく.(FeatureDetectorはPyramidとかGrid,DescriptorExtractorはOpponentも組み合わせて使えるが,ここでは省略.詳細はこことかここを参照.)

アルゴリズム
FeatureDetector FAST,FASTX,STAR,SIFT,SURF,ORB,BRISK,
MSER,GFTT,HARRIS,Dense,SimpleBlob
DescriptorExtractor SIFT,SURF,BRIEF,BRISK,ORB,FREAK
DescriptorMatcher BruteForce,BruteForce-L1,BruteForce-SL2,
BruteForce-Hamming,BruteForce-Hamming(2),
FlannBased

全部まとめたサンプルコード

二枚の画像のマッチング行う関数を以下に示す.detectorType,descriptorExtractorType,descriptorMatcherTypeで特徴点抽出,特徴記述,マッチングのアルゴリズムを指定する.

#include <opencv2/opencv.hpp>
#include <opencv2/nonfree/nonfree.hpp> // SIFT・SURFモジュール用

void FeatureMatching(
	const std::string& filename1,			// 画像1のファイル名
	const std::string& filename2,			// 画像2のファイル名
	const std::string& featureDetectorName,		// detectorType
	const std::string& descriptorExtractorName,	// descriptorExtractorType
	const std::string& descriptorMatcherName,	// descriptorMatcherType
	bool crossCheck = true)				// マッチング結果をクロスチェックするかどうか
{
	// 画像の読み込み
	cv::Mat img1 = cv::imread(filename1);
	cv::Mat img2 = cv::imread(filename2);

	// SIFT・SURFモジュールの初期化
	cv::initModule_nonfree();

	// 特徴点抽出
	cv::Ptr<cv::FeatureDetector> detector = cv::FeatureDetector::create(featureDetectorName);
	std::vector<cv::KeyPoint> keypoint1, keypoint2;
	detector->detect(img1, keypoint1);
	detector->detect(img2, keypoint2);

	// 特徴記述
	cv::Ptr<cv::DescriptorExtractor> extractor = cv::DescriptorExtractor::create(descriptorExtractorName);
	cv::Mat descriptor1, descriptor2;
	extractor->compute(img1, keypoint1, descriptor1);
	extractor->compute(img2, keypoint2, descriptor2);

	// マッチング
	cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create(descriptorMatcherName);
	std::vector<cv::DMatch> dmatch;
	if (crossCheck)
	{
		// クロスチェックする場合
		std::vector<cv::DMatch> match12, match21;
		matcher->match(descriptor1, descriptor2, match12);
		matcher->match(descriptor2, descriptor1, match21);
		for (size_t i = 0; i < match12.size(); i++)
		{
			cv::DMatch forward = match12[i];
			cv::DMatch backward = match21[forward.trainIdx];
			if (backward.trainIdx == forward.queryIdx)
				dmatch.push_back(forward);
		}
	}
	else
	{
		// クロスチェックしない場合
		matcher->match(descriptor1, descriptor2, dmatch);
	}

	// マッチング結果の表示
	cv::Mat out;
	cv::drawMatches(img1, keypoint1, img2, keypoint2, dmatch, out);
	cv::imshow("matching", out);
	while (cv::waitKey(1) == -1);
}