4

I have written a C++ code using opencv, I converted the C++ code as a "DLL" and I need to call a method from this dll in python which receives cv::Mat as datatype. But I am getting error here. The below are the samples of C++ code and python code.

On googling I found we need to use Boost library but am not sure how to convert Python mat to C++ cv::Mat and how to make interface between them.

C++ dll code:

DLLEXPORT int FromPython ( cv :: Mat InputSrc) {

    imshow ( "FromPython", InputSrc );

        return 0;
}

Python Code

import cv2 as cv
from ctypes import cdll

cap = cv.VideoCapture(0)

while(1):
    ret, frame = cap.read()

    cv.imshow('frame',frame)
    mydll = cdll.LoadLibrary('C:\Users\Documents\FromPythonDLL.dll')
    i = mydll.FromPython(frame)
    print(i)

    k = cv.waitKey(1) & 0xff
    if k == 27:
        break

cap.release()
cv.destroyAllWindows()
user2727765
  • 616
  • 1
  • 12
  • 28

1 Answers1

4

You can have a look at the OpenCV Python wrapper. In the OpenCV folder in modules/python/src2/cv2.cpp (depending on the version, I use OpenCV 2.4) there are some functions called pyopencv_to used by the OpenCV Python wrapper. One of those is used to convert PyObject to cv::Mat. Your "FromPython" function needs to get PyObject as input. I personally use boost::python::object to pass the numpy arrays returned by the Python OpenCV functions to the C++ function/class. You should end up having something like this in C++:

///PythonToOCV.h

#ifndef __PYTHONTOOCV_H_INCLUDED__
#define __PYTHONTOOCV_H_INCLUDED__

#include <iostream>
#include <Python.h>
#include <boost/python.hpp>
#include "numpy/ndarrayobject.h"
#include "opencv2/core/core.hpp"

/////////////////////////////////////////////////////////////////////////////
/// \brief Import Numpy array. Necessary to avoid PyArray_Check() to crash
void doImport( );

int failmsg( const char *fmt, ... );

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 cv::MatAllocator
{
public:
NumpyAllocator( ) { }
~NumpyAllocator( ) { }

void allocate( int dims, const int* sizes, int type, int*& refcount,
uchar*& datastart, uchar*& data, size_t* step );

void deallocate( int* refcount, uchar* datastart, uchar* data );
};


/////////////////////////////////////////////////////////////////////////////
/// \brief Convert a numpy array to a cv::Mat. This is used to import images
/// from Python.
/// This function is extracted from opencv/modules/python/src2/cv2.cpp
/// in OpenCV 2.4
int pyopencv_to( const PyObject* o, cv::Mat& m, const char* name = "<unknown>", bool allowND=true );
#endif //__PYTHONTOOCV_H_INCLUDED__

///PythonToOCV.cpp

#include "PythonToOpenCV.h"

void doImport( )
{
    import_array( );
}

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

