2012-10-18 21 views
17

Tôi đang cố gắng viết một trình bao bọc python cho một số mã C++ sử dụng OpenCV nhưng tôi gặp khó khăn khi trả về kết quả, đó là đối tượng OpenCV C++ Mat, trình thông dịch python.Viết các ràng buộc Python cho mã C++ sử dụng OpenCV

Tôi đã xem nguồn của OpenCV và tìm thấy tệp cv2.cpp có chức năng chuyển đổi để thực hiện chuyển đổi và chuyển đổi giữa PyObject * và Mat của OpenCV. Tôi đã sử dụng các chức năng chuyển đổi đó nhưng có lỗi phân đoạn khi tôi cố gắng sử dụng chúng. Về cơ bản tôi cần một số gợi ý/mã mẫu/tham khảo trực tuyến về cách giao diện mã python và C++ sử dụng OpenCV, cụ thể với khả năng trả về C++ Mat của OpenCV cho trình thông dịch python hoặc có thể là gợi ý về cách/ở đâu bắt đầu điều tra nguyên nhân của lỗi phân đoạn.

Hiện tại tôi đang sử dụng Boost Python để bọc mã.

Cảm ơn trước mọi câu trả lời.

Mã liên quan:

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

Các chức năng chuyển đổi được lấy từ nguồn của OpenCV sau. Mã chuyển đổi cung cấp lỗi phân đoạn tại dòng nhận xét với "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); 
} 

chương trình thử nghiệm python của tôi:

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

Trả lời

29

tôi đã giải quyết được vấn đề vì vậy tôi nghĩ tôi sẽ chia sẻ nó ở đây với những người có thể có cùng một vấn đề.

Về cơ bản, để loại bỏ lỗi phân đoạn, tôi cần gọi hàm import_array() của numpy.

Các "cao cấp" xem để chạy mã C++ từ python là thế này:

Giả sử bạn có một hàm foo(arg) trong python đó là một ràng buộc đối với một số chức năng ++ C. Khi bạn gọi foo(myObj), phải có một số mã để chuyển đổi đối tượng python "myObj" thành biểu mẫu mà mã C++ của bạn có thể hoạt động. Mã này thường được tạo tự động bằng cách sử dụng các công cụ như SWIG hoặc Boost :: Python. (Tôi sử dụng Boost :: Python trong các ví dụ bên dưới.)

Bây giờ, foo(arg) là một ràng buộc python cho một số hàm C++. Hàm C++ này sẽ nhận một con trỏ chung là PyObject làm đối số. Bạn sẽ cần phải có mã C++ để chuyển đổi con trỏ PyObject này thành đối tượng C++ tương đương. Trong trường hợp của tôi, mã python của tôi chuyển một mảng khối u OpenCV cho một hình ảnh OpenCV làm đối số cho hàm. Biểu mẫu "tương đương" trong C++ là một đối tượng OpenCV C++ Mat. OpenCV cung cấp một số mã trong cv2.cpp (được sao chép dưới đây) để chuyển đổi con trỏ PyObject (đại diện cho mảng numpy) thành một C++ Mat. Các kiểu dữ liệu đơn giản như int và string không cần người dùng viết các hàm chuyển đổi này vì chúng được tự động chuyển đổi bằng Boost :: Python.

Sau khi con trỏ PyObject được chuyển đổi thành dạng C++ phù hợp, mã C++ có thể hoạt động trên đó. Khi dữ liệu phải được trả về từ C++ thành python, một tình huống tương tự phát sinh khi mã C++ là cần thiết để chuyển đổi biểu diễn C++ của dữ liệu thành dạng số PyObject. Boost :: Python sẽ xử lý phần còn lại trong việc chuyển đổi PyObject thành dạng python tương ứng. Khi foo(arg) trả về kết quả trong python, nó ở dạng có thể sử dụng được bằng python. Đó là nó.

Đoạn mã dưới đây cho thấy cách bọc một lớp C++ "ABC" và hiển thị phương thức "doSomething" của nó trong mảng numpy (cho hình ảnh) từ python, chuyển nó sang C++ Mat của OpenCV, thực hiện xử lý, chuyển đổi kết quả cho PyObject *, và trả lại nó cho trình thông dịch python. Bạn có thể trưng ra nhiều hàm/phương thức bạn muốn (xem chú thích trong đoạn mã dưới đây).

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 
} 

