When working with images there is literally an infinite number of operations we can perform on a single pixel. However, blindly manipulating the image will rarely, if ever, produce a desirable result. For example, if an image is already too bright, we wouldn’t want to run a ‘brighten’ function on it. We need to rely on context to achieve sensible results. In the case of spatial filters our context is the neighborhood of the pixel.

**Neighborhoods**

What is a neighborhood? Neighborhoods are simply pixels adjacent to the pixel we are working with.

Neighborhoods are typically (but not always) squares with an odd dimension like 3×3 or 5×5. While this is not a requirement, for our purposes we will stick with 3×3. Later we may throw in a 5×5. Incidentally, per pixel operations are a special case of neighborhood operations where the neighborhood is 1×1.

**Filters**

The term *filter* actually arises from frequency analysis (something we will get to later). Some other terms that you might see used are *convolution* *mask *or* kernel*. For now, we will think of a filter as a set of weights that correspond to pixels in the neighborhood. If we have a 3×3 filter with weights *w* then we often express the weights as:

When we “apply a filter” we are just calculating the average using the specified weights. Let’s look at an example.

Imagine our neighborhood…

and our filter…

So our transformed pixel becomes:

This identity^{*} filter is pretty useless for (hopefully) obvious reasons, but it demonstrates the point. If we apply this filter for every pixel in the image we will get the same image back.

** in mathematics, the ‘identity’ returns the original value for a given operation: e.g. 0 is the identity for addition so 1+0 = 1*.

**Code**

Finally, let’s take a look at a filter function.

private WriteableBitmap Filter(WriteableBitmap grayscale, int[,] filter)

{

// we are going to create a new image

// because we don’t want to modify the

// old image as we are processing it

WriteableBitmap filtered =

new WriteableBitmap(

grayscale.PixelWidth,

grayscale.PixelHeight);

// boiler plate code for our

// histogram stuff

int[] histogram = new int[256];

int maxIntensity = 0;

// this is the magnitude

// of the weights |w|

// we will divide by this later

int filterMagnitude = 0;

for (int x = 0; x < 3; x++)

{

for (int y = 0; y < 3; y++)

{

filterMagnitude += filter[x, y];

}

}

// the math is easier if we create two loops

// instead of one

for (int y = 0; y < grayscale.PixelHeight; y++)

{

for (int x = 0; x < grayscale.PixelWidth; x++)

{

//here’s the pixel we’re centered on

int pixel = x + y * grayscale.PixelWidth;

byte intensity = (byte)grayscale.Pixels[pixel];

//if we are on an edge we are going to skip it

if (y == 0 ||

x == 0 ||

y == grayscale.PixelHeight – 1 ||

x == grayscale.PixelWidth – 1)

{

histogram[intensity]++;

if (histogram[intensity] > maxIntensity)

{

maxIntensity = histogram[intensity];

}

continue;

}

int newIntensity = 0;

//going from -1 to 1 makes the math easier here too

for (int yoffset = -1; yoffset <= 1; yoffset++)

{

for (int xoffset = -1; xoffset <= 1; xoffset++)

{

// we loop through each pixel in the neighborhood

// and apply the filter. by ‘apply the filter’

// I mean multiply it by the appropriate weight

newIntensity +=

((byte)grayscale.Pixels

[(x + xoffset)

+ (y + yoffset) * grayscale.PixelWidth])

* filter[(yoffset + 1), (xoffset + 1)];

}

}

// here we are scaling the new intensity back

newIntensity /= filterMagnitude;

newIntensity =

Math.Max(Math.Min(newIntensity, 255), 0);

// and now just set the color to the

// new intensity

filtered.Pixels[pixel] = (255 << 24)

| (byte)newIntensity << 16

| (byte)newIntensity << 8

| (byte)newIntensity;

histogram[(byte)newIntensity]++;

if (histogram[(byte)newIntensity] > maxIntensity)

{

maxIntensity = histogram[(byte)newIntensity];

}

}

}

PlotHistogram(histogram, maxIntensity);

return filtered;

}

Here’s a simple way to call this with the identity filter described above:

// create the identity filter

// important note: this is ROW by COLUMN (y, x)

int[,] filter = new int[,] {{0,0,0}, {0,1,0}, {0,0,0}};

WriteableBitmap filtered = Filter(grayscale, filter);

As expected, using the identity filter the results are exactly the same. This is a good test to make sure we didn’t mess anything up. Next time we will use this code to apply some useful filters.

**Summary**

Though the formulas might look a little daunting when you write them down, the concept of spatial filtering is pretty easy. Now that we have some code that makes it trivial to test different filters, I suggest you do just that. Play around with this and over the next few lessons we will talk about some specific filters.

**Download Code**

Up next: Smoothing Filters