• Home
  • >
  • OpenCV 3
  • >
  • How to convert your OpenCV C++ code into a Python module

How to convert your OpenCV C++ code into a Python module

Imagine writing a Computer Vision library using OpenCV. You want your code to be easily portable to Linux, Mac, Windows, iOS, Android and even embedded devices. So you choose to build your library in C++ using OpenCV. Excellent choice! Along comes a client who wants to license your entire library

OpenCV C++ Code to Python Module

Imagine writing a Computer Vision library using OpenCV. You want your code to be easily portable to Linux, Mac, Windows, iOS, Android and even embedded devices. So you choose to build your library in C++ using OpenCV. Excellent choice!

Along comes a client who wants to license your entire library but they want it delivered as a Python module. You say, “No problem!” and search the internet for a solution. BOOM! you land on this post! Awesome! We are going to learn how to build a Python module from your OpenCV C++ code.

This tutorial has been tested on Linux 16.04 with Python 3.5.2 and OpenCV 3.4.

Python Bindings for C++ code

The neat thing about a library written in a system programming language like C++ is that there are standard ways of creating a binding for this library in a higher level language like Python. Before we jump into our solution, I want to briefly explain how to create Python bindings for your C++ code. If you want to understand the technical nitty gritty of how your generic C++ code can be used to build a python module, check out this tutorial. To summarize the steps, you need the following pieces

  1. Write a Python callable wrapper function: This function parses the arguments and calls the actual C/C++ function. It also handles any errors.
  2. Register functions and methods : Next you need use PyMethodDef to register the function in the module’s symbol table.
  3. Create an init function: Finally, we need use Py_InitModule to create an initialization function for the module.

This is all fine and dandy but if you have a large library, doing this by hand is cumbersome and error prone. So we will generate most of this code automatically using a python script.

Python bindings for OpenCV based C++ code

As you may know, OpenCV is written in C++. The good folks at OpenCV have created bindings for Python which enables us to compile OpenCV into a Python module (cv2).

Wouldn’t it be cool if we could piggyback on the work already done by the community? So we took inspiration from this tutorial and created a simplified example.

We will use the same scripts used by OpenCV to generate their Python module and minimally change their main wrapper file (cv2.cpp) to create our own module. We will call this module bv after my consulting company Big Vision LLC.

There are huge benefits to this approach.

  1. Efficient and consistent mapping: Having efficient Python types mapped to efficient C++ types consistent with OpenCV’s mappings.
  2. Suitable idioms: Having idioms suitable to the library means we are leveraging the hard work done by the community. For instance, in OpenCV using the argument type to be OutputArray instead of Mat in a function definition automatically makes it an a output returned by the function in the exported Python module.
  3. Easy function and class definition : This allows easy mapping of classes and functions. We have to define them only once in C++. If done by hand you will end up doing work equivalent to defining a class again in Python.

The code is inside the pymodule directory of the code base. The directory structure is shown on the left.

File Structure for Python Module

It has the following files

  1. bv.cpp, pycompat.hpp : bv.cpp is a slightly modified version of the wrapper file (cv2.cpp) that comes with OpenCV. It uses pycompat.hpp for Python 2 / 3 compatibility checks.
  2. bvtest.py : Python code for testing our module (bv) once we have built it. In our example, we have a function bv.fillHoles and a class bv.bv_Filters exposed in the Python module. The C++ implementations of fillHoles and Filters are in src/bvmodule.cpp.
Download Code To easily follow along this tutorial, please download code by clicking on the button below. It's FREE!
import cv2 
import sys sys.path.append('build') 
import bv im = cv2.imread('holes.jpg', cv2.IMREAD_GRAYSCALE) 
imfilled = im.copy() 
bv.fillHoles(imfilled) filters = bv.bv_Filters() 
imedge = filters.edge(im) 
cv2.imshow("Original image", im) 
cv2.imshow("Python Module Function Example", imfilled) 
cv2.imshow("Python Module Class Example", imedge) 
cv2.waitKey(0)

3. gen2.py, hdr_parser.py : The Python bindings generator script (gen2.py) calls the header parser script (hdr_parser.py). These files are provided as part of the OpenCV source files. According to the OpenCV tutorial, “this header parser splits the complete header file into small Python lists. So these lists contain all details about a particular function, class etc.” In other words, these scripts automatically parse the header files and register the functions, classes, methods etc. with the module.
4. headers.txt: A text file containing all the header files to be compiled into the module. In our example, it contains just one line src/bvmodule.hpp.
5. holes.jpg: Example image used by our Python module test script bvtest.py
6. src/bvmodule.cpp: This cpp file contains the functions and class definitions. In this example, we implemented a function fillHoles and a class Filters with just one method called edge. The function fillHoles takes a gray scale image and fills any holes (dark regions surround by white areas). The method edge simply performs Canny edge detection.

