VLFeat ライブラリを使った SIFT の計算

またまた SIFT のエントリになってしまいますが,TheiaSfM の特徴点計算で OpenCV ではなくて VLFeat が使われていたこともあって,試しに使ってみることにしました.どこかの論文に書いてあったと思うのですが,OpenCV の SIFT よりも VLFeat の SIFT のほうが特徴点がたくさんでるとか出ないとか...ここで生成した SIFT を保存して,いよいよ次のエントリで画像間の類似度計算をします.

X. VLFeat

VLFeat ライブラリは画像識別や特徴点の抽出/マッチングに特化したアルゴリズムのライブラリになります.VLFeat で実装されているアルゴリズムの中には,OpenCV に実装の無いものもあって,この後の画像類似度計算のところで使う Fisher Vector 生成の実装も現状では OpenCV には無いような気がします.ドキュメント周りも結構整備してくれてありまして,例えば SIFT の計算のチュートリアルは下記になります.

www.vlfeat.org

X. VLFeat の SIFT の使い方

で,実際に VLFeat を使った SIFT 特徴量の計算ですが,大きな流れは下記のようになります.次の Octave に行くかどうかがライブラリユーザに委ねられてるところとか,ちょっと面白い作りだなと思いました.

0. u8 型から float 型に型変更.
  // 0. Convert image to float.
  cv::Mat float_img(gray_image.rows, gray_image.cols, CV_32FC1);
  gray_image.convertTo(float_img, CV_32FC1);
1. 1st オクターブをどうするか決定します.この値が負の値なら,入力画像をアップサンプル(拡大)します.
  // 1. Compute Valid First Octave.
  const int first_octave = GetValidFirstOctave(params.def_first_octave_, float_img.cols,
                                               float_img.rows, params.max_scaled_dim_);
2. VLFeat の SIFT 生成器を生成します.(vl_sift_new)
  // 2. Create new sift filter.
  VlSiftFilt* sift_filter = vl_sift_new(float_img.cols, float_img.rows, params.num_octave_,
                                        params.num_levels_, first_octave);
3. 必要なパラメータを設定.
  // 3. Set Parameters.
  vl_sift_set_peak_thresh(sift_filter, params.peak_threshold_);
  vl_sift_set_edge_thresh(sift_filter, params.edge_threshold_);
  vl_sift_set_norm_thresh(sift_filter, params.norm_threshold_);
  vl_sift_set_magnif(sift_filter, params.magnif_);
  vl_sift_set_window_size(sift_filter, params.window_size_);
4. 1st オクターブを処理します.
  // 4. Process First Octave.
  int octave_no = 1;
  int vl_status =
      vl_sift_process_first_octave(sift_filter, reinterpret_cast<float*>(float_img.data));
5.1. 現在のオクターブに関して,キーポイントを計算・取得します.
    // Detect Key Points.
    vl_sift_detect(sift_filter);
5.2. 取得したキーポイント一つ一つに対してループをまわし,識別子を計算する.
      // Compute orientations.
      double angles[4];
      int num_angles = vl_sift_calc_keypoint_orientations(sift_filter, angles, &vl_keypoints[i]);

      // Compute descriptor for all keypoints and orientations.
      Eigen::VectorXf descriptor(params.num_sift_dimensions_);
      for (int j = 0; j < num_angles; ++j) {
        descriptor.setZero();
        vl_sift_calc_keypoint_descriptor(sift_filter, descriptor.data(), &vl_keypoints[i],
                                         angles[j]);
        descriptors.push_back(descriptor);
        KeyPoint kp(vl_keypoints[i].x, vl_keypoints[i].y, vl_keypoints[i].sigma, angles[j], 0,
                    vl_keypoints[i].o);
        keypoints.push_back(kp);
      }
5.3. 次のオクターブへ向かう.
    // Proceed to next octave.
    vl_status = vl_sift_process_next_octave(sift_filter);
    octave_no = octave_no + 1;
