Python 中如何使用pybind11调用C++

Reference:

  1. 混合编程:如何用pybind11调用C++
  2. pybind11 — Seamless operability between C++11 and Python(这个官方文档更清晰)
  3. Ubuntu pybind11教程

在实际开发过程中,免不了涉及到混合编程,比如,对于python这种脚本语言,性能还是有限的,在一些对性能要求高的情景下面,还是需要使用c/c++来完成。那怎样做呢?我们能使用pybind11作为桥梁,pybind11的优点是对C++ 11支持很好,API比较简单,现在我们就简单记下Pybind11的入门操作。

1. pybind11简介与环境安装

Pybind11 是一个轻量级只包含头文件的库,用于 Python 和 C++ 之间接口转换,可以为现有的 C++ 代码创建 Python 接口绑定。Pybind11 通过 C++ 编译时的自省来推断类型信息,来最大程度地减少传统拓展 Python 模块时繁杂的样板代码, 已经实现了 STL 数据结构、智能指针、类、函数重载、实例方法等到Python的转换,其中函数可以接收和返回自定义数据类型的值、指针或引用

直接使用pip安装

pip3 install pybind11

由于pybind11依赖于pytest,所以在安装前需要先把pytest给安装上

pip3 install pytest

2. 求和函数

首先,我们编写一个C++源文件,命名为example.cpp

// pybind11 头文件和命名空间
#include <pybind11/pybind11.h>
namespace py = pybind11;

int add(int i, int j)
{
    
    
    return i + j;
}

PYBIND11_MODULE(example, m)
{
    
    
    // 可选,说明这个模块是做什么的
    m.doc() = "pybind11 example plugin";
    //def("给python调用方法名", &实际操作的函数, "函数功能说明",默认参数). 其中函数功能说明为可选
    m.def("add", &add, "A function which adds two numbers", py::arg("i")=1, py::arg("j")=2);
}

PYBIND11_MODULE()宏函数将会创建一个函数,在由Python发起import语句时该函数将会被调用。模块名字“example”,由宏的第一个参数指定(千万不能出现引号)。第二个参数"m",定义了一个py::module的变量。函数py::module::def()生成绑定代码,将add()函数暴露给Python。(py::module::def()的最后两个参数:py::arg(“i”)=1, py::arg(“j”)=2,是给函数添加默认值的)

我们使用CMake进行编译。首先写一个CMakeLists.txt。

cmake_minimum_required(VERSION 3.1)
project(example)

add_subdirectory(pybind11)
pybind11_add_module(example example.cpp)

就是CMakeList.txt和example.cpp放在一个目录下面。

cmake .
make

会生成example.cpython-36m-x86_64-linux-gnu.so文件。

这个文件就是python可以调用的文件。还是在相同目录下运行python,进入python命令行

import example
example.add(3, 4)
[out]: 7

更推荐的用法是:

import os,sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from lib import example

if __name__ == "__main__":
    output = example.add()
    print(output)

3. STL和python内建数据类型的对应关系

在使用python编程时,常使用内建容器作为函数的参数和返回值,python语言的这种特性使我们的程序变得非常灵活和易于理解。那么在使用pybind11封装C++实现的函数的时候,如何保留这一特性呢?下面介绍pybind11实现list和dict作为参数及返回值的方法。

C++ STL Python
std::vector list
std::array list
std::map dict
std::set set

3.1 返回vector

//文件名:func.cpp  
#include "func.h"  
  
vector<long> list_square(vector<long> &in_list, vector<long>& out_list){
    
      
    vector<long>::iterator iter;  
    for(iter = in_list.begin(); iter != in_list.end(); iter++){
    
      
        out_list.push_back(*iter * *iter);  
    }  
    return out_list;  
}  
  
map<string, long> dict_square(map<string, long>& in_dict, map<string, long>& out_dict){
    
      
    map<string, long>::iterator iter;  
    iter = in_dict.begin();  
    while(iter != in_dict.end()){
    
      
        out_dict.insert({
    
    iter->first, iter->second * iter->second});  
        iter++;  
    }  
    return out_dict;  
}
  • 写pybind11封装函数
//文件名:func_wrapper.cpp  
#include <pybind11/pybind11.h>  
#include<pybind11/stl.h>  
#include "func.h"  
  
PYBIND11_MODULE(square, m){
    
      
    m.doc() = "Square the members of the container";  
    m.def("list_square", &list_square);  
    m.def("dict_square", &dict_square);  
}

3.2 返回struct

