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

作者: 侠之大者_7d3f | 来源:发表于2019-05-16 17:47 被阅读0次

    前言

    在之前的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 方式实现


    开发环境

    • 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);
    //
    //
    //}
    
    

    相关文章

      网友评论

        本文标题:pybind11—python C/C++扩展编译

        本文链接:https://www.haomeiwen.com/subject/jryiaqtx.html