What is the easiest way to convert ndarray to cv :: Mat?

I am trying to create a Python / Cython wrapper for a C ++ library that uses the cv::Mat class from OpenCV. In the official Python shell, all functions accept NumPy ndarray instead of cv::Mat , which is pretty convenient. But in my own shell, how do I do such a conversion? That is, how do I create cv::Mat from np.ndarray ?

+4
source share
5 answers

As kyamagu suggested, you can use the official OpenCV shell code, especially pyopencv_to and pyopencv_from .

I struggled in the same way as with all dependencies and generated header files. However, you can reduce the complexity of this by β€œcleaning” cv2.cpp as the light chemist did here to preserve only what is needed. You will need to adapt it to your needs and the version of OpenCV that you use, but its basically the same code that I used.

 #include <Python.h> #include "numpy/ndarrayobject.h" #include "opencv2/core/core.hpp" static PyObject* opencv_error = 0; static int failmsg(const char *fmt, ...) { char str[1000]; va_list ap; va_start(ap, fmt); vsnprintf(str, sizeof(str), fmt, ap); va_end(ap); PyErr_SetString(PyExc_TypeError, str); return 0; } class PyAllowThreads { public: PyAllowThreads() : _state(PyEval_SaveThread()) {} ~PyAllowThreads() { PyEval_RestoreThread(_state); } private: PyThreadState* _state; }; class PyEnsureGIL { public: PyEnsureGIL() : _state(PyGILState_Ensure()) {} ~PyEnsureGIL() { PyGILState_Release(_state); } private: PyGILState_STATE _state; }; #define ERRWRAP2(expr) \ try \ { \ PyAllowThreads allowThreads; \ expr; \ } \ catch (const cv::Exception &e) \ { \ PyErr_SetString(opencv_error, e.what()); \ return 0; \ } using namespace cv; static PyObject* failmsgp(const char *fmt, ...) { char str[1000]; va_list ap; va_start(ap, fmt); vsnprintf(str, sizeof(str), fmt, ap); va_end(ap); PyErr_SetString(PyExc_TypeError, str); return 0; } static size_t REFCOUNT_OFFSET = (size_t)&(((PyObject*)0)->ob_refcnt) + (0x12345678 != *(const size_t*)"\x78\x56\x34\x12\0\0\0\0\0")*sizeof(int); static inline PyObject* pyObjectFromRefcount(const int* refcount) { return (PyObject*)((size_t)refcount - REFCOUNT_OFFSET); } static inline int* refcountFromPyObject(const PyObject* obj) { return (int*)((size_t)obj + REFCOUNT_OFFSET); } class NumpyAllocator : public MatAllocator { public: NumpyAllocator() {} ~NumpyAllocator() {} void allocate(int dims, const int* sizes, int type, int*& refcount, uchar*& datastart, uchar*& data, size_t* step) { PyEnsureGIL gil; int depth = CV_MAT_DEPTH(type); int cn = CV_MAT_CN(type); 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; int i; npy_intp _sizes[CV_MAX_DIM+1]; for( i = 0; i < dims; i++ ) _sizes[i] = sizes[i]; if( cn > 1 ) { /*if( _sizes[dims-1] == 1 ) _sizes[dims-1] = cn; else*/ _sizes[dims++] = cn; } PyObject* o = PyArray_SimpleNew(dims, _sizes, typenum); if(!o) CV_Error_(CV_StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims)); refcount = refcountFromPyObject(o); npy_intp* _strides = PyArray_STRIDES(o); for( i = 0; i < dims - (cn > 1); i++ ) step[i] = (size_t)_strides[i]; datastart = data = (uchar*)PyArray_DATA(o); } void deallocate(int* refcount, uchar*, uchar*) { PyEnsureGIL gil; if( !refcount ) return; PyObject* o = pyObjectFromRefcount(refcount); Py_INCREF(o); Py_DECREF(o); } }; NumpyAllocator g_numpyAllocator; enum { ARG_NONE = 0, ARG_MAT = 1, ARG_SCALAR = 2 }; static int pyopencv_to(const PyObject* o, Mat& m, const char* name = "<unknown>", bool allowND=true) { if(!o || o == Py_None) { if( !m.data ) m.allocator = &g_numpyAllocator; return true; } if( PyInt_Check(o) ) { double v[] = {PyInt_AsLong((PyObject*)o), 0., 0., 0.}; m = Mat(4, 1, CV_64F, v).clone(); return true; } if( PyFloat_Check(o) ) { double v[] = {PyFloat_AsDouble((PyObject*)o), 0., 0., 0.}; m = Mat(4, 1, CV_64F, v).clone(); return true; } if( PyTuple_Check(o) ) { int i, sz = (int)PyTuple_Size((PyObject*)o); m = Mat(sz, 1, CV_64F); for( i = 0; i < sz; i++ ) { PyObject* oi = PyTuple_GET_ITEM(o, i); if( PyInt_Check(oi) ) m.at<double>(i) = (double)PyInt_AsLong(oi); else if( PyFloat_Check(oi) ) m.at<double>(i) = (double)PyFloat_AsDouble(oi); else { failmsg("%s is not a numerical tuple", name); m.release(); return false; } } return true; } if( !PyArray_Check(o) ) { failmsg("%s is not a numpy array, neither a scalar", name); return false; } bool needcopy = false, needcast = false; int typenum = PyArray_TYPE(o), new_typenum = typenum; int type = typenum == NPY_UBYTE ? CV_8U : typenum == NPY_BYTE ? CV_8S : typenum == NPY_USHORT ? CV_16U : typenum == NPY_SHORT ? CV_16S : typenum == NPY_INT ? CV_32S : typenum == NPY_INT32 ? CV_32S : typenum == NPY_FLOAT ? CV_32F : typenum == NPY_DOUBLE ? CV_64F : -1; if( type < 0 ) { if( typenum == NPY_INT64 || typenum == NPY_UINT64 || type == NPY_LONG ) { needcopy = needcast = true; new_typenum = NPY_INT; type = CV_32S; } else { failmsg("%s data type = %d is not supported", name, typenum); return false; } } int ndims = PyArray_NDIM(o); if(ndims >= CV_MAX_DIM) { failmsg("%s dimensionality (=%d) is too high", name, ndims); return false; } int size[CV_MAX_DIM+1]; size_t step[CV_MAX_DIM+1], elemsize = CV_ELEM_SIZE1(type); const npy_intp* _sizes = PyArray_DIMS(o); const npy_intp* _strides = PyArray_STRIDES(o); bool ismultichannel = ndims == 3 && _sizes[2] <= CV_CN_MAX; for( int i = ndims-1; i >= 0 && !needcopy; i-- ) { // these checks handle cases of // a) multi-dimensional (ndims > 2) arrays, as well as simpler 1- and 2-dimensional cases // b) transposed arrays, where _strides[] elements go in non-descending order // c) flipped arrays, where some of _strides[] elements are negative if( (i == ndims-1 && (size_t)_strides[i] != elemsize) || (i < ndims-1 && _strides[i] < _strides[i+1]) ) needcopy = true; } if( ismultichannel && _strides[1] != (npy_intp)elemsize*_sizes[2] ) needcopy = true; if (needcopy) { if( needcast ) o = (PyObject*)PyArray_Cast((PyArrayObject*)o, new_typenum); else o = (PyObject*)PyArray_GETCONTIGUOUS((PyArrayObject*)o); _strides = PyArray_STRIDES(o); } for(int i = 0; i < ndims; i++) { size[i] = (int)_sizes[i]; step[i] = (size_t)_strides[i]; } // handle degenerate case if( ndims == 0) { size[ndims] = 1; step[ndims] = elemsize; ndims++; } if( ismultichannel ) { ndims--; type |= CV_MAKETYPE(0, size[2]); } if( ndims > 2 && !allowND ) { failmsg("%s has more than 2 dimensions", name); return false; } m = Mat(ndims, size, type, PyArray_DATA(o), step); if( m.data ) { m.refcount = refcountFromPyObject(o); if (!needcopy) { m.addref(); // protect the original numpy array from deallocation // (since Mat destructor will decrement the reference counter) } }; m.allocator = &g_numpyAllocator; return true; } static PyObject* pyopencv_from(const Mat& m) { if( !m.data ) Py_RETURN_NONE; Mat temp, *p = (Mat*)&m; if(!p->refcount || p->allocator != &g_numpyAllocator) { temp.allocator = &g_numpyAllocator; ERRWRAP2(m.copyTo(temp)); p = &temp; } p->addref(); return pyObjectFromRefcount(p->refcount); } 

