はじめに
画像解析やAIの分野では、Python 言語でアルゴリズム検討を行うケースが増えています。
一方、組込み機器や処理性能を考えると C 言語や C++ 言語の利用が一般的です。
Python で記述したアルゴリズムを C++ に書き換えるのが面倒だったり、
逆に C++ で記述されたプログラムを Python に書き換えるのも面倒。
そこで、C++ 言語と Python 言語を繋げるためのライブラリ pybind11 が登場しました。
今回は、C++ で定義したクラスのインスタンスを引数に、Python 側の関数を呼び出す方法を紹介します。
pybind11 とは?
pybind11 は、C 言語向けの Python ライブラリをラップしているヘッダーオンリーのライブラリで、
C++ の言語仕様を駆使して実装されています。
ドキュメントやサンプルも充実しており、BSD ライセンスであることから、広く利用されています。
以下の手順で簡単にインストールすることが可能です。
command
$ pip install pybind11
本記事の内容
Python から C++ 関数を呼び出すサンプルや、C++ から int などの標準型を引数に Python 側の関数を呼び出すサンプルは多いのですが、C++ で定義したクラスのインスタンスを引数に 、Python 側の関数を呼び出すサンプルが見つかりませんでした。
画像解析であれば cv::Mat を引数に、または独自に C++ で定義したクラスのインスタンスを渡したい。
ということで、本記事を書くことにしました。簡単なサンプルを紹介します。
動作環境は、以下の通りです。
OS Version
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.4 LTS"
VERSION_ID="20.04"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
Python環境
$ python3 --version
Python 3.8.10
$ python3 -m pybind11 --version
2.11.1
コンパイラ
$ g++ -v
Target: x86_64-linux-gnu
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)
サンプルの構成
各ファイルの構成は、以下のようにします。
Structure of Files
/home/user/
|- pymodule/
| |- __init__.py
| |- AnalyzeEngine.py
|- main.cpp
|- make.sh
|
C++ の Makefile
pybind11 関連のインクルードパス、コンパイルオプション、リンクすべきライブラリは、
以下のように記述することで取得できます。--embed を付けるのがポイントです。
1 2 3 4 5 6 7 |
INCLUDES=`python3 -m pybind11 --includes` CXXFLAGS=`python3-config --cflags --embed` LDFLAGS=`python3-config --embed --ldflags` g++ -O3 -Wall -Wextra -Werror -fPIE -std=gnu++11 \ ${INCLUDES} ${CXXFLAGS} -o pytest main.cpp ${LDFLAGS} |
スポンサーリンク
Python 側のコード
ディレクトリと関数の位置関係を定義します。
1 2 |
from pymodule.AnalyzeEngine import * |
Python 側で実装されたアルゴリズムを実行するロジックを記述します。
今回は img、x、y のプロパティを持つオブジェクトを引数でもらい、C++ 側から呼び出してもらう想定。
1 2 3 4 5 6 7 8 |
def do_analyze(data): print(type(data)) print(data.img) print(data.x) print(data.y) return 0 |
スポンサーリンク
C++ 側のコード
C++ 側で Python 側にデータを渡すためのクラス AnalyzeData を定義します。
Python 側でも使えるように PyAnalyzeData というクラスを C++ 側から Python 側へ実装します。
ポイント
C++ 側で Python 側のクラスを実装する場合は、PYBIND11_EMBEDDED_MODULE を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <algorithm> #include <iostream> #include <vector> #include <pybind11/pybind11.h> #include <pybind11/embed.h> #include <pybind11/stl.h> namespace py = pybind11; class AnalyzeData { public: const std::vector<char>& GetImage() { return mImg; } int GetX() { return mX; } int GetY() { return mY; } void SetImage(const std::vector<char>& data) { std::vector<char> tmp(data.size()); std::copy(data.begin(), data.end(), tmp.begin()); mImg.swap(tmp); } void SetX(int x) { mX = x; } void SetY(int y) { mY = y; } private: std::vector<char> mImg; int mX; int mY; }; PYBIND11_EMBEDDED_MODULE(PyAnalyzeData, m) { py::class_<AnalyzeData>(m, "PyAnalyzeData") .def_property("img", &AnalyzeData::GetImage, &AnalyzeData::SetImage) .def_property("x", &AnalyzeData::GetX, &AnalyzeData::SetX) .def_property("y", &AnalyzeData::GetY, &AnalyzeData::SetY); } int main() { AnalyzeData* obj = new AnalyzeData(); std::vector<char> bytes; bytes.push_back('0'); bytes.push_back('1'); bytes.push_back('2'); obj->SetImage(bytes); obj->SetX(640); obj->SetY(480); py::scoped_interpreter guard{}; py::object pydata = py::module_::import("PyAnalyzeData"); py::object pyfunc = py::module_::import("pymodule").attr("do_analyze"); auto result = pyfunc(obj).cast<int>(); std::cout << "Python returned [" << result << "]" << std::endl; return 0; } |
スポンサーリンク
実行結果
ビルド&実行
$ ./make.sh
$ ./pytest
<class 'PyAnalyzeData.PyAnalyzeData'>
['0', '1', '2']
640
480
Python returned [0]
おわりに
C++ 側から Python の関数を呼び出す方法を検索し、引数を与えて呼び出す方法までは見つかりましたが、
C++ で定義したクラスのインスタンスを引数にする方法が見つかりませんでした。
pybind11 のコードを少し読んで実装しましたので、他に良い方法があれば教えてください。