基于pybind11的python调用c++模块

基于ubuntu 18.04系统,使用python3调用c++生成的动态链接库

1. pybind11的三种安装方式:

(1) 命令安装

sudo apt-get install python-pybind11

(2) pip命令安装

pip3 install pybind11

(3) 源码编译安装

pip3 install pytest
git clone https://github.com/pybind/pybind11.git
cd pybind11
mkdir build
cd build
cmake …
make -j4
sudo make install

2. 使用CMakeList生成动态链接库

这里简单列一下以前使用pybind11写的一个借口函数,并使用CMakeLists.txt生成 .so 动态链接库,如下所示,
map_interface.h

#include 
#include 

namespace py = pybind11;

constexpr int kLocalDescriptorSize2  = 256;
constexpr int kGlobalDescriptorSize2 = 4096;

class MapInterface
{
public:
    int CreateObj(int img_row, int img_col);

    void SetCameraParas(int camera_model, Eigen::Ref<Eigen::Matrix<float, 3, 3, Eigen::RowMajor>> camera_intrinsic, 
                        Eigen::Ref<Eigen::Matrix<float, 5, 1, Eigen::ColMajor>> camera_distort);

    bool AddImgFeatures(int frame_index, Eigen::Ref<Eigen::Matrix<float, 4, 4, Eigen::RowMajor>> camera_pose,
                Eigen::Ref<Eigen::Matrix<float, 1, kGlobalDescriptorSize2, Eigen::RowMajor>> global_descriptor,
                Eigen::Ref<Eigen::Matrix<float, 2, Eigen::Dynamic, Eigen::RowMajor>> row_image_keypoints,
                Eigen::Ref<Eigen::Matrix<float, kLocalDescriptorSize2, Eigen::Dynamic, Eigen::RowMajor>> row_local_descriptors);

    // 为了便于pybind11 调用,这里暂时不使用重载函数功能
    bool AddImgFeatures2(int frame_index, Eigen::Ref<Eigen::Matrix<float, 4, 4, Eigen::RowMajor>> camera_pose,
                Eigen::Ref<Eigen::Matrix<float, 1, kGlobalDescriptorSize2, Eigen::RowMajor>> global_descriptor,
                Eigen::Ref<Eigen::Matrix<float, 2, Eigen::Dynamic, Eigen::RowMajor>> row_image_keypoints,
                Eigen::Ref<Eigen::Matrix<float, 2, Eigen::Dynamic, Eigen::RowMajor>> row_norm_keypoints,
                Eigen::Ref<Eigen::Matrix<float, kLocalDescriptorSize2, Eigen::Dynamic, Eigen::RowMajor>> row_local_descriptors);

    void DestroyObj();
};

PYBIND11_MODULE(map_interface, m) {
    m.doc() = "pybind11 Hierarchical Localization cpp backend";

    py::class_<MapInterface>(m, "MapInterface")
    .def(py::init())
    .def("CreateObj", &MapInterface::CreateObj, py::return_value_policy::copy)
    .def("SetCameraParas", &MapInterface::SetCameraParas)
    .def("AddImgFeatures", &MapInterface::AddImgFeatures, py::return_value_policy::copy)
    .def("AddImgFeatures2", &MapInterface::AddImgFeatures2, py::return_value_policy::copy)
    .def("DestroyObj", &MapInterface::DestroyObj);
};

注意需要添加#include 头文件, 因为使用Eigen作为数据传输的接口,因此需要使用#include 文件, 在使用Eigen作为函数参数,用来传输数据时, 注意要指定Eigen矩阵的行列有限排列,即Eigen::RowMajor/ColMajor. 需要特别注意的是,当矩阵是m × \times × 1(m行,1列)情况下,必须要使用列优先排列(Eigen::ColMajor),不然会出现错误. 例如:

    void SetCameraParas(int camera_model, Eigen::Ref<Eigen::Matrix<float, 3, 3, Eigen::RowMajor>> camera_intrinsic, 
                        Eigen::Ref<Eigen::Matrix<float, 5, 1, Eigen::ColMajor>> camera_distort);

当然也可以使用numpy做函数参数的数据传输.
这里将c++的函数接口封装到类里面, 在最后添加了python调用c++的接口函数

