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)