【问题标题】:What is the easiest way to convert ndarray into cv::Mat?将 ndarray 转换为 cv::Mat 的最简单方法是什么?
【发布时间】:2014-05-09 07:37:12
【问题描述】:

我正在尝试为使用来自 OpenCV 的 cv::Mat 类的 C++ 库创建 Python/Cython 包装器。在官方 Python 包装器中,所有函数都采用 NumPy 的 ndarray 而不是 cv::Mat,这非常方便。但是在我自己的包装器中,我该如何进行这种转换?也就是说,我如何创建 cv::Mat 来自 np.ndarray

【问题讨论】:

    标签: python c++ opencv numpy cython


    【解决方案1】:

    我猜你可以直接使用或从the converter from the official python wrapper 中获取一些逻辑。这个模块的文档不多,但也许包装器生成器的输出有助于理解如何使用它。

    【讨论】:

    • 感谢您的回答,抱歉回复晚了。我花了几天时间尝试合并这个转换器,但不幸的是,它与其他文件密切相关,而这些文件又依赖于整个 OpenCV 基础架构,包括项目布局、生成的文件等。我会尝试更多方法,但如果您知道替代转换器,我会很高兴看到它们。谢谢。
    【解决方案2】:

    事实证明,没有简单的方法可以将(任何)np.ndarray 转换为相应的cv::Mat。基本上,一个人只需要做两件事:

    1. 创建相应大小和类型的空cv::Mat
    2. 复制数据。

    然而,魔鬼隐藏在细节中。 ndarrayMat 都可能包含完全不同的数据格式。例如,NumPy 数组中的数据可能是 C 或 Fortran 顺序,数组对象可能拥有其数据或保留另一个数组的视图,通道可能以不同的顺序排列(NumPy 中的 RGB 与 OpenCV 中的 BGR)等。

    因此,我没有尝试解决一般问题,而是决定使用适合我需要的简单代码,并且任何感兴趣的人都可以轻松地对其进行修改。

    Cython 中的以下代码使用默认字节顺序的 float32/CV_32FC1 图像:

    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)
    

    要在 Cython 中使用此代码,还需要声明一些类型和常量,例如像这样:

    import numpy as np
    # Cython makes it simple to import NumPy
    cimport numpy as np
    
    
    # OpenCV's 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
    

    相反的转换(从cv::Matnp.ndarray)可以通过类似的方式实现。

    奖励:blog post 也很好地描述了 RGB/BGR 图像的相同类型的转换。

    【讨论】:

      【解决方案3】:

      按照 kyamagu 的建议,您可以使用 OpenCV 的官方 python 包装代码,尤其是 pyopencv_topyopencv_from

      我一直在努力处理所有依赖项和生成的头文件。尽管如此,可以通过“清理”cv2.cpplightalchemist did here 来降低复杂性,以便只保留必要的内容。您需要根据您的需要和您正在使用的 OpenCV 版本对其进行调整,但它与我使用的代码基本相同。

      #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);
      }
      

      清理完cv2.cpp 文件后,这里有一些负责转换的 Cython 代码。注意import_array() 函数的定义和调用(它是在包含在cv2.cpp 某处的标头中定义的NumPy 函数),这是定义pyopencv_to 使用的一些宏所必需的,如果你不叫它你将得到分段错误lightalchemist pointed out

      from cpython.ref cimport PyObject
      
      # Declares OpenCV's cv::Mat class
      cdef extern from "opencv2/core/core.hpp":
          cdef cppclass Mat:
              pass
      
      # Declares the official wrapper conversion functions + NumPy's 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)
      

      注意:由于import_array 宏中有一个奇怪的返回语句,不知何故我在 Fedora 20 上的 NumPy 1.8.0 出现错误,我不得不手动删除它以使其工作,但我找不到这个NumPy 1.8.0 GitHub源码中的return语句

      【讨论】:

      • 我现在无法自己测试,但它看起来比我使用的方法更好,所以我不经验证就接受了。
      • github.com/numpy/numpy/blob/…如果你使用Python3,宏返回一个NULL值。使用 Python 3 时,您可以修改 init 函数以返回 void 指针而不是任何内容。您可以查看我的答案以获得 Python3/OpenCV3 兼容版本。
      【解决方案4】:

      如果有帮助,我写了一个包装器来做这个。这是一个方便的库,它注册了一个 boost::python 转换器,以在 OpenCV 流行的 cv::Mat 数据类型和 NumPy 流行的 np.array() 数据类型之间进行隐式转换。这使开发人员可以相对轻松地在使用 NumPy 编写的 OpenCV C++ API 和 Python API 之间来回切换,从而避免编写额外的包装器来处理传递或返回的 PyObjects。

      看看: https://github.com/spillai/numpy-opencv-converter

      【讨论】:

        【解决方案5】:

        根据 tlorieul 的回答,这是我用于构建 Python/C++ 模块的代码:

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

        已经用 Python3 和 OpenCV3 测试过。

        【讨论】:

        • 链接无法访问,能否更新一个新链接?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-05
        • 2012-01-07
        • 2020-01-16
        相关资源
        最近更新 更多