cv::drawKeypoints / drawMatches の使い方【OpenCV/C++】〜特徴点・マッチング結果を可視化する〜
特徴点検出やマッチングのデバッグで最初に使う関数が cv::drawKeypoints と cv::drawMatches です。検出した KeyPoint の位置・スケール・向きを円で重ね描きしたり、2枚の画像を並べてマッチング対応線を引いたりできます。
最小の呼び出しイメージは次のとおりです。
cv::drawKeypoints(img, keypoints, out, cv::Scalar::all(-1),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::drawMatches(img1, kp1, img2, kp2, good_matches, out);
動作環境
- OpenCV 4.x
- コンパイル例:
g++ -std=c++17 main.cpp `pkg-config --cflags --libs opencv4` -o main
環境構築がまだの方は環境構築ガイドを先にどうぞ。
基本の使い方
ORB で特徴点を検出し、drawKeypoints で描画する最小完全プログラムです。
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
#include <iostream>
int main()
{
// グレースケールで読み込む(特徴点検出はグレースケール推奨)
cv::Mat gray = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE);
if (gray.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return 1;
}
// ORB 検出器を生成(最大500点)
cv::Ptr<cv::ORB> orb = cv::ORB::create(500);
std::vector<cv::KeyPoint> keypoints;
orb->detect(gray, keypoints);
std::cout << "検出された特徴点数: " << keypoints.size() << std::endl;
// drawKeypoints: 特徴点をカラー画像に重ね描き
cv::Mat out;
cv::drawKeypoints(
gray, // 入力画像
keypoints, // KeyPoint のリスト
out, // 出力画像
cv::Scalar::all(-1), // -1 でランダム色
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS // スケール・向きも描画
);
cv::imwrite("keypoints_out.jpg", out);
std::cout << "keypoints_out.jpg を保存しました" << std::endl;
return 0;
}
実行結果:
検出された特徴点数: 487
keypoints_out.jpg を保存しました
keypoints_out.jpg を開くと、各特徴点の位置に円(大きさ=スケール、線=向き)がランダムな色で描かれています。
引数と戻り値
cv::drawKeypoints
| 引数 | 型 | 説明 |
|---|---|---|
image |
const cv::Mat& |
入力画像(グレー・カラー問わず) |
keypoints |
const std::vector<KeyPoint>& |
描画する特徴点リスト |
outImage |
cv::Mat& |
出力画像(image と同一でも可) |
color |
const cv::Scalar& |
描画色。Scalar::all(-1) でランダム色 |
flags |
cv::DrawMatchesFlags |
描画オプション(下表参照) |
DrawMatchesFlags の主要値:
| フラグ | 意味 |
|---|---|
DEFAULT |
中心点のみ描画 |
DRAW_RICH_KEYPOINTS |
スケール・向きも円で描画 |
NOT_DRAW_SINGLE_POINTS |
マッチしていない点を描画しない |
DRAW_OVER_OUTIMG |
出力画像を初期化せず上書き |
cv::drawMatches
| 引数 | 型 | 説明 |
|---|---|---|
img1 |
const cv::Mat& |
1枚目の入力画像 |
keypoints1 |
const std::vector<KeyPoint>& |
1枚目の特徴点 |
img2 |
const cv::Mat& |
2枚目の入力画像 |
keypoints2 |
const std::vector<KeyPoint>& |
2枚目の特徴点 |
matches1to2 |
const std::vector<DMatch>& |
マッチング結果 |
outImg |
cv::Mat& |
出力画像(2枚を横並びに合成) |
matchColor |
cv::Scalar |
マッチング線の色(省略可) |
singlePointColor |
cv::Scalar |
未マッチ点の色(省略可) |
matchesMask |
std::vector<char> |
描画するマッチを絞るマスク(省略可) |
flags |
cv::DrawMatchesFlags |
描画オプション(省略可) |
戻り値はなし(outImg に結果が書き込まれます)。
実践例
2枚の画像間でマッチング結果を可視化する
BFMatcher で ORB 記述子をマッチングし、距離でフィルタした結果を描画します。
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
#include <iostream>
#include <algorithm>
int main()
{
cv::Mat img1 = cv::imread("scene1.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat img2 = cv::imread("scene2.jpg", cv::IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return 1;
}
cv::Ptr<cv::ORB> orb = cv::ORB::create(1000);
std::vector<cv::KeyPoint> kp1, kp2;
cv::Mat desc1, desc2;
// detect + compute を同時に行う
orb->detectAndCompute(img1, cv::noArray(), kp1, desc1);
orb->detectAndCompute(img2, cv::noArray(), kp2, desc2);
// BFMatcher: ORB は NORM_HAMMING を使う
cv::BFMatcher matcher(cv::NORM_HAMMING, /*crossCheck=*/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; });
if (matches.size() > 50)
matches.resize(50);
std::cout << "表示するマッチ数: " << matches.size() << std::endl;
// drawMatches で 2 枚を横並びにしてマッチング線を描画
cv::Mat out;
cv::drawMatches(
img1, kp1,
img2, kp2,
matches, out,
cv::Scalar(0, 255, 0), // マッチング線: 緑
cv::Scalar(255, 0, 0), // 未マッチ点: 青
std::vector<char>(),
cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS
);
cv::imwrite("matches_out.jpg", out);
std::cout << "matches_out.jpg を保存しました" << std::endl;
return 0;
}
実行結果:
表示するマッチ数: 50
matches_out.jpg を保存しました
matches_out.jpg には img1・img2 が横に並び、対応する特徴点が緑の線で結ばれた画像が出力されます。
knnMatch 結果を ratio test でフィルタして描画する
Lowe’s ratio test を使いたい場合は drawMatchesKnn ではなく、DMatch に変換してから drawMatches を使うのが実務的です。
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
#include <iostream>
int main()
{
cv::Mat img1 = cv::imread("scene1.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat img2 = cv::imread("scene2.jpg", cv::IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return 1;
}
cv::Ptr<cv::ORB> orb = cv::ORB::create(1000);
std::vector<cv::KeyPoint> kp1, kp2;
cv::Mat desc1, desc2;
orb->detectAndCompute(img1, cv::noArray(), kp1, desc1);
orb->detectAndCompute(img2, cv::noArray(), kp2, desc2);
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<std::vector<cv::DMatch>> knn_matches;
// k=2 で最近傍を 2 点取得
matcher.knnMatch(desc1, desc2, knn_matches, 2);
// Lowe's ratio test: 0.75 は調整可
const float ratio_thresh = 0.75f;
std::vector<cv::DMatch> good;
for (const auto& m : knn_matches) {
if (m.size() == 2 && m[0].distance < ratio_thresh * m[1].distance)
good.push_back(m[0]);
}
std::cout << "ratio test 通過数: " << good.size() << std::endl;
cv::Mat out;
cv::drawMatches(img1, kp1, img2, kp2, good, out);
cv::imwrite("ratio_matches_out.jpg", out);
std::cout << "ratio_matches_out.jpg を保存しました" << std::endl;
return 0;
}
実行結果:
ratio test 通過数: 38
ratio_matches_out.jpg を保存しました
つまずきポイント
⚠️ ORB の記述子に NORM_L2 を使うとマッチング結果がおかしくなる
ORB・BRISK・BRIEF などバイナリ記述子は cv::NORM_HAMMING を使う必要があります。SIFT・SURF など浮動小数点記述子は cv::NORM_L2 を使います。誤ったノルムを指定しても例外は出ず、単に距離の意味が壊れるため、drawMatches の出力を見てマッチ線が明らかにおかしい場合はまずここを確認してください。
// ORB → NORM_HAMMING
cv::BFMatcher matcher(cv::NORM_HAMMING);
// SIFT → NORM_L2
cv::BFMatcher matcher(cv::NORM_L2);
⚠️ crossCheck=true と knnMatch は併用できない
BFMatcher の crossCheck=true を有効にすると knnMatch を呼び出したとき実行時エラーになります(OpenCV 4.x 時点)。ratio test を使いたい場合は crossCheck=false のまま knnMatch を呼び、自前でフィルタしてください。
⚠️ desc1 または desc2 が空のときに match() がクラッシュする
特徴点が 0 件の場合、記述子 Mat が空になり matcher.match() が内部アサーションで落ちます。match() を呼ぶ前に desc1.empty() || desc2.empty() のチェックを入れるのが確実です。
if (desc1.empty() || desc2.empty()) {
std::cerr << "記述子が空です(特徴点が検出されていない可能性)" << std::endl;
return 1;
}
関連する関数
- cv::ORB::create / cv::SIFT::create — 特徴点検出器の生成。
drawKeypointsと組み合わせて使う基本セット - cv::BFMatcher / cv::FlannBasedMatcher —
drawMatchesの入力となるマッチング結果を生成する - cv::drawMatchesKnn —
knnMatchの結果(vector<vector<DMatch>>)をそのまま渡せるオーバーロード。ratio test 前の全候補を可視化したい場合に便利 - cv::CascadeClassifier の使い方【OpenCV/C++】〜Haar Cascadeで顔検出〜 — 別の物体検出アプローチ。特徴点ベースとの使い分け参考に
まとめ
cv::drawKeypointsで特徴点の位置・スケール・向きを手軽に可視化できる。DRAW_RICH_KEYPOINTSフラグを付けると情報量が増えデバッグに役立つ。cv::drawMatchesは2枚の画像を横並びにしてマッチング対応線を描画する。フィルタ前後を比較することでパラメータ調整の指針になる。- BFMatcher を使う際はノルム指定(
NORM_HAMMINGvsNORM_L2)とcrossCheckの制約を把握しておくと余計なバグを避けられる。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。
→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

