使用SWIG 4对接OpenCV 4.1.2:Python向C++传图像的类型适配问题
问题:OpenCV C++库通过opencv-swig生成Python绑定后参数类型不匹配及对象兼容性问题
初始错误场景
调用Python绑定模块时出现参数类型不匹配错误:
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/config/.local/lib/python3.10/site-packages/example.py", line 12422, in opencv_receiver return _libReconDMI.opencv_receiver(image) TypeError: in method 'opencv_receiver', argument 1 of type 'cv::Mat'
对应的C++实现:
#include "opencv2/core.hpp" void opencv_receiver(cv::Mat image){ (void)image; }
当前使用OpenCV 4.1.2 + SWIG 4.0.2,存在两个疑问:
- opencv-swig是否需要指定其他类型来接收图像?
- 有无兼容新版OpenCV的替代方案(例如用numpy.i接收numpy数组再转为cv::Mat)?
更新问题(2024/06/05)
模块生成的cv::Mat在Python中为<example.Mat; proxy of <Swig Object of type 'cv::Mat *' at 0x7f4b1b3e31e0> >,但无法用cv2的threshold函数处理,报错:
>>> ret2, reference_image_bin_1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) Traceback (most recent call last): File "<stdin>", line 1, in <module> cv2.error: OpenCV(4.9.0) :-1: error: (-5:Bad argument) in function 'threshold' > Overload resolution failed: > - src is not a numpy array, neither a scalar > - Expected Ptr<cv::UMat> for argument 'src'
改为cv::UMat也无法解决,说明类型与Python端OpenCV的对象不匹配。
解决方案
1. opencv-swig的类型适配问题
- 必须确保SWIG接口文件中正确引入opencv-swig的接口定义:添加
%include "opencv.i",同时配置好OpenCV头文件的搜索路径,让SWIG能正确解析cv::Mat类型。 - 注意:opencv-swig生成的
cv::Mat代理对象和Python cv2库生成的numpy数组/UMat是不同类型,直接传递cv2的对象到swig绑定的函数会触发类型不匹配错误,必须传递opencv-swig自己创建的cv::Mat对象。
2. 兼容新版OpenCV的替代方案:使用numpy.i实现numpy数组与cv::Mat互转
这是更通用的跨版本方案,能直接让Python端的cv2 numpy数组和C++的cv::Mat无缝交互:
步骤1:配置SWIG接口文件
在.i接口文件中引入numpy支持,并初始化numpy:
%include "numpy.i" %init %{ import_array(); %}
步骤2:编写类型映射(typemap)
添加numpy array转cv::Mat的输入类型映射,让SWIG自动完成转换:
%typemap(in) cv::Mat { if (!PyArray_Check($input)) { PyErr_SetString(PyExc_TypeError, "参数必须是numpy数组"); return NULL; } PyArrayObject* arr = (PyArrayObject*)$input; // 匹配numpy dtype和OpenCV的Mat类型 int depth; switch(PyArray_TYPE(arr)) { case NPY_UBYTE: depth = CV_8U; break; case NPY_INT16: depth = CV_16S; break; case NPY_FLOAT32: depth = CV_32F; break; case NPY_FLOAT64: depth = CV_64F; break; default: PyErr_SetString(PyExc_TypeError, "不支持的numpy数据类型"); return NULL; } int channels = PyArray_NDIM(arr) == 3 ? PyArray_SHAPE(arr)[2] : 1; int cv_type = CV_MAKETYPE(depth, channels); // 直接复用numpy数组的内存创建cv::Mat(无拷贝,需注意内存生命周期) $1 = cv::Mat( PyArray_SHAPE(arr)[0], PyArray_SHAPE(arr)[1], cv_type, PyArray_DATA(arr), PyArray_STRIDES(arr)[0] ); }
如果需要从C++返回cv::Mat到Python并自动转为numpy数组,可添加输出类型映射:
%typemap(out) cv::Mat { int depth = $1.depth(); int npy_type; switch(depth) { case CV_8U: npy_type = NPY_UBYTE; break; case CV_16S: npy_type = NPY_INT16; break; case CV_32F: npy_type = NPY_FLOAT32; break; case CV_64F: npy_type = NPY_FLOAT64; break; default: PyErr_SetString(PyExc_TypeError, "不支持的OpenCV Mat类型"); return NULL; } int ndim = $1.channels() > 1 ? 3 : 2; npy_intp dims[3] = { $1.rows, $1.cols, $1.channels() }; PyObject* arr = PyArray_SimpleNewFromData( ndim, dims, npy_type, $1.data ); // 设置numpy数组的所有权,避免C++释放内存后Python访问出错 PyArray_SetBaseObject((PyArrayObject*)arr, NULL); $result = arr; }
效果
修改后,Python端可以直接传递cv2读取/生成的numpy数组到C函数,同时C返回的cv::Mat会自动转为numpy数组,可直接用cv2的函数处理,解决类型不兼容问题。
内容的提问来源于stack exchange,提问作者user24849449
相关产品推荐
相关产品推荐