Once you have the cleaned cv2.cpp file, here is some kind of Cython code that takes care of the conversion. Pay attention to the definition and call of the import_array() function (this is the NumPy function defined in the header included somewhere in cv2.cpp ), this is necessary to determine some macros used by pyopencv_to , if you do not name it, they will receive segmentation errors, as noted the chemist .

 from cpython.ref cimport PyObject # Declares OpenCV cv::Mat class cdef extern from "opencv2/core/core.hpp": cdef cppclass Mat: pass # Declares the official wrapper conversion functions + NumPy import_array() function cdef extern from "cv2.cpp": void import_array() PyObject* pyopencv_from(const _Mat&) int pyopencv_to(PyObject*, _Mat&) # Function to be called at initialization cdef void init(): import_array() # Python to C++ conversion cdef Mat nparrayToMat(object array): cdef Mat mat cdef PyObject* pyobject = <PyObject*> array pyopencv_to(pyobject, mat) return <Mat> mat # C++ to Python conversion cdef object matToNparray(Mat mat): return <object> pyopencv_from(mat) 

Note: somehow I got an error with NumPy 1.8.0 on Fedora 20 when compiling due to the strange return statement in the import_array macro, I had to manually delete it to work, but I can not find this return in the NumPy 1.8 source code .0 GitHub

