Digital Reactor
画像・3D処理

画像の特徴点検出アルゴリズム比較実験

画像の特徴点検出アルゴリズム比較実験

はじめに

特徴点検出は、画像内で際立った点を見つける処理で、画像マッチングや物体認識、位置合わせの土台になります。SIFT、KAZE、AKAZE、ORB、BRISK、FREAKと候補は多く、どれを選ぶかは精度・速度・メモリのどれを優先するかで変わります。名前と定性的な評判だけで選ぶと、あとで検出数や記述子の性質が用途に合わないと分かることがあるので、この6つを同じ画像に適用して、検出のされ方と記述子の性質を横に並べて確かめます。

対象読者:

  • 画像処理の基礎を理解している方
  • 特徴点検出アルゴリズムの選択に関心のある方
  • OpenCVを用いたPythonプログラミングの経験がある方

記事のポイント:

  • 各特徴点検出アルゴリズムの理論的な背景を解説
  • OpenCVを用いたPythonによる実装例を提示
  • 実際の画像を用いた実験結果を比較
  • 各アルゴリズムの長所と短所、使い分けについて考察

ライセンスと特許について

性能の比較に入る前に、ライセンスと特許の状況を押さえておきます。実務では、性能より先にここで候補が絞られることがあります。

アルゴリズムライセンス特許状況
SIFT2020年3月に失効、現在は自由に使用可能
KAZEBSDライセンス商用利用も自由
AKAZEBSDライセンス商用利用も自由
ORBBSDライセンス商用利用も自由
BRISKBSDライセンス商用利用も自由
FREAKBSDライセンス商用利用も自由

商用利用を検討している場合、上記のアルゴリズムはすべて自由に使用できます。今回は比較しませんが、比較対象によく上がるSURFは現在も商用利用にはライセンス費用を支払う必要があります。

理論的背景

各アルゴリズムがどんな原理で特徴点を検出しているかを見ていきます。仕組みを押さえておくと、後の実験結果で手法ごとの差がなぜ出るのかを解釈しやすくなります。

SIFT (Scale-Invariant Feature Transform)

SIFTは、スケール空間でのガウシアン差分(DoG: Difference of Gaussian)を利用して特徴点を検出します。スケール空間とは、画像に異なる大きさのガウシアンフィルタを適用して得られる空間です。複数のスケールを見比べて特徴点を探すため、被写体が拡大・縮小されても同じ点を検出しやすくなります。

DoGは以下の式で表現されます。

D(x,y,σ)=(G(x,y,kσ)G(x,y,σ))I(x,y)D(x,y,\sigma) = (G(x,y,k\sigma) - G(x,y,\sigma)) * I(x,y)

ここで、

  • G(x,y,σ)G(x,y,\sigma) はガウシアンカーネル (標準偏差 σ\sigma のガウス分布)
  • I(x,y)I(x,y) は入力画像
  • kk はスケール係数 (スケール空間を生成するためのパラメータ)

です。つまり、DoGは、異なるスケールのガウシアンぼかし画像の差分を計算することで、画像内のエッジやコーナーなどの特徴を強調します。

KAZE

KAZEは、非線形スケールスペースを使用して特徴点を検出します。ガウシアンスケールスペースが画像全体を一様にぼかすのに対し、エッジを保ったままノイズだけを除去できる点が異なります。

非線形拡散フィルタリングは以下の式で表現されます。

Lt=div(c(x,y,t)L)\frac{\partial L}{\partial t} = \text{div}(c(x,y,t) \cdot \nabla L)

ここで、

  • LL は輝度 (画像におけるピクセルの明るさ)
  • cc は伝導係数 (拡散の速さを制御するパラメータ)
  • tt は時間(スケール)パラメータ

です。伝導係数 cc は、画像のエッジ部分で小さくなるように設計されており、これによりエッジをぼかすことなくノイズを除去できます。

AKAZE (Accelerated-KAZE)

AKAZEは、KAZEの高速版として開発されました。高速な非線形スケールスペース(FED: Fast Explicit Diffusion)を使用することで、KAZEと同等の性能を維持しながら、処理速度を大幅に向上させています。

ORB (Oriented FAST and Rotated BRIEF)

ORBは、FAST(Features from Accelerated Segment Test)とBRIEF(Binary Robust Independent Elementary Features)を組み合わせた手法です。FASTで高速にコーナーを検出し、BRIEFのバイナリ記述子で特徴量を表現します。元のFASTとBRIEFは画像の回転に弱いため、ORBは特徴点ごとに向きを推定して回転不変性を持たせています。計算が軽く、リアルタイム処理でよく使われます。

BRISK (Binary Robust Invariant Scalable Keypoints)

BRISKは、スケール空間でのキーポイント検出とバイナリ記述子を組み合わせた手法です。サンプリングパターンを同心円状に配置して、特徴量の計算を軽くしています。

FREAK (Fast Retina Keypoint)

FREAKは、人間の網膜の仕組みを模したサンプリングパターン(中心ほど密に、周辺ほど粗く点を取る配置)を使う特徴量記述子です。記述子のみの手法なので、FASTなどの検出器と組み合わせて使います。自然画像で良い性能が報告されています。

6手法を同じ関数で切り替える実装

OpenCVを使えばどの手法も数行で呼び出せます。比較しやすいよう、アルゴリズム名を渡すだけで切り替えられる形にまとめました。

import cv2
import numpy as np

