Индексация и сегментация изображений¶
В этой демонстрации показано, как работать с индексированным изображением с помощью IndirectArrays, а также разобрана возможность сегментации изображения при помощи ImageSegmentation.
Индексация¶
Индексированное изображение состоит из двух частей: индексов и самих пикселей.
Ниже приводится «прямое» представление изображения как массива.
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)]
В качестве альтернативы мы могли бы сохранить его в формате индексированного изображения:
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
Теперь проанализируем плюсы и минусы использования IndirectArrays.
Обычное индексированное изображения могут потребовать больше памяти за счёт хранения двух отдельных полей. Если же мы используем unique(img), размер близок к собственному размеру изображения. Кроме того, для стандартного индексирование требуется две операции, поэтому его использование может быть медленнее, чем прямое представление изображения (и не поддаваться векторизации SIMD).
Также использование формата индексированного изображения с двумя отдельными массивами может быть неудобным, поэтому IndirectArrays обеспечивает абстракцию массива для объединения этих двух частей:
using IndirectArrays
indexed_img = IndirectArray(indices, palette)
img == indexed_img
Поскольку IndirectArray – это просто массив, к этому типу применимы общие операции с изображениями, например:
new_img = imresize(indexed_img; ratio=2)
dump(indexed_img)
dump(new_img)
Как мы видим, после операции на индексированном массиве он был приведён к виду стандартного массива пикселей.
Сегментация¶
Далее сегментируем изображение с помощью алгоритма водораздела. Это основной инструмент математической морфологии для сегментации изображений.
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
Функция feature_transform позволяет нам найти преобразование признаков двоичного изображения (bw). Он находит ближайший «объект» (позиции, где bw находится в состоянии true) для каждого местоположения в bw.
В случаях, когда два или более объекта находятся на одинаковом расстоянии, выбирается произвольный объект.
Например, ближайший true в bw_example[1,1]существует в CartesianIndex(2, 2), поэтому ему присвоено значение CartesianIndex(2, 2).
bw_transform = feature_transform(bw)
bw_transform_example = feature_transform(bw_example)
функция distance transform выполняет трансформацию по заданным меткам.
Например, в bw_transform[1,1] есть CartesianIndex(2, 2), и D[i] для этого элемента будет расстояние между CartesianIndex(1, 1) и CartesianIndex(2, 2), которое равно sqrt(2).
dist = 1 .- distance_transform(bw_transform)
dist_example = 1 .- distance_transform(bw_transform_example)
функция label_components находит связанные компоненты в двоичном массиве dist_trans. Вы можете предоставить список, указывающий, какие измерения используются для определения связности. Например, region = [1,3] не будет проверяться на наличие связности соседей по измерению 2. Это соответствует только ближайшим соседям, т.е. 4-связности в 2d и 6-связности в 3d матрицах. Значение по умолчанию region = 1:ndims(A): . Выходные данные label представляют собой целочисленный массив, где 0 используется для фоновых пикселей, а каждая отдельная область связных пикселей получает свой целочисленный индекс.
dist_trans = dist .< 1
markers = label_components(dist_trans)
markers_example = label_components(dist_example .< 0.5)
Gray.(markers/32.0)
Функция watershed Метод сегментирует изображение с помощью преобразования водораздела.
Каждый бассейн, образованный преобразованием водораздела, соответствует сегменту. В матрице маркеров ноль означает отсутствие маркера. Если два маркера имеют одинаковый индекс, их регионы будут объединены в один регион.
segments = watershed(dist, markers)
segments_example = watershed(dist_example , markers_example)
На рисунке ниже слева направо показаны исходное изображение, области маркеров и итоговое сегментированное изображение, разбивающие сегменты изображения по цветам.
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)
Вывод¶
В этой демонстрации мы показали, как использовать индексирования изображений для сегментации отдельных областей изображения. Данные методы могут быть полезны при выделение областей интереса из исходного изображения.