为使用 OpenCV 的 C++ 代码编写 Python 绑定 [英] Writing Python bindings for C++ code that use OpenCV

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

问题描述

我正在尝试为一些使用 OpenCV 的 C++ 代码编写一个 python 包装器,但我在将结果(OpenCV C++ Mat 对象)返回给 python 解释器时遇到了困难.

我查看了 OpenCV 的源代码并找到了文件 cv2.cpp,该文件具有转换功能,可以在 PyObject* 和 OpenCV 的 Mat 之间来回执行转换.我使用了这些转换函数,但在尝试使用它们时遇到了分段错误.

我基本上需要一些关于如何连接使用 OpenCV 的 python 和 C++ 代码的建议/示例代码/在线参考,特别是能够将 OpenCV 的 C++ Mat 返回给 python 解释器,或者可能是关于如何/从哪里开始的建议调查分段错误的原因.

目前我正在使用 Boost Python 来包装代码.

在此先感谢您的回复.

相关代码:

//这是导致分段错误的函数.PyObject* ABC::doSomething(PyObject* 图像){垫米;pyopencv_to(图像,米);//这一行给出了分段错误.//从使用 OpenCV 的 CPP 库创建 cppObj 的一些代码cv::Mat 处理图像 = cppObj-> align(m);返回 pyopencv_from(processedImage);}

取自 OpenCV 源代码的转换函数如下.转换代码在if (!PyArray_Check(o)) ..."的注释行给出分段错误.

