Calling OpenCV functions via Cython from Python 3.X.

I have managed to run OpenCV function from Python3 via Cython today, so I want to write about it. Before you start something like this, you should know you have to read at least header files of C library to write right definition in Cython,because Cython does not do that for you. I am using virtualenv, python from pythonz, and home-brewed OpenCV. Almost everything of C++ works just fine with Cython, but still, you should do some Voodoo things like for integer template argument.
ctypedef void* int_parameter
ctypedef int_parameter two "2"
ctypedef Point_[float, two] Point2f
Now, main sample Cython code is following.
#===== testopencv.pyx ========
import numpy as np
cimport numpy as np # for np.ndarray
from libcpp.string cimport string
from libc.string cimport memcpy

cdef extern from "opencv2/core/core.hpp":
  cdef int  CV_WINDOW_AUTOSIZE
  cdef int CV_8UC3

cdef extern from "opencv2/core/core.hpp" namespace "cv":
  cdef cppclass Mat:
    Mat() except +
    void create(int, int, int)
    void* data

cdef extern from "opencv2/highgui/highgui.hpp" namespace "cv":
  void namedWindow(const string, int flag)
  void imshow(const string, Mat)
  int  waitKey(int delay)


cdef void ary2cvMat(np.ndarray ary, Mat& out):
  assert(ary.ndim==3 and ary.shape[2]==3, "ASSERT::3channel RGB only!!")
  ary = np.dstack((ary[...,2], ary[...,1], ary[...,0])) #RGB -> BGR
  
  cdef np.ndarray[np.uint8_t, ndim=3, mode = 'c'] np_buff = np.ascontiguousarray(ary, dtype = np.uint8)
  cdef unsigned int* im_buff = <unsigned int*> np_buff.data
  cdef int r = ary.shape[0]
  cdef int c = ary.shape[1]
  out.create(r, c, CV_8UC3)
  memcpy(out.data, im_buff, r*c*3)


cdef showMat(Mat m):
  namedWindow( "WIN", CV_WINDOW_AUTOSIZE )
  imshow( "WIN", m )
  waitKey(0)

def openImage(pil_img):
  cdef Mat m
  ary2cvMat(np.array(pil_img), m)
  showMat(m)
The last function openImage(pil_image) will open pil_image in different window. (I know you can do this without OpenCV. Just for a sample.) This sample changes pil_image to numpy array and then convert it to OpenCV's cv::Mat. To compile this, you need a setup file like this.
#======== setup.py ===========
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
from Cython.Build import cythonize
import numpy
import subprocess

proc_libs = subprocess.check_output("pkg-config --libs opencv".split())
proc_incs = subprocess.check_output("pkg-config --cflags opencv".split())
libs = [lib for lib in str(proc_libs, "utf-8").split()]
#print("OPENCV LIBS=", libs)

setup(
  cmdclass = {'build_ext': build_ext},
  ext_modules = cythonize(Extension("opencvsample",
    sources = ["opencvsample.pyx"],
    language = "c++",
    include_dirs=[numpy.get_include(), "/usr/local/Cellar/opencv/2.4.5/include/"],
    extra_link_args=libs
    )
  )
)
To compile and execute, you should do
python setup.py build_ext --inplace
python -c "import testopencv as to; from PIL import Image; to.openImage(Image.open('test.jpg'))"
I spent several days to work this all through, so I hope this might be helpful to somebody. Feedbacks are always appreciated.
 ---------
update: Mar 3 '14 <unsinged int> was missing. Thank you, Andrey.

Comments

Andrey said…
Thanks for this. I followed your tutorial, and this shows error when compiling:

testopencv.pyx:28:38: Cannot assign type 'char *' to 'unsigned int *'

at:
cdef unsigned int* im_buff = np_buff.data

Do you know how I can fix this error?
Maker wannabe said…
You are right.
The correct code should be
cdef unsigned int* im_buff = <unsigned int*> np_buff.data

<...> was omitted by tag-thing. I'll fix it.
Thanks for the comment!
Andy Jones said…
Just posting to say thanks, this is extremely useful!
John said…
Great work!
Anonymous said…
Hi thank you for your tutorial, how about the opencv that installed using pip?, where is the include directory?
Unknown said…
very helpful thanks!

Popular posts from this blog

Showing CPU/Memory usage on tmux status bar(tmuxのステータスバーにCPUとMemoryの使用状況を表示する)

Subclassing and Signal connect on a same widget crashes PySide application on exit.