Archive

Posts Tagged ‘Image Processing’

visualising the nationality of Nobel Peace Prize Winners

October 9, 2009 Leave a comment

Visualizing the nationality of Nobel Peace Prize winners over time

Advertisements

Image Steganography with PIL

October 7, 2009 Leave a comment

Steganography is greek for ‘hidden writing‘; the act of hiding a message inside another message.

In this case, hiding an image inside another image, without it being obvious to the viewer. The example I’ll give here is only a ‘toy’ implementation, for two reasons:-

  • easily cracked: it wouldn’t take the authorities long to spot the hidden message, not least because the algorithm is described on wikipedia 😉
  • fragile: the hidden image-within-an-image can easily be broken, if the image has its colours changed afterwards.

But it does illustrate how to do bitwise-manipulation of images in PIL using the ImageMath module, which is the purpose of the post.

How it works

The watermark – the image we wish to hide – is a bitonal image, with black and white pixels only. It’s then resized to be the same size as the original image.

We ‘smuggle’ the watermark inside the original by replacing the LSB (least significant bit) of each colour channel (R,G and B) in the original with the corresponding pixel in the watermark – either 1 for white, or 0 for black.

This image shows the binary arithmetic…least significant bit on the right.

watermarking

Hiding the watermark image inside our image

For this, we’ll need these imports…

from PIL import Image, ImageMath

and open the two files. The watermark is scaled to match the size of the original image.

watermark=Image.open(r"c:\watermark.png")
original=Image.open(r"c:\original.jpg")
watermark=watermark.resize(original.size)

ImageMath only works with single channel (greyscale) images, so we need to split the two images into their three channels (Red, Green and Blue) using the split() method.

red, green, blue = original.split()
wred, wgreen, wblue = watermark.split()

Now, using ImageMath. ImageMath lets you write simple expressions using values from one or more images. Here, ‘a’ and ‘b’ are bound to the values in the original and watermarked images, respectively. The convert() call is needed to prevent problems later; we need to cast the results back to a greyscale image (mode ‘L’).

red2 = ImageMath.eval("convert(a&0xFE|b&0x1,'L')", a=red, b=wred)
green2 = ImageMath.eval("convert(a&0xFE|b&0x1,'L')", a=green, b=wgreen)
blue2 = ImageMath.eval("convert(a&0xFE|b&0x1,'L')", a=blue, b=wblue)

Okay, so now we have three channels whose LSBs have been replaced with the LSB of the watermark.

But we need to combine the 3 channels back to get an RGB image ready for saving.

out = Image.merge("RGB", (red2, green2, blue2))
out.save(r"c:\merged.png")

Open the original and the processed images; can you see any difference?

Extracting the hidden image

All this is for nought if you can’t extract the hidden image afterwards.

This is simpler, as we only need to produce a black/white image from the LSB of the image. Here, I’ve only bothered with the Red channel.

stegged=Image.open(r"c:\merged.png")
red, green, blue = stegged.split()
watermark=ImageMath.eval("(a&0x1)*255",a=red) # convert to 0 or 255
watermark=watermark.convert("L")
watermark.save(r"c:\extracted-watermark.png")

greyscale versus greyfail

September 7, 2009 Leave a comment

If you’ve ever wondered why desaturate gives disappointing greyscale results in Photoshop or the Gimp, here’s a (slightly) scientific demo of why.

The diagram below is the sRGB gamut, viewed from above. In reality the gamut a twisted blob in 3d space, but this is a ‘plan’ – you’re looking down on it, so you’re seeing every possible RGB hue at its highest luminosity level. You’re seeing the bright sunlit version, rather than the shadowy underbelly. Darker versions of the same hue are hidden behind the pixels you see. (This was generated with a C# program I wrote a while back.)

rgb-gamut

This triangle is called the Maxwell Triangle; the primary colours (R, G and B) are the vertices of the triangle, and every hue is a weighted average of those three primaries in different proportions. The secondary colours (Cyan, Magenta, Yellow) are mixtures of two of the primaries, and appear on the edges of the triangle.

Draw an imaginary line between each primary and the opposite secondary; where the 3 lines cross, you have the White Point – this is the axis (disappearing into your screen) of neutral tones between white and black.

Look what happens if you apply Grayscale to it

rgb-gamut-after-greyscale

Notice how Blue is darkest, Red is dark, and that White and Yellow are close together in brightness.

This is good; the greyscale algorithm takes into account human sensitivity to colour, and the influence of colour on tone. Pure yellow is lighter than pure Blue, as it is in real life.

Contrast this with the effect of applying Desaturate on the same gamut image.

rgb-gamut-after-desaturatio

Notice how white maps to white; as you’d expect. Now, the fully saturated hues (those lying on the boundary of the triangle) are all mid-grey.

Yellow is now the same tone as pure blue.

Greyscale takes into account the true tonal values of colours.

Desaturate is a simple average of the tonal values of each channel.

Desaturate? Don’t bother.

You can get a CC-NC-BY version of the gamut image at higher resolution on my Flickr stream here.