Exposing OpenCV-based C++ function with Mat/Numpy

2019-05-14 08:38发布

问题:

I've ran into a problem trying to expose dynamic C++ library functions, linked to OpenCV and using OpenCV's Mat datatype, to Python 2.7 with the use of Numpy ndarray.

I've come up with a solution similar to lightalchemist's solution here and I've also tried using boost::python and boost::numpy (also linked to Python 2.7) described in this SO question.

Right now I'm sticking with the former. I've got to a point where I can load the module in iPython, and I see a function I'm trying to port using the inspect module, I can even call it and it even executes. However, the problem occurs specifically when I try to use the NumpyAllocator (see lightalchemist's solution) class to convert the Mat object back to ndarray. Firstly, when I try to call pyopencv_from function from an outside C++ executable, and it uses the NumpyAllocator as it's coded, it segfaults at

PyEnsureGIL gil;

, with no message, every time. Lightalchemist's solution doesn't use it in pyopencv_to (EDIT: if the ndarray passed-in is already allocated), and it seems to work. However, the official OpenCV cv2.cpp does use the allocator in that as well, so if I try to use that the function can't even convert the input ndarray to Mat.

When I try to use the module from iPython, it sees the function. Again, it executes it correctly (prints progress to the console), but when it reaches the pyopencv_from, it segfaults and kills the iPython shell.

EDIT: I'm using exaclty the same source as lightalchemist, except I'm exposing a single function, the same way as the official OpenCV port does:

static PyMethodDef methods[] = {
{"findEdgesCGTG", (PyCFunction)pycvex_findEdgesCGTG, METH_KEYWORDS, "findEdgesCGTG(source) -> edgeGradient, edgeOrientations"},
    {NULL, NULL}
};
extern "C"
#if defined WIN32 || defined _WIN32
__declspec(dllexport)
#endif
void initcvex()
{
  import_array();
  PyObject* m = Py_InitModule(MODULESTR, methods);
  PyObject* d = PyModule_GetDict(m);
  opencv_error = PyErr_NewException((char*)MODULESTR".error", NULL, NULL);
  PyDict_SetItemString(d, "error", opencv_error);
}

Does anyone have any clue on how to resolve this conversion problem?

回答1:

The problem was not the

PyEnsureGIL gil;

statement. That only didn't work if I tried to use an external C++ function. The problem actually happened when I tried to pack two output values into one here:

return Py_BuildValue("(NN)", pyopencv_from(edgeGrad), pyopencv_from(edgeOri));

The temporary work-around was just to use the following statement:

return pyopencv_from(edgeGrad);

EDIT: Sorry, I messed up again. The Py_BuildValue works just fine. Just tested my function again, it works perfectly. I must have forgotten to put the return keyword last time I tested it, so it gave me "error return without exception set."

Here's the full function code (I hope it's useful for anyone porting OpenCV stuff to python)

static PyObject* pycvex_findEdgesCGTG(PyObject* , PyObject* args, PyObject* kw)
{
    PyObject* pyobj_source = NULL;
    Mat source;
    Mat edgeGrad;
    Mat edgeOri;
    const char* keywords[] = { "src", NULL };
    if( PyArg_ParseTupleAndKeywords(args, kw, "O:findEdgesCGTG", (char**)keywords, &pyobj_source) &&
        pyopencv_to(pyobj_source, source));
    {
        ERRWRAP2(findEdgesCGTG(source,edgeGrad,edgeOri));
        return Py_BuildValue("(NN)", pyopencv_from(edgeGrad), pyopencv_from(edgeOri));
    }
    return NULL;
}

UPDATE:

Here is the github repo with the open C++ code I wrote for exposing code using OpenCV's Mat class with as little pain as possible. This is for OpenCV 3.X.

For OpenCV 2.X you can use this code / example by Yati Sagade. If you'd like to expose functions using the opencv Mat class without worrying about explicit conversions, it's easy to adapt the Booost converters from my code with the conversion functions in Yati's code.