OpenCV and Python

by JS

You may have come across the following issue with OpenCV Python bindings:

image = cv.cvCreateImage(size, cv.IPL_DEPTH_8U, 3)
image.imageData = data # set some data

Trying to set the “data” of an OpenCV image directly fails. The problem is in the underlying SWIG code that attempts to make the imageData field available for writing at the python level. You can see in CVS that the code gets fixed immediately after the 1.0 release.

But wait! Doesn’t OpenCV come with an adaptors.py that is supposed to provide methods for translating between various Python data types (NumPy < => PIL < => IplImage) ?

A quick look at the code:

def PIL2Ipl(input):
    """Converts a PIL image to the OpenCV/IPL CvMat data format.
 
    Supported input image formats are:
        RGB
        L
        F
    """
 
    if not isinstance(input, PIL.Image.Image):
       raise TypeError, 'must be called with PIL.Image.Image!'
 
    size = cv.cvSize(input.size[0], input.size[1])
 
    # mode dictionary:
    # (pil_mode : (ipl_depth, ipl_channels, color model, channel Seq)
    mode_list = {
        "RGB" : (cv.IPL_DEPTH_8U, 3),
        "L"   : (cv.IPL_DEPTH_8U, 1),
        "F"   : (cv.IPL_DEPTH_32F, 1)
        }
 
    if not mode_list.has_key(input.mode):
        raise ValueError, 'unknown or unsupported input mode'
 
    modes = mode_list[input.mode]
 
    result = cv.cvCreateImage(
        size,
        modes[0], # depth
        modes[1]  # channels
        )
 
    # set imageData
    result.imageData=input.tostring() #FAIL!
 
    return result

As you can see, adaptors.py tries to set imageData as well, and so fails to convert from any format into IplImage. Your options are to work from the repository version of OpenCV or to back port the change from the repository to your local installation. Both require compiling from source and so as you may imagine, neither case is convenient when working with libcv as installed from your favorite package manager of choice.

A third option is to back port the changes from the repository to your own custom swig module whose only goal is to implement a function that sets the imageData field properly. That’s the approach I took, and though it took a considerable amount of time to implement (because I don’t really understand SWIG), the end result did not require many lines of code. Here’s my iplimage.i file:

%module iplimage
%{
#include "cv.h"
void set(CvMat *self, char *string);
%}
 
void set(CvMat *self, char *string);

And here’s iplimage.c copied with minor changes directly out of the repository for OpenCV:

#include "cv.h"
 
void set(CvMat * self, char *string)
{
	int depth = CV_MAT_DEPTH(self-&gt;type);
	int cn = CV_MAT_CN(self-&gt;type);
	int step = self-&gt;step ? self-&gt;step : CV_ELEM_SIZE(self-&gt;type) * self-&gt;cols;
	long line;
	long pixel;
 
	if (depth == CV_8U &amp;&amp; cn==3){
		// RGB case
		// The data is reordered beause OpenCV uses BGR instead of RGB
 
		for (line = 0; line &lt; self-&gt;rows; ++line)
			for (pixel = 0; pixel &lt; self-&gt;cols; ++pixel)
			{
				// In OpenCV the beginning of the lines are aligned
				// to 4 Bytes. So use step instead of cols.
				long position = line*step + pixel*3;
				long sourcepos = line*self-&gt;cols*3 + pixel*3;
				self-&gt;data.ptr[position  ] = string[sourcepos+2];
				self-&gt;data.ptr[position+1] = string[sourcepos+1];
				self-&gt;data.ptr[position+2] = string[sourcepos  ];
			}
	}
	else if (depth == CV_8U &amp;&amp; cn==1)
	{
		// Grayscale 8bit case
 
		for (line = 0; line &lt; self-&gt;rows; ++line)
		{
			// In OpenCV the beginning of the lines are aligned
			// to 4 Bytes. So use step instead of cols.
			memcpy
				(
				 self-&gt;data.ptr + line*step,
				 string + line*self-&gt;cols,
				 step
				);
		}
	}
	else if ( depth == CV_32F )
	{
		// float (32bit) case
		for (line = 0; line &lt; self-&gt;rows; ++line)
		{
			// here we don not have to care about alignment as the Floats are
			// as long as the alignment
			memcpy
				(
				 self-&gt;data.ptr + line*step,
				 string + line*self-&gt;cols*sizeof(float),
				 step
				);
		}
	}
	else if ( depth == CV_64F )
	{
		// double (64bit) case
		for (line = 0; line &lt; self-&gt;rows; ++line)
		{
			// here we don not have to care about alignment as the Floats are
			// as long as the alignment
			memcpy
				(
				 self-&gt;data.ptr + line*step,
				 string + line*self-&gt;cols*sizeof(double),
				 step
				);
		}
	}
	else
	{
	}
}

An example makefile that works on my system:

CFLAGS= `pkg-config --cflags opencv`
LDFLAGS= `pkg-config --libs opencv` -L.
CXX=g++
 
_iplimage.so: iplimage.c
	swig -python iplimage.i
	$(CXX) $(CFLAGS) -I/usr/include/python2.5 -c iplimage.c iplimage_wrap.c
	$(CXX) $(LDFLAGS) -shared iplimage.o iplimage_wrap.o -o _iplimage.so

If you manage to build the module, you can then invoke it from python using a call to iplimage.set(image,data) to set the imageData field for real. You can use the default OpenCV Python bindings that come with your package manager and simply install this small additional module. Hopefully, we’ll see a new release of OpenCV soon that negates the need for any of these acrobatics.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Tumblr
  • Twitter