cmake引用pybind11

python调用C++用pybind11来封装接口比较方便易用,由于pybind11和python环境有匹配关系,cmake构建工程时先检测python环境,然后导入pybind11环境,这篇博客提供一个参考的cmakelists和一个简单的测试。

安装pybind11

有几种方式可以安装pybind11, 推荐 pip3 install pybind11, 这样可以直接套用下面的cmakelists

当然也可以github源码安装,因为pybind11是head only的,所以直接拷贝头文件到自己工程目录也是可以的,只要最后C程序能找到。

cmakelists

注意我的python3 的环境选择了解释器和开发环境,如果没有安装dev可以在安装python时候勾选开发调试库等选项,

博主这里使用python官网的环境,没有使用conda,如果使用conda的话,需要确认下pybind11_DIR和PYTHON_EXECUTABLE 是不是正确,以及是否匹配需要激活的环境。下面的cmake lists提供mac 和 windows的模板,其它平台需要自己注意pybind11_DIR是否正确

cmake_minimum_required(VERSION 3.24)
project(comp LANGUAGES CXX)

find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
message("PYTHON3_VERSION: ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}")
message("Python3_EXECUTABLE: ${Python3_EXECUTABLE}")
set(PYBIND11_PYTHON_VERSION ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR})
set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
if(MSVC)
  set(pybind11_DIR ${Python3_INCLUDE_DIRS}/../Lib/site-packages/pybind11/share/cmake/pybind11)
elseif(APPLE)
  set(pybind11_DIR ${Python3_INCLUDE_DIRS}/../../Lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/pybind11/share/cmake/pybind11)
endif()
find_package(pybind11 CONFIG REQUIRED)
link_directories(${Python3_INCLUDE_DIRS}/../libs)
pybind11_add_module(comp main.cpp)

如果要附加引用opencv,也可以用下面的模板:

cmake_minimum_required(VERSION 3.24)
project(comp LANGUAGES CXX)

find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
message("PYTHON3_VERSION: ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}")
message("Python3_EXECUTABLE: ${Python3_EXECUTABLE}")
set(PYBIND11_PYTHON_VERSION ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR})
set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
if(MSVC)
  set(pybind11_DIR ${Python3_INCLUDE_DIRS}/../Lib/site-packages/pybind11/share/cmake/pybind11)
elseif(APPLE)
  set(pybind11_DIR ${Python3_INCLUDE_DIRS}/../../Lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/pybind11/share/cmake/pybind11)
endif()
find_package(pybind11 CONFIG REQUIRED)
link_directories(${Python3_INCLUDE_DIRS}/../libs)

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

FILE(GLOB INCS ${CMAKE_CURRENT_SOURCE_DIR}/include/*)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source SRCS)

pybind11_add_module(comp ${SRCS} ${INCS})
target_link_libraries(comp PRIVATE ${OpenCV_LIBS})

一些容易出错的地方

跑例子之前提示几个容易出错的地方

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

PYBIND11_MODULE(map_interface, m)

add_library(map_interface MODULE … )

import map_interface # c++接口

否则再运行python程序时会报错 ImportError: dynamic module does not define module export function (PyInit_libxxxx)

这里也需要注意linux编译时候会自动给库加上lib前缀,这些都是容易出错的地方

CPP和Python引用例子

这里我们用numpy举了一个复杂些的例子,因为numpy我们常用,顺带看下

CPP代码:

#include <iostream>
#include <pybind11/pybind11.h>
#undef max
#include <pybind11/numpy.h>
 
namespace py = pybind11;

py::array_t<double> add_arrays_1d(py::array_t<double>& input1, py::array_t<double>& input2) {

    // 获取input1, input2的信息
    py::buffer_info buf1 = input1.request();
    py::buffer_info buf2 = input2.request();

    if (buf1.ndim !=1 || buf2.ndim !=1)
    {
        throw std::runtime_error("Number of dimensions must be one");
    }

    if (buf1.size !=buf2.size)
    {
        throw std::runtime_error("Input shape must match");
    }

    //申请空间
    auto result = py::array_t<double>(buf1.size);
    py::buffer_info buf3 = result.request();

    //获取numpy.ndarray 数据指针
    double* ptr1 = (double*)buf1.ptr;
    double* ptr2 = (double*)buf2.ptr;
    double* ptr3 = (double*)buf3.ptr;

    //指针访问numpy.ndarray
    for (int i = 0; i < buf1.shape[0]; i++)
    {
        ptr3[i] = ptr1[i] + ptr2[i];
    }

    return result;

}


py::array_t<double> add_arrays_2d(py::array_t<double>& input1, py::array_t<double>& input2) {

    py::buffer_info buf1 = input1.request();
    py::buffer_info buf2 = input2.request();

    if (buf1.ndim != 2 || buf2.ndim != 2)
    {
        throw std::runtime_error("numpy.ndarray dims must be 2!");
    }
    if ((buf1.shape[0] != buf2.shape[0])|| (buf1.shape[1] != buf2.shape[1]))
    {
        throw std::runtime_error("two array shape must be match!");
    }

    //申请内存
    auto result = py::array_t<double>(buf1.size);
    //转换为2d矩阵
    result.resize({buf1.shape[0],buf1.shape[1]});


    py::buffer_info buf_result = result.request();

    //指针访问读写 numpy.ndarray
    double* ptr1 = (double*)buf1.ptr;
    double* ptr2 = (double*)buf2.ptr;
    double* ptr_result = (double*)buf_result.ptr;

    for (int i = 0; i < buf1.shape[0]; i++)
    {
        for (int j = 0; j < buf1.shape[1]; j++)
        {
            auto value1 = ptr1[i*buf1.shape[1] + j];
            auto value2 = ptr2[i*buf2.shape[1] + j];

            ptr_result[i*buf_result.shape[1] + j] = value1 + value2;
        }
    }

    return result;

}

/*
numpy.ndarray 相加,  3d矩阵
@return 3d numpy.ndarray
*/
py::array_t<double> add_arrays_3d(py::array_t<double>& input1, py::array_t<double>& input2) {

    //unchecked<N> --------------can be non-writeable
    //mutable_unchecked<N>-------can be writeable
    auto r1 = input1.unchecked<3>();
    auto r2 = input2.unchecked<3>();

    py::array_t<double> out = py::array_t<double>(input1.size());
    out.resize({ input1.shape()[0], input1.shape()[1], input1.shape()[2] });
    auto r3 = out.mutable_unchecked<3>();

    for (int i = 0; i < input1.shape()[0]; i++)
    {
        for (int j = 0; j < input1.shape()[1]; j++)
        {
            for (int k = 0; k < input1.shape()[2]; k++)
            {
                double value1 = r1(i, j, k);
                double value2 = r2(i, j, k);

                //下标索引访问 numpy.ndarray
                r3(i, j, k) = value1 + value2;

            }
        }
    }

    return out;
}


PYBIND11_MODULE(comp, m) {

    m.doc() = "Simple demo using numpy!";

    m.def("add_arrays_1d", &add_arrays_1d);
    m.def("add_arrays_2d", &add_arrays_2d);
    m.def("add_arrays_3d", &add_arrays_3d);
}

Python代码:

    import components.build.Release.comp as comp
    var = comp.add_arrays_1d(np.array([1,2]), np.array([3,4]))

参考链接:

pybind11—python numpy与C++数据传递 – 简书 (jianshu.com)

Python传numpy矩阵调c++(求3D图像连通区域) – 知乎 (zhihu.com)

基于pybind11的python调用c++模块_pybind11_overload-CSDN博客

发表评论