cv::DenseFeatureDetector の引数は何を意味しているのか

# はじめに

OpenCV には `DenseFeatureDetector` という便利なものがあります。
これは主に一般物体認識で Dense Sampling をしたいときに利用します。

通常、たとえば SIFT や SURF では特徴点を抽出してから特徴記述を行うわけですが、
特徴点の抽出をせず、画像の至る所をグリッドサンプリングしていきたいことがあります。
そういう時に使うのがこの `DenseFeatureDetector` です。
ところがコイツはパラメータが多い上にいまいちわかりにくいので、引数の解説をしたいと思います。

```cpp
    class DenseFeatureDetector : public FeatureDetector
    {
    public:
            DenseFeatureDetector( float initFeatureScale=1.f, int featureScaleLevels=1,
                          float featureScaleMul=0.1f,
                          int initXyStep=6, int initImgBound=0,
                          bool varyXyStepWithScale=true,
                          bool varyImgBoundWithScale=false );
    protected:
    ...
};
```

# 各パラメータ解説

## initFeatureScale

初期の特徴のサイズ（半径）です。`float` ですが単位はピクセルです。
詳しくは後述。

## featureScaleLevels

DenseFeatureDetector では特徴のサイズを変えて何回かサンプリングできます。
SIFT や SURF ではこの大きさを適当に決めてくれるのですが、今回は自分で指定するので大きさの変化に対してロバストにするために色々なサイズで特徴抽出をしたほうがよいわけです。
ここでは何段階特徴のサイズを変更するかを決めます。デフォルトは1。
詳しくは後述。

## featureScaleMul

`featureScaleLevels` が1増えるごとにどれくらい特徴のサイズを大きくするかを決めます。

つまり、最初は特徴のサイズ `r` は `initFeatureScale` で初期化され、そのあと `featureScaleLevels` 回、 `featureScaleMul` を掛けた大きさでサンプリングします。
最終的な特徴のサイズは `initFeatureScale * pow(featureScaleMul, featureScaleLevels)` px になります。

たとえば `initFeatureScale = 5.0`, `featureScaleLevels = 3`, `featureScaleMul = 2.0` の場合だと
まずは特徴サイズが 5px でサンプリングされ、次に 5 * 3 = 15px でサンプリングされ、最後に 15 * 3 = 45px でサンプリングされます。


## initXyStep

上記3つは特徴の大きさに関する値でしたが、これはどれくらいの密度でサンプリングするかの値です。デフォルトは1。単位はpx。
これは1pxごとに特徴抽出すると数が多すぎるので、数pxおきにしたいなどといった場合に指定します。たとえば5を指定すると5pxおきに特徴抽出してくれます。
なお名前からわかるように x と y で別々の値を指定することはできません。

## initImgBound

画像の端っこは特徴抽出したくない時に指定します。
単位はpxで、1以上を指定するとその大きさぶん画像の端から離れた場所から特徴抽出します。
たとえば画像が黒く縁取られているのでそのあたりは無視したい、といった場合などに使うんだと思います。

## varyXyStepWithScale

`true` にすると `XyStep` にも `featureScaleMul` を乗算します。
デフォルトは `false` です。一般物体認識タスクでは精度を高めたいなら密にサンプリングできる `false`, リソースの消費が気になる場合は `true` で良いと思います。

## varyImgBoundWithScale

これは `varyXyStepWithScale` の `initImgBound` です。
基本的に `false` のままで良いと思います。

# サンプルコード

```cpp

#include <iostream>
#include <fstream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>

int main(int argc, char* argv[])
{
    cv::Mat img = cv::imread(argv[1]);
    if (img.empty()) {
        exit(EXIT_FAILURE);
    }
 
    cv::DenseFeatureDetector detector(
        8.0f,     //initFeatureScale:   初期の特徴のサイズ（直径）[px]
        3,		  //featureScaleLevels: 何段階サイズ変更してしてサンプリングするか(>=1)
        1.414f,   //featureScaleMul:	ScaleLevelごとにどれくらい拡大縮小するか(!=0)
        4,		  //initXyStep:         特徴点をどれくらいの量ずらしながらサンプリングするか
        0,		  //initImgBound:       画像の端からどれくらい離すか(>=0)
        false,	  //varyXyStepWithScale:    XyStepにもScaleMul を掛けるか
        false     //varyImgBoundWithScale:  BoundにもScaleMul を掛けるか
    );
 
    std::vector<cv::KeyPoint> keypoints;
    detector.detect(img, keypoints);
 
    // 記述子として次のいずれかを指定する
    // FAST, GFTT, SIFT (nonfree), SURF (nonfree),
    // MSER, STAR, ORB, BRISK, FREAK, BRIEF
    cv::Ptr<cv::DescriptorExtractor> extractor = cv::DescriptorExtractor::create("BRISK");
    cv::Mat descriptors;
    extractor->compute(img, keypoints, descriptors);
 
    if(argc > 3) {
        std::ofstream stream(argv[2]);
        stream << cv::format(descriptors, "csv");
    } else {
        std::cout << cv::format(descriptors, "csv") << std::endl;
    }
 
    return 0;
}
```

各特徴記述子 (descriptor) についても何を選んだら良いのかよくわからないかもしれませんが、これらの違いについてはまた別の機会に。