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中