c++ python boost opencv boost-python

Escribiendo enlaces de Python para código C++ que usan OpenCV



boost boost-python (3)

Espero que esto ayude a las personas que buscan una manera rápida y fácil.

Aquí está el repositorio de github con el código abierto de C ++ que escribí para exponer el código usando la clase Mat de OpenCV con el menor dolor posible.

[Actualización] Este código ahora funciona para OpenCV 2.X y OpenCV 3.X. CMake y soporte experimental para Python 3.X ahora también están disponibles.

Estoy tratando de escribir una envoltura de python para algunos códigos C ++ que utilizan OpenCV pero tengo dificultades para devolver el resultado, que es un objeto OpenCV C ++ Mat, al intérprete de python.

He mirado la fuente de OpenCV y encontré el archivo cv2.cpp que tiene funciones de conversión para realizar conversiones de PyObject * y Mat de OpenCV. Hice uso de esas funciones de conversión, pero tuve una falla de segmentación cuando intenté usarlas.

Básicamente necesito algunas sugerencias / código de ejemplo / referencias en línea sobre cómo interactuar con Python y código C ++ que utilizan OpenCV, específicamente con la capacidad de devolver el C ++ Mat de OpenCV al intérprete de python o quizás sugerencias sobre cómo / dónde comenzar a investigar la causa de la falla de segmentación.

Actualmente estoy usando Boost Python para envolver el código.

Gracias de antemano a cualquier respuesta.

El código relevante:

// This is the function that is giving the segmentation fault. PyObject* ABC::doSomething(PyObject* image) { Mat m; pyopencv_to(image, m); // This line gives segmentation fault. // Some code to create cppObj from CPP library that uses OpenCV cv::Mat processedImage = cppObj->align(m); return pyopencv_from(processedImage); }

Las funciones de conversión tomadas de la fuente de OpenCV siguen. El código de conversión da un error de segmentación en la línea comentada con "si (! PyArray_Check (o)) ...".

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( !PyArray_Check(o) ) // Segmentation fault inside PyArray_Check(o) { failmsg("%s is not a numpy array", name); return false; } 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 = 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 ) { Mat tmp; tmp.allocator = &g_numpyAllocator; transpose(m, tmp); m = tmp; } 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; m.copyTo(temp); p = &temp; } p->addref(); return pyObjectFromRefcount(p->refcount); }

Mi programa de prueba de python:

import pysomemodule # My python wrapped library. import cv2 def main(): myobj = pysomemodule.ABC("faces.train") # Create python object. This works. image = cv2.imread(''61.jpg'') processedImage = myobj.doSomething(image) cv2.imshow("test", processedImage) cv2.waitKey() if __name__ == "__main__": main()


Resolví el problema, así que pensé que lo compartiría aquí con otras personas que pueden tener el mismo problema.

Básicamente, para deshacerme de la falla de segmentación, necesito llamar a la función import_array () de numpy.

La vista de "alto nivel" para ejecutar código C ++ desde Python es la siguiente:

Supongamos que tiene una función foo(arg) en python que es un enlace para alguna función de C ++. Cuando llama a foo(myObj) , debe haber algún código para convertir el objeto de Python "myObj" en una forma en que su código de C ++ puede actuar. Este código generalmente se crea de forma semiautomática con herramientas como SWIG o Boost :: Python. (Yo uso Boost :: Python en los ejemplos a continuación.)

Ahora, foo(arg) es un enlace de python para alguna función de C ++. Esta función de C ++ recibirá un puntero PyObject genérico como argumento. Necesitará tener código C ++ para convertir este puntero de PyObject en un objeto C ++ "equivalente". En mi caso, mi código de Python pasa una matriz numpy OpenCV para una imagen OpenCV como un argumento a la función. La forma "equivalente" en C ++ es un objeto OpenCV C ++ Mat. OpenCV proporciona algo de código en cv2.cpp (reproducido a continuación) para convertir el puntero de PyObject (que representa la matriz numpy) en una Mat C ++. Los tipos de datos más simples, como int y string, no necesitan que el usuario escriba estas funciones de conversión, ya que Boost :: Python las convierte automáticamente.

Una vez que el puntero de PyObject se convierte a un formulario C ++ adecuado, el código C ++ puede actuar sobre él. Cuando hay que devolver los datos de C ++ a python, surge una situación análoga en la que se necesita el código de C ++ para convertir la representación de C ++ de los datos en alguna forma de PyObject . Boost :: Python se encargará del resto al convertir el PyObject a una forma de python correspondiente. Cuando foo(arg) devuelve el resultado en python, está en una forma utilizable por python. Eso es.

El siguiente código muestra cómo envolver una clase de C ++ "ABC" y exponer su método "doSomething" que toma una matriz numpy (para una imagen) de python, la convierte a C ++ Mat de OpenCV, realiza un procesamiento, convierte el resultado a PyObject *, y devuélvalo al intérprete de python. Puede exponer tantas funciones / métodos que desee (vea los comentarios en el código a continuación).