#include"bvmodule.hpp"

namespace bv
{
  void fillHoles(Mat &im)
  {
    Mat im_th;

    // Binarize the image by thresholding
    threshold(im, im_th, 128, 255, THRESH_BINARY);
    // Flood fill
    Mat im_floodfill = im_th.clone();
    floodFill(im_floodfill, cv::Point(0,0), Scalar(255));

    // Invert floodfilled image
    Mat im_floodfill_inv;
    bitwise_not(im_floodfill, im_floodfill_inv);

    // Combine the two images to fill holes
    im = (im_th | im_floodfill_inv);
  }

  void Filters::edge(InputArray im, OutputArray imedge) 
  {
    // Perform canny edge detection
    Canny(im,imedge,100,200); 
  }

  Filters::Filters() 
  {
  }
}

7. src/bvmodule.hpp: Not all functions and methods in your code need to be exposed to the Python module. This header file explictly mentions which ones we want to export.

#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

namespace bv
{
	CV_EXPORTS_W void fillHoles(Mat &mat);
	class CV_EXPORTS_W Filters 
	{
	public:
		CV_WRAP Filters();
		CV_WRAP void edge(InputArray im, OutputArray imedge);
	};
}

Note: We used InputArray and OutputArray instead of Mat in the edge method of the class. Doing so makes imedge the output in the exported Python code and we are able to use this line in the Python example above.

imedge = filters.edge(im)

Steps for building the Python module

We are now ready to go over the steps for building our Python module.

  1. Step 1: Put your c++ source code and header files inside the src directory.
  2. Step 2: Include your header file in headers.txt
  3. Step 3: Make a build directory.
mkdir build

Step 4: Use gen2.py to generate the Python binding files. You need to specify the prefix (pybv), the location of the temporary files (build) and the location of the header files (headers.txt).

python3 gen2.py pybv build headers.txt

This should generate a whole bunch of header files with prefix pybv_*.h. If you are curious, feel free to inspect the generated files.

Step 5: Compile the module 

g++ -shared -rdynamic -g -O3 -Wall -fPIC \ bv.cpp src/bvmodule.cpp \ -DMODULE_STR=bv -DMODULE_PREFIX=pybv \ -DNDEBUG -DPY_MAJOR_VERSION=3 \ `pkg-config --cflags --libs opencv`  \ `python3-config --includes --ldflags` \ -I . -I/usr/local/lib/python3.5/dist-packages/numpy/core/include \ -o build/bv.so

In Line 2 we specify the source files. In Line 3, we set the module name using MODULE_STR and MODULE_PREFIX (pybv) used in the previous step. In Line 4 we specify the Python version. In Line 5 we include OpenCV library and the header files and in Line 6 we include Python 3 related header files and some standard libraries. In my machine, numpy was not in the included path and so I had to add an extra Line 7 for numpy. Your location for numpy may be different. Finally, in Line 8 we specify the location of the output module (build/bv.so).

Testing Python Module

The script bvtest.py load the module and uses the function bv.fillHoles and the exported class bv.bv_Filters. If you compile everything correctly and run the python script, you will see the result shown below.

Python Module from OpenCV C++ Example

Great! we have built module and are ready to test.



Read Next

VideoRAG: Redefining Long-Context Video Comprehension

VideoRAG: Redefining Long-Context Video Comprehension

Discover VideoRAG, a framework that fuses graph-based reasoning and multi-modal retrieval to enhance LLMs' ability to understand multi-hour videos efficiently.

AI Agent in Action: Automating Desktop Tasks with VLMs

AI Agent in Action: Automating Desktop Tasks with VLMs

Learn how to build AI agent from scratch using Moondream3 and Gemini. It is a generic task based agent free from…

The Ultimate Guide To VLM Evaluation Metrics, Datasets, And Benchmarks

The Ultimate Guide To VLM Evaluation Metrics, Datasets, And Benchmarks

Get a comprehensive overview of VLM Evaluation Metrics, Benchmarks and various datasets for tasks like VQA, OCR and Image Captioning.

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.

Subscribe to receive the download link, receive updates, and be notified of bug fixes

Which email should I send you the download link?

 

Get Started with OpenCV

Subscribe To Receive

We hate SPAM and promise to keep your email address safe.​