欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

基于 pybind11 用 c++ 编写的 Python 调用 CV 算法的实现 - Next (Linux+Cmake)

最编程 2024-07-15 09:39:40
...

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​

基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_Python

安装pytest
​​​pip install pytest​

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

​​​​​ ​​​​ cd pybind11 ​​​

​ mkdir build
cd build
cmake ..
cmake  -- build .  -- config Release  --​

  ​


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_OpenCV_02​​


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_03​​


这个编译的过程非常专业。



2、编译最简单的代码


在Linux上编译,我们一般选择gcc的方式。



​++  - O3  - Wall  - shared  - std = c ++ 11  - fPIC  ` python3  - m pybind11  -- includes `  example.cpp  - o example ` python3 - config  -- extension - suffix `



​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_04​​


成功调用。但是目前直接是使用gcc进行编译的,实际情况是可能需要调用其它的库,比如OpenCV,这样就需要进一步研究。



3、使用 Cmake进行编译


使用 cmake 创建工程,编译为动态库,然后使用 python 测试。 


写一个CMakeLists.txt,注意要理解它的意思


​2. 8. 12)
project(example)   

add_subdirectory(pybind11)
pybind11_add_module(example example.cpp) ​



这里要求example.cpp放在和pybind11同一级的目录下,因为我们在CMakeLists.txt中调用了同目录pybind11和同目录的example.cpp文件。在当前目录下执行。这里需要注意,正确的文件方法:


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_05​​


就是CMakeList.txt和example.cpp和pybind11(最高层)放在一个目录下面。


​ cmake .
make ​



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


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_Python_06​​


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


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



​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_Python_07​​


非常好的效果,对于解决系列问题来说都是有帮助的。



4、如何和OpenCV相结合


这个部分一定要注意,可以说是本篇博客最有价值的地方,也是我花费时间最长的地方。


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 ​​

​简单分析一下这段Cmake,除了必须的项目名称等以外,就是简单地去寻找OpenCV等的地址,而后将lib输入进去。​


​ 其中注意两点: 1、target_link_libraries(example PRIVATE ${OpenCV_LIBS}) 放最后 2、xample PRIVATE 不可缺少,否则报这个错 基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_OpenCV_08 成功调用结果, 注意绝对地址。 基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_Python_09




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})


​​


应该是一次性通过的。这些没有太大问题。比较关键的问题就是部署,然后就是总结备份了。


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_10​​


可以直接将现有算法以“三明治”的方式添加上去。其中需要注意GOCVHelper改动较多。


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_11​​



6、移植和封装


我希望FindPip能够成为一个比较标准的库,也就是在Python中能够以标准的方法调用:输入图片,输出圆心组;并且在新的系统中能够直接通过Cmake+make进行部署。这样的结果才方便别人使用。现在的话,应该可以将.so文件和它的支持库文件,一起拷贝吧。


基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_OpenCV_12


现在的话,只要他们两个在一起,main.py可以直接调用GOPyWarper***.so。这个结果是能够被接受的。


基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_13


我需要在另一台Ubuntu上进行实验,如果可行就可以发布了。但是需要注意pybind11可能产生级联问题。【还需要跟多异构实验】


具体方法:


1、下载解压;


​-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文件是需要保留的,可以拷贝出来,其他文件可以删除。


结果截图:


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_14​​


全部命令:

​​-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


​​




需要注意的一点是,



​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_python_15​​


那么也就是说opencv_python那种命令行安装的方式是不行的,必须采用cmake完整安装。我在一个全新的ubuntu上安装最新版OpenCV后获得如下回显:

​​-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$
​​



​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_OpenCV_16​​


7、性能比较



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



GaussBlur

windows实体机 c++原生

ubuntu虚拟机 opencv_python

ubuntu虚拟机 pbind11

1次

32

34

40

重复100次

2819

3740

3891



参考代码


原生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(
101,
101),
1.
0,
1.
0);

printf(
"%d times %f ms\n", 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三者都必须是统一的。这里不一定必须是这样,但是目前是有效的。


​​基于pybind11实现Python调用c++编写的CV算法--下 (Linux+Cmake)_OpenCV_17​​



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