20200411更新:

经过评论区 @鹤汀凫渚 的指导,我成功的用最简单的方法在python中调用到了GPU加速后的函数,这里把这位朋友的评论贴出来供各位参考:

以下原文:

本文的核心目的就是加速,在实时图像处理的路上,没有什么比得上加速,速度足够快就能上更复杂的模型,速度足够快就能有更多的预处理,总之,加速就是一切。

为了弥补Opencv-Python接口没有cuda加速的缺陷,本文旨在通过调用C++上才有的cuda模块,对Opencv进行加速,然后将其打包为Python接口后调用。

ps:本来笔者最开始采用的是Cython的第三方解决方案,但是经过同事指教后发现官方的API更加明晰,故改回使用Python官方的API来完成。

目录:图像数组的跨平台传递。

在C++下编写cuda加速的OpenCV代码。

--------------------------------------------------------------------------------

图像数组的跨平台传递

在采用PyTorch或者TF等深度学习框架的项目中,OpenCV主要的工作是针对图像的预处理,因此绝大部分情况下,数据的IO都是通过Python进行的,如何将图像数据从Python跨平台传输到C++下就是我们首先要解决的问题,而在Python下的图像数据基本都是保存为ndarray类型,因此我们需要在C++下也同样引入Numpy库,这个库可以通过APT直接安装,头文件会与Python3的头文件放在一起。

本文主要涉及的C++第三方库包括:OpenCV

Numpy

Python3.6

代码逻辑为:接收来自Python的ndarray数组,将其转为PyArrayObject类

将PyArrayObject转为OpenCV的Mat类数据

调用OpenCV对Mat进行图像处理,本文为了演示仅做BGR2GRAY处理

将处理后的Mat转换为PyArrayObject类

将PyArrayObject传回给Python,Python会自动将其识别为ndarray类对象

为了完成上面的逻辑,我们需要新建三个类文件:interface.cpp/.h :接口类,主要负责步骤1,5

conversion.cpp/.h :转换类,主要负责步骤2,4

process.cpp/.h :处理类,主要负责步骤3

先从interface.cpp文件开始:

//interface.cpp// Created by liuky on 2020-01-02.//#include #include
"numpy/arrayobject.h"#include "numpy/ndarrayobject.h"#include
"opencv2/imgcodecs.hpp"#include "conversion.h"#include
"process.h"//参数检测与转换,将会检测Python端输入参数的合法性,并转为PyObject类static PyObject*
array2array(PyObject* self,PyObject* args){

PyObject *array;

if (not PyArg_ParseTuple(args,"O",&array)){
//判断args输入的参数是否为PyOBJ,并且将array指针指向参数 PyErr_SetString(PyExc_TypeError,"input arg
is not pyobject");//如果不是则报错 }

PyObject* res = process(array);//调用图像处理函数对array进行处理 return
Py_BuildValue("O",res);

}

//声明要暴露出去的方法集合,该集合为PyMethodDef结构static PyMethodDef my_test_methods[]={

{"array2array",array2array,METH_VARARGS},

{NULL,NULL,0,NULL}

};

//声明模组,模组包含一个HEAD_INIT头初始化,模组名,以及描述字符等,最后必须包含方法集合static PyModuleDef
my_test_modules={

PyModuleDef_HEAD_INIT,

"my_test",

"DES",

-1,

my_test_methods,

};

//包初始化,当在Python下
import这个包时就会调用初始化函数,要注意初始化函数名的命名规则,必须是PyInit_xxx(包名)PyMODINIT_FUNC
PyInit_my_test(void){

import_array();//要注意array在每个类文件下都要重新初始化 conversion_init();

return PyModule_Create(&my_test_modules);

}

然后是conversion.cpp:

//conversion.cpp// Created by liuky on 2020-01-02.//#include "conversion.h"int
conversion_init(){

import_array();

return 0;

}

cv::Mat array2mat(PyObject *pyobj){

cv::Mat mat;

PyArrayObject* py_array_obj = nullptr;

//检查是否为PyArray if(!PyArray_Check(pyobj)){

free(py_array_obj);

PyErr_SetString(PyExc_TypeError,"input obj is not PyArrayObject");

} else py_array_obj = (PyArrayObject*) pyobj;

bool needcopy = false; //判断PyArray是否内存地址连续

//判断PyArray的元素类型,并且找到对应的Mat元素类型 int array_dtype = PyArray_TYPE(py_array_obj);

int mat_dtype =array_dtype == NPY_UBYTE ? CV_8U :

array_dtype == NPY_BYTE ? CV_8S :

array_dtype == NPY_USHORT ? CV_16U :

array_dtype == NPY_SHORT ? CV_16S :

array_dtype == NPY_INT ? CV_32S :

array_dtype == NPY_INT32 ? CV_32S :

array_dtype == NPY_FLOAT ? CV_32F :

array_dtype == NPY_DOUBLE ? CV_64F : -1;

