Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

後日談というか、今回のオチ

  • 既にWeb上にいくつも記事が見られるので、N番煎じですが、OpenCV付属のコマンドラインパーサを紹介します。1234

プログラムの挙動を変える

  • ある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から、使い方を見てみましょう(見易くするために整形してあります)
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関数に渡されるargcargvをそのまま渡します
    • 第3引数に以下のフォーマットで文字列を渡します
      • パラメータごとに{}で区切る
      • {}内は、|で区切り、それぞれ{パラメータ名|デフォルト値|説明文}を指定する
    • 第3引数のパラメータ名は{n number num|10|number of input images}{h help||help message}のように複数指定可能です
      • サンプルプログラムを見ると、"{knn knearset||}"という指定があり、knnknearestという文字列どちらでも指定可能です
      • 各パラメータは基本的に名前を持つ必要があります。パラメータ名を省略する方法はこのサンプルプログラムには無いですが、後述します
      • 各パラメータにはデフォルト値を設定できます。サンプルコードのdataオプションがデフォルト値../data/letter-recognition.dataを指定されています
      • 最後の説明文は後述するprintMessage()で使われます
  • とりあえずそこそこの機能はありますので、「車輪の再発明」をしないでもいいぐらいには良くできてるとおもいます

パラメータの取得

  • 同様に同じサンプルコードを読むと、以下のようにパラメータを取得しています
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_filenamestring型で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もどちらも受け付けられません)

ヘルプメッセージ、著作権、オプショナルなパラメータについて

  • 前述のサンプルコードには出てきませんでしたが、外にオプショナルなパラメータ、ヘルプメッセージ、プログラムの著作権について記述することができます
  • 以下に、サンプルコードだけではカバーしきれてない解説コードを
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の中で指定することで、名前無しのパラメータを宣言できます
    "{@input||}"; // ここで@inputを指定している
  • この場合は、ちゃんとhasメソッドでパラメータが渡されたか確認する必要があります
    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
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away