+3
source

I think you can directly use or take some logic from the converter from the official python shell . The documentation for this module is not enough, but perhaps the output of the cover generator helps to understand how to use it.

+2
source

Turns out there is no easy way to convert (any) np.ndarray to the corresponding cv::Mat . Basically, you need to do only 2 things:

  • Create an empty cv::Mat appropriate size and type.
  • Copy data.

However, the devil is in the details. Both ndarray and Mat can contain quite different data formats. For example, data in NumPy arrays can be in C or in Fortran order, an array object can own its data or save a view in another array, channels can go in a different order (RGB in NumPy versus BGR in OpenCV), etc.

Therefore, instead of trying to solve a common problem, I decided to stay with simple code that meets my needs and can be easily changed by anyone who is interested.

The following code in Cython works with float32 / CV_32FC1 images with default byte order:

 cdef void array2mat(np.ndarray arr, Mat& mat): cdef int r = arr.shape[0] cdef int c = arr.shape[1] cdef int mat_type = CV_32FC1 # or CV_64FC1, or CV_8UC3, or whatever mat.create(r, c, mat_type) cdef unsigned int px_size = 4 # 8 for single-channel double image or # 1*3 for three-channel uint8 image memcpy(mat.data, arr.data, r*c*px_size) 

To use this code in Cython, you must also declare some types and constants, for example. eg:

 import numpy as np # Cython makes it simple to import NumPy cimport numpy as np # OpenCV matrix class cdef extern from "opencv2/opencv.hpp" namespace "cv": cdef cppclass Mat: Mat() except + Mat(int, int, int, void*) except + void create(int, int, int) void* data int type() const int cols int rows int channels() Mat clone() const # some OpenCV matrix types cdef extern from "opencv2/opencv.hpp": cdef int CV_8UC3 cdef int CV_8UC1 cdef int CV_32FC1 cdef int CV_64FC1 

The opposite conversion (from cv::Mat to np.ndarray ) can be achieved in a similar way.

Bonus: there is also a nice blog post describing the same RGB / BGR image conversion.

+2
source

Based on tlorieul's answer, here is the code I used to build the Python / C ++ module:

https://gist.github.com/des0ps/88f1332319867a678a74bdbc0e7401c2

This has been tested with Python3 and OpenCV3.

+2
source

If that helps, I wrote a wrapper that does just that. This is a convenience library that registers the boost :: python converter for implicit conversion between the Cv :: Mat public directory and the NumPy type of the popular type np.array (). This allows the developer to navigate between the Open CV C ++ API and the Python API written using NumPy with relative ease, avoiding the need to write additional wrappers that handle PyObjects that are passed or returned.

Take a look: https://github.com/spillai/numpy-opencv-converter

+1
source

Source: https://habr.com/ru/post/1263501/


All Articles