0% found this document useful (0 votes)
103 views

Java Image Processing

This document provides an introduction to image blurring techniques for beginners. It discusses different types of blurring like box blur and Gaussian blur. For box blur, it describes how to implement an efficient separation technique by doing horizontal and vertical 1D blurs. For Gaussian blur, it generates a 1D Gaussian kernel that can be convolved horizontally and vertically for faster 2D blurring compared to a direct 2D convolution. Code examples in Java are provided to demonstrate implementations of various blur filters.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
103 views

Java Image Processing

This document provides an introduction to image blurring techniques for beginners. It discusses different types of blurring like box blur and Gaussian blur. For box blur, it describes how to implement an efficient separation technique by doing horizontal and vertical 1D blurs. For Gaussian blur, it generates a 1D Gaussian kernel that can be convolved horizontally and vertically for faster 2D blurring compared to a direct 2D convolution. Code examples in Java are provided to demonstrate implementations of various blur filters.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 15

Java Image Processing

Home page» Image Processing» Blurring

Blurring for Beginners


Introduction
This is a short tutorial on blurring techniques for beginners. When I was learning this stuff, there
was very little available material which was useful. That's not true of course - there was masses
of material, but half of it was way too simple and the other half began "Let T be a vector function
evaluated over the half-open interval...." and was full of very scary multi-line equations with
those big sigma symbols and things. This article is meant to remedy that. I'll talk about various
kinds of blur and the effects you can use them for, with source code in Java.

A Disclaimer
Whenever blurring is mentioned, there's always somebody who says "Hey! That's not a real
motion blur!", or writes angry letters in green ink complaining that the mathematics is dubious or
that there's a much faster way to do this using the sponglerizer registers on the HAL-9000.
Ignore these people. This is a big subject, and this article is just for beginners (of which I can
proudly say I am one). What matters is you get the results that you're aiming for, and if the
results you're aiming for require dubious mathematics, then so be it. If the results you're aiming
for look horrible to me, then that's fine, as long as they look good to you.

Another Disclaimer
There's source code in Java for pretty well everything I talk about here. I make no claims that
these are optimised in any way - I've opted for simplicity over speed everywhere and you'll
probably be able to make most of these thing go faster with a bit of effort. You can use the
source code for anything you want, including commercial purposes, but there's no liability. If
your nuclear power station or missile system fails because of an improper blur, it's not my fault.

What is Blurring?
We all know what blurring is, don't we? It's that thing that happens when your camera is out of
focus or the dog steals your glasses. What happens is that what should be seen as a sharp point
gets smeared out, usually into a disc shape. In image terms this means that each pixel in the
source image gets spread over and mixed into surrounding pixels. Another way to look at this is
that each pixel in the destination image is made up out of a mixture of surrounding pixels from
the source image.
The operation we need for this is called convolution. This sounds complicated but thats only
because mathematicians like to make things sound complicated in order to maintain that air of
magic and keep the funding rolling in. Well, I'm onto them and I can reveal that convolution is
not that complicated (at my level anyway). The way it works is this: we imagine sliding a
rectangular array of numbers over our image. This array is called the convolution kernel. For
every pixel in the image, we take the corresponding numbers from the kernel and the pixels they
are over, multiply them together and add all the results together to make the new pixel. For
example, imagine we want to do a really simple blur where we just average together each pixel
and its eight immediate neighbours. The kernel we need is:

1/9 1/9 1/9


1/9 1/9 1/9
1/9 1/9 1/9

Notice that these all add up to 1, which means that our resulting image will be just as bright as
the original.

Without further ado, let's blur an image in Java. All that convolution stuff sounds tricky to
implement, but luckily Java comes with a built-in and ready-to-use operator to do exactly that.
I'm talking ConvolveOp here. Here's the code:

float[] matrix = {
0.111f, 0.111f, 0.111f,
0.111f, 0.111f, 0.111f,
0.111f, 0.111f, 0.111f,
};

