cv::BFMatcher / FLANN の使い方【OpenCV/C++】〜特徴点マッチングを実装する〜

OpenCV for C++
📌 準備: OpenCV の環境構築がまだの方はこちら → C++ 環境構築ガイド / C#(OpenCvSharp)セットアップ

cv::BFMatcher / FLANN の使い方【OpenCV/C++】〜特徴点マッチングを実装する〜

2枚の画像から抽出した特徴点記述子を照合するのが特徴点マッチングです。OpenCV には総当たり方式の cv::BFMatcher と近似最近傍探索を使う cv::FlannBasedMatcher の2種類が用意されており、記述子の種類や速度要件に応じて使い分けます。

最小の呼び出しイメージは以下のとおりです。

cv::Ptr<cv::BFMatcher> matcher = cv::BFMatcher::create(cv::NORM_HAMMING, true);
std::vector<cv::DMatch> matches;
matcher->match(desc1, desc2, matches);

動作環境

  • OpenCV 4.x
  • コンパイル例: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)

環境構築がまだの方は環境構築ガイドを先にどうぞ。


基本の使い方(BFMatcher + ORB)

ORB 記述子は2値なので距離指標に NORM_HAMMING を使います。クロスチェック (crossCheck=true) を有効にすると双方向に最良のマッチのみ残り、外れ値を大幅に減らせます。

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>

