Pybind11 绑定编译失败 - shared_ptr of Eigen::Matrix 的静态断言失败

Pybind11 binding compilation fails - Static assertion fails with shared_ptr of Eigen::Matrix

我正在尝试为 OpenVSLAM/Stella_vslam 的开源项目编译 these Python bindings

根据存储库说明,我执行以下命令:

/usr/bin/g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) -I/usr/local/include/openvslam/3rd/json/include -DUSE_DBOW2 /home/user/OpenVSLAM-Python-bindings/openvslam_bindings.cpp -o openvslam$(python3-config --extension-suffix) -lopenvslam

但是编译失败 following error:

In file included from /home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/detail/../attr.h:13,
                 from /home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/detail/class.h:12,
                 from /home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/pybind11.h:13,
                 from /home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/stl.h:12,
                 from /home/squiro/Desktop/OpenVSLAM-Python-bindings/openvslam_bindings.cpp:6:
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/detail/../cast.h: In instantiation of ‘struct pybind11::detail::copyable_holder_caster<Eigen::Matrix<double, 4, 4>, std::shared_ptr<Eigen::Matrix<double, 4, 4> >, void>’:
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/detail/../cast.h:826:7:   required from ‘class pybind11::detail::type_caster<std::shared_ptr<Eigen::Matrix<double, 4, 4> >, void>’
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/pybind11.h:242:63:   recursively required by substitution of ‘template<class Return> struct pybind11::detail::return_value_policy_override<Return, typename std::enable_if<std::is_base_of<pybind11::detail::type_caster_generic, pybind11::detail::type_caster<typename pybind11::detail::intrinsic_type<T>::type> >::value, void>::type> [with Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >]’
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/pybind11.h:242:63:   required from ‘void pybind11::cpp_function::initialize(Func&&, Return (*)(Args ...), const Extra& ...) [with Func = pybind11::cpp_function::cpp_function(Return (Class::*)(Arg ...), const Extra& ...) [with Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >; Class = stella_vslam::system; Arg = {const cv::Mat&, double, const cv::Mat&}; Extra = {pybind11::name, pybind11::is_method, pybind11::sibling, pybind11::arg, pybind11::arg, pybind11::arg_v}]::<lambda(stella_vslam::system*, const cv::Mat&, double, const cv::Mat&)>; Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >; Args = {stella_vslam::system*, const cv::Mat&, double, const cv::Mat&}; Extra = {pybind11::name, pybind11::is_method, pybind11::sibling, pybind11::arg, pybind11::arg, pybind11::arg_v}]’
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/pybind11.h:108:9:   required from ‘pybind11::cpp_function::cpp_function(Return (Class::*)(Arg ...), const Extra& ...) [with Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >; Class = stella_vslam::system; Arg = {const cv::Mat&, double, const cv::Mat&}; Extra = {pybind11::name, pybind11::is_method, pybind11::sibling, pybind11::arg, pybind11::arg, pybind11::arg_v}]’
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/pybind11.h:1578:22:   required from ‘pybind11::class_<type_, options>& pybind11::class_<type_, options>::def(const char*, Func&&, const Extra& ...) [with Func = std::shared_ptr<Eigen::Matrix<double, 4, 4> > (stella_vslam::system::*)(const cv::Mat&, double, const cv::Mat&); Extra = {pybind11::arg, pybind11::arg, pybind11::arg_v}; type_ = stella_vslam::system; options = {}]’
/home/squiro/Desktop/OpenVSLAM-Python-bindings/openvslam_bindings.cpp:357:134:   required from here
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/detail/../cast.h:753:61: error: static assertion failed: Holder classes are only supported for custom types
  753 |     static_assert(std::is_base_of<base, type_caster<type>>::value,
      |                                                             ^~~~~
/home/squiro/.local/lib/python3.8/site-packages/pybind11/include/pybind11/detail/../cast.h:1408:55: error: ‘pybind11::detail::enable_if_t<(! std::is_void<_Yp>::value), Return> pybind11::detail::argument_loader<Args>::call(Func&&) && [with Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >; Guard = pybind11::detail::void_type; Func = pybind11::cpp_function::cpp_function(Return (Class::*)(Arg ...), const Extra& ...) [with Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >; Class = stella_vslam::system; Arg = {const cv::Mat&, double, const cv::Mat&}; Extra = {pybind11::name, pybind11::is_method, pybind11::sibling, pybind11::arg, pybind11::arg, pybind11::arg_v}]::<lambda(stella_vslam::system*, const cv::Mat&, double, const cv::Mat&)>&; Args = {stella_vslam::system*, const cv::Mat&, double, const cv::Mat&}; pybind11::detail::enable_if_t<(! std::is_void<_Yp>::value), Return> = std::shared_ptr<Eigen::Matrix<double, 4, 4> >]’, declared using local type ‘pybind11::cpp_function::cpp_function(Return (Class::*)(Arg ...), const Extra& ...) [with Return = std::shared_ptr<Eigen::Matrix<double, 4, 4> >; Class = stella_vslam::system; Arg = {const cv::Mat&, double, const cv::Mat&}; Extra = {pybind11::name, pybind11::is_method, pybind11::sibling, pybind11::arg, pybind11::arg, pybind11::arg_v}]::<lambda(stella_vslam::system*, const cv::Mat&, double, const cv::Mat&)>’, is used but never defined [-fpermissive]
 1408 |     enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) && {
      |                                                       ^~~~

这是line 356 of openvslam_bindings.cpp中定义的:

.def("feed_monocular_frame", &system::feed_monocular_frame, py::arg("img"), py::arg("timestamp"), py::arg("mask") = cv::Mat{})

为了更清楚起见,feed_monocular_frame 是一个 returns a std:shared_ptrEigen::Matrix4d 的函数,如图this image.

我对 Pybind11 一般不熟悉,所以尽管我尝试了,但我无法找到解决此问题的方法。最近几天我一直在寻找答案,研究 pybind 的源代码......但仍然无法解决这个问题。

郑重声明,我使用的是 Ubuntu 20.04; Pybind 2.9.2 和 Eigen 3.3.0.

感谢任何帮助。

在这里回答自己,因为我在做更多研究后设法找到了解决方案。

我发现 a message on Gitter 与错误密切相关。我引用用户 YannickJadoul:

Don't return a std::shared_ptrEigen::ArrayXd, because pybind11 does not know what to do with it, because Eigen matrices get converted into np.ndarray and there's no way to hide the std::shared_ptr refcounting in there

这就是在尝试构建绑定时触发静态断言错误的原因。

我找到了两种解决方法:

  1. 要么修改库的源代码,改变return类型...
  2. 或者我们可以将绑定包装在 return 矩阵(而不是指向它的指针)的 lambda 函数中。

我选择了第二种解决方案,因为它不涉及修改库。

这是绑定的原始定义:

.def("feed_monocular_frame", &system::feed_monocular_frame, py::arg("img"), py::arg("timestamp"), py::arg("mask") = cv::Mat{})

这是解决问题的修改版本:

.def("feed_monocular_frame", 
    [](stella_vslam::system &self, const cv::Mat &img, const double timestamp, const cv::Mat& mask = cv::Mat{}) { 
                    std::shared_ptr<Mat44_t> matrix = self.feed_monocular_frame(img, timestamp, mask); 
                    if (matrix)
                        return *matrix;
                    else 
                    {
                        Eigen::Matrix4d m(4,4);;
                        return m;
                    }
                }, 
                py::arg("img"), py::arg("timestamp"), py::arg("mask") = cv::Mat{})

请记住,空指针检查与此问题无关。如果需要,您可以直接 return 矩阵(但如果指针为空,您可能会以分段错误结束,考虑一下!)。