![]() |
OpenCV 5.0.0
Open Source Computer Vision
|
学ぶこと:
OpenCVでは、すべてのアルゴリズムがC++で実装されている。しかし、これらのアルゴリズムはPythonやJavaなどさまざまな言語から利用できる。これはバインディングジェネレータによって可能になっている。これらのジェネレータはC++とPythonの間の橋渡しを作り出し、ユーザがPythonからC++関数を呼び出せるようにする。背後で何が起きているかの全体像をつかむには、Python/C APIの十分な知識が必要である。C++関数をPythonへ拡張する簡単な例は、公式のPythonドキュメント[1]にある。したがって、OpenCVのすべての関数のラッパー関数を手作業で書いてPythonへ拡張するのは、時間のかかる作業である。そこでOpenCVはより賢い方法でこれを行う。OpenCVは、modules/python/src2 にあるPythonスクリプトを使って、C++ヘッダからこれらのラッパー関数を自動的に生成する。それらが何をするのかを見ていく。
まず、modules/python/CMakeFiles.txt はPythonに拡張するモジュールをチェックするCMakeスクリプトである。拡張対象のすべてのモジュールを自動的にチェックし、そのヘッダファイルを取得する。これらのヘッダファイルには、その特定のモジュールに含まれるすべてのクラス、関数、定数などのリストが含まれている。
次に、これらのヘッダファイルはPythonスクリプト modules/python/src2/gen2.py に渡される。これはPythonバインディング生成スクリプトである。これは別のPythonスクリプト modules/python/src2/hdr_parser.py を呼び出す。これはヘッダパーサスクリプトである。このヘッダパーサは、完全なヘッダファイルを小さなPythonのリストに分割する。これらのリストには、特定の関数やクラスなどに関するすべての詳細が含まれる。たとえば、関数は解析され、関数名、戻り値の型、入力引数、引数の型などを含むリストが得られる。最終的なリストには、そのヘッダファイル内のすべての関数、enum、構造体、クラスなどの詳細が含まれる。
ただしヘッダパーサは、ヘッダファイル内のすべての関数/クラスを解析するわけではない。どの関数をPythonにエクスポートするかは開発者が指定する必要がある。そのために、これらの宣言の先頭に特定のマクロが追加され、ヘッダパーサが解析すべき関数を識別できるようになっている。これらのマクロは、その特定の関数をプログラムする開発者によって追加される。要するに、どの関数をPythonに拡張し、どれを拡張しないかを開発者が決めるのである。これらのマクロの詳細は次のセッションで述べる。
こうしてヘッダパーサは、解析された関数の最終的な大きなリストを返す。生成スクリプト(gen2.py)は、ヘッダパーサによって解析されたすべての関数/クラス/enum/構造体に対してラッパー関数を作成する(これらのヘッダファイルは、コンパイル時に build/modules/python/ フォルダ内に pyopencv_generated_*.h ファイルとして見つけることができる)。しかし、Mat、Vec4i、Size のような基本的なOpenCVデータ型もある。これらは手動で拡張する必要がある。たとえば、Mat型はNumpy配列に拡張すべきであり、Sizeは2つの整数のタプルに拡張すべきである、といった具合である。同様に、手動で拡張する必要のある複雑な構造体/クラス/関数なども存在する。そうした手動のラッパー関数はすべて modules/python/src2/cv2.cpp に置かれている。
あとはこれらのラッパーファイルをコンパイルするだけであり、それによって cv2 モジュールが得られる。したがって、Pythonで例えば res = equalizeHist(img1,img2) のように関数を呼び出すと、2つのnumpy配列を渡し、出力として別のnumpy配列が返ってくることを期待する。これらのnumpy配列は cv::Mat に変換され、その後C++のequalizeHist()関数が呼び出される。最終結果である res は、再びNumpy配列へ変換される。要するに、ほとんどすべての処理はC++で行われ、それによってC++とほぼ同等の速度が得られる。
これがOpenCV-Pythonバインディングがどのように生成されるかの基本的な仕組みである。
numpy.ndarray から派生した cv.Mat ラッパーが用意されている。ヘッダパーサは、関数宣言に追加されたラッパーマクロに基づいてヘッダファイルを解析する。列挙定数にはラッパーマクロは不要である。それらは自動的にラップされる。しかし、残りの関数やクラスなどにはラッパーマクロが必要である。
関数は CV_EXPORTS_W マクロを使って拡張する。例を以下に示す。
ヘッダパーサは、InputArray、OutputArray などのキーワードから入力引数と出力引数を理解できる。引数のセマンティクスはPythonでも保たれる。C++で変更されるものはPythonでも変更され、その逆も同様である。読み取り専用のPythonオブジェクトは、出力として使われてもOpenCVによって変更できない。そのような状況ではPython例外が発生する。場合によっては、C++で参照渡しされる引数が入力、出力、またはその両方として使われることがある。マクロ CV_OUT、CV_IN_OUT によって、この曖昧さを解消し、正しいバインディングを生成できる。
大きなクラスについても、CV_EXPORTS_W が使われる。クラスのメソッドを拡張するには CV_WRAP を使う。同様に、クラスのフィールドには CV_PROP を使う。
オーバーロードされた関数は CV_EXPORTS_AS を使って拡張できる。ただし、各関数がPythonでその名前で呼ばれるように、新しい名前を渡す必要がある。以下のintegral関数のケースを考える。3つの関数が用意されているので、それぞれにPythonでサフィックス付きの名前が付けられる。同様に CV_WRAP_AS を使ってオーバーロードされたメソッドをラップできる。
小さなクラス/構造体は CV_EXPORTS_W_SIMPLE を使って拡張する。これらの構造体は値渡しでC++関数に渡される。例として KeyPoint、Match などがある。それらのメソッドは CV_WRAP で拡張され、フィールドは CV_PROP_RW で拡張される。
他の一部の小さなクラス/構造体は CV_EXPORTS_W_MAP を使ってエクスポートでき、これはPythonネイティブの辞書としてエクスポートされる。Moments() はその一例である。
以上が、OpenCVで利用できる主要な拡張マクロである。通常、開発者は適切なマクロを適切な位置に配置するだけでよい。残りは生成スクリプトが行う。場合によっては、生成スクリプトがラッパーを作成できない例外的なケースもある。そのような関数は手動で処理する必要があり、これを行うには独自の pyopencv_*.hpp 拡張ヘッダを書き、モジュールの misc/python サブディレクトリに置く。しかしたいていの場合、OpenCVのコーディングガイドラインに従って書かれたコードは、生成スクリプトによって自動的にラップされる。
より高度なケースでは、追加のメソッド、型マッピング、デフォルト引数の提供など、C++インターフェースには存在しない追加機能をPythonに提供することが含まれる。そのようなケースの例として、後ほど UMat データ型を取り上げる。まず、Python固有のメソッドを提供するには、CV_WRAP_PHANTOM を CV_WRAP と同様の方法で利用する。ただし、これはメソッドヘッダを引数として取り、メソッド本体は独自の pyopencv_*.hpp 拡張で提供する必要がある。UMat::queue() と UMat::context() は、C++インターフェースには存在しないが、Python側でOpenCL機能を扱うために必要なファントムメソッドの例である。次に、既存のデータ型が自分のクラスにマッピング可能な場合は、独自のバインディング関数を作るよりも、ソース型を引数とする CV_WRAP_MAPPABLE を使ってその能力を示すことが強く推奨される。これは Mat からマッピングされる UMat のケースである。最後に、デフォルト引数が必要だがネイティブのC++インターフェースでは提供されていない場合は、CV_WRAP_DEFAULT の引数としてPython側に提供できる。以下の UMat::getMat の例のように。