pybind11—python C/C++扩展编译

前言

在之前的pybind11系列实践中,开发流程大致是这样的:

  • 第一步: 首先在C/C++ IDE中编写C/C++函数,然后采用pybind11封装为python可调用的包装函数, 之后采用C/C++编译器生成.pyd文件
image.png
  • 第二步:将生成的.pyd文件复制到python工程中,之后作为python module import导入使用
    image.png

存在的问题
不同操作系统下直接调用生成的pyd可能会出错,不能跨平台调用

在上述过程中,pyd动态链接库的生成是在本地PC上,但是如果想在不同的操作系统、硬件平台上调用之前生成的pyd,显然是会出错的。比如在windows上编译生成了一个python扩展.pyd, 但是Ubuntu系统或者树莓派上想调用这个python扩展显然就不行了。

为了使得C/C++创建的python扩展可以跨平台使用,那么最简单的办法就是直接发布源码, 然后在该操作系统、硬件平台上编译生成python扩展。

本节内容利用python setuptools 方式实现

pybind11系列文章:
https://www.jianshu.com/p/82a5748ed0fb


开发环境

  • windows 10 64bit
  • Anaconda3, with python 3.7
  • pybind11

C/C++ python扩展的实现

Project1

创建一个简单的工程, 之后创建一个package,取名demo1:

image.png

创建如下文件:

  • __init__.py 创建包默认生成的
  • example.cpp C++代码
  • setup.py 用于编译C++代码,生成C/C++ python扩展
  • test.py 测试

在Visual Studio中测试通过之后,将C/C++源码文件添加的python工程目录中,然后创建一个setup.py文件,编写如下代码:

Extension中设置C/C++源码文件、第三方库的依赖文件,由于本工程采用pybind11进行C++ 与python接口的封装,因此需要包含pybind11库的头文件,pybind11库是header-only,因此无需包含lib。

setup.py

from setuptools import setup
from setuptools import Extension

example_module = Extension(name='numpy_demo',  # 模块名称
                           sources=['example.cpp'],    # 源码
                           include_dirs=[r'D:\Anaconda3_2\include',     # 依赖的第三方库的头文件
                                         r'D:\pybind11-master\include']
                           )

setup(ext_modules=[example_module])

example.cpp

#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
#include<fstream>
#include<iostream>

namespace py = pybind11;

/*
https://blog.csdn.net/u013701860/article/details/86313781
https://blog.csdn.net/u011021773/article/details/83188012
*/

py::array_t<float> calcMul(py::array_t<float>& input1, py::array_t<float>& input2) {

    // read inputs arrays buffer_info
    py::buffer_info buf1 = input1.request();
    py::buffer_info buf2 = input2.request();

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

    // allocate the output buffer
    py::array_t<double> result = py::array_t<double>(buf1.size);



}

class Matrix
{
public:
    Matrix() {};
    Matrix(int rows, int cols) {
        this->m_rows = rows;
        this->m_cols = cols;
        m_data = new float[rows*cols];
    }
    ~Matrix() {};

private:
    int m_rows;
    int m_cols;
    float* m_data;

public:
    float* data() { return m_data; };
    int rows() { return m_rows; };
    int cols() { return m_cols; };

};




void save_2d_numpy_array(py::array_t<float, py::array::c_style> a, std::string file_name) {

    std::ofstream out;
    out.open(file_name, std::ios::out);
    std::cout << a.ndim() << std::endl;
    for (int i = 0; i < a.ndim(); i++)
    {
        std::cout << a.shape()[i] << std::endl;
    }
    for (int i = 0; i < a.shape()[0]; i++)
    {
        for (int j = 0; j < a.shape()[1]; j++)
        {
            if (j == a.shape()[1]-1)
            {
                //访问读取,索引 numpy.ndarray 中的元素
                out << a.at(i, j)<< std::endl;
            }
            else {
                out << a.at(i, j) << " ";
            }
        }
    }

}

