C++/Python Image Pixel Manipulation Benchmarks  Hot PDF Print E-mail
Tag it:
Delicious
Furl it!
Digg
NewsVine
Reddit
YahooMyWeb
Technorati
Articles Reviews Python
Written by jackvalmadre.wordpress.com   
Tuesday, 23 September 2008

I’ve been playing around with Python a bit lately for a new project. I got up to a stage where I needed image manipulation on a per-pixel level. Here’s a few different approaches I tried out, with performance results and code snippets.


The benchmark test was a transformation from rectangular to polar co-ordinates. Input image was 3328×1664 JPEG (~480kB), output was 3327×3327 JPEG (all methods produced ~790kB).

Python

I implemented it purely in Python first (using the Python Imaging Library PIL.Image module), but it took ages for higher resolution images.

from PIL import Image
 
def RectToPolar(oldImg):
    w, h = oldImg.size
    oldPixels = oldImg.load()
    ...
    diameter = 2 * h - 1
    newImg = Image.new("RGB", (diameter, diameter), bg)
    newPixels = newImg.load()
    ...
    for x in range(diameter):
        for y in range(diameter):
        ...
        newPixels[x, y] = oldPixels[s, (h-1)-r]
    return newImg
 
original = Image.open("../Home.jpg")
warped = RectToPolar(original)
warped.save("warped.jpg")

Python and C++ (Boost)

I decided to try using Boost’s Python library, so I could write the actual processing code in C++ for improved performance. I originally tried loading the image in Python and passing the pixel data to and from C++ as a string. I expected to cop a performance hit doing this, but the Python libraries for loading an image (with pixel access) are really nice and simple, so I gave it a shot.

class ImageMapper
{
public:
    ImageMapper(std::string pixelStr, int width, int height);
    ...
    std::string rectToPolar()
    {
        std::string result;
        for (int y = max; y >= min; y--)
        {
            for (int x = min; x <= max; x++)
            {
                ...
                result += pixel(u, v);
            }
        }
        return result;
    }
};
 
BOOST_PYTHON_MODULE(imagemap)
{
    class_<ImageMapper>("ImageMapper", init<std::string, int, int>())
        .def("pixel", &ImageMapper::pixel)
        .def("rectToPolar", &ImageMapper::rectToPolar)
    ;
}
from PIL import Image
import imagemap
 
img1 = Image.open("../Home.jpg")
w, h = img1.size
mapper = imagemap.ImageMapper(img1.tostring(), w, h)
data = mapper.rectToPolar()
img2 = Image.fromstring("RGB", (3327, 3327), data)
img2.save("warped.jpg")

To compile against the Boost Python libraries:

g++ -Wall -O3 imagemap.cpp -shared -o imagemap.so -I/usr/include -I/usr/include/python2.5 -L/usr/lib -lpython2.5 -lboost_python

C++ and GTK/gtkmm

Finally, I tried loading, processing and saving the image purely in C++, using GTK and gtkmm (GTK wrapped up in C++).

#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
...
 
class ImageMapper
{
private:
    GdkPixbuf *original_;
    GdkPixbuf *warped_;
    static guchar *get_pixel(GdkPixbuf *pixbuf, int x, int y)
    {
        return gdk_pixbuf_get_pixels(pixbuf) +
            y * gdk_pixbuf_get_rowstride(pixbuf) +
            x * gdk_pixbuf_get_n_channels(pixbuf);
    }
 
public:
    ImageMapper(const char *filename)
    {
        GError *error = NULL;
        original_ = gdk_pixbuf_new_from_file(filename, &error);
        if (original_ == NULL)
            throw runtime_error("Could not load image");
    }
    ...
    void rectToPolar()
    {
        warped_ = gdk_pixbuf_new(GDK_COLORSPACE_RGB, false, 8, diameter, diameter);
        if (warped_ == NULL)
            throw runtime_error("Unable to create output image");
        for (int j = 0; j < diameter; j++)
        {
            for (int i = 0; i < diameter; i++)
            {
                memcpy(
                    get_pixel(warped_, i, j),
                    get_pixel(original_, u, v),
                    channels * sizeof(guchar));
            }
        }
    }
 
    void transform(const char *filename, const char *type)
    {
        rectToPolar();
        GError *error = NULL;
        gboolean result = gdk_pixbuf_save(warped_, filename, type,
            &error, "quality", "75", NULL);
        if (!result)
            throw runtime_error("Could not save image");
 
    }
};
 
int main(int argc, char *argv[])
{
    gtk_init (&argc, &argv);
    ImageMapper mapper = ImageMapper("../Home.jpg");
    mapper.transform("warped.jpg", "jpeg");
    return 0;
}

To compile in Linux, it was just a matter of:

g++ -Wall -O3 warpgtk.cpp -o warpgtk `pkg-config --cflags --libs gtk+-2.0`

There was just a few differences with gtkmm, namely…

Glib::RefPtr<Gdk::Pixbuf> original_;
Glib::RefPtr<Gdk::Pixbuf> warped_;
static guchar *get_pixel(Glib::RefPtr<Gdk::Pixbuf> &pixbuf, int x, int y)
{
    return pixbuf->get_pixels() +
        y * pixbuf->get_rowstride() +
        x * pixbuf->get_n_channels();
}
original_ = Gdk::Pixbuf::create_from_file(filename);
warped_ = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, diameter, diameter);

And compiling: (might need to grab a few repository packages)

g++ -Wall -O3 warpgtkmm.cpp -o warpgtkmm `pkg-config gtkmm-2.4 --cflags --libs`

The Results

  1. 3.04s; C++ and GTK
  2. 3.53s; C++ and gtkmm
  3. 8.89s; C++ with Python data via Boost
  4. 32.9s; Python

So I’ll probably go with either GTK or gtkmm. The slight speed penalty is probably worth it for the C++iness.

 

Source of the article


User reviews

There are no user reviews for this item.

Add new review




Powered by jReviews

 
Next >