Документация Engee
Notebook

Индексация и сегментация изображений

Открыть пример в Engee

В этой демонстрации показано, как работать с индексированным изображением с помощью IndirectArrays, а также разобрана возможность сегментации изображения при помощи ImageSegmentation.

Индексация

Индексированное изображение состоит из двух частей: индексов и самих пикселей.

Ниже приводится «прямое» представление изображения как массива.

In [ ]:
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)]
Out[0]:
No description has been provided for this image

В качестве альтернативы мы могли бы сохранить его в формате индексированного изображения:

In [ ]:
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
Out[0]:
true

Теперь проанализируем плюсы и минусы использования IndirectArrays.

Обычное индексированное изображения могут потребовать больше памяти за счёт хранения двух отдельных полей. Если же мы используем unique(img), размер близок к собственному размеру изображения. Кроме того, для стандартного индексирование требуется две операции, поэтому его использование может быть медленнее, чем прямое представление изображения (и не поддаваться векторизации SIMD).

Также использование формата индексированного изображения с двумя отдельными массивами может быть неудобным, поэтому IndirectArrays обеспечивает абстракцию массива для объединения этих двух частей:

In [ ]:
using IndirectArrays

indexed_img = IndirectArray(indices, palette)
img == indexed_img
Out[0]:
true

Поскольку IndirectArray – это просто массив, к этому типу применимы общие операции с изображениями, например:

In [ ]:
new_img = imresize(indexed_img; ratio=2)
Out[0]:
No description has been provided for this image
In [ ]:
dump(indexed_img)
IndirectArray{RGB{Float64}, 2, Int64, Matrix{Int64}, Vector{RGB{Float64}}}
  index: Array{Int64}((5, 5)) [1 2 … 4 5; 2 3 … 5 1; … ; 4 5 … 2 3; 5 1 … 3 4]
  values: Array{RGB{Float64}}((5,))
    1: RGB{Float64}
      r: Float64 0.0
      g: Float64 0.0
      b: Float64 0.0
    2: RGB{Float64}
      r: Float64 1.0
      g: Float64 0.0
      b: Float64 0.0
    3: RGB{Float64}
      r: Float64 0.0
      g: Float64 1.0
      b: Float64 0.0
    4: RGB{Float64}
      r: Float64 0.0
      g: Float64 0.0
      b: Float64 1.0
    5: RGB{Float64}
      r: Float64 1.0
      g: Float64 1.0
      b: Float64 1.0
In [ ]:
dump(new_img)
Array{RGB{Float64}}((10, 10))
  1: RGB{Float64}
    r: Float64 0.0
    g: Float64 0.0
    b: Float64 0.0
  2: RGB{Float64}
    r: Float64 0.25
    g: Float64 0.0
    b: Float64 0.0
  3: RGB{Float64}
    r: Float64 0.75
    g: Float64 0.0
    b: Float64 0.0
  4: RGB{Float64}
    r: Float64 0.75
    g: Float64 0.25
    b: Float64 0.0
  5: RGB{Float64}
    r: Float64 0.25
    g: Float64 0.75
    b: Float64 0.0
  ...
  96: RGB{Float64}
    r: Float64 0.75
    g: Float64 0.25
    b: Float64 0.0
  97: RGB{Float64}
    r: Float64 0.25
    g: Float64 0.75
    b: Float64 0.0
  98: RGB{Float64}
    r: Float64 0.0
    g: Float64 0.75
    b: Float64 0.25
  99: RGB{Float64}
    r: Float64 0.0
    g: Float64 0.25
    b: Float64 0.75
  100: RGB{Float64}
    r: Float64 0.0
    g: Float64 0.0
    b: Float64 1.0

Как мы видим, после операции на индексированном массиве он был приведён к виду стандартного массива пикселей.

Сегментация

Далее сегментируем изображение с помощью алгоритма водораздела. Это основной инструмент математической морфологии для сегментации изображений.