//
//py::array_t<unsigned char, py::array::c_style> rgb_to_gray(py::array_t<unsigned char, py::array::c_style>& a) {
//
//  py::array_t<unsigned char, py::array::c_style> dst = py::array_t<unsigned char, py::array::c_style>(a.shape()[0] * a.shape()[1]);
//  //指针访问numpy矩阵
//  unsigned char* p = (unsigned char*)dst.ptr();
//
//  for (int i = 0; i < a.shape()[0]; i++)
//  {
//      for (int j = 0; j < a.shape()[1]; j++)
//      {
//          auto var = a.data(i, j);
//          auto R = var[0];
//          auto G = var[1];
//          auto B = var[2];
//
//          //RGB to gray
//          auto gray = (R * 30 + G * 59 + B * 11 + 50) / 100;
//
//          std::cout << static_cast<int>(R) << " " << static_cast<int>(G) << " " << static_cast<int>(B)<< std::endl;
//
//          //p[i*a.shape()[1] + j] = static_cast<unsigned char>(gray);
//
//      }
//  }
//}

PYBIND11_MODULE(numpy_demo, m) {

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

    py::class_<Matrix>(m,"Matrix",py::buffer_protocol())
        .def_buffer([](Matrix& mm)->py::buffer_info {
        return py::buffer_info(
            mm.data(),          //Pointer to buffer, 数据指针
            sizeof(float),      //Size of one scalar, 每个元素大小(byte)
            py::format_descriptor<float>::format(), //python struct-style foramt descriptor
            2,                      //Number of dims, 维度
            {mm.rows(), mm.cols()}, //strides (in bytes)
            {sizeof(float) * mm.cols(),sizeof(float)}
        );
    });

    m.def("save_2d_numpy_array", &save_2d_numpy_array);
    //m.def("rgb_to_gray", &rgb_to_gray);
}

在pycharm底下,打开终端:


image.png

将路径切换到setup.py所在目录,然后执行命令:


image.png

生成python扩展库:

image.png
image.png

最后,会发现工程中增加了一些新的目录和文件,其中xxxx.pyd就是生成的python扩展库。

image.png
  • python扩展库测试

test.py

import demo1.numpy_demo as numpy_demo
import numpy as np


help(numpy_demo)

mat1 = numpy_demo.save_2d_numpy_array(np.zeros(shape=[10,10], dtype=np.float32), r'./data.dat')

print(mat1)

image.png
image.png
image.png

Project2 opencv工程

第一个工程比较简单,没有包含和依赖第三方库,一般C/C++工程中,往往包含和依赖许多第三方库,本工程以opencv库为例,实现C/C++ python扩展模块的编译。

image.png

编写setup.py

from setuptools import Extension
from setuptools import setup


__version__ = '0.0.1'

# 扩展模块
ext_module = Extension(
    # 模块名称
    name='cv_demo1',
    # 源码
    sources=[r'mat_warper.cpp', r'main.cpp'],
    # 包含头文件
    include_dirs=[r'D:\Anaconda3_2\include',
                  r'D:\opencv-4.1.0\opencv\build\include',
                  r'D:\pybind11-master\include'
                  ],
    # 库目录
    library_dirs=[r'D:\opencv-4.1.0\opencv\build\x64\vc15\lib'],
    # 链接库文件
    libraries=[r'opencv_world410'],
    language='c++'
)

setup(
    name='cv_demo1',
    version=__version__,
    author_email='xxxx@qq.com',
    description='A simaple demo',
    ext_modules=[ext_module],
    install_requires=['numpy']
)

编译python扩展库:
在终端执行命令


image.png
image.png

python代码测试:

test.py

import demo2.cv_demo1 as cv_demo
import numpy as np
import cv2
import matplotlib.pyplot as plt


help(cv_demo)

image = cv2.imread(r'F:\lena\lena_gray.jpg', cv2.IMREAD_GRAYSCALE)
# canny
img_canny = cv_demo.test_gray_canny(image)
plt.figure('canny')
plt.imshow(img_canny, cmap=plt.gray())
# pyramid
imgs_pyramid = cv_demo.test_pyramid_image(image)
plt.figure('pyramid')
for i in range(1, len(imgs_pyramid)):
    plt.subplot(2, 2, i)
    plt.imshow(imgs_pyramid[i])

