OpenCVのコマンドラインパーサ


# はじめに
 - この記事は[OpenCV Advent Calendar 2017](https://qiita.com/advent-calendar/2017/opencv) 4日目の記事です。関連記事は[目次](https://qiita.com/advent-calendar/2017/opencv)にまとめられています。
 - また、この記事は[OpenCV command line parser](https://github.com/tomoaki0705/opencv/wiki/OpenCV-command-line-parser)の翻訳でもあります。

# 後日談というか、今回のオチ
 - 既にWeb上にいくつも記事が見られるので、N番煎じですが、OpenCV付属のコマンドラインパーサを紹介します。[^1][^2][^3][^4]

[^1]: OpenCV Advent Calendarとしては2015、2016も含めて初出のようです
[^2]: [OpenCV3のCommandLineParserを使って簡単にコマンドライン引数を受け取る | Yucchiy&#39;s blog ](http://blog.yucchiy.com/2014/10/24/opencv3-commandline-parser/)
[^3]: [OpenCVのコマンドラインパーサ - glass5er&#39;s blog](http://glass5er.hatenablog.com/entry/20120109/1326081127)
[^4]: [その他の機能 &mdash; OpenCV-CookBook](http://opencv.jp/cookbook/opencv_utility.html#id3)

# プログラムの挙動を変える
 - あるOpenCVを使ったプログラムの挙動を変えたい場合、あなたはどうしますか？
 - 取りうるアプローチはいくつかあります。
  1. 値(挙動)をソースコードにハードコードし、挙動を変えるたびにビルドし直す
  2. highguiのウィンドウからパラメータを渡す(ex: マウスクリックやキータイプなど)
  3. コマンドラインからオプションを指定する(```--help```とか```--type=edge```とか)
 - 選択肢1番は厳密には挙動を変えているわけではないので、ここでは論外とします
 - 選択肢2番は座標を与えたり、簡単なインタラクションには向いています。しかし、「前処理のエッジ検出をSobelフィルタに変更」と言った複雑な司令になると、途端に難しくなります。
    - 元来、OpenCVのウィンドウはデバッグ用途なので、そこまで複雑なことを期待してはいけません。
    - また、手作業を踏まえた実験は、再現性に難があります
 - という訳で、**3番のコマンドラインオプション**に帰結します。
 - コマンドラインのプログラムとしてはよくある方法ですね(ex: ```ls -ltr```とか```rm -rf /```とか)
 - しかし、通常は文字列の配列としてC++のプログラムに渡されるので、プログラム側で文字列をパースする必要があります

# 「文字列をパース」とは
 - 文字列を**分割**して**解釈**することを一般的にパース(parse)、パースする機能/プログラムをパーサと呼びます
 - 有名所では```getopt```がありますが、OpenCV内部にも、```cv::CommandLineParser```があります。
 - ググると、解説記事が出てきますが、ここで再度紹介したいと思います(知ってる人にはがっかりな記事でごめんね！)

# パラメータの定義
 - ここでは、OpenCV付属のサンプル```samples\cpp\letter_recog.cpp```から、使い方を見てみましょう(見易くするために整形してあります)

```cpp:letter_recog.cpp
    cv::CommandLineParser parser(
        argc, 
        argv, 
        "{data|../data/letter-recognition.data|}"
        "{save||}"
        "{load||}"
        "{boost||}"
        "{mlp||}"
        "{knn knearest||}"
        "{nbayes||}{svm||}"
    );
```
# 制約というか解説
 - CommandLineParserは3つの引数をコンストラクタに取ります
   - 第1引数と第2引数は```main```関数に渡される```argc```と```argv```をそのまま渡します
   - 第3引数に以下のフォーマットで文字列を渡します
     - パラメータごとに```{``````}```で区切る
     - ```{}```内は、```|```で区切り、それぞれ```{パラメータ名|デフォルト値|説明文}```を指定する
   - 第3引数のパラメータ名は```{n number num|10|number of input images}```や```{h help||help message}```のように複数指定可能です
     - サンプルプログラムを見ると、"```{knn knearset||}```"という指定があり、```knn```と```knearest```という文字列どちらでも指定可能です
     - 各パラメータは基本的に名前を持つ必要があります。パラメータ名を省略する方法はこのサンプルプログラムには無いですが、後述します
     - 各パラメータにはデフォルト値を設定できます。サンプルコードの```data```オプションがデフォルト値```../data/letter-recognition.data```を指定されています
     - 最後の説明文は後述する```printMessage()```で使われます
 - とりあえずそこそこの機能はありますので、「**車輪の再発明**」をしないでもいいぐらいには良くできてるとおもいます

# パラメータの取得 
 - 同様に同じサンプルコードを読むと、以下のようにパラメータを取得しています

```cpp:letter_recog.cpp
    data_filename = parser.get<string>("data");
    if (parser.has("save"))
        filename_to_save = parser.get<string>("save");
    if (parser.has("load"))
        filename_to_load = parser.get<string>("load");
    if (parser.has("boost"))
        method = 1;
    else if (parser.has("mlp"))
        method = 2;
    else if (parser.has("knearest"))
        method = 3;
    else if (parser.has("nbayes"))
        method = 4;
    else if (parser.has("svm"))
        method = 5;
```
 - サンプルコードのように、パラメータをプログラムから読むためには、パラメータの名前と型を指定する必要があります。

```
    data_filename = parser.get<string>("data");
```
 - この例ですと、```data_filename```は```string```型で```data```オプションを読み取ります
 - 型を指定するのは、```float```/```double```/```int```などをごちゃ混ぜにしても大丈夫なように、です

```
threshold = parser.get<double>("threshold");
```
  - のように```double```で受け付けることも

```
threshold = parser.get<int>("threshold");
```
  - のように```int```で受け付けることもできます

# 実行時の指定方法
 - オプションを指定するためには、以下のいずれかのフォーマットを利用します(パラメータ名を省略する方法は後述)
  - ```-h```
  - ```--help```
  - ```--threshold=20```
 - 一つ注意があるのは、複数のオプションを同時に指定する場合です
    - 例えば```ls```コマンドは、```ls -ltr```と指定すると、```ls -l -t -r```と解釈してくれます。
    - しかし、OpenCVのコマンドラインパーサでは```"ltr"```という名前のオプションと解釈されるため、個別に``-l -t -r``と指定する必要があります
 - また、数値を指定する場合は、オプションと値を```=```でつなぐ必要があります
    - 例えば```number```オプションに20という数字を渡したいときは、```--number=20```と指定します。(```--number 20```も```-20```もどちらも受け付けられません)

# ヘルプメッセージ、著作権、オプショナルなパラメータについて
 - 前述のサンプルコードには出てきませんでしたが、外にオプショナルなパラメータ、ヘルプメッセージ、プログラムの著作権について記述することができます
 - 以下に、サンプルコードだけではカバーしきれてない解説コードを

```cpp:sample.cpp
#include <opencv2/opencv.hpp>
#include <iostream>

int main(int argc, char** argv)
{
    cv::String keys = "{help h ?|false|show help command}"
        "{n number|10|number option}"
        "{r ratio|0.5|ratio option}"
        "{@input||}";

    cv::String about = "copyright foo bar";

    cv::CommandLineParser parser(argc, argv, keys);
    parser.about(about);
    if (parser.has("help"))
    {
        parser.printMessage();
        return 0;
    }

    int number = parser.get<int>("number");
    float ratio = parser.get<float>("ratio");

    std::cout << "number is " << number << std::endl;
    std::cout << "ratio is " << ratio << std::endl;
    if (parser.has("@input"))
    {
        cv::String filename = parser.get<cv::String>("@input");
        std::cout << "input has been passed which is " << filename << std::endl;
    }
    else
    {
        std::cout << "No input specified" << std::endl;
    }

    return 0;
}
```
## ヘルプメッセージ
 - ヘルプメッセージは特段指定しなくても、```CommandLineParser```のコンストラクタに渡した説明文から自動的に生成してくれます
 - 生成されたメッセージは```printMessage```メソッドで表示することができます

```
# 通常実行
commandLineParser\build\Debug>commandLineParser.exe
number is 10
ratio is 0.5
No input specified


# ヘルプ表示
commandLineParser\build\Debug>commandLineParser.exe --help
copyright foo bar
Usage: commandLineParser.exe [params] input

        -?, -h, --help (value:true)
                show help command
        -n, --number (value:10)
                number option
        -r, --ratio (value:0.5)
                ratio option

        input
```  

 - 大量にオプションを指定しても、説明書きを忘れなければ、いつでもヘルプメッセージから状況を参照できます
 - Usageや、パラメータの詳細、現状の値なども表示してくれます。すごい！

## 著作権について
 - ```CommandLineParser```には```about```メソッドがあり、著作権に相当する情報を入れることができます。
 - こちらも、ヘルプメッセージ同様```printMessage```メソッドで表示できます

```
# 1行目の出力が著作権
commandLineParser\build\Debug>commandLineParser.exe --help
copyright foo bar
Usage: commandLineParser.exe [params] input

        -?, -h, --help (value:true)
                show help command
        -n, --number (value:10)
                number option
        -r, --ratio (value:0.5)
                ratio option

        input

```

## 名前の指定を省略したパラメータ
 - 前述までのパラメータは全て```--help```か、```-N=20```の形で渡す必要がありました
 - しかし、```ls a.bmp```のように、単体のファイル名を指定こともよくあります
 - その場合は ```@```をkeysの中で指定することで、名前無しのパラメータを宣言できます

```cpp
    "{@input||}"; // ここで@inputを指定している
```

 - この場合は、ちゃんと```has```メソッドでパラメータが渡されたか確認する必要があります

```cpp
    if (parser.has("@input"))
    {
        cv::String filename = parser.get<cv::String>("@input");
        std::cout << "input has been passed which is " << filename << std::endl;
    }
```

 - 実行時に指定する場合は以下のように指定します

```
commandLineParser\build\Debug>commandLineParser.exe
number is 0
ratio is 0
No input specified


commandLineParser\build\Debug>commandLineParser.exe C:\work\foo\bar\commandLineParser\build\Debug\commandLineParser.ilk
number is 0
ratio is 0
input has been passed which is C:\work\foo\bar\commandLineParser\build\Debug\commandLineParser.ilk
```

# まとめ
- コマンドラインのオプションのパーサ、```cv::CommandLineParser```を紹介しました
- 筆者は以下の環境で執筆しました

|項目|バージョン|
|:--|:--|
|OS|Windows 7 Professional 64bit|
|OpenCV|3.3.0|
|コンパイラ|Visual Studio 2013 Update 5|
|CMake|3.9.1|
|CUDA |8.0|
- 良いOpenCVライフを！
- 明日はみんな大好き[UnaNancyOwenさん](https://twitter.com/unanancyowen)の記事[Vcpkgを使用してOpenCVを導入する](https://qiita.com/UnaNancyOwen/items/54c6334f269d03646cad)です！全お犬様歓喜！