PYBIND11_MODULE(map_interface, m) {
    m.doc() = "pybind11 Hierarchical Localization cpp backend";

    py::class_<MapInterface>(m, "MapInterface")
    .def(py::init())
    .def("CreateObj", &MapInterface::CreateObj, py::return_value_policy::copy)
    .def("SetCameraParas", &MapInterface::SetCameraParas)
    .def("AddImgFeatures", &MapInterface::AddImgFeatures, py::return_value_policy::copy)
    .def("AddImgFeatures2", &MapInterface::AddImgFeatures2, py::return_value_policy::copy)
    .def("DestroyObj", &MapInterface::DestroyObj);
};

注意,上面接口为了方(偷)便(懒), AddImgFeatures()没有如果使用重载函数的话. 如果使用pybind11实现累的重载函数接口时,需要使用py::overload_cast, 具体可参考https://blog.csdn.net/weixin_41521681/article/details/106200017

.def("AddImgFeatures", py::overload_cast< 函数具体的参数 >(&MapInterface::AddImgFeatures))

上面函数的具体实现是在map_interface.cpp文件中, 这里不具体列. 需要特别注意的是,在写接口类,或者接口函数时,接口所在的.h文件尽量少的依赖自己写的一些.h文件, 这些依赖可以具体写在.cpp中. 这样, 在使用python调用(或者是主函数调用)时,使用接口的.h文件可以避免大量.h文件暴露出来. 另外为了更加规范,可以将PYBIND11_MODULE这个模块单独写在一个.h文件中.

CMakeList.txt文件的写法

cmake_minimum_required(VERSION 2.8.12)
project(HFnet_map)
add_compile_options(-std=c++11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g")
set(CMAKE_BUILD_TYPE "Release")
set(PYBIND11_CPP_STANDARD -std=c++11)


find_package(pybind11 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(Eigen3 REQUIRED)

INCLUDE_DIRECTORIES(
  ${OpenCV_INCLUDE_DIRS}
  ${EIGEN3_INCLUDE_DIR}
  ${pybind11_INCLUDE_DIRS}
  ./
)

add_library(map_interface MODULE 
            map_interface.cpp
            xxx.cpp
            xxx.cpp
            xxx.cpp
    )
target_link_libraries(map_interface 
            pybind11::module
            ${OpenCV_LIBS}
  )

CMakeList里面需要注意对pybind11的引用, 也有其他引用的方式,例如使用 pybind11_add_module,可参考pybind11的官方doc

3. python调用

python调用代码如下:

import os
import cv2
... 

import sys
sys.path.append("./build/")
import map_interface  # c++接口

# 创建c++接口对象
map_obj = map_interface.MapInterface()
# 调用函数
map_obj.CreateObj()
map_obj.SetCameraParas(<传入调用的参数>)
...

在使用python调用c++接口函数时, 如果python代码和.so不在同一个路径, 则需要指定生成的.so路径

import sys
sys.path.append("so的路径")

写在最后

在使用过程中, 有三个地方的变量命名需要一致,分别是上述代码中的: PYBIND11_MODULE接口函数中的map_interface, CMakeList中的add_library中的map_interface, python代码中的 import map_interface. 如下,

  1. PYBIND11_MODULE(map_interface, m)
  2. add_library(map_interface MODULE … )
  3. import map_interface # c++接口

这里,全部统一定义成map_interface. 否则在python调用时会出现如下等错误:

ModuleNotFoundError: No module named ’ ’

在运行python程序时,还会出现错误:

ImportError: dynamic module does not define module export function (PyInit_libxxxx)

这个问题仍然是这三个地方的命名不一致导致的,需要注意的是, linux 系统在使用 CMake 编译 c++ 动态链接库的时候, 会加一个前缀lib, 生成libmap_interface.so, 因此,这三个地方的命名应该修改如下:

  1. PYBIND11_MODULE(libmap_interface, m)
  2. add_library(map_interface MODULE … )
  3. import libmap_interface # c++接口

因为这个问题,也是调试了很久.

reference

  1. https://pybind11.readthedocs.io/en/stable/index.html
  2. https://zhuanlan.zhihu.com/p/80884925
  3. https://blog.csdn.net/weixin_41521681/article/details/106200017

你可能感兴趣的:(code之路)