BufferedImageOp op = new ConvolveOp( new Kernel(3, 3, matrix) );


blurredImage = op.filter(sourceImage, destImage);

The Original and Blurred Images

Fantastic! A blurry image! It's not very blurry though. Let's do a really big blur like this:

float[] matrix = new float[400];


for (int i = 0; i < 400; i++)
matrix[i] = 1.0f/400.0f;

BufferedImageOp op = new ConvolveOp( new Kernel(20, 20, matrix),


ConvolveOp.EDGE_NO_OP, null );
blurredImage = op.filter(sourceImage, destImage);
Big Blur with ConvolveOp

Hmmmmmm. Well that's not so good. Not only did it take a really long time, but the result is
slightly odd - everything looks, well, sort of square, and what on earth has happened around the
edges?

First the edges: ConvolveOp is a timid namby-pamby thing which is scared of falling off the
edge of the image. If the kernel would overlap the edge of the image, it just gives up and just
leaves the pixel unchanged. You can change this by passing EDGE_ZERO_FILL instead of
EDGE_NO_OP, but that's even worse - the pixels round the edge just get set to zero and
effectively disappear. What shall we do? Well, we could pad the image out around the edges
before blurring and crop the result, but that's just giving in, and besides we wouldn't learn
anything. Instead, we'll write a proper, fearless, no-nonsense operator which isn't scared of
edges. We'll call it ConvolveFilter to distinguish it from ConvolveOp. I'm not going to do into
details of the source in this article - there's not enough time or space and we have a lot more
filters to write yet, but you can download or view the source and it should be fairly self-
explanatory.

View ConvolveFilter.java

Now the squareness problem: The reason everything looks square is because what we're doing
here is called a box blur - our kernel is shaped like a square, as if we're using a camera which
has a square aperture. Incidentally, don't let anyone tell you that box blurs are useless - in fact if
you're simulating the shadow cast by a square light, it's exactly what you want. Anyway, they'll
come in useful further on. Another thing: don't get confused - I'm using the term box blur to refer
to the shape of the kernel, not its profile, which I'm going to call a box filter. More on this later
on. To get a more realistic blur, what we should have done is used a circle-shaped kernel. This
simulates much better what a real camera does.
That's much better. We'll come back to this later on, but first a diversion back to the box blur.

Box Blur
We've solved the edge pixel problem, but our blur is still going really slowly, and things are only
going to get worse. The problem is that the number of multiplications in the convolution is going
up as the square of the kernel radius. With a 100x100 kernel, we're going to be doing 10000
multiplies and adds per pixel (approx). How can we get round this? It turns out that there are
more ways to go about this than I've possibly got time to write about, or even bother to look at.
One way I will mention quickly before sweeping it under the rug is this: You can do a box blur
by shrinking down your image, blurring it and scaling it up again. This may be fine for your
purposes, and you should bear it in mind. One problem is that it doesn't animate very well, but
may not be a concern to you.

Let's look at the box blur again: It turns out that there's a couple of really easy ways to speed this
up. Firstly, it turns out that the box blur is separable. This means that we can do a 2D blur by
doing two 1D blurs, once in the horizontal direction and once in the vertical direction. This is
much faster than doing the 2D blur because the time taken goes up in proportion to the kernel
size, not as its square. Secondly, Think about the window that we're sliding across the image. As
we move it from left to right, pixels come in at the right edge and are added to the total and at the
same time pixels leave the left edge and are subtracted from the total. All we need to do is just do
the add and subtract for the entering and leaving pixels at each step instead of adding together all
the pixels in the window. We only need to store a set of running totals which are the width or
height of the kernel. This gives a massive speed improvement at the cost of having to write some
code. Luckily, I've written the code for you, so you win all round. We need two passes, once to
blur horizontally and once vertically. The code for these is, of course, quite different. But wait!
There's a trick we can do which allows us just to write the code once. If we write a blurring
function which does the horizontal blur but writes its output image transposed, then we can just
call it twice. The first pass blurs horizontally and transposes, the second pass does the same, but
as the image is now transposed, it's really doing a vertical blur. The second transposition makes
the image the right way up again and voila! - a very fast box blur. Try it out in this applet:

And here's the source code....

View BoxBlurFilter.java

You may have noticed that we have only used an integer radius so far which makes it easy to
work out the array indices for the blurring. We can extend the technique to do sub-pixel blurring
(i.e. a non-integral radius) simply by linear interpolation between the array values. My source
code doesn't do this, but it's easy to add.

Gaussian Blur
Now it's time to address the speed and square-looking blur issues at the same time. To get rid of
the square look to the blur, we need a circular-shaped kernel. Unfortunately, the trick we used for
box blurs doesn't work with a circle but there's a loophole: If the kernel has the right profile - the
Gaussian profile - then we can do a 2D blur by performing two 1D blurs, just like we did with
the box blur. It's not so fast because the sliding window trick doesn't work, but it's still a lot
faster than doing the 2D convolution. The profile we need is the familiar bell-shaped, or
Gaussian curve that you've heard of:

Gaussian Blur

Here's some code to create a 1D Gaussian kernel for a given radius. All we need to do is to apply
this twice, once horizontally and once vertically. As a bonus, I've wrapped it up in a
GaussianFilter to make it easy to use.

View GaussianFilter.java

This is why the Gaussian blur is found in every graphics package - it's much faster than other
types of blur. The only problem is that it's not very realistic when it comes to simulating camera
lenses, but more on that later. If you want to do things like simulating shadows, then the
Gaussian blur, or even the box blur may be just fine. There's a place for all these effects - just
because they aren't realistic doesn't mean they're not useful.

The Gaussian blur is much faster, but it's nowhere near as fast as our box blur we did earlier on.
If only there was some way to combine the two. I imagine you've guessed by now that there
might be one, so I'll not hold the suspense any longer: If you do a lot of box blurs, the result
looks more and more like a Gaussian blur. In fact, you can prove it mathematically if you've a
spare moment (but don't tell me how - I'm not interested). In practice, 3 to 5 box blurs look pretty
good. Don't just take my word for it: The box blur applet above has an "Iterations" slider so you
can try it out for yourself.

Box Blur: 1, 2 and 3 iterations

Alpha Channels
A quick diversion here to discuss a problem which often crops up: Imagine you want to blur a
shape which is on a transparent background. You've got an empty image, and you draw a shape
on it, then blur the image. Hang on - why does the blurry bit look too dark? The reason is that
we've blurred each channel separately, but where the alpha channel is zero (the transparent bits),
the red, green and blue channels are zero, or black. When you do the blur, the black gets mixed
in with the opaque bits and you get a dark shadow. The solution is to premultiply the image
alpha before blurring and unpremultiply it afterwards. Of course, if your images are already
premultiplied, you're all set.

Blur: Separate and Premultiplied Alpha

Motion Blur
Time for a change of direction. So far we've only talked about uniform blurs, but there are other
types. Motion blur is the blur you get when an object (or the camera) moves during the exposure.
The image gets blurred along the apparent path of the object. Here we're just going to be talking
about simulating motion blur on an existing still image - doing motion blur in animations is a
whole different area. We're also only going to be blurring the whole image - we're not going to
try and blur an object in the image.

The good news is that we've already done simple motion blur. Go back to the box blur applet
above and set the horizontal radius to, say 10, and the vertical radius to zero. This gives you a
nice horizontal motion blur. For some purposes, this may be all you need. For example, one way
to produce a brushed metal texture is to take an image consisting of random noise and apply a
motion blur.

