Writing Python wrappers for C++ (OpenCV) code, part I.

As I mentioned a couple of posts ago, we love to use C++ to make our methods to run fast. And, as many Vision engineers, we use OpenCV. However, there are a couple of things that can be tricky in C++, such as web services. Instead, for an upcoming API that we are developing (!), we decided to go with Python for the web part while not losing the performance gain of C++. Hence, there is a need to build a Python wrapper for the C++ code.

There are a couple of library options to that, but for our needs the Boost.Python is by far the best fit. The major aspect of writing these wrappers is to convert the data from/to Python to/from C++. The first conversion that it will be required is between matrices types: cv::Mat from OpenCV to/from numpy nd arrays.

Fortunately, there is a very useful lib that implements this converter called numpy-opencv-converter. However, there are a lot of these conversions that need to be implemented manually as it is impossible to predict the combination of data types one can write in his/her code.

We will begin with a simple example that uses the boost library to convert the Python parameters (tuples) to a C++ function (cv::Rect).

The definition of the method is this:

cv::Mat FaceDetection::getPose(const cv::Mat &image, const cv::Rect &rect);

Notice that, using the converter lib mentioned above, we will not have problems for the return value and the image parameter as they are cv::Mat. However, the Python code has no idea what is a cv::Rect, therefore we need a helper function to call this method.

Usually, you can export a method using boost.python as simple as this:

py::class_("FaceDetection")
       .def("getPose", &FaceDetection::getPose)

But again, if we do this without a converter (will be discussed in Part II), there will be a problem when calling this method. Now, we can create a helper function to allow the conversion to cv::Rect when calling this method. In the Python version of OpenCV, a rectangle is defined by tuples, so the helper function becomes:

cv::Mat FaceDetection_getPose(FaceDetection& self, const cv::Mat &image, py::tuple rect) {
 py::tuple tl = py::extract(rect[0])();
 py::tuple br = py::extract(rect[1])();

 int tl_x = py::extract(tl[0])();
 int tl_y = py::extract(tl[1])();
 int br_x = py::extract(br[0])();
 int br_y = py::extract(br[1])();

 cv::Rect cv_rect(cv::Point(tl_x, tl_y), cv::Point(br_x, br_y));

 return self.getPose(image, cv_rect);
}

Now, if I call this function with a tuple the system knows what to do. The only thing is to bind this helper function to the method of our class:

py::class_("FaceDetection")
       .def("getPose", &FaceDetection_getPose)

And voilà. I can call the method FaceDetection.getPose(…) from the Python (once the module is imported, of course) without any problem. This is nice and all, but you may be wondering if you have to do this kind of functions every time your data is not natively support by the boost.python. The answer is no, and it is fairly simple to create some converters for your datatype. We’ll show that in a future post, Part II.

2 comments

  1. Manuel · October 1, 2015

    Hi, great post, I was just figuring out what the heck is Boost.Python and how to use it. Did you manage to create part2?

    • meerkat · October 1

      Yes, it will be out in a couple of weeks.