C++/Python Image Pixel Manipulation Benchmarks

September 13, 2008 by jackvalmadre

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.

Astronomy Symbols in TeX

September 9, 2008 by jackvalmadre

I’ve been hoping to do up the MECH2210 orbital mechanics project in TeX, but first I had to work out how to do those special planet symbols. I tracked them down in a LaTeX symbol list (pdf) (page 68), in a package called mathabx. There’s also astronomy symbols in the wasysym and marvosym packages, which are included in texlive-fonts-recommended from the Ubuntu repositories, but their Earth and/or Sun symbols weren’t consistent with what we used in lectures.

The mathabx font isn’t included in any software repositories that I could see, so I downloaded it from the ctan.org catalogue and unzipped to my desktop. Installing it was a tad tricky, I worked from the README and a post in the Ubuntu forums.

$ cd /home/jack/Desktop/mathabx/
$ TEXMF=/usr/share/texmf-texlive

$ sudo mkdir $TEXMF/fonts/source/public/mathabx/
$ sudo cp source/*.mf $TEXMF/fonts/source/public/mathabx
$ sudo cp texinputs/mathabx.* $TEXMF/tex/generic/misc/

$ cd $TEXMF
$ sudo mktexlsr

Now in TeX…

\usepackage{mathabx}
...
$\Sun$ Sun
$\Earth$ Earth
$\Moon$ Moon
$\Venus$ Venus
$\Mars$ Mars

And tadah!

Planet Symbols in TeX

Planet symbols in TeX

Talk by Microsoft at UQ this Thursday

September 2, 2008 by jackvalmadre
Windoze

Windoze

Carrie Read from Microsoft Recruiting is doing a presentation this Thursday (4 September), 1-2pm in GP South (building 78), room 622. Definitely worth checking out if you’re software-inclined! There will be pizza :)

Open to anyone, except maybe unco Hungarian egg-throwing nerds.

Team Project I: Killer Satellite Robot

August 13, 2008 by jackvalmadre

The second year mechatronics team project at the University of Queensland is to build a prototype for a defence satellite. At the end of the semester, the robot must demonstrate the ability to locate targets (infrared emitting bullseyes) and shoot them with a laser, the robot itself hanging from the test frame. The targets can be about 1-3m away.

The innermost zone on the target (about 2cm in diameter) is worth 10 points, the next is 5 points and the outer area is 1 point. The satellite has to hold the laser on the target for a minimum of 2 seconds. The least valuable zone it shoots in this time is counted. To get a 7 for the product, you need to score 200 points in 5 minutes (1 point is a pass).

No external propulsion devices are permitted, so all teams went with an inertial flywheel drive. They supplied us with a nice little Faulhaber gearmotor. We used an L298 motor controller with PWM operating at around 15kHz (nice and audible :P ). The flywheels designs were all submitted to the uni workshop and lathed out of stainless steel.

Gearmotor attached to the flywheel and mounting plate.

Gearmotor attached to flywheel and mounting plate.

The course is also an introduction to simple control systems, with most teams employing some form of PID (proportional integral derivative) control. We programmed an Atmel AVR ATmega8 microcontroller in C. It was hooked up to the computer via RS232 (and a long wire) for live debugging. Shane put together a great app in C# to graph live sensor data and PID data. A pair of phototransistors was used to locate the targets, the difference between sensors giving the error signal.

The PCB’s were designed in Altium and the uni organised for them to be done up externally. Double sided components to save money – one free percent per $10 under budget. Final board size was 2×3″ (50×75mm) The total budget was $100, not including the motor or any mechanical components we designed.

Printed Circut Board, Bottom

Printed Circut Board, Bottom

Printed Circuit Board, Top

Printed Circuit Board, Top

Here’s the other three members in my team! Shane (left), Tom (thumbs up) and Andrew.

METR2800 Team 11 2008

METR2800 Team 11 2008

Final Product, Top View

Final Product, Top View

Spiral "paint" job, +1 hypnosis

Spiral "paint job", +1 hypnosis. Electrical tape has the added bonus of covering up unused holes.

Uni Timetable Manager v2 Beta & Source Code Released

July 21, 2008 by jackvalmadre

A beta release of version 2 is now available for download from the new site, http://unitimetable.googlepages.com/. I’ve also opened a Google Code webpage at http://code.google.com/p/unitimetable/ so it’s officially an open source project! If anyone’s interested in becoming a developer let me know. If you have any suggestions or bugs, you can add them to the Issues section at the Google Code site, or email/messenger (MSN/Gtalk) me on jack.valmadre@gmail.com.

New version 2 user interface

New version 2 user interface

Here’s a list of the major changes in version 2:

  1. Revamped user interface, including drag-droppable classes, nicer-looking classes, a sidebar showing remaining streams and ginormous vista-ified toolbar icons.
  2. An Import Wizard to make it easier to load the timetable data into the program. The code was also modularised to allow other universities to use the same program. A quick UNSW importer was hooked up (not guaranteed to work) and a QUT version is in the pipeline.
  3. You can now save and open files in XML format. Exporting as an image now includes support for EMF vector graphics (if a little buggy) and higher quality JPEGs. Alternatively you can export directly to the desktop background and the program will put a transparent overlay of the timetable on your wallpaper.
  4. I played around with the algorithm a bit. No major improvements though, I was mainly working on the interface.
Colour schemes + export to wallpaper features.

Colour schemes + export to wallpaper.

I’ve got a feeling there’s some features I’ve forgotten but I’ll edit them in as I remember. There’s also a hidden Easter Egg which can be unlocked by entering the “Konami Code” and pressing Enter :)