def detect_features(image, algorithm, n_features=500):
    """
    指定されたアルゴリズムを使用して画像から特徴点を検出する。

    Args:
        image (np.ndarray): 入力画像 (BGR形式)。
        algorithm (str): 使用するアルゴリズムの名前 ('SIFT', 'KAZE', 'AKAZE', 'ORB', 'BRISK', 'FREAK')。
        n_features (int, optional): 検出する特徴点の最大数 (ORB, SIFTに適用)。デフォルトは500。

    Returns:
        tuple: (結果画像, キーポイント, 記述子)
    """
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    result = image.copy()

    if algorithm == 'SIFT':
        sift = cv2.SIFT_create(nfeatures=n_features)
        keypoints, descriptors = sift.detectAndCompute(gray, None)
    elif algorithm == 'KAZE':
        kaze = cv2.KAZE_create()
        keypoints, descriptors = kaze.detectAndCompute(gray, None)
    elif algorithm == 'AKAZE':
        akaze = cv2.AKAZE_create()
        keypoints, descriptors = akaze.detectAndCompute(gray, None)
    elif algorithm == 'ORB':
        orb = cv2.ORB_create(nfeatures=n_features)
        keypoints, descriptors = orb.detectAndCompute(gray, None)
    elif algorithm == 'BRISK':
        brisk = cv2.BRISK_create()
        keypoints, descriptors = brisk.detectAndCompute(gray, None)
    elif algorithm == 'FREAK':
        fast = cv2.FastFeatureDetector_create()
        keypoints = fast.detect(gray, None)
        freak = cv2.xfeatures2d.FREAK_create()
        keypoints, descriptors = freak.compute(gray, keypoints)
    else:
        raise ValueError("無効なアルゴリズム名です。")

    if keypoints:
        result = cv2.drawKeypoints(result, keypoints, result, color=(0, 255, 0), flags=0)

    return result, keypoints, descriptors

# 各アルゴリズムを試す
if __name__ == '__main__':
    # テスト画像の読み込み (例: 'image.jpg')
    test_image = cv2.imread('image.jpg')
    if test_image is None:
        print("画像の読み込みに失敗しました。")
        exit()

    algorithms = ['SIFT', 'KAZE', 'AKAZE', 'ORB', 'BRISK', 'FREAK']

    for algo in algorithms:
        try:
            detected_image, keypoints, descriptors = detect_features(test_image, algo)
            if keypoints:
                print(f"{algo}: {len(keypoints)}個の特徴点を検出")
                cv2.imshow(f'{algo} Feature Detection', detected_image)
            else:
                print(f"{algo}: 特徴点は検出されませんでした")
        except Exception as e:
            print(f"{algo}でエラーが発生しました: {e}")

    cv2.waitKey(0)
    cv2.destroyAllWindows()

検出した特徴点は cv2.drawKeypoints で画像上に描画しています。実装上の注意は2点です。SIFTとORBは n_features で検出数の上限を指定でき、後の実験でORBが500点ちょうどになるのはこの設定によるものです。FREAKは記述子のみの手法なので、FASTで検出した特徴点に対して記述子を計算する2段構えになります。他の4手法はデフォルト設定のまま使い、実務ではこのコードを土台に、検出数の上限やしきい値を用途に合わせて調整します。

同じ画像に適用して比べる

同じ画像に6つのアルゴリズムを適用しました。mean_euclidean_distance / mean_hamming_distanceは、記述子の平均ユークリッド距離 / 平均ハミング距離です。この値が大きいほど記述子どうしを区別しやすく、マッチングの精度が高いことを示します。

検出された特徴点の数:
SIFT: 1325
KAZE: 1199
AKAZE: 1120
ORB: 500
BRISK: 2540
FREAK: 4088

特徴量記述子の統計情報:

SIFT:
  type: 浮動小数点記述子
  dimension: 128
  mean_euclidean_distance: 535.435541714496

KAZE:
  type: 浮動小数点記述子
  dimension: 64
  mean_euclidean_distance: 0.815855450914292

AKAZE:
  type: バイナリ記述子
  dimension: 61
  mean_hamming_distance: 219.85932114132515

ORB:
  type: バイナリ記述子
  dimension: 32
  mean_hamming_distance: 123.53995991983967

BRISK:
  type: バイナリ記述子
  dimension: 64
  mean_hamming_distance: 236.3147283480073

FREAK:
  type: バイナリ記述子
  dimension: 64
  mean_hamming_distance: 222.01950830206223

特徴点の分布を可視化する

検出数だけでは、特徴点が画像のどこに集まっているかが分かりません。そこで各アルゴリズムについて、特徴点の画像上の位置(空間分布)、特徴点としての確からしさ(レスポンス強度、カラーマップで表示)、特徴点の大きさ(スケール、点の大きさで表現)の3つを可視化しました。

まとめ

同じ画像で6つを並べると、検出数も記述子の性質も手法ごとにはっきり分かれました。使い分けの目安は、高精度なマッチングを求めるならSIFT、エッジ情報を保ちたいならKAZEかAKAZE、処理速度を優先するならORBかBRISK、記述子のメモリ効率を重視するならFREAKです。特徴点検出は単体で完結する処理ではなく、画像の位置合わせや物体追跡の前段に置かれるものなので、選定では後段のマッチング処理まで含めて速度と精度のバランスを決めることになります。

近年は深層学習を用いた特徴点検出も盛んで、SuperPointやD2-NetはSIFTやORBを上回る性能が報告されています。これらは自己教師あり学習や教師あり学習で訓練され、特定のタスクに最適化された特徴量を抽出できます。導入を検討する場合も、まず今回のような従来手法でベースラインを作っておくと、置き換えたときの改善幅を測りやすくなります。

関連記事

← 技術ブログ一覧へ