This post is currently a draft. It is subject to change and modifications.

As a school assignment, we were asked to build a simple OCR system. Basically we’re given 9 clean pages from a book and 6 noisy ones. We need to train a classifier on the 9 pages and then evaluate its accuracy on the 6 test pages. The noise on the test images increase with each successive image, and by time it comes to the last image I can’t even read it myself.

I built a classifier based on Principal Component Analysis and Nearest Neighbour. While this worked for the images with little noise, I wasn’t satisfied with the accuracy for the noisy ones. I wanted to write a filter for denoising the images. I tried some simple methods first, like thresholding and median filters but they made the overall accuracy worse.

After this I decided to write a filter that used multiple Convolution Kernels together in order to reduce the noise. I already knew that image kernels, although they are very simple, can create complex effects like edge detection. So in theory, stacking different kernels on top of each other could produce an effect that reduces the noise and makes the image more legible. But there is one small problem, the simple 3x3 kernels for edge detection only have 9 parameters to tweak. But I was planning to use four 5x5 kernels, which has 100 parameters to tweak. But before that, let’s write the definition of our multikernel.

Defining the Kernels

In order to have a multikernel, you need to have a single kernel first. And a kernel is a really simple data structure, in fact a 3x3 kernel is just a 3x3 matrix. So we can define a function to create an empty one.

def create_kernel(x=3, y=3):
    return np.zeros((x, y))

After this, it’s easy to create a multikernel.

kernel = [create_kernel(5, 5), create_kernel(5, 5), create_kernel(5, 5), create_kernel(5, 5)]

When we have a kernel that is convigured, applying it is just iterating over the list and using scipy.ndimage.filters.convolve. This is a function that comes with scipy, it takes an image and a convolution matrix.

def apply_multikernel(data, kernels):
    for kernel in kernels:
        data = filters.convolve(data, kernel)
    return data

Tweaking the Parameters

So now we have a multikernel and a function to apply it to images, but if you test the code you will see that it returns an empty image no matter what you give as the input. This is because we haven’t tweaked any of the parameters yet and initially they are all zeros.

In order to tweak the parameters to give the result we want, we are going to use a genetic algorithm. But because the problem is relatively simple, we will use the simplest method of Genetic Algoritms, the hill climbing algorithm.