はじめに
- この記事はOpenCV Advent Calendar 2017 4日目の記事です。関連記事は目次にまとめられています。
- また、この記事はOpenCV command line parserの翻訳でもあります。
後日談というか、今回のオチ
プログラムの挙動を変える
- あるOpenCVを使ったプログラムの挙動を変えたい場合、あなたはどうしますか?
- 取りうるアプローチはいくつかあります。
- 値(挙動)をソースコードにハードコードし、挙動を変えるたびにビルドし直す
- highguiのウィンドウからパラメータを渡す(ex: マウスクリックやキータイプなど)
- コマンドラインからオプションを指定する(
--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
関数に渡される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()
で使われます
- サンプルプログラムを見ると、"
- 第1引数と第2引数は
- とりあえずそこそこの機能はありますので、「車輪の再発明」をしないでもいいぐらいには良くできてるとおもいます
パラメータの取得
- 同様に同じサンプルコードを読むと、以下のようにパラメータを取得しています
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
もどちらも受け付けられません)
- 例えば
ヘルプメッセージ、著作権、オプショナルなパラメータについて
- 前述のサンプルコードには出てきませんでしたが、外にオプショナルなパラメータ、ヘルプメッセージ、プログラムの著作権について記述することができます
- 以下に、サンプルコードだけではカバーしきれてない解説コードを
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 |
- 良いOpenCVライフを!
- 明日はみんな大好きUnaNancyOwenさんの記事Vcpkgを使用してOpenCVを導入するです!全お犬様歓喜!
-
OpenCV Advent Calendarとしては2015、2016も含めて初出のようです ↩
-
OpenCV3のCommandLineParserを使って簡単にコマンドライン引数を受け取る | Yucchiy's blog ↩