写作C ++ code Python绑定使用的OpenCV [英] Writing Python bindings for C++ code that use OpenCV

查看:1183
本文介绍了写作C ++ code Python绑定使用的OpenCV的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试着写一些C ++ code蟒包装,利用OpenCV的,但我在返回的结果,这是一个OpenCV的C ++垫目标困难,蟒蛇间preTER 。

我看的OpenCV的源代码,并发现它具有转换功能来执行转换来来回回的PyObject *和OpenCV的垫之间的文件cv2.cpp。我利用了这些转换的功能,但有一个分段错误,当我试图使用它们。

基本上,我需要一些建议/样品code /网络如何连接Python和C ++ code上使用的OpenCV的,特别是与以OpenCV中的C ++垫回蟒蛇间$ P $的能力引用如何/从哪里开始调查分段故障的原因PTER或者建议。

目前我使用升压Python来包裹code。

在此先感谢任何答复。

相关code:

  //这是给段错误的功能。
的PyObject * ABC :: DoSomething的(*的PyObject图片)
{
    垫米;
    pyopencv_to(图像,M); //此行给出了分段故障。    //有些code,以从使用了OpenCV CPP库中创建cppObj
    CV ::垫processedImage = cppObj->对齐(米);    返回pyopencv_from(processedImage);
}

