如何通过pybind11实现cv::cuda::GpuMat与cv2.cuda_GpuMat无拷贝映射?
我正在用C++通过pybind11从相机接收图像到cv::cuda::GpuMat结构中,希望在Python里不用拷贝数据就能把它转成cv2.cuda_GpuMat结构使用。有没有办法把cv::cuda::GpuMat的属性映射到对应的cv2.cuda_GpuMat属性,让支持CUDA的Python版OpenCV可以直接用?
最小化测试代码(C++)
#include <pybind11/pybind11.h> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/cudaimgproc.hpp> #include <opencv2/cudaarithm.hpp> #include <cuda_runtime.h> #include <iostream> #include <string> #include <vector> namespace py=pybind11; using namespace cv; using namespace std; struct miniGpuMat{ miniGpuMat() { cout << "construct miniGPUmat" << endl;} ~miniGpuMat() { cout << "destruction miniGPUmat" << endl;} void *imageGpu; void *imageBp; cuda::GpuMat gpu_mat; void getImage(){ cudaHostGetDevicePointer(&imageGpu, imageBp, 0); // Create GpuMat from the device pointer gpu_mat = cuda::GpuMat(16, 16, CV_8UC1, imageGpu); } }; PYBIND11_MODULE(pgxcam, m) { py::class_<miniGpuMat>(m, "miniGpuMat") .def(py::init<>()) .def_readwrite("imageGpu", &miniGpuMat::imageGpu) .def_readwrite("imageBp", &miniGpuMat::imageBp) .def_readwrite("gpu_mat", &miniGpuMat::gpu_mat) ;}
Python测试代码
import cv2 import pgxcam as xc testMini = xc.miniGpuMat() dir(testMini) type(testMini.gpu_mat) del(testMini)
运行报错
>>> construct miniGPUmat >>> ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'gpu_mat', 'imageBp', 'imageGpu'] >>> TypeError: Unregistered type : cv::cuda::GpuMat The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<string>", line 19, in __PYTHON_EL_eval File "<...>/src/mini.py", line 1, in <module> TypeError: Unable to convert function return value to a Python type! The signature was (self: pgxcam.miniGpuMat) -> cv::cuda::GpuMat >>> destruction miniGPUmat
已调研内容
我本质上想实现类似cv2.cuda_GpuMat(cv::cuda::GpuMat)的效果。在OpenCV的cuda.hpp里有包装CV_WRAP GpuMat(const GpuMat& m);,其实现是:
inline GpuMat::GpuMat(const GpuMat& m) : flags(m.flags), rows(m.rows), cols(m.cols), step(m.step), data(m.data), refcount(m.refcount), datastart(m.datastart), dataend(m.dataend), allocator(m.allocator) { if (refcount) CV_XADD(refcount, 1); }
这个实现正是我需要的——只拷贝属性不拷贝数据,同时增加引用计数。
另外在pyopencv_cuda.hpp里还有CV_PY_FROM_CLASS(cuda::GpuMat);、CV_PY_TO_CLASS_PTR(cuda::GpuMat);的包装,定义如下:
#define CV_PY_FROM_CLASS(TYPE) \ template<> \ PyObject* pyopencv_from(const TYPE& src) \ { \ Ptr<TYPE> ptr(new TYPE()); \ \ *ptr = src; \ return pyopencv_from(ptr); \ }
想请教两个问题:
CV_PY_FROM_CLASS(cuda::GpuMat)是否会创建cv2.cuda_GpuMat?- 能不能构造或修改持有
cv2.cuda_GpuMat的PyObject的cv::cuda::GpuMat属性,从而利用GpuMat(const GpuMat& m)拷贝构造函数?
我已经查过OpenCV论坛的一个未解决类似问题(2020年)、cv::cuda::GpuMat官方文档、Python C API及数据模型文档。
核心思路
要实现零拷贝将C++的cv::cuda::GpuMat转为Python的cv2.cuda_GpuMat,关键是复用OpenCV自身的Python绑定逻辑,而非让pybind11处理未注册的类型。
具体实现步骤
复用OpenCV的
pyopencv_from转换函数CV_PY_FROM_CLASS(cuda::GpuMat)宏确实会生成转换cv::cuda::GpuMat到Pythoncv2.cuda_GpuMat的代码,但需要在pybind11模块中主动调用该转换逻辑,而非直接暴露gpu_mat成员。修改C++代码,给
miniGpuMat添加返回Python可识别对象的方法:struct miniGpuMat{ // ... 原有代码 ... py::object get_cuda_mat() { // 调用OpenCV的转换函数,生成cv2.cuda_GpuMat对应的Python对象 PyObject* obj = pyopencv_from(gpu_mat); return py::reinterpret_borrow<py::object>(obj); } }; PYBIND11_MODULE(pgxcam, m) { py::class_<miniGpuMat>(m, "miniGpuMat") .def(py::init<>()) .def_readwrite("imageGpu", &miniGpuMat::imageGpu) .def_readwrite("imageBp", &miniGpuMat::imageBp) .def("get_cuda_mat", &miniGpuMat::get_cuda_mat) // 新增方法 ; }编译时链接OpenCV Python绑定库
编译pybind11模块时,必须链接OpenCV的Python绑定库(如opencv_python3),确保能找到pyopencv_from的实现。CMake配置示例:find_package(OpenCV REQUIRED COMPONENTS core cudaimgproc cudaarithm python3) target_link_libraries(pgxcam PRIVATE ${OpenCV_LIBS} pybind11::module)Python端使用
修改后的Python代码可直接获取零拷贝的cv2.cuda_GpuMat:import cv2 import pgxcam as xc testMini = xc.miniGpuMat() testMini.getImage() # 初始化gpu_mat cuda_mat = testMini.get_cuda_mat() print(type(cuda_mat)) # 输出 <class 'cv2.cuda_GpuMat'> # 直接用OpenCV CUDA函数处理 processed = cv2.cuda.cvtColor(cuda_mat, cv2.COLOR_BGR2GRAY) del(testMini)
关键说明
pyopencv_from会创建cv2.cuda_GpuMat对象,通过引用计数共享底层CUDA内存,无数据拷贝。- 不能直接让pybind11暴露
cv::cuda::GpuMat成员,因为pybind11默认未注册该类型的转换规则,OpenCV自身维护了一套绑定逻辑。 - 生命周期自动管理:Python的
cv2.cuda_GpuMat会持有底层cv::cuda::GpuMat的引用,只要Python对象存在,C++端内存就不会被释放,避免悬空指针。
内容的提问来源于stack exchange,提问作者randy37773

