The first part in this two-part articleexplains how you can modify an image without changing its basic content. It shows how to brighten or darken a picture, increase its contrast, or remove redeye. All of those techniques rely only on the value of the pixel at the point you are manipulating so they are called point processes. (Part 1 also explains useful ways to load and save, compress, and manipulate images quickly so you may want to look over that article before you read this one.)
This article explains some useful area processes, techniques that use the values of several pixels to give a pixel a new value. This article groups area processes into three broad categories: basic filters, edge detectors, and artistic processes.
In spatial filtering (sometimes called neighborhood processing), the value of a pixel is determined by the pixels in its neighborhood. For example, a simple blurring filter might set a pixel's new value to the average of its value and the pixels next to it.
When you apply a spatial filter, you must place the new pixel values in a new image instead of modifying the original image. If you tried to save the new values in the original image, a pixel's new value would affect the values of its neighbors that had not yet been computed. The result might be interesting but it won't match what you see in this article.
A filter's kernel is the area or neighborhood used by a spatial filter.
The result you get from a spatial filter depends on the kernel and the operations that you apply to the pixels under it. For some applications these can be quite involved and can produce all sorts of odd results. Some of the artistic filters described later in this article use some unusual kernels and operations but this section focuses on what is probably the most common type of spatial filtering: linear spatial filters.
A linear spatial filter calculates a pixel's value from a linear combination of the values of its neighbors.
In your program, you can store information about the filter's kernel in a square array of numbers. This array is sometimes called the mask, although it's often simply called the kernel because, for this kind of filter, it basically defines the kernel (in the more general sense) and the operation that you will perform.
To apply the kernel to a pixel, you conceptually center the array over the pixel. You then multiply each neighbor's value by the corresponding number in the array. You add these products and assign the result to the target pixel.
Note that the target pixel is under a position in the array, too, so it can contribute to its own value. You can also remove the target pixel or any other pixel in the neighborhood from the calculating by setting its kernel value to 0.
For example, consider the following kernel and some pixel values, and suppose you want to apply the kernel to the center pixel with value 6.
To find the center pixel's new value, multiply the kernel's values by the corresponding pixel values and add them up. In this example, the center pixel's new value is:
1/16 * 3 + 2/16 * 4 + 1/16 * 5 +
2/16 * 5 + 4/16 * 6 + 2/16 * 7 +
1/16 * 7 + 2/16 * 7 + 1/16 * 8 = 5.8125
To make it easier to write down and work with kernels, people often give the entries integer values. Then after multiplying the kernels values by the neighborhood's pixel values and adding them all up, you divide by a new value called the kernel's weight.
For example, you can write the previous kernel with integer values and a weight of 16 as follows:
This makes the kernel less cluttered so it's easier to understand what the kernel does.
The code to apply a filter is relatively straightforward. For each pixel in the image, you simply loop through the kernel's values multiplying them by the neighboring pixels' values, add them up, and divide by the kernel's weight. To handle color, you work with the red, green, and blue color components separately. The only real trick is that you need to be careful near the image's edges so you don't try to multiply a kernel value by a neighboring pixel that lies off the edge of the image.
The AreaProcesses example program, which is available for download in C# and Visual Basic versions and shown in Figure 1, demonstrates all of the techniques described in this article. It uses the Filter class to represent filters. That class contains a two-dimensional array of floats to represent the kernel. It also has a Weight property to represent the kernel's weight.
Figure 1. Area of Study: The AreaProcesses example program demonstrates 32 different area processing techniques.
The program uses the Bitmap32 class to manipulate images. Its ApplyFilter method applies a filter to an image. The code is a relatively straightforward series of nested loops so it isn't shown here to save space. Download the example to see the details.
At this point you know enough about kernels and filtering to see some specific kernels and learn what they do.
The previous kernel produces an averaging or blurring filter. Each pixel's new value includes some amount taken from the pixels around it so their values spread slightly. This kind of filter is called a Gaussian filter because its coefficients are distributed according to a Gaussian function (or normal distribution).
To see how the filter works, suppose you have a white image with a single black pixel in the middle. When you apply the filter to the black pixel, its new value will include some of the white from its neighbors so it becomes grayer. Similarly when you consider one of the neighboring white pixels, some of the new value comes from the black pixel so those pixels become darker. The result is that the single black pixel becomes a fuzzy gray dot.
This kernel weights the pixels closest to the center most heavily so they have the greatest influence on the outcome. The following kernel places an equal weight on all of the pixels in the neighborhood so the center pixel's new value depends more heavily on its neighbors. That makes this kernel's result more blurred than the results of the previous kernel.
Figure 2 shows the results of 5x5 versions of these filters. The picture on the left shows the original image. The center picture shows the result after applying a 5x5 Gaussian filter. The picture on the right shows the result of a 5x5 averaging filter. You can make the results even more blurred by using larger kernels.
Figure 2. Do I Need Glasses? Gaussian and averaging filters blur an image.