#include <pybind11/pybind11.h>
#include <iostream>
struct Foo {
    
    
    std::string a;
};

void show(Foo f) {
    
    
    std::cout << f.a << std::endl;
}

namespace py = pybind11;

PYBIND11_PLUGIN(example) {
    
    
    py::module m("example", "pybind11 example plugin");

    m.def("show", &show, "Prints a");
    py::class_<Foo>(m, "Foo")
    .def_readwrite("a",    &Foo::a);

    return m.ptr();
}
  • 写pybind11封装函数
import sys
sys.path.append(".")
import example

b = example.Foo
b.a = "Hello"
example.show(b)

4. pybind11与numpy图像数据接口和速度对比:以图像rgb转化为gray的例子

  1. 编写pybind11的C++代码:
#include<iostream>
#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
namespace py=pybind11;
py::array_t<double> rgb_to_gray(py::array_t<unsigned char>& img_rgb)
{
    
    
	if(img_rgb.ndim()!=3)
	{
    
    
		throw std::runtime_error("RGB image must has 3 channels!");
	}
	py::array_t<unsigned char> img_gray=py::array_t<unsigned char>(img_rgb.shape()[0]*img_rgb.shape()[1]);
	img_gray.resize({
    
    img_rgb.shape()[0],img_rgb.shape()[1]});
	auto rgb=img_rgb.unchecked<3>();
	auto gray=img_gray.mutable_unchecked<2>();
	for(int i=0;i<img_rgb.shape()[0];i++)
	{
    
    
		for(int j=0;j<img_rgb.shape()[1];j++)
		{
    
    
			auto R=rgb(i,j,0);
			auto G=rgb(i,j,1);
			auto B=rgb(i,j,2);
			auto GRAY=(R*30+G*59+B*11+50)/100;
			gray(i,j)=static_cast<unsigned char>(GRAY);
		}
	}
	return img_gray;
}
PYBIND11_MODULE(example,m)
{
    
    
	m.doc()="simple demo";
	m.def("rgb_to_gray",&rgb_to_gray);
}
  1. 编写CMakeList.txt:
cmake_minimum_required(VERSION 3.1)
project(example)
 
add_subdirectory(pybind11)
pybind11_add_module(example example.cpp)
  1. 与rgb换成gray的python代码速度和cv2自带的cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)对比:
    a. python代码
import cv2
import time
import numpy as np
def rgb_to_gray(img_rgb):
        if img_rgb.shape[2]!=3:
                print('image channels is 3')
        h,w,c=img_rgb.shape
        gray=np.zeros(shape=(h,w),dtype=np.uint8)
        for i in range(h):
                for j in range(w):
                        R=img_rgb[i,j,0]
                        G=img_rgb[i,j,1]
                        B=img_rgb[i,j,2]
                        GRAY=(R*30+G*59+B*11+50)/100
                        gray[i,j]=np.uint8(GRAY)
        return gray

b. 对比pybind11 c++的速度:

import cv2
import example
import time
import numpy as np
def rgb_to_gray(img_rgb):
	if img_rgb.shape[2]!=3:
		print('image channels is 3')
	h,w,c=img_rgb.shape
	gray=np.zeros(shape=(h,w),dtype=np.uint8)
	for i in range(h):
		for j in range(w):
			R=img_rgb[i,j,0]
			G=img_rgb[i,j,1]
			B=img_rgb[i,j,2]
			GRAY=(R*30+G*59+B*11+50)/100
			gray[i,j]=np.uint8(GRAY)
	return gray
img_rgb=cv2.imread("lena.png")
for i in range(1000):
	t1=time.clock()
	rgb_to_gray(img_rgb)
	t2=time.clock()
	print("python time:{}/s".format(t2-t1))
	print("...................................")
	t1=time.clock()
	example.rgb_to_gray(img_rgb)
	t2=time.clock()
	print("pybind11 time:{}/s".format(t2-t1))
	print("...................................")
	t1=time.clock()
	cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
	t2=time.clock()
	print("cv2.cvtColor time:{}/s".format(t2-t1))
	print("...................................")

c. 结果和分析:
在这里插入图片描述

分析,用python直接写的速度最慢,cv2自带的函数最快,结论,像素级别的操作,能用cv2自带的函数就用,如果cv2没有,那就用pybind11编写c++代码转换为python API接口,像素级别的处理,python太慢了,根本原因是python的for效率太低了。

猜你喜欢

转载自blog.csdn.net/qq_28087491/article/details/128305877