# rgb to gray
plt.figure('rgb->gray')
img_gray = cv_demo.test_rgb_to_gray(cv2.imread(r'F:\lena\lena_rgb.jpg'))
plt.imshow(img_gray)
plt.show()

python测试结果:

  • canny边缘检测


    image.png
  • Gaussian图像金字塔


    image.png
  • 图像RGB转Gray


    image.png

C++源码:
main.cpp

#include<iostream>
#include<vector>
#include<opencv2/opencv.hpp>
#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
#include<pybind11/stl.h>
#include"mat_warper.h"

namespace py = pybind11;

py::array_t<unsigned char> test_rgb_to_gray(py::array_t<unsigned char>& input) {

    cv::Mat img_rgb = numpy_uint8_3c_to_cv_mat(input);
    cv::Mat dst;
    cv::cvtColor(img_rgb, dst, cv::COLOR_RGB2GRAY);
    return cv_mat_uint8_1c_to_numpy(dst);

}

py::array_t<unsigned char> test_gray_canny(py::array_t<unsigned char>& input) {
    cv::Mat src = numpy_uint8_1c_to_cv_mat(input);
    cv::Mat dst;
    cv::Canny(src, dst, 30, 60);
    return cv_mat_uint8_1c_to_numpy(dst);
}


/*
@return Python list
*/
py::list test_pyramid_image(py::array_t<unsigned char>& input) {
    cv::Mat src = numpy_uint8_1c_to_cv_mat(input);
    std::vector<cv::Mat> dst;

    cv::buildPyramid(src, dst, 4);

    py::list out;
    for (int i = 0; i < dst.size(); i++)
    {
        out.append<py::array_t<unsigned char>>(cv_mat_uint8_1c_to_numpy(dst.at(i)));
    }
    
    return out;
}

PYBIND11_MODULE(cv_demo1, m) {
    
    m.doc() = "Simple opencv demo";

    m.def("test_rgb_to_gray", &test_rgb_to_gray);
    m.def("test_gray_canny", &test_gray_canny);
    m.def("test_pyramid_image", &test_pyramid_image);

}

mat_warper.h

#ifndef MAT_WARPER_H_

#include<opencv2/opencv.hpp>
#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>

namespace py = pybind11;

cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t<unsigned char>& input);

cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t<unsigned char>& input);

py::array_t<unsigned char> cv_mat_uint8_1c_to_numpy(cv::Mat & input);

py::array_t<unsigned char> cv_mat_uint8_3c_to_numpy(cv::Mat & input);

#endif // !MAT_WARPER_H_

mat_warper.cpp

#include"mat_warper.h"
#include <pybind11/numpy.h>

/*
Python->C++ Mat
*/


cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t<unsigned char>& input) {

    if (input.ndim() != 2)
        throw std::runtime_error("1-channel image must be 2 dims ");

    py::buffer_info buf = input.request();

    cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC1, (unsigned char*)buf.ptr);
    
    return mat;
}


cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t<unsigned char>& input) {

    if (input.ndim() != 3)
        throw std::runtime_error("3-channel image must be 3 dims ");

    py::buffer_info buf = input.request();

    cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC3, (unsigned char*)buf.ptr);

    return mat;
}


/*
C++ Mat ->numpy
*/
py::array_t<unsigned char> cv_mat_uint8_1c_to_numpy(cv::Mat& input) {

    py::array_t<unsigned char> dst = py::array_t<unsigned char>({ input.rows,input.cols }, input.data);
    return dst;
}

py::array_t<unsigned char> cv_mat_uint8_3c_to_numpy(cv::Mat& input) {

    py::array_t<unsigned char> dst = py::array_t<unsigned char>({ input.rows,input.cols,3}, input.data);
    return dst;
}



//PYBIND11_MODULE(cv_mat_warper, m) {
//
//  m.doc() = "OpenCV Mat -> Numpy.ndarray warper";
//
//  m.def("numpy_uint8_1c_to_cv_mat", &numpy_uint8_1c_to_cv_mat);
//  m.def("numpy_uint8_1c_to_cv_mat", &numpy_uint8_1c_to_cv_mat);
//
//
//}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容