C++で書いたコードをPythonで動かすには【pybind11】
はじめに
弊社には過去にC/C++で作った画像処理と機械学習の高速なライブラリがありました。
このライブラリの機能を組み合わせて使うには、C/C++でプログラムを書いてコンパイルする必要があり、ちょっと手軽には使えませんでした。
そこで、このライブラリを最近流行りのPythonで気軽に動かしたいと考え、pybind11というC++とPythonの橋渡しをするライブラリを使うことにしました。
今回は、pybind11の簡単な紹介をしたいと思います。
pybind11について
C++とPythonの連携にはいくつか方法がありますが、pybind11は新しいライブラリで、C++11の機能を使っているので、とてもやりやすいです。BSDライセンスです。
公式のチュートリアルやリファレンスに詳しく書いてありますが、どんなことができるか、よく使う機能を簡単に紹介します。
C++の関数をPythonから実行
例えば、2つの整数の和を返すC++の関数があったとして、Pythonのモジュールにするには以下のように書くだけです。
#include <pybind11/pybind11.h>
int add(int x, int y) {
return x + y;
}
PYBIND11_MODULE(myadd, m) {
m.def("add", &add);
}
これをビルドしてできたモジュールは、Pythonでimportして使うことができます。
import myadd
result = myadd.add(1, 2)
print(result) # output 3
かんたんですね!
PythonのコードをC++から実行
逆に、Pythonのコードを直接実行したり、Pythonのモジュールを読み込んでメソッドを実行したりすることもできます。
def mul(x: int, y: int):
return x * y
#include <iostream>
#include <pybind11/embed.h>
namespace py = pybind11;
int main() {
auto m = py::module::import("mymul");
auto result = m.attr("mul")(3, 4).cast<int>();
std::cout << result << std::endl; // output 12
}
C++で定義したクラスをPythonで使う
C++で定義したクラスもモジュール化して、Pythonでimportして使うこともできます。
#include <iostream>
#include <string>
#include <pybind11/pybind11.h>
namespace py = pybind11;
class Cat {
public:
explicit Cat(const std::string& name) : name_{ name } {}
void say() const { std::cout << name_ << " said meow." << std::endl; }
private:
std::string name_;
};
PYBIND11_MODULE(mycat, m) {
py::class_<Cat>(m, "Cat")
.def(py::init<const std::string&>())
.def("say", &Cat::say)
;
}
import mycat
cat = mycat.Cat("Shiro")
cat.say() # output "Shiro said meow."
STLコンテナとPythonのデータ型の相互変換
STLコンテナは、Pythonのデータ型と相互に変換できます。
-
std::vector<>
,std::deque<>
,std::list<>
,std::array<>
⇔list
-
std::set<>
,std::unordered_set<>
⇔set
-
std::map<>
,std::unordered_map<>
⇔dict
#include <vector>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
std::vector<int> x2(const std::vector<int>& x) {
std::vector<int> y;
for (int a : x)
y.push_back(a * 2);
return y;
}
PYBIND11_MODULE(myvec, m) {
m.def("x2", &x2);
}
import myvec
result = myvec.x2([1, 2, 3, 4, 5])
print(result) # output [2, 4, 6, 8, 10]
Eigen ⇔ NumPy
Eigen(C++の線形代数ライブラリ)の行列とNumPy(Pythonの数値演算ライブラリ)の行列も相互に変換できます。
-
Eigen::Matrix<>
⇔numpy.ndarray
#include <Eigen/Dense>
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
namespace py = pybind11;
Eigen::MatrixXd inverse(const Eigen::MatrixXd& x) {
return x.inverse();
}
PYBIND11_MODULE(mymat, m) {
m.def("inverse", &inverse);
}
import numpy as np
import mymat
result = mymat.inverse(np.array([[1., 2.], [3., 4.]]))
print(result) # output [[-2. 1. ] [ 1.5 -0.5]]
画像処理への応用
過去にC++で作った画像フィルタをPythonで使いたいと思い、pybind11でモジュール化しています。
Python版のOpenCVは画像をNumPyのndarrayで扱うので、このような画像フィルターを用意しておけば、あとはPython上で実行したい処理を書くだけです。
#include <Eigen/Dense>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/eigen.h>
namespace py = pybind11;
using Image8bpp =
Eigen::Matrix<uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
class ReverseFilter {
public:
ReverseFilter() {}
Image8bpp transform(const Image8bpp& x) {
return Image8bpp::Ones(x.rows(), x.cols()) * 255 - x;
}
};
PYBIND11_MODULE(myimage, m) {
py::class_<ReverseFilter>(m, "ReverseFilter")
.def(py::init<>())
.def("transform", &ReverseFilter::transform)
;
}
import numpy as np
import cv2
import myimage
x = cv2.imread('kuro-neko.png', cv2.IMREAD_GRAYSCALE)
f = myimage.ReverseFilter()
y = f.transform(x)
cv2.imwrite('shiro-neko.png', y)
さいごに
今回は、C++とPythonの橋渡しをするpybind11について説明しました。
Pythonだけでピクセル単位の画像処理をするととても重いので、そういうときはC++で高速に動作するプログラムをつくって、pybind11でモジュール化すると良いですね。
メンバー募集中です
アダコテックは上記のような画像処理技術を使って、大手メーカーの検査ラインを自動化するソフトウェアを開発している会社です。
機械学習や画像処理の内部ロジックに興味がある方、ご連絡下さい!
我々と一緒にモノづくりに革新を起こしましょう!
Discussion