/*得到PyArray的维度信息,包含ndim,dims,strides,element size等,*
根据元素大小和strides的值的比较判断Py_array内存是否连续*/

#ifndef CV_MAX_DIM const int CV_MAX_DIM = 32; //最大接收channel为32的图像array#endif
int ndim = PyArray_NDIM(py_array_obj);

npy_intp* dims = PyArray_DIMS(py_array_obj);

npy_intp* strides = PyArray_STRIDES(py_array_obj);

size_t element_size = CV_ELEM_SIZE1(mat_dtype);

bool ismultichannel = ((ndim == 3) && (dims[2] <= CV_CN_MAX));
//判断PyArray是否为3维数组 // 1 如果最后一个维度(储存单个元素)的stride值与element_size不相等,表示内存不连续,需要值拷贝
// 2 如果排序较前的维度stride小于排序较后的维度,表示维度被transpose过,内存不连续,需要值拷贝 for(int i =
ndim-1;i>=0 && !needcopy;i--){

if ((i == ndim-1 && (size_t)strides[i] !=element_size) ||

(i

needcopy = true;

}

}

// 3 如果是3维度图像数组,则维度2的stride应该等于维度3的stride(即元素大小)*该维度的元素个数,否则内存不连续 if
(ismultichannel && strides[1]!=(npy_intp)element_size * dims[2]){

needcopy=true;

}

if (needcopy){

py_array_obj = PyArray_GETCONTIGUOUS(py_array_obj);

pyobj = (PyObject*) py_array_obj;

strides = PyArray_STRIDES(py_array_obj);

}

// 将dims,strides转换为mat接受的数据格式 int sizes[ndim];

size_t steps[ndim];

for (int i = 0;i

sizes[i] = (int) dims[i];

steps[i] = (size_t)strides[i];

}

//如果PyArray是空数组,则留一个element_size的内存,返回一个元素数量为1的数组 if (ndim == 0){

sizes[ndim] = 1;

steps[ndim] = element_size;

ndim ++;

}

//如果是3维度图像数组,则将最后一个维度转换为Vec数据类型。 if(ismultichannel){

ndim --; //去掉最后一个维度 mat_dtype = CV_MAKETYPE(mat_dtype,sizes[2]);

}

mat = cv::Mat(ndim,sizes,mat_dtype,PyArray_DATA(py_array_obj),steps);

return mat;

}

PyObject* mat2array(const cv::Mat &mat){

int type = mat.type();//获得mat的数据类型 int depth =
CV_MAT_DEPTH(type);//根据数据类型,得到元素类型,比如CV_8UC3会得到CV_8U int cn =
CV_MAT_CN(type);//mat最后一个维度的channel int dims = mat.dims;

const int f = (int)(sizeof(size_t) / 8);

int typenum =

depth == CV_8U ? NPY_UBYTE :

depth == CV_8S ? NPY_BYTE :

depth == CV_16U ? NPY_USHORT :

depth == CV_16S ? NPY_SHORT :

depth == CV_32S ? NPY_INT :

depth == CV_32F ? NPY_FLOAT :

depth == CV_64F ? NPY_DOUBLE :

f * NPY_ULONGLONG + (f ^ 1) * NPY_UINT;

cv::AutoBuffer sizes(dims + 1);

const int *matSizes = mat.size;

for (int i = 0 ; i < dims ; i++)

{

sizes[i] = matSizes[i];

}

if (cn > 1)

{

sizes[dims++] = cn;

}

PyObject *array = PyArray_SimpleNew(dims, sizes, typenum);

void *data = PyArray_DATA(array);

int len = mat.total() * mat.elemSize();

memcpy(data, mat.data, len);

return array;

}

最后是process.cpp:

//process.cpp// Created by liuky on 2020-01-02.//PyObject * process(PyObject*
obj){

if (not PyArray_Check(obj)){

PyErr_SetString(PyExc_TypeError,"input obj is not a array");

return nullptr;

}

else{

cv::Mat mat = array2mat(obj);//PyArrayObject转mat
cv::cvtColor(mat,mat,cv::COLOR_BGR2GRAY);//彩图转黑白 PyObject* res =
mat2array(mat);//mat转回PyArrayObject return res;

}

}

最后给出setup.py文件:

from distutils.core import setup,Extension

testModule = Extension("my_test",

["py_init.cpp",'conversion.cpp','process.cpp'],

include_dirs=[

"/usr/local/include/opencv4",

"/usr/include/python3.6m",

],

extra_compile_args=["-std=c++11"],

extra_link_args=[

"-lpython3.6m",

"-lopencv_core",

"-lopencv_video",

"-lopencv_videoio",

"-lopencv_imgproc",

"-lopencv_cudaimgproc",

"-lopencv_imgcodecs",

]

)

setup(name='my_test',version="0.1",ext_modules=[testModule])

通过Python3 setup.py install build_ext --inplace命令就可以将包安装进Python3中

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信