C++/Python相互運用

現代のSLAM研究は2つの世界に生きている。性能が重要な推定コードはC++で書かれ、実験、深層学習、評価はPythonで行われる。C++/Python相互運用はその橋渡しである。C++コアをPythonバインディングでラップすることで、同じトラッカーやオプティマイザをノートブックから動かしたり、PyTorchモデルと組み合わせたり、Pythonツールでベンチマークしたりできるようになる。何も書き直す必要はない。

PyBind11は、これらのバインディングを書くためのデファクトスタンダードである。ヘッダーオンリーのC++ライブラリであり、非常に少ないボイラープレートでC++のクラスや関数をPythonモジュールとして公開できる。

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>   // automatic Eigen <-> NumPy conversion

Eigen::Matrix4d track(const Eigen::Matrix4d& T_prev, const cv::Mat& img);

PYBIND11_MODULE(myslam, m) {
    m.def("track", &track, "Track one frame and return the new pose");
}

pybind11/eigen.hヘッダーは、Eigenの行列とNumPy配列を自動的に相互変換する。これはまさにSLAMコードが必要とするものだ。姿勢、点群、ヤコビアンは、手動のコピーコードなしに配列として言語の境界を越える。あなたが使う多くのライブラリはこの方式でラップされている——GTSAMは公式のPythonラッパーを提供しており、COLMAPのpycolmapのようなプロジェクトも同じパターンに従っている。

自由関数ではなく、システム全体をバインドする場合は以下のようになる——GILガードに注目してほしい。これは、Pythonが待機している間もマルチスレッドのSLAMコアを動かし続けるために必要なものである。

namespace py = pybind11;

py::class_<SlamSystem>(m, "SlamSystem")
    .def(py::init<const std::string&>())              // config file path
    .def("track", &SlamSystem::track,
         py::call_guard<py::gil_scoped_release>())    // release GIL during C++ work
    .def_property_readonly("map_points", &SlamSystem::mapPoints);

nanobindは、同じ作者による後継ライブラリで、バインディングのオーバーヘッドを下げ、バイナリサイズを小さくし、コンパイル時間を短くするために再設計されている。そのAPIは意図的にPyBind11に近く、知識がそのまま転用できる。呼び出しオーバーヘッドを重視する新しいプロジェクト(例: 特徴点ごとやフレームごとに呼ばれる小さな関数のバインディング)は、nanobindを選ぶ傾向が増えている。

SLAMコードをラップする際に理解すべきこと

よくある落とし穴

SLAMにおける意義

この分野は、古典的なC++バックエンドと、Python/PyTorchで動作する学習済みフロントエンド(特徴点検出器、深度ネットワーク、マッチャー)を組み合わせたハイブリッドシステムへと収束しつつある。C++のオプティマイザをPythonにバインドできること——あるいはエクスポートされたモデルを介してC++から学習済みマッチャーを呼び出せること——が、こうした組み合わせを実用的にしている。それはまた、あなた自身のC++コードも変える。一度ラップすれば、それはスクリプト可能になり、pytestから単体テスト可能になり、Pythonツールでデータセットに対して評価しやすくなる。

ハンズオン

関連ノート