Filtering images
Introduction
The ImageFilters.jl package provides tools for applying transformations to arrays, with a particular focus on the kinds of operations used in image processing, such as blurring, sharpening, and edge-enhancement.
The term filtering emerges in the context of a Fourier transformation of an image, which maps an image from its canonical spatial domain to its concomitant frequency domain. Manipulating an image in the frequency domain amounts to retaining or discarding particular frequency components --- a process analogous to sifting or filtering [1]. Because the Fourier transform establishes a link between the spatial and frequency representation of an image, one can interpret various image manipulations in the spatial domain as filtering operations which accept or reject specific frequencies.
The phrase spatial filtering is often used to emphasize that an operation is, at least conceptually, devised in the context of the spatial domain of an image. We further distinguish between linear and non-linear spatial filtering. A filter is called linear if the operation performed on the pixels is linear, and is labeled non-linear otherwise.
Function options
The imfilter()
function filters a one, two or multidimensional array img
with a kernel
by computing their correlation.
The syntax for imfilter()
is as follows:
imfilter([T], img, kernel, [border="replicate"], [alg])
imfilter([r], img, kernel, [border="replicate"], [alg])
imfilter(r, T, img, kernel, [border="replicate"], [alg])
The following subsections describe valid options for the function arguments in more detail.
Choices for r
Optionally, you can dispatch to different implementations by passing in a resource r
as defined by the ComputationalResources package.
For example:
imfilter(ArrayFireLibs(), img, kernel)
would request that the computation be performed on the GPU using the ArrayFire libraries.
Choices for T
Optionally, you can control the element type of the output image by passing in a type T
as the first argument.
Choices for kernel
The kernel[0, 0,..]
parameter corresponds to the origin (zero displacement) of the kernel; you can use centered
to place the origin at the array center, or use the OffsetArrays package to set kernel
's indices manually. For example, to filter with a random centered 3x3 kernel, you could use either of the following:
kernel = centered(rand(3,3))
kernel = OffsetArray(rand(3,3), -1:1, -1:1)
The kernel
parameter can be specified as an array or as a "factored kernel", a tuple (filt1, filt2, ...)
of filters to apply along each axis of the image. In cases where you know your kernel is separable, this format can speed processing. Each of these should have the same dimensionality as the image itself, and be shaped in a manner that indicates the filtering axis, e.g., a 3x1 filter for filtering the first dimension and a 1x3 filter for filtering the second dimension. In two dimensions, any kernel passed as a single matrix is checked for separability; if you want to eliminate that check, pass the kernel as a single-element tuple, (kernel,)
.
Choices for border
At the image edge, border
is used to specify the padding which will be used to extrapolate the image beyond its original bounds.
As an indicative example of each option, the results of the padding are illustrated on an image consisting of a row of six pixels which are specified alphabetically:
┏━━━━━━┓
┃abcdef┃
┗━━━━━━┛
We show the effects of padding only on the left and right border, but analogous consequences hold for the top and bottom border.
"replicate"
(default)
The border pixels extend beyond the image boundaries.
╭────┏━━━━━━┓────╮
│aaaa┃abcdef┃ffff│
╰────┗━━━━━━┛────╯
"circular"
The border pixels wrap around. For instance, indexing beyond the left border returns values starting from the right border.
╭────┏━━━━━━┓────╮
│cdef┃abcdef┃abcd│
╰────┗━━━━━━┛────╯
"reflect"
The border pixels reflect relative to a position between pixels. That is, the border pixel is omitted when mirroring.
╭────┏━━━━━━┓────╮
│dcba┃abcdef┃fedc│
╰────┗━━━━━━┛────╯
"symmetric"
The border pixels reflect relative to the edge itself.
╭────┏━━━━━━┓────╮
│edcb┃abcdef┃edcb│
╰────┗━━━━━━┛────╯
Fill(m)
The border pixels are filled with a specified value .
╭────┏━━━━━━┓────╮
│mmmm┃abcdef┃mmmm│
╰────┗━━━━━━┛────╯
Inner()
Indicate that edges are to be discarded in filtering, only the interior of the result is to be returned.
NA()
Choose filtering using "NA" (Not Available) boundary conditions. This is most appropriate for filters that have only positive weights summing to 1, such as blurring filters---rather than "make up" values beyond the edges, the result is normalized by the number of in-bounds pixels (similar to nanmean
).
For example:
julia> img = [0, 0, 1, 0, 0, 0, 1];
julia> imfilter(img, centered([1, 1, 1]/3), NA())
7-element Vector{Float64}:
0.0
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.0
0.3333333333333333
0.5
Choices for alg
The alg
parameter allows you to choose the particular algorithm: Algorithm.FIR()
(finite impulse response, aka traditional digital filtering) or Algorithm.FFT()
(Fourier-based filtering). If no choice is specified, one will be chosen based on the size of the image and kernel in a way that strives to deliver good performance. Alternatively you can use a custom filter type, like KernelFactors.IIRGaussian
.
Convolution versus correlation
The default operation of imfilter
is correlation.
In the following example, consider the image matrix f
and a centered filter coefficient mask w
.
The specification Fill(0, w)
indicates that we wish to pad the border of f
with zeros. The amount of padding is automatically determined by considering the length of w
.
# Create a two-dimensional discrete unit impulse function.
f = fill(0, (9, 9));
f[5, 5] = 1
# Specify a filter coefficient mask and set the center of the mask as the origin.
w = centered([1 2 3; 4 5 6 ; 7 8 9]);
By reflecting w
we compute the convolution of f
and w
. Compare the correlation and convolution:
correlation = imfilter(f, w, Fill(0, w))
convolution = imfilter(f, reflect(w), Fill(0, w))
Miscellaneous border padding options
Given the following example values:
f = reshape(1.0:81.0, 9, 9)
w = centered(reshape(1.0:9.0, 3, 3))
you can designate the type of padding by supplying an appropriate string:
imfilter(f, w, "replicate")
imfilter(f, w, "circular")
imfilter(f, w, "symmetric")
imfilter(f, w, "reflect")
Alternatively, you can explicitly use the Pad
type to designate the padding style:
imfilter(f, w, Pad(:replicate))
imfilter(f, w, Pad(:circular))
imfilter(f, w, Pad(:symmetric))
imfilter(f, w, Pad(:reflect))
If you want to pad with a specific value, use the Fill
type.
imfilter(f, w, Fill(0, w))
imfilter(f, w, Fill(1, w))
imfilter(f, w, Fill(-1, w))
Specify Inner()
if you want to retrieve the interior sub-array of f for which the filtering operation is defined without padding:
imfilter(f, w, Inner())
The imfilter!
function
The imfilter!()
function filters an array img
with kernel kernel
by computing their correlation, storing the result in imgfilt
.
imfilter!(imgfilt, img, kernel, [border="replicate"], [alg])
imfilter!(r, imgfilt, img, kernel, border::Pad)
imfilter!(r, imgfilt, img, kernel, border::NoPad, [inds=axes(imgfilt)])
The indices of imgfilt
determine the region over which the filtered image is computed — you can use this fact to select just a specific region of interest, although be aware that the input img
might still get padded.
Alteratively, explicitly provide the indices inds
of imgfilt
that you want to calculate, and use NoPad
boundary conditions. In such cases, you are responsible for supplying appropriate padding: img
must be indexable for all of the locations needed for calculating the output. This syntax is best-supported for FIR filtering; in particular, the IIR filtering can lead to results that are inconsistent with respect to filtering the entire array.