Indexing and segmentation of images¶
This demonstration shows how to work with an indexed image using IndirectArrays, and also breaks down how to segment an image using ImageSegmentation.
Indexing¶
An indexed image consists of two parts: the indexes and the pixels themselves.
The following is a "direct" representation of the image as an array.
Pkg.add(["IndirectArrays", "TestImages"])
using Images
img = [
RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0)
RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0)
RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0)
RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0)
RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0)]
Alternatively, we could save it as an indexed image:
indices = [1 2 3 4 5; 2 3 4 5 1; 3 4 5 1 2; 4 5 1 2 3; 5 1 2 3 4] # `i` records the pixel value `palette[i]`
palette = [RGB(0.0, 0.0, 0.0), RGB(1.0, 0.0, 0.0), RGB(0.0, 1.0, 0.0), RGB(0.0, 0.0, 1.0), RGB(1.0, 1.0, 1.0)]
palette[indices] == img
Let's now analyse the pros and cons of using IndirectArrays.
Normal indexed images may require more memory due to storing two separate fields. If we use unique(img), on the other hand, the size is close to the image's own size. Also, standard indexing requires two operations, so it may be slower than direct image representation (and not amenable to SIMD vectorisation).
Also, using the indexed image format with two separate arrays can be awkward, so IndirectArrays provides an array abstraction to combine the two parts:
using IndirectArrays
indexed_img = IndirectArray(indices, palette)
img == indexed_img
Because an IndirectArray is simply an array, common image operations apply to this type, such as:
new_img = imresize(indexed_img; ratio=2)
dump(indexed_img)
dump(new_img)
As we can see, after the operation on the indexed array, it has been reduced to the form of a standard array of pixels.
Segmentation¶
Next, we segment the image using watershed algorithm. This is the basic tool of mathematical morphology for image segmentation.
using TestImages
using IndirectArrays
img = testimage("blobs")
img_example = zeros(Gray, 5, 5)
img_example[2:4,2:4] .= Gray(0.6)
bw = Gray.(img) .> 0.5
bw_example = img_example .> 0.5
The feature_transform function allows us to find the binary image's feature transform (bw). It finds the nearest "feature" (positions where bw is true) for each location in bw.
In cases where two or more objects are at the same distance, an arbitrary object is chosen.
For example, the nearest true in bw_example[1,1]exists at CartesianIndex(2, 2), so it is assigned the value CartesianIndex(2, 2).
bw_transform = feature_transform(bw)
bw_transform_example = feature_transform(bw_example)
the distance transform function performs transformation by specified labels.
For example, bw_transform[1,1] has CartesianIndex(2, 2), and D[i] for this element will be the distance between CartesianIndex(1, 1) and CartesianIndex(2, 2), which is equal to sqrt(2).
dist = 1 .- distance_transform(bw_transform)
dist_example = 1 .- distance_transform(bw_transform_example)
The label_components function finds related components in the dist_trans binary array. You can provide a list indicating which dimensions are used to determine connectedness. For example, region = [1,3] will not be checked for connectedness of neighbours on dimension 2. This only corresponds to nearest neighbours, i.e. 4-connectivity in 2d and 6-connectivity in 3d matrices. Default value region = 1:ndims(A): . The output label is an integer array, where 0 is used for background pixels and each individual region of connected pixels gets its own integer index.
dist_trans = dist .< 1
markers = label_components(dist_trans)
markers_example = label_components(dist_example .< 0.5)
Gray.(markers/32.0)
Function watershed Method segments the image using a watershed transformation.
Each basin formed by the watershed transformation corresponds to a segment. In the marker matrix, zero means no marker. If two markers have the same index, their regions will be merged into one region.
segments = watershed(dist, markers)
segments_example = watershed(dist_example , markers_example)
The figure below shows from left to right the original image, the marker regions and the final segmented image, dividing the image segments by colour.
labels = labels_map(segments)
colored_labels = IndirectArray(labels, distinguishable_colors(maximum(labels)))
masked_colored_labels = colored_labels .* (1 .- bw)
mosaic(img, colored_labels, masked_colored_labels; nrow=1)
Conclusion¶
In this demonstration, we have shown how to use image indexing to segment individual image regions. These techniques can be useful in isolating regions of interest from the original image.