abc.hpp:

#ifndef ABC_HPP #define ABC_HPP #include <Python.h> #include <string> class ABC { // Other declarations ABC(); ABC(const std::string& someConfigFile); virtual ~ABC(); PyObject* doSomething(PyObject* image); // We want our python code to be able to call this function to do some processing using OpenCV and return the result. // Other declarations }; #endif

abc.cpp:

#include "abc.hpp" #include "my_cpp_library.h" // This is what we want to make available in python. It uses OpenCV to perform some processing. #include "numpy/ndarrayobject.h" #include "opencv2/core/core.hpp" // The following conversion functions are taken from OpenCV''s cv2.cpp file inside modules/python/src2 folder. 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) { //NumpyAllocator g_numpyAllocator; 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; } 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 = 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 ) { Mat tmp; tmp.allocator = &g_numpyAllocator; transpose(m, tmp); m = tmp; } 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; m.copyTo(temp); p = &temp; } p->addref(); return pyObjectFromRefcount(p->refcount); } ABC::ABC() {} ABC::~ABC() {} // Note the import_array() from NumPy must be called else you will experience segmentation faults. ABC::ABC(const std::string &someConfigFile) { // Initialization code. Possibly store someConfigFile etc. import_array(); // This is a function from NumPy that MUST be called. // Do other stuff } // The conversions functions above are taken from OpenCV. The following function is // what we define to access the C++ code we are interested in. PyObject* ABC::doSomething(PyObject* image) { cv::Mat cvImage; pyopencv_to(image, cvImage); // From OpenCV''s source MyCPPClass obj; // Some object from the C++ library. cv::Mat processedImage = obj.process(cvImage); return pyopencv_from(processedImage); // From OpenCV''s source }

El código para usar Boost Python para crear el módulo de python. Tomé este y el siguiente Makefile de http://jayrambhia.wordpress.com/tag/boost/ :

pysomemodule.cpp:

#include <string> #include<boost/python.hpp> #include "abc.hpp" using namespace boost::python; BOOST_PYTHON_MODULE(pysomemodule) { class_<ABC>("ABC", init<const std::string &>()) .def(init<const std::string &>()) .def("doSomething", &ABC::doSomething) // doSomething is the method in class ABC you wish to expose. One line for each method (or function depending on how you structure your code). Note: You don''t have to expose everything in the library, just the ones you wish to make available to python. ; }

Y finalmente, el Makefile (compilado exitosamente en Ubuntu pero debería funcionar en otro lugar posiblemente con ajustes mínimos).

PYTHON_VERSION = 2.7 PYTHON_INCLUDE = /usr/include/python$(PYTHON_VERSION) # location of the Boost Python include files and library BOOST_INC = /usr/local/include/boost BOOST_LIB = /usr/local/lib OPENCV_LIB = `pkg-config --libs opencv` OPENCV_CFLAGS = `pkg-config --cflags opencv` MY_CPP_LIB = lib_my_cpp_library.so TARGET = pysomemodule SRC = pysomemodule.cpp abc.cpp OBJ = pysomemodule.o abc.o $(TARGET).so: $(OBJ) g++ -shared $(OBJ) -L$(BOOST_LIB) -lboost_python -L/usr/lib/python$(PYTHON_VERSION)/config -lpython$(PYTHON_VERSION) -o $(TARGET).so $(OPENCV_LIB) $(MY_CPP_LIB) $(OBJ): $(SRC) g++ -I$(PYTHON_INCLUDE) -I$(BOOST_INC) $(OPENCV_CFLAGS) -fPIC -c $(SRC) clean: rm -f $(OBJ) rm -f $(TARGET).so

Una vez que haya compilado correctamente la biblioteca, debe tener un archivo "pysomemodule.so" en el directorio. Coloque este archivo lib en un lugar accesible por su intérprete de python. Luego puede importar este módulo y crear una instancia de la clase "ABC" de la siguiente manera:

import pysomemodule foo = pysomemodule.ABC("config.txt") # This will create an instance of ABC

Ahora, dada una imagen de matriz numpy de OpenCV, podemos llamar a la función C ++ usando:

processedImage = foo.doSomething(image) # Where the argument "image" is a OpenCV numpy image.

Tenga en cuenta que necesitará Boost Python, Numpy dev, así como también Python dev library para crear los enlaces.

Los documentos de NumPy en los dos enlaces siguientes son particularmente útiles para ayudarnos a comprender los métodos que se utilizaron en el código de conversión y por qué se debe llamar a import_array () En particular, el documento oficial numpy es útil para dar sentido al código de enlace de Python de OpenCV.

http://dsnra.jpl.nasa.gov/software/Python/numpydoc/numpy-13.html http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html

Espero que esto ayude.