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

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

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

cv::AKAZE は、スケール・回転に対してロバストな特徴点を検出し、バイナリ記述子を生成するクラスです。SIFTやSURFに比べてライセンス上の制約が少なく、OpenCVの標準モジュール(features2d)に含まれているため、追加のcontribビルドなしに使えます。

auto akaze = cv::AKAZE::create();
akaze->detectAndCompute(img, cv::noArray(), keypoints, descriptors);

動作環境

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

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


基本の使い方

以下は、1枚の画像から AKAZE 特徴点を検出してウィンドウに描画する最小構成の完全プログラムです。

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

int main()
{
    // 画像の読み込み
    cv::Mat img = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE);
    if (img.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // AKAZE 検出器の生成
    auto akaze = cv::AKAZE::create();

    // 特徴点と記述子を同時に計算
    std::vector<cv::KeyPoint> keypoints;
    cv::Mat descriptors;
    akaze->detectAndCompute(img, 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::imshow("AKAZE Keypoints", output);
    cv::waitKey(0);

    return 0;
}

実行結果

検出された特徴点数: 512
記述子サイズ: 512 x 61

特徴点数は画像の内容によって変わります。各特徴点は位置・スケール・向きを持ち、DRAW_RICH_KEYPOINTS フラグを渡すと円のサイズと向きも描画されます。記述子はデフォルトで CV_8U のバイナリ形式(MLDB)です。

コードのポイント

  • cv::AKAZE::create() — 検出器をデフォルトパラメータで生成します
  • detectAndCompute() — 特徴点の検出と記述子の計算を1回で実行します。第2引数はマスク(不要なら cv::noArray())
  • cv::drawKeypoints() — DRAW_RICH_KEYPOINTS を指定することでスケールと向きも可視化できます

引数と戻り値

cv::AKAZE::create()

引数 型 デフォルト 説明
descriptor_type int AKAZE::DESCRIPTOR_MLDB 記述子の種類(DESCRIPTOR_KAZE, DESCRIPTOR_MLDB_UPRIGHT 等)
descriptor_size int 0 記述子のビット数(0 = フル)
descriptor_channels int 3 記述子のチャンネル数
threshold float 0.001f 特徴点の応答値閾値。大きくすると特徴点数が減る
nOctaves int 4 スケールスペースのオクターブ数
nOctaveLayers int 4 各オクターブのサブレベル数
diffusivity int KAZE::DIFF_PM_G2 拡散フィルタの種類

戻り値: cv::Ptr<cv::AKAZE> — スマートポインタで返されます

detectAndCompute()

引数 型 説明
image cv::InputArray 入力画像(グレースケール推奨)
mask cv::InputArray 検出マスク(不要な場合は cv::noArray())
keypoints std::vector<cv::KeyPoint>& 出力される特徴点リスト
descriptors cv::OutputArray 出力される記述子(CV_8U のバイナリ)
useProvidedKeypoints bool true のとき既存のキーポイントを再利用

実践例

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

AKAZE の最も典型的な用途は、2画像間のマッチングです。バイナリ記述子なので cv::BFMatcher にはハミング距離を使います。

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

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

    auto akaze = cv::AKAZE::create();

    std::vector<cv::KeyPoint> kp1, kp2;
    cv::Mat desc1, desc2;
    akaze->detectAndCompute(img1, cv::noArray(), kp1, desc1);
    akaze->detectAndCompute(img2, cv::noArray(), kp2, desc2);

    // バイナリ記述子にはハミング距離を使う
    cv::BFMatcher matcher(cv::NORM_HAMMING);
    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.size() >= 2 && m[0].distance < 0.75f * 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("Matches", result);
    cv::waitKey(0);

    return 0;
}

実行結果

良いマッチ数: 87

Lowe’s ratio test(0.75f の閾値)を通過したマッチのみが描画されます。値を小さくするほど厳しくなり、マッチ数は減りますが精度が上がります。

threshold で特徴点数を制御する

シーンによって特徴点が多すぎる・少なすぎると感じたときは threshold を調整します。

auto akaze = cv::AKAZE::create(
    cv::AKAZE::DESCRIPTOR_MLDB,
    0, 3,
    0.003f  // デフォルト 0.001f より大きくして特徴点数を絞る
);

つまずきポイント

⚠️ NORM_L2 を使うとassertionエラーになる

AKAZE の記述子はデフォルトでバイナリ形式(CV_8U)です。cv::BFMatcher に cv::NORM_L2 を渡すと実行時エラーになります。必ず cv::NORM_HAMMING を使ってください。

// NG: バイナリ記述子に L2 距離は使えない
cv::BFMatcher matcher(cv::NORM_L2);

// OK
cv::BFMatcher matcher(cv::NORM_HAMMING);

⚠️ カラー画像を渡しても動くが結果が変わることがある

detectAndCompute() はカラー画像(CV_8UC3)を渡すと内部でグレースケール変換して処理します。ただし変換コストが発生するため、事前に cv::cvtColor() でグレースケール変換しておくのが確実です。

⚠️ 記述子が空のときにマッチャーがクラッシュする

特徴点が1点も検出されなかった場合、descriptors は空の Mat になります。matcher.match() や matcher.knnMatch() に空行列を渡すと assertion 例外が発生します。desc1.empty() チェックをマッチング前に入れておきましょう。

if (desc1.empty() || desc2.empty()) {
    std::cerr << "記述子が空です。画像を確認してください" << std::endl;
    return 1;
}

関連する関数

  • cv::ORB::create() — 同じくバイナリ記述子を持つ特徴量検出器。AKAZEより高速だが精度はやや落ちる
  • cv::SIFT::create() — OpenCV 4.4 以降は標準モジュールに含まれる。浮動小数点記述子(NORM_L2 使用)
  • cv::BFMatcher — ブルートフォースマッチャー。小規模データに向く
  • cv::FlannBasedMatcher — 大規模データ向けの近似最近傍マッチャー(バイナリ記述子を使う場合は LSH インデックスが必要)

まとめ

cv::AKAZE は追加ビルド不要で使えるロバストな特徴量検出器です。バイナリ記述子なので NORM_HAMMING でマッチングし、threshold で特徴点数を調整するのが実装の基本です。Lowe’s ratio test を組み合わせることで精度の高いマッチング結果が得られます。

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

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

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

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