来自OpenCV的源采取的转换函数如下。转换code给出分段故障在与注释行如果(!PyArray_Check(O))......

 静态INT pyopencv_to(常量的PyObject * 0,垫&安培;男,为const char * name =<&不明GT;,布尔allowND = TRUE)
{
    如果(O!||Ø== Py_None)
    {
        如果(!m.data)
            m.allocator =安培; g_numpyAllocator;
        返回true;
    }    如果(!PyArray_Check(O))内PyArray_Check //分割故障(O)
    {
        failmsg(%s不是一个numpy的数组,名);
        返回false;
    }    INT typenum = PyArray_TYPE(O);
    整型= ty​​penum == 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;    如果(类型< 0)
    {
        failmsg(%s的数据类型=%d不支持,名称,typenum);
        返回false;
    }    INT为ndims = PyArray_NDIM(O);
    如果(为ndims> = CV_MAX_DIM)
    {
        failmsg(%S维数(=%d)为过高,名称为ndims);
        返回false;
    }    INT尺寸[CV_MAX_DIM + 1];
    为size_t步骤[CV_MAX_DIM + 1〕,可达elemsize = CV_ELEM_SIZE1(类型);
    常量npy_intp * _sizes = PyArray_DIMS(O);
    常量npy_intp * _strides = PyArray_STRIDES(O);
    布尔换位= FALSE;    的for(int i = 0; I<为ndims;我++)
    {
        大小[I] =(int)的_sizes [I]
        步[I] =(为size_t)_strides [I]
    }    如果(为ndims == 0 ||步骤[为ndims-1]≥可达elemsize){
        尺寸[为ndims] = 1;
        步骤[为ndims] =可达elemsize;
        为ndims ++;
    }    如果(为ndims> = 2和;&放大器;步骤[0]<步骤[1])
    {
        的std ::交换(大小[0],尺寸[1]);
        性病::交换(步骤[0],步骤[1]);
        换位= TRUE;
    }    如果(为ndims == 3和;&放大器;尺寸[2]&下; = CV_CN_MAX&放大器;&放大器;步骤[1] ==可达elemsize *尺寸[2])
    {
        ndims--;
        类型| = CV_MAKETYPE(0,尺寸[2]);
    }    如果(为ndims→2&放大器;&放大器;!allowND)
    {
        failmsg(%S有超过2个维度,名);
        返回false;
    }    M =垫(为ndims,大小,类型,PyArray_DATA(O),步骤);    如果(m.data)
    {
        m.refcount = refcountFromPyObject(O);
        m.addref(); //防止释放原来的numpy的数组
                    //(因为垫析构函数将减少参考计数器)
    };
    m.allocator =安培; g_numpyAllocator;    如果(转)
    {
        垫TMP;
        tmp.allocator =安培; g_numpyAllocator;
        转(男,TMP);
        M = tmp目录;
    }
    返回true;
}静态的PyObject * pyopencv_from(常量垫&安培; M)
{
    如果(!m.data)
        Py_RETURN_NONE;
    垫温度,* P =(马太福音*)及米;
    如果(对GT;!引用计数|| P->!分配器=安培; g_numpyAllocator)
    {
        temp.allocator =安培; g_numpyAllocator;
        m.copyTo(临时);
        P =&放大器;温度;
    }
    对 - >的AddRef();
    返回pyObjectFromRefcount(对GT;引用计数);
}

我的Python测试程序:

 进口pysomemodule#我的Python包库。
进口CV2高清的main():
    MyObj中= pysomemodule.ABC(faces.train)#创建Python对象。这工作。
    图像= cv2.imread('61 .JPG)
    processedImage = myobj.doSomething(图)
    cv2.imshow(测试,processedImage)
    cv2.waitKey()如果__name__ ==__main__:
    主要()


解决方案

我解决了,所以我想我会在这里与他人分享谁可能有同样的问题的问题。

基本上摆脱了段错误的,我需要调用numpy的的import_array()函数。

在高层次的观点从蟒蛇运行C ++ code是这样的:

假设你有一个函数富(ARG)Python的是一些C ++函数结合。当你调用富(MyObj中),必须有一些code到Python对象MyObj中转换为表格C ++ code可作用。这code是一般半自动创建的使用工具,如SWIG或Boost :: Python的。 (我在下面的示例中使用升压::的Python)

现在,富(ARG)是一个Python一些C ++函数具有约束力。这个C ++函数将收到一个普通的的PyObject 指针作为参数。你需要有C ++ code这个的PyObject 指针转换为相当于C ++对象。就我而言,我的蟒蛇code通过了OpenCV的图像作为参数传递给函数的OpenCV numpy的数组。在等价的形式在C ++是一种OpenCV的C ++垫目标。 OpenCV提供的cv2.cpp一些code(转载如下)在的PyObject 指针转换(重presenting的numpy的数组)到C ++垫。简单的数据类型,如int和string不需要用户编写这些转换功能,因为它们会自动升压:: Python的转换。

的PyObject 指针被转换成合适的C ++表格后,C ++ code可采取行动。当数据必须从C返回++到Python,类似的情况出现在需要C ++ code到C ++转换再度数据某种形式的的PyObject 。的boost :: Python会照顾其余的转换的PyObject 到相应的蟒蛇形式。当富(ARG)返回结果的蟒蛇,它是在一个形式,蟒蛇使用。这就是它。

在code以下显示了如何包装一个C ++类ABC,并从蟒暴露其方法DoSomething的,在numpy的阵列需要(用于图像),将其转换为OpenCV中的C ++垫,做一些处理,结果转换成的PyObject *,并返回给蟒蛇间preTER。您可以公开为许多功能/方法你想(请参阅下面的code评论)

abc.hpp:

 的#ifndef ABC_HPP
#定义ABC_HPP#包括LT&;&Python.h GT;
#包括LT&;串GT;ABC类
{
  //其他声明
    ABC();
    美国广播公司(常量标准::字符串&安培; someConfigFile);
    虚拟〜ABC();
    *的PyObject doSomething的(*的PyObject图像); //我们希望我们的蟒蛇code,能够调用此函数用OpenCV的做一些处理并返回结果。
  //其他声明
};#万一

abc.cpp:

 的#includeabc.hpp
#包括my_cpp_library.h//这是我们想在蟒蛇可用。它使用了OpenCV进行一些处理。#包括numpy的/ ndarrayobject.h
#包括opencv2 /核心/ core.hpp//下面的转换函数从内模块/蟒蛇/ SRC2文件夹的OpenCV的cv2.cpp文件中获取。
静态的PyObject * opencv_error = 0;静态INT failmsg(为const char * FMT,...)
{
    焦炭海峡[1000];    va_list的AP;
    的va_start(AP,FMT);
    vsnprintf(海峡,sizeof的(STR),格式化,AP);
    va_end用来(AP);    PyErr_SetString(PyExc_TypeError,STR);
    返回0;
}类PyAllowThreads
{
上市:
    PyAllowThreads():_state(PyEval_SaveThread()){}
    〜PyAllowThreads()
    {
        PyEval_RestoreThread(_state);
    }
私人的:
    PyThreadState * _state;
};类PyEnsureGIL
{
上市:
    PyEnsureGIL():_state(PyGILState_Ensure()){}
    〜PyEnsureGIL()
    {
        PyGILState_Release(_state);
    }
私人的:
    PyGILState_STATE _state;
};#定义ERRWRAP2(表达式)\\
尝试\\
{\\
    PyAllowThreads allowThreads; \\
    EXPR; \\
} \\
赶上(const的CV ::异常急症)\\
{\\
    PyErr_SetString(opencv_error,e.what()); \\
    返回0; \\
}使用命名空间的简历;静态的PyObject * failmsgp(为const char * FMT,...)
{
  焦炭海峡[1000];  va_list的AP;
  的va_start(AP,FMT);
  vsnprintf(海峡,sizeof的(STR),格式化,AP);
  va_end用来(AP);  PyErr_SetString(PyExc_TypeError,STR);
  返回0;
}静态为size_t REFCOUNT_OFFSET =(为size_t)及(((的PyObject *)0) - > ob_ref​​cnt)+
    (为0x12345678 = *(常量为size_t *)\\ X78 \\ X56 \\ X34 \\ X12 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0!)* sizeof的(INT);静态内嵌的PyObject * pyObjectFromRefcount(co​​nst int的*引用计数)
{
    回报(*的PyObject)((为size_t)引用计数 - REFCOUNT_OFFSET);
}静态内嵌INT * refcountFromPyObject(常量的PyObject * OBJ)
{
    回报为(int *)((为size_t)OBJ + REFCOUNT_OFFSET);
}类NumpyAllocator:公​​共MatAllocator
{
上市:
    NumpyAllocator(){}
    〜NumpyAllocator(){}    无效分配(INT变暗,const int的*的大小,int型的,为int *&放大器;引用计数,
                  UCHAR *放大器; datastart,UCHAR *放大器;数据,为size_t *步骤)
    {
        PyEnsureGIL吉尔;        INT深度= CV_MAT_DEPTH(类型);
        INT CN = CV_MAT_CN(类型);
        const int的F =(INT)(的sizeof(为size_t)/ 8);
        INT typenum =深度== CV_8U? NPY_UBYTE:深度== CV_8S? NPY_BYTE:
                      深度== CV_16U? NPY_USHORT:深度== CV_16S? NPY_SHORT:
                      深度== CV_32S? NPY_INT:深度== CV_32F? NPY_FLOAT:
                      深度== CV_64F? NPY_DOUBLE:F * NPY_ULONGLONG +(F ^ 1)* NPY_UINT;
        INT I;
        npy_intp _sizes [CV_MAX_DIM + 1〕;
        对于(i = 0; I<变暗;我++)
        {
            _sizes [I] =尺寸[I]
        }        如果(CN→1)
        {
            / *如果(_sizes [DIMS-1] == 1)
                _sizes [DIMS-1] = CN;
            其他*/
                _sizes [变暗++] = CN;
        }        *的PyObject O = PyArray_SimpleNew(DIMS,_sizes,typenum);        如果(!O)
        {
            CV_Error_(CV_StsError,(,typenumtypenum =%d个,为ndims =%d个无法创建的numpy的数组,变暗));
        }
        引用计数= refcountFromPyObject(O);        npy_intp * _strides = PyArray_STRIDES(O);
        对于(I = 0; I&下;变暗 - (CN→1);我+ +)
            步[I] =(为size_t)_strides [I]
        datastart =数据=(UCHAR *)PyArray_DATA(O);
    }    无效DEALLOCATE(INT *引用计数,UCHAR *,* UCHAR)
    {
        PyEnsureGIL吉尔;
        如果(!引用计数)
            返回;
        *的PyObject O = pyObjectFromRefcount(引用计数);
        Py_INCREF(O);
        Py_DECREF(O);
    }
};NumpyAllocator g_numpyAllocator;枚举{ARG_NONE = 0,ARG_MAT = 1,ARG_SCALAR = 2};静态INT pyopencv_to(常量的PyObject * 0,垫&安培;男,为const char * name =<&不明GT;,布尔allowND = TRUE)
{
    // NumpyAllocator g_numpyAllocator;
    如果(O!||Ø== Py_None)
    {
        如果(!m.data)
            m.allocator =安培; g_numpyAllocator;
        返回true;
    }    如果(!PyArray_Check(O))
    {
        failmsg(%s不是一个numpy的数组,名);
        返回false;
    }    INT typenum = PyArray_TYPE(O);
    整型= ty​​penum == 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;    如果(类型< 0)
    {
        failmsg(%s的数据类型=%d不支持,名称,typenum);
        返回false;
    }    INT为ndims = PyArray_NDIM(O);
    如果(为ndims> = CV_MAX_DIM)
    {
        failmsg(%S维数(=%d)为过高,名称为ndims);
        返回false;
    }    INT尺寸[CV_MAX_DIM + 1];
    为size_t步骤[CV_MAX_DIM + 1〕,可达elemsize = CV_ELEM_SIZE1(类型);
    常量npy_intp * _sizes = PyArray_DIMS(O);
    常量npy_intp * _strides = PyArray_STRIDES(O);
    布尔换位= FALSE;    的for(int i = 0; I<为ndims;我++)
    {
        大小[I] =(int)的_sizes [I]
        步[I] =(为size_t)_strides [I]
    }    如果(为ndims == 0 ||步骤[为ndims-1]≥可达elemsize){
        尺寸[为ndims] = 1;
        步骤[为ndims] =可达elemsize;
        为ndims ++;
    }    如果(为ndims> = 2和;&放大器;步骤[0]<步骤[1])
    {
        的std ::交换(大小[0],尺寸[1]);
        性病::交换(步骤[0],步骤[1]);
        换位= TRUE;
    }    如果(为ndims == 3和;&放大器;尺寸[2]&下; = CV_CN_MAX&放大器;&放大器;步骤[1] ==可达elemsize *尺寸[2])
    {
        ndims--;
        类型| = CV_MAKETYPE(0,尺寸[2]);
    }    如果(为ndims→2&放大器;&放大器;!allowND)
    {
        failmsg(%S有超过2个维度,名);
        返回false;
    }    M =垫(为ndims,大小,类型,PyArray_DATA(O),步骤);    如果(m.data)
    {
        m.refcount = refcountFromPyObject(O);
        m.addref(); //防止释放原来的numpy的数组
                    //(因为垫析构函数将减少参考计数器)
    };
    m.allocator =安培; g_numpyAllocator;    如果(转)
    {
        垫TMP;
        tmp.allocator =安培; g_numpyAllocator;
        转(男,TMP);
        M = tmp目录;
    }
    返回true;
}静态的PyObject * pyopencv_from(常量垫&安培; M)
{
    如果(!m.data)
        Py_RETURN_NONE;
    垫温度,* P =(马太福音*)及米;
    如果(对GT;!引用计数|| P->!分配器=安培; g_numpyAllocator)
    {
        temp.allocator =安培; g_numpyAllocator;
        m.copyTo(临时);
        P =&放大器;温度;
    }
    对 - >的AddRef();
    返回pyObjectFromRefcount(对GT;引用计数);
}ABC :: ABC(){}
ABC ::〜ABC(){}
//注意从numpy的的import_array()必须被调用否则你将体验到段故障。
ABC :: ABC(常量标准::字符串&安培; someConfigFile)
{
  //初始化code。可能存储someConfigFile等。
  import_array(); //这是numpy的一个函数,必须调用。
  //做其他的东西
}//上述转换的功能是从OpenCV的拍摄。以下功能
//我们定义访问C ++ code,我们感兴趣的东西。
的PyObject * ABC :: DoSomething的(*的PyObject图片)
{
    CV ::垫cvImage;
    pyopencv_to(图像,cvImage); //从OpenCV的源    MyCPPClass OBJ; //从C ++库中的一些对象。
    CV ::垫processedImage = obj.process(cvImage);    返回pyopencv_from(processedImage); //从OpenCV的源
}

在code使用升压Python创建Python模块。我把这个和下面的Makefile <一个href=\"http://jayrambhia.word$p$pss.com/tag/boost/\">http://jayrambhia.word$p$pss.com/tag/boost/:

pysomemodule.cpp:

 的#include&LT;串GT;
#包括LT&;升压/ python.hpp&GT;
#包括abc.hpp使用空间boost ::蟒蛇;BOOST_PYTHON_MODULE(pysomemodule)
{
    class_&LT; ABC&GT;(ABC时,init&LT;常量标准::字符串&放大器;&GT;())
      .DEF(INIT&LT;常量标准::字符串&放大器;&GT;())
      .DEF(DoSomething的,&安培; ABC :: DoSomething的)// DoSomething的是ABC类要暴露的方法。每种方法一号线(或功能取决于你如何组织你的code)。注意:您不必暴露在图书馆的一切,只是希望提供给蟒蛇的人。
    ;
}

最后,在Makefile(编译成功在Ubuntu上,而应在其他地方可能以最小的调整工作)。

  PYTHON_VERSION = 2.7
PYTHON_INCLUDE = / usr / include目录/蟒蛇$(PYTHON_VERSION)#升压的Python的位置,包括文件和库
BOOST_INC =的/ usr /本地/包括/升压
BOOST_LIB =在/ usr / local / lib目录OPENCV_LIB =`pkg配置--libs opencv`
OPENCV_CFLAGS =`pkg配置--cflags opencv`MY_CPP_LIB = lib_my_cpp_library.soTARGET = 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_VERSION)/配置-lpython $(PYTHON_VERSION)-o $(TARGET)。所以$(OPENCV_LIB)$(MY_CPP_LIB )$(OBJ):$(SRC)
    G ++ -I $(PYTHON_INCLUDE)-I $(BOOST_INC)$(OPENCV_CFLAGS)-fPIC -C $(SRC)清洁:
    RM -f $(OBJ)
    RM -f $(TARGET)的.so

您已经成功地编译库后,你应该在目录中的文件pysomemodule.so。把这个库文件由你的Python间preTER访问的地方。然后,您可以导入这个模块,并创建一个类ABC上面如下的一个实例:

 进口pysomemodule富= pysomemodule.ABC(config.txt的)#这将创建ABC的一个实例

现在,给定一个OpenCV的numpy的阵列图像,我们可以通过调用C ++函数:

  processedImage = foo.doSomething(图像)#凡的说法形象是一个OpenCV的numpy的形象。

请注意,您需要升压的Python,numpy的开发,以及Python的开发库来创建绑定。

在以下两个链接的numpy的文档是帮助人懂在转换code中使用的方法和为什么import_array()必须调用特别有用。特别是,官方文档numpy的是使OpenCV中的蟒蛇意识结合code有帮助的。

<一个href=\"http://dsnra.jpl.nasa.gov/software/Python/numpydoc/numpy-13.html\">http://dsnra.jpl.nasa.gov/software/Python/numpydoc/numpy-13.html
<一href=\"http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html\">http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html

希望这有助于。

I'm trying to write a python wrapper for some C++ code that make use of OpenCV but I'm having difficulties returning the result, which is a OpenCV C++ Mat object, to the python interpreter.

I've looked at OpenCV's source and found the file cv2.cpp which has conversions functions to perform conversions to and fro between PyObject* and OpenCV's Mat. I made use of those conversions functions but got a segmentation fault when I tried to use them.

I basically need some suggestions/sample code/online references on how to interface python and C++ code that make use of OpenCV, specifically with the ability to return OpenCV's C++ Mat to the python interpreter or perhaps suggestions on how/where to start investigating the cause of the segmentation fault.

Currently I'm using Boost Python to wrap the code.

Thanks in advance to any replies.

The relevant code:

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

The conversion functions taken from OpenCV's source follows. The conversion code gives segmentation fault at the commented line with "if (!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);
}

My python test program:

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()

解决方案

I solved the problem so I thought I'll share it here with others who may have the same problem.

Basically, to get rid of the segmentation fault, I need to call numpy's import_array() function.

The "high level" view for running C++ code from python is this:

Suppose you have a function foo(arg) in python that is a binding for some C++ function. When you call foo(myObj), there must be some code to convert the python object "myObj" to a form your C++ code can act on. This code is generally semi-automatically created using tools such as SWIG or Boost::Python. (I use Boost::Python in the examples below.)

Now, foo(arg) is a python binding for some C++ function. This C++ function will receive a generic PyObject pointer as an argument. You will need to have C++ code to convert this PyObject pointer to an "equivalent" C++ object. In my case, my python code passes a OpenCV numpy array for a OpenCV image as an argument to the function. The "equivalent" form in C++ is a OpenCV C++ Mat object. OpenCV provides some code in cv2.cpp (reproduced below) to convert the PyObject pointer (representing the numpy array) to a C++ Mat. Simpler data types such as int and string do not need the user to write these conversion functions as they are automatically converted by Boost::Python.

After the PyObject pointer is converted to a suitable C++ form, C++ code can act on it. When data has to be returned from C++ to python, an analogous situation arises where C++ code is needed to convert the C++ representation of the data to some form of PyObject. Boost::Python will take care of the rest in converting the PyObject to a corresponding python form. When foo(arg) returns the result in python, it is in a form usable by python. That's it.

The code below shows how to wrap a C++ class "ABC" and expose its method "doSomething" that takes in a numpy array (for an image) from python, convert it to OpenCV's C++ Mat, do some processing, convert the result to PyObject *, and return it to the python interpreter. You can expose as many functions/method you wish (see comments in the code below).

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
}

The code to use Boost Python to create the python module. I took this and the following Makefile from 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.
    ;
}

And finally, the Makefile (successfully compiled on Ubuntu but should work elsewhere possibly with minimal adjustments).

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

After you have successfully compiled the library, you should have a file "pysomemodule.so" in the directory. Put this lib file in a place accessible by your python interpreter. You can then import this module and create an instance of the class "ABC" above as follows:

import pysomemodule

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

Now, given an OpenCV numpy array image, we can call the C++ function using:

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

Note that you will need Boost Python, Numpy dev, as well as Python dev library to create the bindings.

The NumPy docs in the following two links are particularly useful in helping one understand the methods that were used in the conversion code and why import_array() must be called. In particular, the official numpy doc is helpful in making sense of OpenCV's python binding code.

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

Hope this helps.

这篇关于写作C ++ code Python绑定使用的OpenCV的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