void NumpyAllocator::allocate( int dims, const int* sizes, int type, int*& refcount, uchar*& datastart, uchar*& data, size_t* step )
{
    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 NumpyAllocator::deallocate( int* refcount, uchar* datastart, uchar* data )
{
    if( !refcount )
       return;
    PyObject* o = pyObjectFromRefcount(refcount);
    Py_INCREF(o);
    Py_DECREF(o);
}

// Declare the object
NumpyAllocator g_numpyAllocator;

int pyopencv_to(const PyObject* o, cv::Mat& m, const char* name, bool allowND )
{
    // to avoid PyArray_Check() to crash even with valid array
    doImport( );

    if(!o || o == Py_None)
    {
        if( !m.data )
            m.allocator = &g_numpyAllocator;
        return true;
    }

    if( !PyArray_Check(o) )
    {
        failmsg("%s is not a numpy array", name);
        return false;
    }

    // NPY_LONG (64 bit) is converted to CV_32S (32 bit)
    int typenum = PyArray_TYPE(o);
    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 || typenum == NPY_LONG ? CV_32S :
        typenum == NPY_FLOAT ? CV_32F :
        typenum == NPY_DOUBLE ? CV_64F : -1;

    if( type < 0 )
    {
        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 transposed = false;

    for(int i = 0; i < ndims; i++)
    {
        size[i] = (int)_sizes[i];
        step[i] = (size_t)_strides[i];
    }

    if( ndims == 0 || step[ndims-1] > elemsize ) {
        size[ndims] = 1;
        step[ndims] = elemsize;
        ndims++;
    }

    if( ndims >= 2 && step[0] < step[1] )
    {
        std::swap(size[0], size[1]);
        std::swap(step[0], step[1]);
        transposed = true;
    }

    if( ndims == 3 && size[2] <= CV_CN_MAX && step[1] == elemsize*size[2] )
    {
        ndims--;
        type |= CV_MAKETYPE(0, size[2]);
    }

    if( ndims > 2 && !allowND )
    {
        failmsg("%s has more than 2 dimensions", name);
        return false;
    }

    m = cv::Mat(ndims, size, type, PyArray_DATA(o), step);

    if( m.data )
    {
        m.refcount = refcountFromPyObject(o);
        m.addref(); // protect the original numpy array from deallocation
        // (since Mat destructor will decrement the reference counter)
    };
    m.allocator = &g_numpyAllocator;

    if( transposed )
    {
        cv::Mat tmp;
        tmp.allocator = &g_numpyAllocator;
        transpose(m, tmp);
        m = tmp;
    }
    return true;
}

Then the function where you can access to cv::Mat will look like:

/// fromPython.h

#ifndef __FROMPYTHON_H_INCLUDED__
#define __FROMPYTHON_H_INCLUDED__

#include "PythonToOCV.h"
#include <boost/python.hpp>

int fromPython( boost::python::object &frame );
#endif //__FROMPYTHON_H_INCLUDED__



/// fromPython.cpp

#include "fromPython.h"

int fromPython( boost::python::object &frame )
{
     cv::Mat image;
     // this is the function from modules/python/src2/cv2.cpp (the third parameter might be ArgInfo in later OpenCV versions)
     pyopencv_to( frame.ptr( ), image, "info", true );

     ///
     ///  HERE code using cv::Mat image          
     ///

     return 1;
} 

This function to be accessible from Python needs to be wrapped in a BOOST_PYTHON_MODULE. Soemthing like:

#include "fromPython.h"
using namespace boost::python; 

/// This function needs to be included to pass PyObjects as numpy array ( http://mail.python.org/pipermail/cplusplus-sig/2006-September/011021.html )
void* extract_pyarray( PyObject* x )
{
    return PyObject_TypeCheck( x, &PyArray_Type ) ? x : 0;
}

BOOST_PYTHON_MODULE( myWrapper )
{
     // This function needs to be included to pass PyObjects as numpy array ( http://mail.python.org/pipermail/cplusplus-sig/2006-September/011021.html )
     boost::python::converter::registry::insert( &extract_pyarray, type_id<PyArrayObject>( ) ); 
     def fromPython( "fromPython", &fromPython );
} 

Then in Python you can call your function from the Python module created by the Boost wrapper. I am using Linux so I compile the code above to get an dynamic library (.so). I am not sure how different it is in Windows. I can access to the module from the dynamic library as:

import myWrapper
import cv2 

def myFunct():
    cap = cv2.VideoCapture(0)
    while(1):
        ret,frame = cap.read()
        myWrapper.fromPython(frame)

You can probably avoid the use of Boost but I haven't tried other ways and I found Boost convenient to wrap C++ classes. NOTE: I haven't tested this code as I stripped it from a project but I hope it can still be useful.

Algold
  • 953
  • 8
  • 10
  • Thanks for the answer.I will wait for some other hours for people to view and accept as an answer. – user2727765 Oct 02 '13 at 13:55
  • I followed this way mentioned in this link http://stackoverflow.com/questions/12008536/boost-python-wrapper-and-opencv-argument-error-with-cvmat but am getting error which I have mentioned in the same link – user2727765 Oct 03 '13 at 10:52
  • Sorry I have tried your answer but couldnt understand, how should I call "frompython" method from python? And what is the second part of the code in your answer. I followed the above mentioned link as it seems to be easy. – user2727765 Oct 03 '13 at 10:57
  • You import the boost Python module from the dynamic library and then you call the function. I edit my answer with the Python code – Algold Oct 03 '13 at 11:07
  • Thanks a lot for the edit. "pyopencv_to( frame.ptr( ), image, "info", true );" do i need to include the definition of this method in my code? Looks like lotta dependencies for this method? – user2727765 Oct 03 '13 at 11:14
  • I basically created a file PythonToOpenCV where I copied the relevant functions from cv2.cpp to use pyopencv_to (like the class NumpyAllocator and PyObjectFromRefcount). Then I include this in the boost python wrapper file and I build it to create the dynamic library. – Algold Oct 03 '13 at 11:23
  • Thanks a lot. I will give a try. The method followed in this link http://stackoverflow.com/questions/12008536/boost-python-wrapper-and-opencv-argument-error-with-cvmat will also work rit? I am have way done with this and stuck with the last step. – user2727765 Oct 03 '13 at 12:29
  • I am getting this error, when I include "BOOST_PYTHON_MODULE" method, What is problem?main.cpp:382:10: error: expected ';' before 'fromPython' main.cpp:382:46: error: statement cannot resolve address of overloaded function – user2727765 Oct 03 '13 at 14:12
  • I cannot answer you on the cause of this issue without seeing all the code or at least at which instruction the error corresponds to. Maybe you are missing an include. Did you include "numpy/ndarrayobject.h"? – Algold Oct 03 '13 at 15:51
  • Regarding the other solution you from the other post, it looks like it can convert only gray (single channel) images. You can try to convert your image to gray just to check this is the problem. – Algold Oct 03 '13 at 16:01
  • I edit my initial answer with more code. Unfortunately I don't have time to test the code for building errors but it should work with limited effort. – Algold Oct 04 '13 at 09:09
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/38601/discussion-between-user2727765-and-algold) – user2727765 Oct 04 '13 at 09:50
  • I have tried the solution. There is no problem in C++ part. But in pyhton side its throwing error the same error mydll.Track(frame) ArgumentError: argument 1: : Don't know how to convert parameter 1 – user2727765 Oct 04 '13 at 09:57
  • I have posted a vice-versa of the same here (return a C++ structure to Python) http://stackoverflow.com/questions/19185574/return-a-structure-to-python-from-c-using-boost-python if possible can you help? – user2727765 Oct 04 '13 at 18:20