前回は単純に特徴点をマッチングするだけだったので、間違ったマッチングが結構ありました。そこで今回はマッチング結果の特徴量の距離によるフィルタリングとRANSACの実行により、どれだけ間違ったフィルタリングが排除されているかを見てみたいと思います。
まず、以下は前回と同様に単純に特徴点をマッチングするまでのコードです。
#include #include#include #include #include #include #include #include #include #include namespace fs = boost::filesystem; using namespace cv; using namespace std; int main() { vector<String> imageFileList; map<string, Mat> images; // マッチング対象となる画像を格納したフォルダと画像ファイルの拡張子をワイルドカードで指定 // imageFileListに画像ファイルパスをリスト形式で取得 glob("C:\\FeatMatcher\\images\\*.jpeg", imageFileList); // 画像ファイル名でリストをソート std::sort(imageFileList.begin(), imageFileList.end()); // 画像ファイルを読み込んでimages配列に画像データを格納 for (const auto& i : imageFileList) { images[i] = imread(i); } // 1画像辺りの最大特徴点数 const int MAX_FEATURES = 500; map<string, vector<KeyPoint> > keypoints; // 特徴点 map<string, Mat> descriptors; // 特徴量 // ORBインスタンスの生成 auto orb = cv::ORB::create(MAX_FEATURES); // 全イメージの特徴点と特徴量を抽出 for (const auto& imageFile : imageFileList) { Mat grayscale; // 画像をグレースケールに変換 cvtColor(images[imageFile], grayscale, COLOR_BGR2GRAY); // 画像ファイルから特徴点と特徴量の抽出 orb->detectAndCompute(grayscale, cv::Mat(), keypoints[imageFile], descriptors[imageFile]); cout << "Found " << keypoints[imageFile].size() << " keypoints for " << imageFile << endl; } // マッチングエンジンインスタンスの生成 Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming"); // マッチング結果を保存するフォルダの作成 fs::path filePath = fs::path(imageFileList[0]).parent_path().append("matches"); if (!fs::exists(filePath)) { fs::create_directory(filePath); } // 全画像を総当たりでマッチング for (size_t i = 0; i < imageFileList.size() - 1; ++i) { for (size_t j = i + 1; j < imageFileList.size(); ++j) { // マッチング対象画像 const string imgI = imageFileList[i]; const string imgJ = imageFileList[j]; vector< vector<DMatch> > _matches; Mat out; // クエリ集合の各ディスクリプタに対して,最も良い上位2個のマッチを取得 matcher->knnMatch(descriptors[imgI], descriptors[imgJ], _matches, 2); cout << "Found " << _matches.size() << " raw matches from " << imgI << "and" << imgJ << endl; // マッチング結果を線で接続してイメージ化 drawMatches(images[imgI], keypoints[imgI], images[imgJ], keypoints[imgJ], _matches, out, CV_RGB(255, 0, 0)); // マッチング結果イメージの保存 string pathNameRaw = filePath.string() + string("\\raw_") + to_string(i) + string("_") + to_string(j) + string(".jpg"); imwrite(pathNameRaw, out);
この結果は前回同様、下記のようにほとんどの線は平行ですが、一部間違った特徴点のマッチング結果が斜めの線となっています。2枚の写真の方向関係にもよりますが、下記のようにほぼ同じ方向から撮影された2枚の写真の場合、平行な線がエピポーラ線上にある特徴点のマッチングです。
そこで、ここで取得できた特徴点をによりフィルタリングします。
vector<DMatch> matches; // 2個のマッチの距離が倍以上のものだけ(特徴の大きいマッチ)をフィルタリングする for (size_t ii = 0; ii < _matches.size(); ii++) { cout << "matches[ii][0].distance:" << _matches[ii][0].distance << " <> _matches[ii][1].distance:" << _matches[ii][1].distance << endl; if (_matches[ii][0].distance < _matches[ii][1].distance * 0.70) { matches.push_back(_matches[ii][0]); } } cout << "Found " << matches.size() << " matches from " << imgI << "and" << imgJ << endl;
そして、次にRANSACを実行してみます。
vector<uchar> status(matches.size()); vector<Point2f> points1, points2; for (const DMatch& m : matches) { points1.push_back(keypoints[imgI][m.queryIdx].pt); points2.push_back(keypoints[imgJ][m.trainIdx].pt); } findFundamentalMat(points1, points2, status, FM_RANSAC); vector<DMatch> finalMatches; for (size_t ii = 0; ii < matches.size(); ii++) { if (status[ii]) { finalMatches.push_back(matches[ii]); } } cout << "Found " << finalMatches.size() << " matches(RANSAC) from " << imgI << "and" << imgJ << endl; // マッチング結果を線で接続してイメージ化 drawMatches(images[imgI], keypoints[imgI], images[imgJ], keypoints[imgJ], finalMatches, out, CV_RGB(255, 0, 0)); // マッチング結果イメージの保存 string pathNameRansac = filePath.string() + string("\\ransac_") + to_string(i) + string("_") + to_string(j) + string(".jpg"); imwrite(pathNameRansac, out);
その結果として取得出来たマッチング結果が下記です。
このように平行な線だけになっていて、間違った特徴点マッチングの結果が排除されており、エピポーラ線上の正しいマッチング結果のみになっています。