Mã này sử dụng Boost Python để tạo ra các mô-đun python. Tôi mất này và Makefile sau từ 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. 
    ; 
} 

Và cuối cùng, Makefile (biên soạn thành công trên Ubuntu nhưng nên làm việc ở nơi khác có thể với điều chỉnh tối thiểu).

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 

Sau khi bạn biên soạn thành công thư viện, bạn nên có tệp "pysomemodule.so" trong thư mục. Đặt tệp lib này ở một nơi có thể truy cập được bởi trình thông dịch python của bạn. Sau đó bạn có thể nhập module này và tạo ra một thể hiện của lớp "ABC" ở trên như sau:

import pysomemodule 

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

Bây giờ, đưa ra một NumPy hình ảnh mảng OpenCV, chúng ta có thể gọi hàm C++ sử dụng:

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

Lưu ý rằng bạn sẽ cần Boost Python, Numpy dev, cũng như thư viện Python dev để tạo các ràng buộc.

Các tài liệu NumPy trong hai liên kết sau đặc biệt hữu ích trong việc giúp người ta hiểu các phương pháp được sử dụng trong mã chuyển đổi và tại sao import_array() phải được gọi. Đặc biệt, tài liệu gọn gàng chính thức là hữu ích trong việc hiểu mã ràng buộc python của 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

Hope this helps.

+0

Xin chào, lightalchemist, cảm ơn bạn đã đăng giải pháp của mình.Tôi đã đến một giải pháp tương tự cho cùng một câu hỏi với OpenCV 2.4.3 (lấy các hàm pyopencv_to và pyopencv_from từ cv2.cpp), và phơi bày một hàm. Các mô-đun tải được trong ipython, chức năng có thể nhìn thấy ở đó, nó phân tích các đối số được rồi, nhưng bị treo ngay sau khi nó đạt đến PyEnsureGIL. Tôi đã thử giải pháp của bạn, và chức năng pyopencv_to cũ hơn hoạt động (nó thực hiện), nhưng bị treo khi cố gắng xuất. Tôi sẽ đăng một câu hỏi riêng biệt và đặt một liên kết ở đây cho bạn trong một giây trong trường hợp bạn nghĩ rằng bạn có thể xem vấn đề là gì. –

+0

Đây là liên kết đến câu hỏi của tôi: http://stackoverflow.com/questions/13745265/exposing-opencv-based-c-function-with-mat-numpy-conversion-to-python –

5

Tôi hy vọng điều này sẽ giúp mọi người tìm kiếm một cách nhanh chóng và dễ dàng.

Dưới đây là github repo với mã C++ mở mà tôi đã viết để hiển thị mã bằng cách sử dụng lớp Mat của OpenCV với ít đau nhất có thể.

[Cập nhật] Mã này hiện hoạt động cho OpenCV 2.XOpenCV 3.X. Hỗ trợ CMake và thử nghiệm cho Python 3.X hiện cũng có sẵn.

+1

Bạn mã cho phiên bản 3.x là tuyệt quá! Lúc đầu tôi nghĩ rằng nó có memleak, nhưng cuối cùng nó bật ra rằng lỗi là trong mã của tôi;) Và cho phiên bản 2.xi đề nghị này https://github.com/spillai/numpy-opencv-converter - tốt mã với bộ chuyển đổi cho nhiều loại (như Mat, Mat3f, Point2d vv - Thiên Chúa chúc lành cho các mẫu :)). – cyriel

+0

Tôi cũng gặp lỗi phân đoạn khi sử dụng mã của bạn. Thêm một 'pbcvt' nhập khẩu trong mã Python của tôi _before_ nhập khẩu thư viện của riêng tôi cuối cùng đã sửa nó. (Nó không phải là trực quan cho tôi tại sao điều này là cần thiết, tuy nhiên, nó là.) –

+0

@ManuCJ, bạn nói đúng, đó là thực sự kỳ lạ. pbcvt thực sự được thiết kế như một mẫu mã cho thư viện của riêng bạn. Do đó, tôi sẽ xem xét những gì pbcvt có mã của bạn không có (một số trong những định nghĩa trong các tiêu đề và các tập tin cpp, có lẽ?). Nếu bạn vẫn bị ảnh hưởng bởi vấn đề đó, tôi khuyên bạn nên gửi nó trên GitHub với một ví dụ mã tối thiểu. –

Các vấn đề liên quan