How hard is it to implement an image processing filter in Julia? Well, it’s not hard at all, and you don’t even need much programming experience. The simplest filters are often the most meaningful, and easiest to implement. There are numerous filters which make use of statistics in the image to facilitate enhancement – like the mean and variance of intensity values. Each pixel is surrounded by a neighbourhood of pixels which influence its interpretation. A pixel whose neighbours all have similar values lies in a uniform region, whereas a pixel who neighbours are vastly different may lie on an edge. Figure 1 shows a 3×3 neighbourhood region centred on a pixel [2,2] with the value 4.
There are some algorithms that use both the local mean and variance to enhance a pixel. The neighbourhood in Fig.1 has a mean of ≈ 10, and a variance of 36.4. This tends to result in subtle details being enhanced at the expense of the features of interest. Consider instead the following equation which maintains its local mean, yet modifies the variance:
where E is the enhanced pixel, I is the original pixel, m is the mean from the neighbourhood surrounding the pixel of size n×n, and k is the gain (ratio of the new local standard deviation to the original standard deviation). When k > 1, the image will undergo sharpening, and when 0 ≤ k < 1, the image will be smoothed (If k=0, then it is equivalent to mean-filtering. (Note, i, and j represent the row and column indices).
So how do we build a Julia function to perform this operation? Let’s start with the basic structure of the function, named localStatENH():
function localStatENH(img, k, n) end
There are three input parameters, or values we want to give the function:
img - the grayscale image to enhance k - the gain n - the size of the local neighbourhood (odd)
Now there are some basic things to add to the function, like finding the dimensions of the image, and calculating the radius of n. The first line uses the function size(), which returns the number of rows and columns in the image img, and stores them in dx and dy respectively. The second line makes a copy of the image img, and stores it in imgN. The last line divides n by 2, returning a whole number – so if n=5, w=2, which represents the “radius” of the neighbourhood.
dx, dy = size(img) imgN = copy(img) w = div(n,2)
Now the function needs to process each pixel in the image.
NOTE: The trick with this is dealing with the edges of the image. As can be seen in Fig.1, the neighbourhood overlays the image when centred on pixel [2,2]. However if the 3×3 neighbourhood were centred on [1,1] it would go outside the image. This problem is solved in two ways: (i) pad the image, or (ii) offset the start to the neighbourhood processing. This example will choose the latter option, so that for a 3×3 neighbourhood, processing will begin at pixel [2,2]. The dashed line in Fig.2 shows the inner boundary of pixels to be processed.