6. SIFT 生成器を削除し,終了.
  // X. Delete sift caculator.
  vl_sift_delete(sift_filter);

X. VLFeat の SIFT の使い方(実際の実装)


void ComputeKeyPointsAndDescriptors(const cv::Mat& gray_image, const SIFTParams& params,
                                    std::vector<KeyPoint>& keypoints,
                                    std::vector<Eigen::VectorXf>& descriptors) {
  keypoints.clear();
  descriptors.clear();

  // 0. Convert image to float.
  cv::Mat float_img(gray_image.rows, gray_image.cols, CV_32FC1);
  gray_image.convertTo(float_img, CV_32FC1);

  // 1. Compute Valid First Octave.
  const int first_octave = GetValidFirstOctave(params.def_first_octave_, float_img.cols,
                                               float_img.rows, params.max_scaled_dim_);

  // 2. Create new sift filter.
  VlSiftFilt* sift_filter = vl_sift_new(float_img.cols, float_img.rows, params.num_octave_,
                                        params.num_levels_, first_octave);

  // 3. Set Parameters.
  vl_sift_set_peak_thresh(sift_filter, params.peak_threshold_);
  vl_sift_set_edge_thresh(sift_filter, params.edge_threshold_);
  vl_sift_set_norm_thresh(sift_filter, params.norm_threshold_);
  vl_sift_set_magnif(sift_filter, params.magnif_);
  vl_sift_set_window_size(sift_filter, params.window_size_);

  // 4. Process First Octave.
  int octave_no = 1;
  int vl_status =
      vl_sift_process_first_octave(sift_filter, reinterpret_cast<float*>(float_img.data));

  // 5. Process Octave until we cant anymore.
  while (vl_status != VL_ERR_EOF) {
    // Detect Key Points.
    vl_sift_detect(sift_filter);

    // Get Key Points.
    const VlSiftKeypoint* vl_keypoints = vl_sift_get_keypoints(sift_filter);
    const int num_keypoints = vl_sift_get_nkeypoints(sift_filter);

    // Compute descriptors for all keypoints detected.
    for (int i = 0; i < num_keypoints; ++i) {

      // Compute orientations.
      double angles[4];
      int num_angles = vl_sift_calc_keypoint_orientations(sift_filter, angles, &vl_keypoints[i]);

      // Compute descriptor for all keypoints and orientations.
      Eigen::VectorXf descriptor(params.num_sift_dimensions_);
      for (int j = 0; j < num_angles; ++j) {
        descriptor.setZero();
        vl_sift_calc_keypoint_descriptor(sift_filter, descriptor.data(), &vl_keypoints[i],
                                         angles[j]);
        descriptors.push_back(descriptor);
        KeyPoint kp(vl_keypoints[i].x, vl_keypoints[i].y, vl_keypoints[i].sigma, angles[j], 0,
                    vl_keypoints[i].o);
        keypoints.push_back(kp);
      }
    }

    // Proceed to next octave.
    vl_status = vl_sift_process_next_octave(sift_filter);
    octave_no = octave_no + 1;
  }

  // X. Delete sift caculator.
  vl_sift_delete(sift_filter);
}

X. この後....

このコードで一応画像毎に SIFT のキーポイントと識別子のファイル出力ができるようになってるんですが,ここで生成した SIFT を使って次のエントリで画像類似度を計算します.使った画像はCornell 大の下記のページからダウンロードできます.
https://research.cs.cornell.edu/1dsfm/

上記サイトの Gendarmenmarkt images (tar, 1.0 GB) をダウンロードして使いました.

X. 実験に使ったコード

github.com

VLFeat & Cereal も Submodule として取り込んであるので,下記のコマンドを叩いて貰えれば動くとこまでは行きます.

git clone --recurse-submodules  git@github.com:koichiHik/blog_vlfeat_sift.git
sh build.sh
./build/sift_extractor --flagfile=./flagfiles/sift_test_flags.txt

X. 参考文献&プロジェクト

TheiaSfM
github.com

vlfeat
github.com