int main() {
    // 画像の読み込み
    cv::Mat img1 = cv::imread("query.jpg",  cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("train.jpg",  cv::IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty()) {
        std::cerr << "画像を読み込めませんでした。パスを確認してください。" << std::endl;
        return 1;
    }

    // ORB で特徴点検出 & 記述子計算
    cv::Ptr<cv::ORB> orb = cv::ORB::create(500);
    std::vector<cv::KeyPoint> kp1, kp2;
    cv::Mat desc1, desc2;
    orb->detectAndCompute(img1, cv::noArray(), kp1, desc1);
    orb->detectAndCompute(img2, cv::noArray(), kp2, desc2);

    if (kp1.empty() || kp2.empty()) {
        std::cerr << "特徴点が検出されませんでした。" << std::endl;
        return 1;
    }

    // BFMatcher: NORM_HAMMING + クロスチェック
    cv::Ptr<cv::BFMatcher> matcher = cv::BFMatcher::create(cv::NORM_HAMMING, true);
    std::vector<cv::DMatch> matches;
    matcher->match(desc1, desc2, matches);

    // 距離でソートして上位 50 件を表示
    std::sort(matches.begin(), matches.end(),
              [](const cv::DMatch& a, const cv::DMatch& b){ return a.distance < b.distance; });
    int topN = std::min(50, static_cast<int>(matches.size()));
    std::vector<cv::DMatch> goodMatches(matches.begin(), matches.begin() + topN);

    // マッチ結果を描画
    cv::Mat result;
    cv::drawMatches(img1, kp1, img2, kp2, goodMatches, result,
                    cv::Scalar::all(-1), cv::Scalar::all(-1),
                    std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    cv::imshow("BFMatcher Result", result);
    cv::waitKey(0);
    return 0;
}

実行結果:

2枚の画像を横並びにした上で、対応する特徴点どうしが線で結ばれたウィンドウが表示されます。
topN を絞っているため視覚的にノイズが少ない状態のマッチが確認できます。

主要行の解説

行 ポイント
cv::ORB::create(500) 最大 500 点を検出。多すぎると後段が重くなる
detectAndCompute 検出と記述子計算を 1 回の呼び出しで済ませる
cv::BFMatcher::create(cv::NORM_HAMMING, true) 第 2 引数 true でクロスチェック有効
matcher->match(...) 1 対 1 マッチング。結果は std::vector<cv::DMatch>
sort → topN 距離が小さい=類似度が高い。上位 N 件に絞るのが定石

引数と戻り値

cv::BFMatcher::create

引数 型 説明
normType int 距離指標。NORM_HAMMING(ORB等2値)/ NORM_L2(SIFT等浮動小数)
crossCheck bool true で双方向チェックし外れ値を減らす
戻り値 cv::Ptr<cv::BFMatcher> マッチャーのスマートポインタ

cv::DescriptorMatcher::match / knnMatch

メソッド 第 3 引数 戻り値の型 備考
match std::vector<cv::DMatch>& なし (void) 1 対 1 マッチング
knnMatch std::vector<std::vector<cv::DMatch>>& なし (void) k 近傍マッチング

cv::DMatch の主なメンバ

メンバ 説明
queryIdx クエリ記述子のインデックス
trainIdx トレーニング記述子のインデックス
distance 2 記述子間の距離(小さいほど類似)

実践例

① knnMatch + Lowe’s Ratio Test(SIFT / FLANN)

SIFT のような浮動小数点記述子には NORM_L2 を使う FLANN が高速です。外れ値除去には 2 近傍を取って距離比を検査する Ratio Test が有効です。

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>

int main() {
    cv::Mat img1 = cv::imread("query.jpg",  cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("train.jpg",  cv::IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty()) {
        std::cerr << "画像を読み込めませんでした。" << std::endl;
        return 1;
    }

    // SIFT で記述子を抽出
    cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
    std::vector<cv::KeyPoint> kp1, kp2;
    cv::Mat desc1, desc2;
    sift->detectAndCompute(img1, cv::noArray(), kp1, desc1);
    sift->detectAndCompute(img2, cv::noArray(), kp2, desc2);

    if (kp1.empty() || kp2.empty()) {
        std::cerr << "特徴点が検出されませんでした。" << std::endl;
        return 1;
    }

    // FLANN ベースマッチャー(浮動小数点記述子向け)
    cv::Ptr<cv::FlannBasedMatcher> matcher = cv::FlannBasedMatcher::create();
    std::vector<std::vector<cv::DMatch>> knnMatches;
    matcher->knnMatch(desc1, desc2, knnMatches, 2); // k=2

    // Lowe's Ratio Test: 最良 / 次良 < 0.75 のみ採用
    const float ratioThresh = 0.75f;
    std::vector<cv::DMatch> goodMatches;
    for (const auto& m : knnMatches) {
        if (m.size() == 2 && m[0].distance < ratioThresh * m[1].distance) {
            goodMatches.push_back(m[0]);
        }
    }

    std::cout << "良好なマッチ数: " << goodMatches.size() << std::endl;

    cv::Mat result;
    cv::drawMatches(img1, kp1, img2, kp2, goodMatches, result,
                    cv::Scalar::all(-1), cv::Scalar::all(-1),
                    std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    cv::imshow("FLANN + Ratio Test", result);
    cv::waitKey(0);
    return 0;
}

実行結果:

良好なマッチ数: (画像によるが数十〜数百件程度)
Ratio Test 通過後の対応線が描画されたウィンドウが表示されます。
BFMatcher のクロスチェックより誤対応がさらに少なくなる傾向があります。

② マッチャーの選択指針まとめ

条件 推奨マッチャー normType
ORB・BRIEF・BRISK(2値) BFMatcher (crossCheck=true) NORM_HAMMING
SIFT・SURF(浮動小数点)+ 精度優先 BFMatcher + knnMatch NORM_L2
SIFT・SURF(浮動小数点)+ 速度優先 FlannBasedMatcher + knnMatch —

つまずきポイント

⚠️ ORB に NORM_L2 を使うと距離が無意味になる

ORB の記述子は CV_8U の2値ベクトルです。NORM_L2 を指定すると数値的には動きますが意味のある距離になりません。ORB には必ず NORM_HAMMING を使ってください。逆に SIFT の CV_32F 記述子に NORM_HAMMING を使うと実行時エラー(OpenCV assertion)が発生します。

⚠️ crossCheck=true と knnMatch の組み合わせは使えない

crossCheck=true で作成した BFMatcher に対して knnMatch(k >= 2) を呼ぶと実行時に cv::Exception が発生します。Ratio Test を使いたい場合は crossCheck=false(デフォルト)で生成し、knnMatch を呼んでください。

⚠️ FLANN を ORB(CV_8U)で使う場合はインデックスパラメータを手動設定する必要がある

cv::FlannBasedMatcher::create() のデフォルトは CV_32F を前提とします。ORB の CV_8U 記述子をそのまま渡すと assertion エラーが出ます。ORB を FLANN で使う場合は desc.convertTo(desc, CV_32F) で変換するか、LSH インデックスを明示的に指定する必要があります。実務では ORB には BFMatcher を使う方がシンプルです。


関連する関数

  • cv::ORB::create / cv::SIFT::create — 記述子の抽出器。マッチャーとセットで使う
  • cv::drawMatches — マッチ結果の可視化。本記事のサンプルで使用
  • cv::findHomography — マッチ点群から射影変換行列を推定する。マッチング後の定番後処理
  • cv::CascadeClassifier の使い方【OpenCV/C++】〜Haar Cascadeで顔検出〜 — 物体認識の別アプローチ

まとめ

  • 2値記述子(ORB など)には cv::BFMatcher + NORM_HAMMING、浮動小数点記述子(SIFT など)で速度が必要なら cv::FlannBasedMatcher を選ぶ。
  • 外れ値除去は「クロスチェック」または「Ratio Test(knnMatch + 閾値 0.75)」の 2 択が実務標準。
  • crossCheck=true と knnMatch は排他なので混在させないこと。

🛠 画像処理のプロが開発するSDK/API

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

タイトルとURLをコピーしました