If we want to blur in a direction other than horizontal or vertical, then things get more
complicated. One technique might be to rotate the image, blur and then rotate back. What we'll
do here though is to do it the hard and slow way. What we need to do is loop over the image, and
for every pixel, add up all the pixels along the motion path. For a straight motion blur, this just
means following a straight line from the pixel, but you could follow a wiggly path if you wanted
to simulate long-exposure camera shake, say.

Spin and Zoom Blur


Once we've got the code for motion blur in place, it's a simple matter to modify it to do zoom and
spin blurs, or even a combination of all three. It's just a matter of following the right path for
each pixel. For radial blurs, just follow a path going from the blur center. For a spin blur, follow
a tangential path.

Zoom and Spin Blur


Try it out in this applet:

Here's the source code for doing these three types of motion blur:

View MotionBlurFilter.java

Faster Motion Blur


You may have noticed that doing the motion blur is a pretty slow business - all those sines and
cosines really slow things down. If we're not so worried about quality though, we can speed this
up. All we need to do is add together a lot of transformed versions of the image in a clever way.
The clever part is that we can do a 1-pixel motion blur by averaging the image and the same
image translated by one pixel. We can do a 2-pixel blur by repeating this with the 1-pixel blurred
images. By repeating this we can do an N-pixel blur in log2(N) operations, which is a lot better
than doing it the hard and slow way. Zoom and spin blurs can be done by scaling and rotating
instead of translating. One filter will do all three using an AffineTransform.

Try it out in this applet:

View MotionBlurOp.java

Domain Shifting
There's yet another way to do these motion blurs: Remember I said you could do the linear
motion blur by rotating the image, doing a horizontal box blur and rotating back? Well, the same
is true of the zoom and spin blurs, except you need something more complicated than rotation.
What you need is the polar transform:

Polar Transform

Once you've transformed your image, a horizontal box blur is a spin when you transform back,
and a vertical box blur gives you a zoom blur. One detail is that you need a special horizontal
box blur which wraps at the edges otherwise you'll get a sharp vertical line in your blurred image
where the spin angle should wrap round.

Blurring by Fourier Transform


The Gaussian blur is very fine when you want that Gaussian blur effect, but what if you want a
proper lens blur which simulates a real camera aperture? Watch any film or TV program for a
while, especially something shot at night with lights in the background, and you'll see that things
which are out of focus form disk shapes, or perhaps pentagons. There's also a phenomenon called
blooming where bright parts of the image wash out the image, becoming even brighter compared
to the rest. These shapes are called Bokeh. Some people love it and some people hate it. We don't
care whether people love it or hate it, we just want to reproduce it.

Bokeh (real, not faked)