In [ ]:
using TestImages
using IndirectArrays
In [ ]:
img = testimage("blobs")
Out[0]:
No description has been provided for this image
In [ ]:
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
Out[0]:
5×5 BitMatrix:
 0  0  0  0  0
 0  1  1  1  0
 0  1  1  1  0
 0  1  1  1  0
 0  0  0  0  0

Функция feature_transform позволяет нам найти преобразование признаков двоичного изображения (bw). Он находит ближайший «объект» (позиции, где bw находится в состоянии true) для каждого местоположения в bw.

В случаях, когда два или более объекта находятся на одинаковом расстоянии, выбирается произвольный объект.

Например, ближайший true в bw_example[1,1]существует в CartesianIndex(2, 2), поэтому ему присвоено значение CartesianIndex(2, 2).

In [ ]:
bw_transform = feature_transform(bw)
bw_transform_example = feature_transform(bw_example)
Out[0]:
5×5 Matrix{CartesianIndex{2}}:
 CartesianIndex(2, 2)  CartesianIndex(2, 2)  …  CartesianIndex(2, 4)
 CartesianIndex(2, 2)  CartesianIndex(2, 2)     CartesianIndex(2, 4)
 CartesianIndex(3, 2)  CartesianIndex(3, 2)     CartesianIndex(3, 4)
 CartesianIndex(4, 2)  CartesianIndex(4, 2)     CartesianIndex(4, 4)
 CartesianIndex(4, 2)  CartesianIndex(4, 2)     CartesianIndex(4, 4)

функция distance transform выполняет трансформацию по заданным меткам.

Например, в bw_transform[1,1] есть CartesianIndex(2, 2), и D[i] для этого элемента будет расстояние между CartesianIndex(1, 1) и CartesianIndex(2, 2), которое равно sqrt(2).

In [ ]:
dist = 1 .- distance_transform(bw_transform)
dist_example = 1 .- distance_transform(bw_transform_example)
Out[0]:
5×5 Matrix{Float64}:
 -0.414214  0.0  0.0  0.0  -0.414214
  0.0       1.0  1.0  1.0   0.0
  0.0       1.0  1.0  1.0   0.0
  0.0       1.0  1.0  1.0   0.0
 -0.414214  0.0  0.0  0.0  -0.414214

функция label_components находит связанные компоненты в двоичном массиве dist_trans. Вы можете предоставить список, указывающий, какие измерения используются для определения связности. Например, region = [1,3] не будет проверяться на наличие связности соседей по измерению 2. Это соответствует только ближайшим соседям, т.е. 4-связности в 2d и 6-связности в 3d матрицах. Значение по умолчанию region = 1:ndims(A): . Выходные данные label представляют собой целочисленный массив, где 0 используется для фоновых пикселей, а каждая отдельная область связных пикселей получает свой целочисленный индекс.

In [ ]:
dist_trans = dist .< 1
markers = label_components(dist_trans)
markers_example = label_components(dist_example .< 0.5)
Gray.(markers/32.0)
Out[0]:
No description has been provided for this image

Функция watershed Метод сегментирует изображение с помощью преобразования водораздела.

Каждый бассейн, образованный преобразованием водораздела, соответствует сегменту. В матрице маркеров ноль означает отсутствие маркера. Если два маркера имеют одинаковый индекс, их регионы будут объединены в один регион.

In [ ]:
segments = watershed(dist, markers)
segments_example = watershed(dist_example , markers_example)
Out[0]:
Segmented Image with:
  labels map: 5×5 Matrix{Int64}
  number of labels: 1

На рисунке ниже слева направо показаны исходное изображение, области маркеров и итоговое сегментированное изображение, разбивающие сегменты изображения по цветам.

In [ ]:
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)
Out[0]:
No description has been provided for this image

Вывод

В этой демонстрации мы показали, как использовать индексирования изображений для сегментации отдельных областей изображения. Данные методы могут быть полезны при выделение областей интереса из исходного изображения.