ImIn (Image interpolation)

Barycentric and bilinear image interpolation and transformation in pure Python

Overview

ImIn (Image Interpolation) module contains Python code for bilinear image interpolation and barycentric image interpolation, working in any color modes from 8 bit L to 16 bit RGBA (presumably it works in other color modes as well but I can't read and write CMYK files to perform testing). That is, it contains functions for reading single pixel at float coordinates, interpolated from real pixels (having integer coordinates), as well as functions for transforming whole image (scaling, rotating, deforming etc.) using interpolation methods above.

It is well known that image interpolation authors tend to increase image analysis area size. With bilinear interpolation, pixel is considered as square, and interpolation takes place between four corners. With bicubic interpolation, more neighbour pixels are added to analysis. This is good for smooth gradients but what about some sharp edges? Why not to try using just half a pixel? For example, split a pixel square ■ diagonally into two triangles like ◤◢ or ◣◥, and use analysis within one half of a pixel using three corners?

Since I appeared to be unable to find such a triangular interpolation in any big programs like Photoshop or GIMP, I decided to write corresponding algorithm myself and see how it works.

It appears that the best coordinate system for triangles is barycentric coordinate system, which is widely used in science, in 3D graphics, sometimes used in 2D vector gradients, but, for unclear reasons, not used in regular 2D bitmap image processing.

So, I wrote my own barycentric interpolation code, and, after a long debugging of simple school math, it appeared to give rather interesting results when used, for example, for image rescaling, so I even tend to prefer using it for my images in some cases, regardless of the fact that Python code is always slower than optimized machine code in Photoshop.

At the same time I got bored by copying my own code for bilinear image interpolation from one program to another (yes, there is an interpolation in, say, POV-Ray 6³ Mosaic or img2mesh).

So, I decided to join my code for bilinear and barycentric image interpolation in some sort of a single module, which can be easily plugged into different image processing Python programs. So, here it goes.

Ladies and gentlemen! Mesdames et messieurs! Товарищи и товарищи! Let me introduce to you the one and only Image Interpolation (imin) module, combining simple yet carefully designed and even somewhat optimized bilinear and barycentric image interpolation in pure Python!

Table 1. Comparison of bilinear and barycentric image interpolation

Source

Bilinear

Barycentric

Source image

3x3 px (black pixel over white background)

Bilinear interpolation

x20 times

Barycentric interpolation

x20 times

Source image

15x15 px (2x2 px chessboard pattern)

Bilinear interpolation

x3 times

Barycentric interpolation

x3 times

Source image

22x16 px

Bilinear interpolation

x5 times

Barycentric interpolation

x5 times

Let's perform a visual comparison of bilinear and barycentric image interpolation used for upscaling images of different nature.

Table 1 in this section contains various test cases. Obviously, bigger images like photos would be more interesting, but, unfortunately, to clearly see interpolation artifacts I should use at least x3 upscaling, so, if I use a photo that barely fits a screen as a source, result will not fit this web page.

In the first row, you may see a result of upscaling single black pixel over white background. Resulting images clearly show the difference between bilinear and barycentric interpolation.

In the second row, you see a problem case: a chessboard pattern. It's a problem for any interpolation: it's unclear what to find in there. Noise over flat color? A pattern?

As you can see, while bilinear interpolation simply blurs everything, barycentric interpolation tries to find diagonals. Most likely, it's not what you want, but, on the other hand, a chessboard really is a set of diagonals; any bishop, whether he's white or black, will confirm this.

(Needless to say, if you want a pixel-sized black and white chessboard to be treated as black and white, you better resort to ScaleNx image rescaling.)

In the third row, you see a small fragment of a photo. It seems like barycentric interpolation gives clearer appearance of overall eye outline shape; rhomb-shaped pupil, however, looks a bit weird. But well, it's a single pixel in original, so you the same rhomb as in the first row.

Module and example programs

Currently default branch is "Functional", bearing this name because it is supposed to be used as functions, not a class. Corresponding class equivalent is under development, and supposed to be published later.

Module content is briefly summarized below.

Detailed functions description and parameters for developers are given in a rather prolific docstrings.

Surely, during development module needs testing. Therefore some GUI shell programs were created for testing and illustration purposes:

Distorter GUI animation

All the final public functions, included into imin module and intended for actual use, have a rather generalized access scheme. For example, a single interpolated pixel with float x and y coordinates may be acquired like this:

pixel_value = pixel(source_image, x, y, edge, method)

where:

Such a generalized syntax simplifies using imin module for writing image editing programs.

Note, however, that, while "Wrap around" mode is present in sample programs GUI, it does't always work as one may expect.

Imagine simple horizontal skewing, when top and bottom edges move against each other, and right and left ones become sort of diagonals. Assume that the source image is seamless pattern. With "Wrap" mode enabled, distorter.py keeps the horizontal part seamless, that is, the triangular part of image that goes away at the right side will appear at the left. What as to vertical part, since top and bottom edges move against each other, their match gets broken and seam formed.

However, if you switch from skewing to sine wave distortion, result of deforming a seamless pattern will be seamless. That's because I use exactly one wave cycle for a displacing function, making ends meet (literally). Well, actually, even with non-seamless sources, in case of sufficient deformations this function deforms edges so much that they become unrecognizable, and image turns into seamless-like (is this a real word?).


Now it's time to either step forward to Image Interpolation repository at GitHub for downloads...

...or Move back to Dnyarri`s Python freeware main page.