zoukankan      html  css  js  c++  java
  • 基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)

    C++ 是一种编译型(compiled)语言,设计重点是性能、效率和使用灵活性,偏向于系统编程、嵌入式、资源受限的软件和系统。

    Python是一种解释型(interpreted)语言,同样也支持不同的编程范式。Python 内置了常用数据结构(str, tuple, list, dict),简洁的语法、丰富的内置库(os,sys,urllib,...)和三方库(numpy, tf, torch ...),功能强大。最为重要的是和能够和多种服务(flask…)和tensorflow、pytorch等无缝联合,从而方便将你的算法开放出去。

    一方面,我们需要编译型语言(C++)性能;一方面,也需要解释型语言(Python)的灵活。这时,pybind11 可以用作 C++ 和 Python 之间沟通的桥梁。

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

    由于在Windows上和在Linux上使用会有较大不同,所以我这里将分为两个部分来说明问题,本文为下篇,具体说明Linux+Cmake实现。

    我认为在Linux上使用python调用c++函数更有现实价值,毕竟许多新的服务、深度运算等都是运行在linux上的。具体步骤可以参考如下。

    1、Linux下python调用c++的安装配置

    下载pybind11
    git clone https://github.com/pybind/pybind11.git

    安装pytest
    pip install pytest

    编译安装。这个地方我建议你首先将下载下来的pybind11备份一份

    cd pybind11
    mkdir build
    cd build
    cmake ..
    cmake --build . --config Release --target check
     
    这个编译的过程非常专业。

    2、编译最简单的代码
    在Linux上编译,我们一般选择gcc的方式。

    $ c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` example.cpp -o example`python3-config --extension-suffix`
    成功调用。但是目前直接是使用gcc进行编译的,实际情况是可能需要调用其它的库,比如OpenCV,这样就需要进一步研究。

    3、使用 Cmake进行编译
    使用 cmake 创建工程,编译为动态库,然后使用 python 测试。 
    写一个CMakeLists.txt,注意要理解它的意思
    cmake_minimum_required(VERSION 2.8.12)
    project(example)   

    add_subdirectory(pybind11)
    pybind11_add_module(example example.cpp)
    这里要求example.cpp放在和pybind11同一级的目录下,因为我们在CMakeLists.txt中调用了同目录pybind11和同目录的example.cpp文件。在当前目录下执行。这里需要注意,正确的文件方法:
    就是CMakeList.txt和example.cpp和pybind11(最高层)放在一个目录下面。
    cmake .
    make
    会生成example.cpython-36m-x86_64-linux-gnu.so文件。
    这个文件就是python可以调用的文件。还是在相同目录下运行python,进入python命令行
    import example
    example.add(34)
    [out]: 7
    非常好的效果,对于解决系列问题来说都是有帮助的。

    4、如何和OpenCV相结合
    这里困难的一点就是使用CMake编译OpenCV程序,在之前,我都是借助QT或者VS来完成这个工作,而这里只能给使用CMake来完成这个工作,这个是首先要解决的问题,然后才是PYD(so)的问题。经过寻觅,终于找到(参考1)。这个部分一定要注意,可以说是本篇博客最有价值的地方,也是我花费时间最长的地方。
    project(example) 
    cmake_minimum_required(VERSION 2.8.12)
    find_package(OpenCV REQUIRED) 
    include_directories(${OpenCV_INCLUDE_DIRS})
    add_subdirectory(pybind11)

    pybind11_add_module(example example.cpp)
    target_link_libraries(example PRIVATE ${OpenCV_LIBS})
    project(example)
    cmake_minimum_required(VERSION 2.8.12)
     
    find_package(OpenCV REQUIRED)
    include_directories(${OpenCV_INCLUDE_DIRS})
    add_subdirectory(pybind11)
     
    SET(SOURCES
      ${CMAKE_CURRENT_SOURCE_DIR}/example.cpp
    )
     
    pybind11_add_module(example ${SOURCES})
    target_link_libraries(example PRIVATE ${OpenCV_LIBS})
    简单分析一下这段Cmake,除了必须的项目名称等以外,就是简单地去寻找OpenCV等的地址,而后将lib输入进去。pybind11_add_module相当于建立项目,使用Set方法方便批量处理。
    其中注意两点:
    1、target_link_libraries(example PRIVATE ${OpenCV_LIBS}) 放最后
    2、xample PRIVATE 不可缺少,否则报这个错
    成功调用结果,注意绝对地址。

    5、Mat输入,Vector输出
    这里继续实际问题的研究,这里仍然会有一些新的Cmake问题。
    project(example) 
    cmake_minimum_required(VERSION 2.8.12)

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

    SET(SOURCES
      ${CMAKE_CURRENT_SOURCE_DIR}/example.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/mat_warper.h
      ${CMAKE_CURRENT_SOURCE_DIR}/mat_warper.cpp
    )
     
    pybind11_add_module(example ${SOURCES})
    target_link_libraries(example PRIVATE ${OpenCV_LIBS})
     
    应该是一次性通过的。这些没有太大问题。比较关键的问题就是部署,然后就是总结备份了。
    可以直接将现有算法以“三明治”的方式添加上去。其中需要注意GOCVHelper改动较多。

    6、移植和封装
    我希望FindPip能够成为一个比较标准的库,也就是在Python中能够以标准的方法调用:输入图片,输出圆心组;并且在新的系统中能够直接通过Cmake+make进行部署。这样的结果才方便别人使用。现在的话,应该可以将.so文件和它的支持库文件,一起拷贝吧。
    现在的话,只要他们两个在一起,main.py可以直接调用GOPyWarper***.so。这个结果是能够被接受的。
    我需要在另一台Ubuntu上进行实验,如果可行就可以发布了。但是需要注意pybind11可能产生级联问题。【还需要跟多异构实验】
    具体方法:
    1、下载解压;
     tar -xvf GOPyWarper0429.tar 

    2、编译(后附完整编译)
    mkdir build
    cd build
    cmake .. 
    make
    cp GOPyWarper.cpython-36m-x86_64-linux-gnu.so ../demo
    cd ../demo
    python3 main.py

    3、demo.py解读

    import cv2
    import GOPyWarper
    import numpy as np

    #获取图片,彩色3通道。
    #中文和空格不支持
    src = cv2.imread('pip.jpg',1

    #GO_FindPips
    #输入mat,输出为list(point1,point2,……),其中point代表一个找到的圆心。.
    varCircles = GOPyWarper.GO_FindPips(src)
    #print(varCircles)

    #GO_Resize
    #输入mat,输出为规则化后文件大小
    varResize = GOPyWarper.GO_Resize(src)

    #绘图
    dst=cv2.resize(src,((int)(varResize[0]),(int)(varResize[1])),interpolation=cv2.INTER_CUBIC)
    for i in varCircles[:]:
        cv2.circle(dst,(i[0],i[1]),5,(0,255,0),-1)
        
    cv2.imshow("dst",dst)
    cv2.waitKey(0)

    var1为圆心数组。这里生成的结果,只有这个*.so文件是需要保留的,可以拷贝出来,其他文件可以删除。
    结果截图:
    全部命令:
    helu@helu-virtual-machine:~/sandbox$ tar -cvf GOPyWarper0430.tar GOPyWarper0430
    ......
    helu@helu-virtual-machine:~/sandbox$ cd GOPyWarper0430
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430$ mkdir build
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430$ cd build
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430/build$ cmake ..
    ......
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430/build$ make 
    Scanning dependencies of target GOPyWarper
    20%] Building CXX object CMakeFiles/GOPyWarper.dir/src/GOPyWarper.cpp.o
    40%] Building CXX object CMakeFiles/GOPyWarper.dir/src/mat_warper.cpp.o
    60%] Building CXX object CMakeFiles/GOPyWarper.dir/src/GOCVHelper_2019_11_29.cpp.o
    80%] Building CXX object CMakeFiles/GOPyWarper.dir/src/GOFindPips.cpp.o
    [100%] Linking CXX shared module GOPyWarper.cpython-36m-x86_64-linux-gnu.so
    [100%] Built target GOPyWarper
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430/build$ cp GOPyWarper.cpython-36m-x86_64-linux-gnu.so ../demo/
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430/build$ cd ../demo/
    helu@helu-virtual-machine:~/sandbox/GOPyWarper0430/demo$ python3 main.py 

    需要注意的一点是,实际部署的时候,发现在没有安装OpenCV的机器上,报这个错误

    那么也就是说opencv_python那种命令行安装的方式是不行的,必须采用cmake完整安装。我在一个全新的ubuntu上安装最新版OpenCV后获得如下回显:
    helu@helu-virtual-machine:~/workstation/GOPyWarper0430$ cd build/
    helu@helu-virtual-machine:~/workstation/GOPyWarper0430/build$ cmake ..
    -- Found OpenCV: /usr/local (found version "4.3.0"
    -- Found PythonInterp: /usr/bin/python3.8 (found version "3.8.2"
    -- Found PythonLibs: /usr/lib/x86_64-linux-gnu/libpython3.8.so
    -- pybind11 v2.5.dev1
    -- Performing Test HAS_FLTO
    -- Performing Test HAS_FLTO - Success
    -- LTO enabled
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/helu/workstation/GOPyWarper0430/build
    helu@helu-virtual-machine:~/workstation/GOPyWarper0430/build$ make
    Scanning dependencies of target GOPyWarper
    20%] Building CXX object CMakeFiles/GOPyWarper.dir/src/GOPyWarper.cpp.o
    40%] Building CXX object CMakeFiles/GOPyWarper.dir/src/mat_warper.cpp.o
    60%] Building CXX object CMakeFiles/GOPyWarper.dir/src/GOCVHelper_2019_11_29.cpp.o
    80%] Building CXX object CMakeFiles/GOPyWarper.dir/src/GOFindPips.cpp.o
    [100%] Linking CXX shared module GOPyWarper.cpython-38-x86_64-linux-gnu.so
    [100%] Built target GOPyWarper
    helu@helu-virtual-machine:~/workstation/GOPyWarper0430/build$ 

    7、性能比较

    从原理上来说,基于python调用C++函数,其性能应该是依次劣于c++原生代码和opencv_python的。为了验证这个结论是否正确,我选择对lena.jpg做经典的GaussBlur操作,并且分别统计在c++原生、opencv_python和pbind11调用情况下的速度。全部以ms计数。我选择了一个比较大的核,这样才能够将时间差异拉出来。

    GaussBlurwindows实体机 c++原生ubuntu虚拟机 opencv_pythonubuntu虚拟机 pbind11
    1次323440
    重复100次281937403891

    参考代码
    原生c++
    int main() {
        string path = "e:/template/lena.jpg";
        cv::Mat src = cv::imread(path);
        Mat     dst;
     
        //开始计时
        double dstart = (double)cv::getTickCount();
        for (int i=0;i<=100;i++)
        {
            cv::GaussianBlur(src, dst, cv::Size(101101), 1.01.0);
            printf("%d times %f ms ", i,1000 * (getTickCount() - dstart) / getTickFrequency());
        
        }
        cv::waitKey(0);
        return 0;
    }
    原生python
    import cv2
    import GOPyWarper
    import numpy as np


    src = cv2.imread('/home/helu/images/lena.jpg',1
    dstart = cv2.getTickCount()

    for i in range(100):
        blur = cv2.GaussianBlur(src,(101,101),1.0,None,1.0,borderType=4)
        print(1000 * ( cv2.getTickCount() - dstart) /cv2.getTickFrequency())

    pybind11调用
    import cv2
    import GOPyWarper
    import numpy as np

    #获取图片,彩色3通道。
    #中文和空格不支持
    src = cv2.imread('/home/helu/images/lena.jpg',1
    dstart = cv2.getTickCount()

    for i in range(100):
        blur = GOPyWarper.test_gaussblur(src)
        print(1000 * ( cv2.getTickCount() - dstart) /cv2.getTickFrequency())

    这里基本能够认识到一些问题,但是也必须认识到,一方面开发效率也是效率,对于现有代码的整合使用,pybind11是不可替代的;此外,对于集成的函数,可能相关结果不一定如这里的单个函数这样明确。如果现在没有非常明确的需求要使用python编写,那么c++&pybind11的方法是首选。

    8、遗留问题
    命名问题,也就是目前在c++函数 、project名称、add_module三者都必须是统一的。这里不一定必须是这样,但是目前是有效的。

    9、小结:主要是在CMake上花费了不少时间,但是只要方向正确,有用的资源一定会源源不断。积累相关经验,继续前进。



    ========================参考==============================







  • 相关阅读:
    selenium介绍
    python爬虫之requests模块介绍
    SQLAlchemy框架用法详解
    JS判断是否为移动版浏览器
    goahead Web Server 环境搭建(Linux)
    Android 应用层APP发送短信
    Git使用相关问题汇总
    Spring boot 默认首页配置
    Android Studio高版本中文输入异常
    Android ADB 常用命令详解
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/12827052.html
Copyright © 2011-2022 走看看