例の如く、自分用の作業メモ。
Python初心者の僕が、PythonとOpenCVで書かれたC/C++ライブラリとを連携する必要が出たため、色々調べてみました。ctypes、SWIG、Boost.Python、Cython等、方法がたくさんありすぎてどれを使うか悩みました。
以下に、PythonとC/C++ライブラリを連携する方法について色々とリストアップされてます。(Boost.Pythonはないけど)
http://www.hexacosa.net/documents/python-extending/
で、今回はctypesを使ってみることにしました。理由は、Pythonをインストールすると標準でついているのと、以下の記事見ると一番評判が良さそうだったので。
http://stackoverflow.com/questions/145270/calling-c-c-from-python
http://stackoverflow.com/questions/1942298/wrapping-a-c-library-in-python-c-cython-or-ctypes
(実は、最初はCythonを試したんですが、インストール後うまく動かすことができず断念)
ctypesの使い方については、今回Python2.6.xを使用したため、以下の記事を参考にしました。
http://www.python.jp/doc/2.6/library/ctypes.html
とても簡単に解説すると、ctypesでは例えば、
import ctypes mydll = ctypes.cdll.LoadLibrary('my.dll')
というコードを追加するだけで、後はmydllのメンバ関数として動的ライブラリ内のコードを直接呼び出すことができるようになります。ちなみに最初は静的ライブラリでやろうとしたんですが、やり方が見つからず。。。
厄介だったのは、今回OpenCVのcv::Mat型のデータをC++ライブラリとPython間でやり取りしなくてはならなかったため、その点についてかなり試行錯誤しました。OpenCV Pythonをインストールすれば簡単にできるんじゃないの?と考えていた自分が甘かった。。。
結論から言うと、ctypesではOpenCV Pythonのcv.CvMat型のデータは扱うことができないため、なんらかの他のデータ形式に変換してやる必要がありました。ここでは、CライブラリからPythonへは、cv::Matのサイズ(rows, cols)と、型(type())、それと配列領域の先頭データポインタ(void*)を渡し、PythonからCライブラリへも同様に全てCの標準で定義可能な型で渡してます。
また、ctypesではC++はサポートしていないようなので、クラス定義なども、一旦Cの形に変換してやる必要があります。
ちなみにctypes-OpenCVというライブラリもあるみたいですが、今回は使いませんでした。
http://code.google.com/p/ctypes-opencv/
何分、Python初心者なので、他にもっとスマートなやり方があったら教えて下さい。
作業環境:
というわけで、まずは共有ライブラリ(ここではDLL)を作成します。
DLLの作成方法は、このサイトを参考にしました。
http://msdn.microsoft.com/ja-jp/library/ms235636.aspx
DLLのコード例
この例では、MyClassというクラスを自分で定義し、そのMyClassに対してSetMyMat()とGetMyMat()という2つのメンバ関数を使用して、cv::Matのデータをやり取りすることを想定しています。
myclass.h
#if WIN32 #define DLLEXPORT extern "C" __declspec(dllexport) #else #define DLLEXPORT extern "C" #endif #include//! 新しいインスタンスを生成 DLLEXPORT MyClass* new_myclass(); //! オブジェクトを削除 DLLEXPORT void delete_myclass(MyClass* h_myclass); //! cv::MatデータをMyClassへ渡す DLLEXPORT int SetMvMat(MyClass* h_myclass, void* my_data, int my_width, int my_height, int my_type); //! cv::Mat型データを返す。 DLLEXPORT void* GetMyMat(MyClass* h_myclass, int* w, int* h, int* type); class MyClass{ public: MyClass(); ~MyClass(); void SetMyMat(cv::Mat& my_mat){MyMat = my_mat;}; cv::Mat& GetMyMat(){return MyMat;}; private: cv::Mat MyMat; };
myclass.cpp
#include "myclass.h" // MyClassを生成し、ポインターを返す。 MyClass* new_myclass() { return new MyClass(); } // MyClassをdelete void delete_myclass(MyClass* h_myclass) { delete h_myclass; } //! cv::MatデータをMyClassへ渡す void SetMvMat(MyClass* h_myclass, void* my_data, int my_width, int my_height, int my_type) { cv::Mat mymat(my_height, my_width, my_type, my_data); h_myclass->SetMyMat(mymat); } //! cv::Mat型データを返す。 void* GetMyMat(MyClass* h_myclass, int* w, int* h, int* type) { mymat = h_myclass->GetMyMat(); w = mymat.cols; h = mymat.rows; type = mymat.type(); return (void*)mymat.data; }
Pythonのコード例
Python側でDLL側のMyClassクラスをラップしたクラスを作成します。MyClassクラスはcv.CvMat型のデータをメンバ関数のSetMyMat()の引数として受け取り、GetMyMat()の返り値として返します。
これは例えば、以下のような形で扱うことができます。
sample.py
import MyClass import cv2.cv as cv # 新規MyClass作成 my_class = MyClass.MyClass() # Mat型のデータを新規作成 my_data = cv.CreateMat(3,3,cv.CV_32SC1) cv.Set(my_data, 7) # 作成したmy_dataをセット my_class.SetMyData(my_data) # my_classからMat型データを取得 my_mat = my_class.GetMyMat()
MyClassのメンバ関数内部では、cv.CvMat型のデータをctypesで扱うことができるデータ型に変換しています。例えばctypes.c_intはint型データをDLLとやり取りするための型、ctypes.c_void_pはVoidポインターをDLLとやり取りするための型です。
MyClass.py
import ctypes import cv2.cv as cv # DLLで定義されたMyClassクラスのPythonラッパー class MyClass: __my_dll = ctypes.cdll.LoadLibrary('MyDll.dll') __my_class = ctypes.c_void_p # MyClassを新規作成 def __init__(self): # 関数の返り値の型を指定 self.__my_dll.new_myclass.restype = ctypes.c_void_p # 64bit機の場合、下記でないとうまく行かない場合があります # self.__my_dll.new_myclass.restype = ctypes.POINTER(ctypes.c_long) # DLL内でクラスの新規作成 self.__my_class = self.__my_dll.new_myclass() # MyClassを削除 def __del__(self): self.__my_dll.delete_myclass(self.__my_class) #### DLLのSetMyMat()を呼び出し、cv.CvMatをセットする #### def SetMyMat(self, mymat): # パラメータをDLLへ渡すために、ctypesの指定するint型へ変換 w = ctypes.c_int(mymat.cols) h = ctypes.c_int(mymat.rows) t = ctypes.c_int(mymat.type) # DLLのSetMyMat()呼び出し self.__my_dll.SetMyMat(self.__my_class, mymat.tostring(), w, h, t) #### DLLのMyClass.GetMyMat()を呼び出し、cv.CvMatを返す #### def GetMyMat(self): # DLLのMyClass.GetMyMatをGetFuncへセット GetFunc = self.__my_dll.GetMyMat # DLLのGetMyMat()の返り値の方をVoidポインターにセット GetFunc.restype = ctypes.c_void_p # GetMyMat()の引数の型をctypesで指定する型へ設定 w_ctype = ctypes.c_int() h_ctype = ctypes.c_int() t_ctype = ctypes.c_int() # DLLのGetMyMat()を呼び出し(w,h,tは参照渡し) data_ptr = GetFunc(self.__my_class, ctypes.byref(w_ctype), ctypes.byref(h_ctype), ctypes.byref(t_ctype)) # DLLのGetMyMat()で取得したデータをcv.CreateMat()用の引数に変換 w = w_ctype.value h = h_ctype.value t = t_ctype.value # cv.CvMatのヘッダー情報を作成 dest_mat = cv.CreateMatHeader(h,w,t) # データをメモリ領域を割り当ててコピー array_size = h * dest_mat.step mat_data = (ctypes.c_byte * array_size)() ctypes.memmove(mat_data, data_ptr, array_size) # cv.CvMatにデータをセット cv.SetData(dest_mat, mat_data, dest_mat.step) return dest_mat
というわけで、思いの外めんどくさかったです。