static int pyopencv_to(const PyObject* o, Mat& m, const char* name = "", bool allowND=true){if(!o || o == Py_None){如果(!m.data)m.allocator = &g_numpyAllocator;返回真;}if( !PyArray_Check(o) )//PyArray_Check(o) 内部的分段错误{failmsg("%s 不是一个 numpy 数组", name);返回假;}int typenum = PyArray_TYPE(o);int type = typenum == NPY_UBYTE ?CV_8U : typenum == NPY_BYTE ?简历_8S :typenum == NPY_USHORT ?CV_16U : typenum == NPY_SHORT ?简历_16S :typenum == NPY_INT ||typenum == NPY_LONG ?CV_32S :typenum == NPY_FLOAT ?简历_32F:typenum == NPY_DOUBLE ?CV_64F:-1;如果(类型 <0 ){failmsg("%s 数据类型 = %d 不被支持", name, typenum);返回假;}int ndims = PyArray_NDIM(o);如果(ndims >= CV_MAX_DIM){failmsg("%s 维数 (=%d) 太高", name, ndims);返回假;}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);布尔转置 = 假;for(int i = 0; i < ndims; i++){大小[i] = (int)_sizes[i];step[i] = (size_t)_strides[i];}if( ndims == 0 || step[ndims-1] > elemsize ) {大小[ndims] = 1;步骤[ndims] = elemsize;ndims++;}if( ndims >= 2 && step[0] < step[1] ){std::swap(大小[0], 大小[1]);std::swap(step[0], step[1]);转置 = 真;}if( ndims == 3 && size[2] <= CV_CN_MAX && step[1] == elemsize*size[2]){ndims--;类型 |= CV_MAKETYPE(0, size[2]);}if( ndims > 2 && !allowND ){failmsg("%s 有超过 2 个维度", name);返回假;}m = Mat(ndims, size, type, PyArray_DATA(o), step);如果( m.data ){m.refcount = refcountFromPyObject(o);m.addref();//保护原始 numpy 数组不被释放//(因为 Mat 析构函数会递减引用计数器)};m.allocator = &g_numpyAllocator;如果(转置){垫 tmp;tmp.allocator = &g_numpyAllocator;转置(m,tmp);m = tmp;}返回真;}静态 PyObject* pyopencv_from(const Mat& m){如果(!m.data)Py_RETURN_NONE;垫子温度,*p = (垫子*)&m;if(!p->refcount || p->allocator != &g_numpyAllocator){temp.allocator = &g_numpyAllocator;m.copyTo(temp);p = &temp;}p-> addref();返回 pyObjectFromRefcount(p->refcount);}

我的python测试程序:

import pysomemodule # 我的 Python 封装库.导入 cv2定义主():myobj = pysomemodule.ABC("faces.train") # 创建 python 对象.这有效.image = cv2.imread('61.jpg')处理图像 = myobj.doSomething(图像)cv2.imshow(测试",已处理图像)cv2.waitKey()如果 __name__ == "__main__":主要的()

解决方案

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

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

从 python 运行 C++ 代码的高级"视图是这样的:

假设您在 python 中有一个函数 foo(arg),它是某个 C++ 函数的绑定.当您调用 foo(myObj) 时,必须有一些代码将 python 对象myObj"转换为您的 C++ 代码可以执行的形式.此代码通常是使用 SWIG 或 Boost::Python 等工具半自动创建的.(我在下面的示例中使用 Boost::Python.)

现在,foo(arg) 是一些 C++ 函数的 Python 绑定.这个 C++ 函数将接收一个通用的 PyObject 指针作为参数.您需要有 C++ 代码才能将此 PyObject 指针转换为等效的"C++ 对象.就我而言,我的 Python 代码将 OpenCV 图像的 OpenCV numpy 数组作为参数传递给函数.C++ 中的等效"形式是 OpenCV C++ Mat 对象.OpenCV 在 cv2.cpp 中提供了一些代码(转载如下)将 PyObject 指针(代表 numpy 数组)转换为 C++ Mat.更简单的数据类型如 int 和 string 不需要用户编写这些转换函数,因为它们由 Boost::Python 自动转换.

PyObject指针转换为合适的C++形式后,C++代码就可以对其进行操作.当数据必须从 C++ 返回到 python 时,会出现一种类似的情况,需要 C++ 代码将数据的 C++ 表示转换为某种形式的 PyObject.Boost::Python 将负责将 PyObject 转换为相应的 Python 形式的其余部分.当foo(arg)在python中返回结果时,它是python可用的形式.就是这样.

下面的代码显示了如何包装一个 C++ 类ABC"并公开它的方法doSomething",它从 python 中接收一个 numpy 数组(用于图像),将其转换为 OpenCV 的 C++ Mat,做一些处理,转换结果到 PyObject *,并将其返回给 python 解释器.您可以公开任意数量的函数/方法(请参阅下面代码中的注释).

abc.hpp:

#ifndef ABC_HPP#define ABC_HPP#include #include <字符串>ABC类{//其他声明ABC();ABC(const std::string& someConfigFile);虚拟 ~ABC();PyObject* doSomething(PyObject* image);//我们希望我们的 python 代码能够调用这个函数来使用 OpenCV 进行一些处理并返回结果.//其他声明};#万一

abc.cpp:

#include "abc.hpp"#include "my_cpp_library.h"//这是我们想要在 python 中可用的内容.它使用 OpenCV 来执行一些处理.#include "numpy/ndarrayobject.h"#include "opencv2/core/core.hpp"//以下转换函数取自modules/python/src2文件夹内的OpenCV的cv2.cpp文件.静态 PyObject* opencv_error = 0;static int failmsg(const char *fmt, ...){字符 str[1000];va_list ap;va_start(ap, fmt);vsnprintf(str, sizeof(str), fmt, 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;};#define ERRWRAP2(expr) 尝试 { PyAllowThreads 允许线程;表达式;} catch (const cv::Exception &e) { PyErr_SetString(opencv_error, e.what());返回0;}使用命名空间 cv;静态 PyObject* failmsgp(const char *fmt, ...){字符 str[1000];va_list ap;va_start(ap, fmt);vsnprintf(str, sizeof(str), fmt, ap);va_end(ap);PyErr_SetString(PyExc_TypeError, str);返回0;}静态 size_t REFCOUNT_OFFSET = (size_t)&(((PyObject*)0)->ob_refcnt) +(0x12345678 != *(const size_t*)"x78x56x34x12")*sizeof(int);静态内联 PyObject* pyObjectFromRefcount(const int* refcount){返回 (PyObject*)((size_t)refcount - REFCOUNT_OFFSET);}静态内联 int* refcountFromPyObject(const PyObject* obj){返回 (int*)((size_t)obj + REFCOUNT_OFFSET);}类 NumpyAllocator :公共 MatAllocator{上市:NumpyAllocator() {}~NumpyAllocator() {}无效分配(int 暗淡,const int* 大小,int 类型,int*& refcount,uchar*&数据开始,uchar*&数据,size_t* 步长){PyEnsureGIL 吉尔;int depth = CV_MAT_DEPTH(type);int cn = CV_MAT_CN(类型);const int f = (int)(sizeof(size_t)/8);int typenum = depth == 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;国际我;npy_intp _sizes[CV_MAX_DIM+1];for( i = 0; i < 暗淡; i++ ){_sizes[i] = 尺寸[i];}如果( cn > 1 ){/*if(_sizes[dims-1] == 1)_sizes[dims-1] = cn;别的*/_sizes[dims++] = cn;}PyObject* o = PyArray_SimpleNew(dims, _sizes, typenum);如果(!o){CV_Error_(CV_StsError, ("无法创建typenum=%d,ndims=%d的numpy数组", 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);}无效解除分配(int * refcount,uchar *,uchar *){PyEnsureGIL 吉尔;如果(!引用计数)返回;PyObject* o = pyObjectFromRefcount(refcount);Py_INCREF(o);Py_DECREF(o);}};NumpyAllocator g_numpyAllocator;枚举 { ARG_NONE = 0, ARG_MAT = 1, ARG_SCALAR = 2 };static int pyopencv_to(const PyObject* o, Mat& m, const char* name = "", bool allowND=true){//NumpyAllocator g_numpyAllocator;if(!o || o == Py_None){如果(!m.data)m.allocator = &g_numpyAllocator;返回真;}如果(!PyArray_Check(o)){failmsg("%s 不是一个 numpy 数组", name);返回假;}int typenum = PyArray_TYPE(o);int type = typenum == NPY_UBYTE ?CV_8U : typenum == NPY_BYTE ?简历_8S :typenum == NPY_USHORT ?CV_16U : typenum == NPY_SHORT ?简历_16S :typenum == NPY_INT ||typenum == NPY_LONG ?CV_32S :typenum == NPY_FLOAT ?简历_32F:typenum == NPY_DOUBLE ?CV_64F:-1;如果(类型 <0 ){failmsg("%s 数据类型 = %d 不被支持", name, typenum);返回假;}int ndims = PyArray_NDIM(o);如果(ndims >= CV_MAX_DIM){failmsg("%s 维数 (=%d) 太高", name, ndims);返回假;}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);布尔转置 = 假;for(int i = 0; i < ndims; i++){大小[i] = (int)_sizes[i];step[i] = (size_t)_strides[i];}if( ndims == 0 || step[ndims-1] > elemsize ) {大小[ndims] = 1;步骤[ndims] = elemsize;ndims++;}if( ndims >= 2 && step[0] < step[1] ){std::swap(大小[0], 大小[1]);std::swap(step[0], step[1]);转置 = 真;}if( ndims == 3 && size[2] <= CV_CN_MAX && step[1] == elemsize*size[2]){ndims--;类型 |= CV_MAKETYPE(0, size[2]);}if( ndims > 2 && !allowND ){failmsg("%s 有超过 2 个维度", name);返回假;}m = Mat(ndims, size, type, PyArray_DATA(o), step);如果( m.data ){m.refcount = refcountFromPyObject(o);m.addref();//保护原始 numpy 数组不被释放//(因为 Mat 析构函数会递减引用计数器)};m.allocator = &g_numpyAllocator;如果(转置){垫 tmp;tmp.allocator = &g_numpyAllocator;转置(m,tmp);m = tmp;}返回真;}静态 PyObject* pyopencv_from(const Mat& m){如果(!m.data)Py_RETURN_NONE;垫子温度,*p = (垫子*)&m;if(!p->refcount || p->allocator != &g_numpyAllocator){temp.allocator = &g_numpyAllocator;m.copyTo(temp);p = &temp;}p-> addref();返回 pyObjectFromRefcount(p->refcount);}ABC::ABC() {}ABC::~ABC() {}//注意 NumPy 中的 import_array() 必须被调用,否则你会遇到分段错误.ABC::ABC(const std::string &someConfigFile){//初始化代码.可能存储一些配置文件等.import_array();//这是来自 NumPy 的一个必须被调用的函数.//做其他事情}//以上转换函数取自 OpenCV.下面的函数是//我们定义的访问我们感兴趣的 C++ 代码的内容.PyObject* ABC::doSomething(PyObject* 图像){简历::垫 cvImage;pyopencv_to(图像,cvImage);//来自 OpenCV 的源代码MyCPPClass 对象;//来自 C++ 库的一些对象.cv::Mat 处理图像 = obj.process(cvImage);返回 pyopencv_from(processedImage);//来自 OpenCV 的源代码}

使用 Boost Python 创建 python 模块的代码.我从 http://jayrambhia.wordpress.com/tag/boost/ 中获取了这个和以下 Makefile::>

pysomemodule.cpp:

#include #include#include "abc.hpp"使用命名空间 boost::python;BOOST_PYTHON_MODULE(pysomemodule){class_("ABC", init()).def(init<const std::string &>()).def("doSomething", &ABC::doSomething)//doSomething 是您希望公开的 ABC 类中的方法.每个方法(或功能取决于您如何构建代码)一行.注意:您不必公开库中的所有内容,只需公开您希望对 Python 可用的内容即可.;}

最后,Makefile(在 Ubuntu 上成功编译,但应该可以在其他地方工作,可能需要进行最少的调整).

PYTHON_VERSION = 2.7PYTHON_INCLUDE =/usr/include/python$(PYTHON_VERSION)# Boost Python 包含文件和库的位置BOOST_INC =/usr/local/include/boostBOOST_LIB =/usr/local/libOPENCV_LIB = `pkg-config --libs opencv`OPENCV_CFLAGS = `pkg-config --cflags opencv`MY_CPP_LIB = lib_my_cpp_library.so目标 = pysomemoduleSRC = pysomemodule.cpp abc.cppOBJ = 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)干净的:rm -f $(OBJ)rm -f $(目标).so

成功编译库后,目录中应该有一个文件pysomemodule.so".将此 lib 文件放在 Python 解释器可访问的位置.然后,您可以导入此模块并创建上面类ABC"的实例,如下所示:

导入pysomemodulefoo = pysomemodule.ABC("config.txt") # 这将创建一个 ABC 的实例

现在,给定一个 OpenCV numpy 数组图像,我们可以使用以下命令调用 C++ 函数:

processedImage = foo.doSomething(image) # 其中参数image"是一个 OpenCV numpy 图像.

请注意,您将需要 Boost Python、Numpy dev 以及 Python dev 库来创建绑定.

以下两个链接中的 NumPy 文档在帮助人们理解转换代码中使用的方法以及为什么必须调用 import_array() 方面特别有用.特别是官方的 numpy 文档有助于理解 OpenCV 的 python 绑定代码.

http://dsnra.jpl.nasa.gov/software/Python/numpydoc/numpy-13.htmlhttp://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*)"x78x56x34x12")*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.

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

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