OpenCV 4.13.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 4.13.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
OpenCV-Pythonバインディングの仕組み

目標

学ぶこと:

  • OpenCV-Pythonバインディングはどのように生成されるか?
  • 新しいOpenCVモジュールをPythonへ拡張するには?

OpenCV-Pythonバインディングはどのように生成されるか?

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 の間に1対1の対応はない。たとえば cv::Mat にはchannelsフィールドがあり、これは numpy.ndarray の最後の次元としてエミュレートされ、暗黙的に変換される。しかし、こうした暗黙の変換は、3D numpy配列をC++コードに渡す際に問題を生じる(最後の次元が暗黙的にチャンネル数として再解釈される)。チャンネルを持つ3D配列やND配列を処理する必要がある場合の回避策については、issue を参照のこと。OpenCV 4.5.4以降では、チャンネルの挙動を明示的に扱うために numpy.ndarray から派生した cv.Mat ラッパーが用意されている。

新しいモジュールをPythonに拡張する方法は?

ヘッダパーサは、関数宣言に追加されたラッパーマクロに基づいてヘッダファイルを解析する。列挙定数にはラッパーマクロは不要である。それらは自動的にラップされる。しかし、残りの関数やクラスなどにはラッパーマクロが必要である。

関数は CV_EXPORTS_W マクロを使って拡張する。例を以下に示す。

CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
#define CV_EXPORTS_W
Definition cvdef.h:474

ヘッダパーサは、InputArray、OutputArray などのキーワードから入力引数と出力引数を理解できる。引数のセマンティクスはPythonでも保たれる。C++で変更されるものはPythonでも変更され、その逆も同様である。読み取り専用のPythonオブジェクトは、出力として使われてもOpenCVによって変更できない。そのような状況ではPython例外が発生する。場合によっては、C++で参照渡しされる引数が入力、出力、またはその両方として使われることがある。マクロ CV_OUTCV_IN_OUT によって、この曖昧さを解消し、正しいバインディングを生成できる。

CV_EXPORTS_W void minEnclosingCircle( InputArray points,
CV_OUT Point2f& center, CV_OUT float& radius );
#define CV_OUT
Definition cvdef.h:480

大きなクラスについても、CV_EXPORTS_W が使われる。クラスのメソッドを拡張するには CV_WRAP を使う。同様に、クラスのフィールドには CV_PROP を使う。

class CV_EXPORTS_W CLAHE : public Algorithm
{
public:
CV_WRAP virtual void apply(InputArray src, OutputArray dst) = 0;
CV_WRAP virtual void setClipLimit(double clipLimit) = 0;
CV_WRAP virtual double getClipLimit() const = 0;
}
#define CV_WRAP
Definition cvdef.h:484

オーバーロードされた関数は CV_EXPORTS_AS を使って拡張できる。ただし、各関数がPythonでその名前で呼ばれるように、新しい名前を渡す必要がある。以下のintegral関数のケースを考える。3つの関数が用意されているので、それぞれにPythonでサフィックス付きの名前が付けられる。同様に CV_WRAP_AS を使ってオーバーロードされたメソッドをラップできる。

CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth = -1 );
CV_EXPORTS_AS(integral2) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, int sdepth = -1, int sqdepth = -1 );
CV_EXPORTS_AS(integral3) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, OutputArray tilted,
int sdepth = -1, int sqdepth = -1 );
#define CV_EXPORTS_AS(synonym)
Definition cvdef.h:476

小さなクラス/構造体は CV_EXPORTS_W_SIMPLE を使って拡張する。これらの構造体は値渡しでC++関数に渡される。例として KeyPointMatch などがある。それらのメソッドは CV_WRAP で拡張され、フィールドは CV_PROP_RW で拡張される。

class CV_EXPORTS_W_SIMPLE DMatch
{
public:
CV_WRAP DMatch();
CV_WRAP DMatch(int _queryIdx, int _trainIdx, float _distance);
CV_WRAP DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance);
CV_PROP_RW int queryIdx; // query descriptor index
CV_PROP_RW int trainIdx; // train descriptor index
CV_PROP_RW int imgIdx; // train image index
CV_PROP_RW float distance;
};
#define CV_EXPORTS_W_SIMPLE
Definition cvdef.h:475
#define CV_PROP_RW
Definition cvdef.h:482

他の一部の小さなクラス/構造体は CV_EXPORTS_W_MAP を使ってエクスポートでき、これはPythonネイティブの辞書としてエクスポートされる。Moments() はその一例である。

class CV_EXPORTS_W_MAP Moments
{
public:
CV_PROP_RW double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
CV_PROP_RW double mu20, mu11, mu02, mu30, mu21, mu12, mu03;
CV_PROP_RW double nu20, nu11, nu02, nu30, nu21, nu12, nu03;
};
#define CV_EXPORTS_W_MAP
Definition cvdef.h:477

以上が、OpenCVで利用できる主要な拡張マクロである。通常、開発者は適切なマクロを適切な位置に配置するだけでよい。残りは生成スクリプトが行う。場合によっては、生成スクリプトがラッパーを作成できない例外的なケースもある。そのような関数は手動で処理する必要があり、これを行うには独自の pyopencv_*.hpp 拡張ヘッダを書き、モジュールの misc/python サブディレクトリに置く。しかしたいていの場合、OpenCVのコーディングガイドラインに従って書かれたコードは、生成スクリプトによって自動的にラップされる。

より高度なケースでは、追加のメソッド、型マッピング、デフォルト引数の提供など、C++インターフェースには存在しない追加機能をPythonに提供することが含まれる。そのようなケースの例として、後ほど UMat データ型を取り上げる。まず、Python固有のメソッドを提供するには、CV_WRAP_PHANTOMCV_WRAP と同様の方法で利用する。ただし、これはメソッドヘッダを引数として取り、メソッド本体は独自の pyopencv_*.hpp 拡張で提供する必要がある。UMat::queue()UMat::context() は、C++インターフェースには存在しないが、Python側でOpenCL機能を扱うために必要なファントムメソッドの例である。次に、既存のデータ型が自分のクラスにマッピング可能な場合は、独自のバインディング関数を作るよりも、ソース型を引数とする CV_WRAP_MAPPABLE を使ってその能力を示すことが強く推奨される。これは Mat からマッピングされる UMat のケースである。最後に、デフォルト引数が必要だがネイティブのC++インターフェースでは提供されていない場合は、CV_WRAP_DEFAULT の引数としてPython側に提供できる。以下の UMat::getMat の例のように。

class CV_EXPORTS_W UMat
{
public:
// You would need to provide `static bool cv_mappable_to(const Ptr<Mat>& src, Ptr<UMat>& dst)`
CV_WRAP_MAPPABLE(Ptr<Mat>);
/! returns the OpenCL queue used by OpenCV UMat.
// You would need to provide the method body in the binder code
CV_WRAP_PHANTOM(static void* queue());
// You would need to provide the method body in the binder code
CV_WRAP_PHANTOM(static void* context());
CV_WRAP_AS(get) Mat getMat(int flags CV_WRAP_DEFAULT(ACCESS_RW)) const;
};
#define CV_WRAP_AS(synonym)
Definition cvdef.h:485
#define CV_WRAP_PHANTOM(phantom_header)
Definition cvdef.h:487
#define CV_WRAP_DEFAULT(val)
Definition cvdef.h:488
#define CV_WRAP_MAPPABLE(mappable)
Definition cvdef.h:486