cv::SIFT の使い方【OpenCV/C++】〜SIFT特徴点の検出と記述子抽出〜

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

cv::SIFT の使い方【OpenCV/C++】〜SIFT特徴点の検出と記述子抽出〜

cv::SIFT は、スケール・回転に不変な特徴点を検出し、128次元の記述子を生成するクラスです。物体認識・画像マッチング・パノラマ合成など、実務で広く使われます。最小構成は以下のとおりです。

auto sift = cv::SIFT::create();
sift->detectAndCompute(gray, cv::noArray(), keypoints, descriptors);

動作環境

  • OpenCV 4.x(4.4以降を推奨)
  • C++17
  • ビルド: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)

⚠️ OpenCV 4.4 以降、SIFTは特許期限切れにより opencv_contrib なしのメインモジュールに移動しています。それ以前のバージョンでは opencv_contrib の xfeatures2d モジュールが必要です。

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


基本の使い方

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

int main()
{
    // 画像読み込み
    cv::Mat img = cv::imread("input.jpg");
    if (img.empty()) {
        std::cerr << "画像の読み込みに失敗しました" << std::endl;
        return 1;
    }

    // グレースケール変換(SIFTはグレースケール画像を入力とする)
    cv::Mat gray;
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

    // SIFTオブジェクトを生成
    auto sift = cv::SIFT::create();

    // 特徴点と記述子を同時に検出
    std::vector<cv::KeyPoint> keypoints;
    cv::Mat descriptors;
    sift->detectAndCompute(gray, cv::noArray(), keypoints, descriptors);

    std::cout << "検出された特徴点数: " << keypoints.size() << std::endl;
    std::cout << "記述子サイズ: " << descriptors.rows << " x " << descriptors.cols << std::endl;

    // 特徴点を元画像に描画
    cv::Mat output;
    cv::drawKeypoints(img, keypoints, output,
                      cv::Scalar::all(-1),
                      cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

    cv::imwrite("sift_keypoints.jpg", output);
    std::cout << "sift_keypoints.jpg に保存しました" << std::endl;

    return 0;
}

実行結果:

検出された特徴点数: 1247
記述子サイズ: 1247 x 128
sift_keypoints.jpg に保存しました

検出点数は画像の内容によって変わります。各特徴点は位置・スケール・方向を持ち、DRAW_RICH_KEYPOINTS を指定することでスケールと向きを円と線で可視化できます。

コードの要点

行の内容 説明
cv::SIFT::create() SIFTインスタンスを生成。引数で特徴点数の上限などを調整できる
cv::noArray() マスクなし。特定領域に限定したい場合は8ビット1チャンネルのマスクを渡す
descriptors CV_32F・128列の行列。各行が1特徴点の記述子
DRAW_RICH_KEYPOINTS スケール・向きを円で描画するフラグ

引数と戻り値

cv::SIFT::create()

引数 型 デフォルト 説明
nfeatures int 0 保持する特徴点の最大数(0=制限なし)
nOctaveLayers int 3 オクターブあたりのレイヤー数
contrastThreshold double 0.04 低コントラスト点を除去する閾値
edgeThreshold double 10 エッジ上の点を除去する閾値
sigma double 1.6 入力画像に適用するガウシアンのσ
enable_precise_upscale bool false 高精度スケールアップ(4.8以降)

detectAndCompute()

引数 型 説明
image cv::InputArray 入力画像(グレースケール推奨)
mask cv::InputArray 検出マスク(不要なら cv::noArray())
keypoints std::vector<cv::KeyPoint>& 出力: 検出された特徴点
descriptors cv::OutputArray 出力: 128次元記述子(CV_32F)
useProvidedKeypoints bool true にすると既存のkeypointsを使い記述子のみ計算

実践例

2枚の画像をSIFTでマッチングする

物体認識や位置合わせでよく使うパターンです。

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

int main()
{
    cv::Mat img1 = cv::imread("scene1.jpg");
    cv::Mat img2 = cv::imread("scene2.jpg");
    if (img1.empty() || img2.empty()) {
        std::cerr << "画像の読み込みに失敗しました" << std::endl;
        return 1;
    }

    cv::Mat gray1, gray2;
    cv::cvtColor(img1, gray1, cv::COLOR_BGR2GRAY);
    cv::cvtColor(img2, gray2, cv::COLOR_BGR2GRAY);

    // 特徴点数を上限500に絞ってSIFTを生成
    auto sift = cv::SIFT::create(500);

    std::vector<cv::KeyPoint> kp1, kp2;
    cv::Mat desc1, desc2;
    sift->detectAndCompute(gray1, cv::noArray(), kp1, desc1);
    sift->detectAndCompute(gray2, cv::noArray(), kp2, desc2);

    // BFMatcherでL2距離マッチング(SIFTはL2が適切)
    cv::BFMatcher matcher(cv::NORM_L2);
    std::vector<std::vector<cv::DMatch>> knnMatches;
    matcher.knnMatch(desc1, desc2, knnMatches, 2);

    // Lowe's ratio testで良いマッチのみ残す
    std::vector<cv::DMatch> goodMatches;
    for (const auto& m : knnMatches) {
        if (m[0].distance < 0.75f * m[1].distance) {
            goodMatches.push_back(m[0]);
        }
    }

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

    // マッチング結果を描画して保存
    cv::Mat matchImg;
    cv::drawMatches(img1, kp1, img2, kp2, goodMatches, matchImg);
    cv::imwrite("sift_matches.jpg", matchImg);
    std::cout << "sift_matches.jpg に保存しました" << std::endl;

    return 0;
}

実行結果:

良いマッチ数: 87
sift_matches.jpg に保存しました

Lowe’s ratio test(0.75f の部分)は精度とマッチ数のトレードオフです。値を下げると精度が上がりマッチ数が減ります。実務では 0.7〜0.8 を試してチューニングしてください。


特定領域だけで特徴点を検出する(マスク使用)

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

int main()
{
    cv::Mat img = cv::imread("input.jpg");
    if (img.empty()) {
        std::cerr << "画像の読み込みに失敗しました" << std::endl;
        return 1;
    }

    cv::Mat gray;
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

    // 画像中央半分のみを有効にするマスクを作成
    cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
    cv::Rect roi(gray.cols / 4, gray.rows / 4,
                 gray.cols / 2, gray.rows / 2);
    mask(roi) = 255;

    auto sift = cv::SIFT::create();
    std::vector<cv::KeyPoint> keypoints;
    cv::Mat descriptors;
    sift->detectAndCompute(gray, mask, keypoints, descriptors);

    std::cout << "ROI内の特徴点数: " << keypoints.size() << std::endl;

    cv::Mat output;
    cv::drawKeypoints(img, keypoints, output,
                      cv::Scalar(0, 255, 0),
                      cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    cv::rectangle(output, roi, cv::Scalar(0, 0, 255), 2);
    cv::imwrite("sift_masked.jpg", output);

    return 0;
}

つまずきポイント

✅ OpenCV 4.4未満では xfeatures2d が必要

OpenCV 4.4 未満では SIFT は opencv_contrib の xfeatures2d モジュールにありました。その場合はヘッダが <opencv2/xfeatures2d.hpp>、クラスが cv::xfeatures2d::SIFT になります。現行のOpenCV 4.4以降なら <opencv2/features2d.hpp> を使い、cv::SIFT で直接アクセスできます。バージョンを確認してからコードを書きましょう。

// OpenCV 4.4未満の場合(参考)
#include <opencv2/xfeatures2d.hpp>
auto sift = cv::xfeatures2d::SIFT::create();

⚠️ 記述子の型は CV_32F・NORM_L2 でマッチング

SIFTの記述子は CV_32F(32ビット浮動小数点)です。BFMatcher を使う際は cv::NORM_L2 を指定してください。ORBやBRIEFで使う cv::NORM_HAMMING を誤って指定するとアサーションエラーになります。

// ✅ 正しい
cv::BFMatcher matcher(cv::NORM_L2);

// ❌ SIFTに NORM_HAMMING は使えない
cv::BFMatcher matcher(cv::NORM_HAMMING);

⚠️ カラー画像をそのまま渡すと結果が変わる場合がある

detectAndCompute にカラー画像(CV_8UC3)を渡しても内部でグレースケール変換されますが、実装の挙動に依存します。明示的に cv::cvtColor でグレースケール変換してから渡すのが安全です。また日本語パスを cv::imread に渡すと環境によって空の Mat が返るため、パスは ASCII のみにしてください。


関連する関数

  • cv::drawKeypoints — 特徴点を画像に描画する
  • cv::drawMatches — 2画像間のマッチング結果を並べて描画する
  • cv::BFMatcher — 総当たりマッチャー。SIFTとよく組み合わせて使う
  • cv::FlannBasedMatcher — 大量の特徴点を高速にマッチングしたい場合に有効
  • cv::ORB — SIFTより高速な代替手法。バイナリ記述子で組込向きに使われる
  • cv::CascadeClassifier の使い方【OpenCV/C++】〜Haar Cascadeで顔検出〜 — 別アプローチの物体検出手法として参照

まとめ

cv::SIFT は OpenCV 4.4以降メインモジュールに統合され、cv::SIFT::create() と detectAndCompute() の2ステップで使えます。マッチングには cv::BFMatcher + NORM_L2 + Lowe’s ratio test の組み合わせが定番です。特徴点数の上限(nfeatures)やコントラスト閾値(contrastThreshold)を用途に合わせて調整することで、精度と速度のバランスをとれます。

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

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

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

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