You won't get those disk shapes with Gaussian blur - it's just too fuzzy round the edges. What
you need to do it use a nice sharp-edged convolution kernel in the shape of your camera aperture.
The problem you'll come across here is that all those tricks to do with separable kernels, iterated
box blurs and the like won't work here - there's no separable kernels which will give you a
pentagon (well, probably - I'm no mathematician) - we're back to the old problem of the blur
time going up as the square of the blur radius. Fear not, we can turn the heavy mathematical guns
onto the problem. I don't know how the heavy guns work, but I can aim them.

The heavy guns are Fourier Transforms. I don't know how they work because I wasn't listening
in my university lectures, but there's a vast amount on the subject you can find on the Internet,
although practically nothing practical (i.e. with source code) on the subject of blurring. With
Fourier Transforms, you can make a blur which takes a time unaffected by the blur radius (in
practice, dealing with the image edges means this isn't quite true). Unfortunately, this means that
for a small radius, it's slow, but you really win with a large radius. One way to deal with this is to
use the simple convolution for small radii, and switch to Fourier Transforms when you reach to
crossover point in time, assuming you've done the experiments to determine where that is. But be
careful, if you're animating a blur, you've got to make sure that you don't get any visible artifacts
at the point where you switch algorithm - the eye is really good at spotting those. For that reason,
you may prefer to stick with one algorithm for the whole of an animation. For still images,
nobody is going to notice. Really.
Lens Blur with Blooming

Does it really look any different? Surely, we can get away with a Gaussian blur? Well, here's an
example which will help you make up your mind.

Original, Lens Blur, Triangle Aperture and Gaussian Blur

Convinced?

The principle behind doing the blur is not too hard, although it seems like magic. What we do is
take the image and the kernel, and perform the Fourier transform on them both. We then multiply
the two together and inverse transform back. This is exactly the same as performing the long
convolution above (apart from rounding errors). You don't actually need to know what a Fourier
transform does to implement this, but anyway, what it does is to convert your image into
frequency space - the resulting image is a strange-looking representation of the spatial
frequencies in the image. The inverse, of course, transforms back to space.., er, space. Think of it
like a graphic equalizer for images. You can think of blurring an image as removing high
frequencies from it, so that's how Fourier transforms come into the picture.
Implementing this is actually fairly straightforward, but there are a lot of nasty details to worry
about. First of all we need some functions to do the transform and its inverse. These can be found
in the class FFT. This is not by any means a super-optimized implementation - you can find
many of those elsewhere on the Internet. Next, we need to convert the kernel into an image the
same size as the image we're blurring (I'm sure there are ways to avoid this, but I don't know
enough maths - if only I'd been listening in those lectures). We also need to pad out our source
image by the radius of the blur, duplicating the edge pixels as it's hard to get the FFT to deal with
edges like this. Now, the FFT works on complex numbers, so we need to copy the image and
kernel into float arrays. We can do a trick here - our images have four channels (alpha, red, green
and blue) so we need to do four transforms plus one for the kernel, making five, but since we're
using complex numbers we can do two transforms at once by puttng one channel in the real part
of the array and one channel in the imaginary part. Now things get easy, just transform the image
and kernel, complex multiply them together and inverse transform and we have our image back,
but convolved with the kernel. One last tiny detail is that the transformation process swaps over
the quadrants of the image so we need to unswap.

Only one small detail remains: the FFT only works on images which are a power of 2 in each
direction. What we have to do is to add twice the blur radius to the width and height, find the
next highest power of 2 and make our arrays that size. For big images this has a couple of
problems: One is that we're using up lots of memory. Remember we have our images in float
arrays and we need 6 of these arrays, each of which is 4 times the size of the image when it's
been expanded to a power of two. Your Java virtual machine may well complain at you if you try
this on a big image (I know, I've tried). The second problem is related: Things just go slower
with the large images because of memory caching problems. The answer is to split the image up
into tiles and blur each tile separately. Choosing a good tile size is an option research problem
(i.e. I haven't been bothered to experiment much), but is tricky - we need to overlap the tiles by
the blur radius so if we chose a tile size of 256 with a blur radius of 127, we'd only be blurring 4
pixels with each tile.

Try it out in this applet:

View LensBlurFilter.java
View FFT.java

Threshold Blurs
Something which is often wanted is a blur which blurs parts of the image which are very similar
but preserves sharp edges. This is digital wrinkle cream and you can see this in any movie poster
ever printed - the stars' faces have all those nasty blemishes ironed out without the image
appearing blurry. Often this is so overdone that the actors look like waxworks or computer-
generated figures.
Original Image and Threshold Blur

The way we do this is to do an ordinary convolution, but only count in surrounding pixels which
are similar to the target pixel. Specifically, we have a threshold and only include a pixel in the
convolution if it differs from the center pixel by less than the threshold. Unfortunately, the short
cuts we took above won't work here as we need to include a different set of surrounding pixels
for each target pixel, so we're back to the full convolution again. Now, although this is extremely
dubious, it actually works quite well to still do the two 1D convolutions for a Gaussian blur
which is faster than doing the full 2D convolution, so that's what I've done here. Feel free to
modify the source to do the full thing.

Try it out in this applet:

View SmartBlurFilter.java

Variable Blurs
So far we've only talked about uniform blurs - where the blur radius is the same at each point.
For some purposes, it's nice to have blurs which have a different radius at each point in the
image. One example is simulating depth of field: You could take an image which is in focus all
over and apply a variable blur to it to make parts look out of focus. Real depth of field is more
complicated than this because an object which is behind another object shouldn't receive any blur
from the object in front, but we'll ignore that and leave it to the professionals.

Now, our fancy tricks above aren't going to help us much here as everything involves
precalculating kernels or relies on the blur radius being the same over the image and at first sight
it looks like we've got no option but to fall back on the full convolution at each pixel, only this
time it's much worse as the kernel might have changed from the previous pixel. However, all is
not lost. Remember that trick with box blurs where we just added in pixels as they entered the
kernel and subtracted them as they left? It seems as though this won't work in the variable radius
case because we'd have to keep totals for every possible radius, but there's a modification we can
make to the trick which enables us to magically pull out the totals for any radius with only one
subtraction. What we do is preprocess the image and replace every pixel by the sum of all the
pixels to the left. That way when we want to find the total of all the pixels between two points in
a scanline, we just need to subtract the first from the second. This enables us to do a fast variable
blur using a modified version of the box blur code above. Dealing with the edges is slightly more
complicated as simply subtracting the totals doesn't work for pixels off the edge, but this is a
minor detail. We also need a bit more storage space because the totals will go above the
maximum value of a pixel - we'll need to to use an int per channel instead of storing four
channels in one int.

Variable Blur - increasing with radius

Well, OK, but this is a Gaussian(ish) blur isn't it? What about doing that lens blur thing with
variable radius? Unfortunately, you're out of luck here. I'm not saying there isn't a super fast way
of doing this, but as far as I know you're going to have to do the full convolution thing.

Try it out in this applet, which blurs more as you move to the right:

View VariableBlurFilter.java

Sharpening by Blurring
You can use a blur to sharpen an image as well as blur it using a technique called unsharp
masking. What you do is take the image and subtract a blurred version, making sure you
compensate for the loss of brightness. This sounds like magic, but it really works: compare this
image with the original.

Sharpening with Unsharp Mask

Try it out in this applet:


View UnsharpFilter.java

Glow
If subtracting a blurred version of an image from itself sharpens it, what does adding it do? As
ever, there's no need to guess - I'm here to inform you. What you get is a sort of glowing effect
which can look quite nice, or quite cheesy depending on your point of view. Varying the amount
of blur added in varies the glowing effect. You can see this effect used a lot on television for
dreamy-looking transitions.

Glowing Blur

Try it out in this applet:

View GlowFilter.java

Making Shadows
Making a shadow is just a matter of creating an image which looks like the silhouette of the the
shadowing object, blurring it, possibly distorting or moving it, and pasting the original image
over the top. As this is a really common thing to want to do, there ought to be a filter to do it,
And here it is...

Adding Shadows
View ShadowFilter.java

This is actually a very simplistic implementation - it just blurs the shadow and draws the original
image over the top. In practice, it's better to not bother blurring the pixels which are completely
hidden by the object.

Casting Rays
We can do the same trick to make light rays appear to come out of an object, only this time
making the shadow color white and using a zoom blur instead of the ordinary blur, then adding
the result on top of the original.

Light Rays

View RaysFilter.java

The rays often look better if you only cast them from bright parts of the image, so the filter has a
threshold which you can set to restrict rays to bright areas. This is a good effect to animate: make
the centre of the rays move across the image and you get the effect of a moving light source
behind the image.

Conclusion
Well, that's it, and I've not even mentioned other blurring methods such as IIR filters, recursive
filters and all those other nasty things. I hope you come away with something useful from this,
even if it's just a burning desire to buy some green ink and write me a letter.

Finally, you may have noticed that the source above relies on some other classes. Don't worry,
here they are:

You might also like