Engee 文档
Notebook

图像的索引和分割

本演示演示如何使用IndirectArrays处理索引图像,以及使用ImageSegmentation进行图像分割的可能性。

索引化

索引图像由两部分组成:索引和像素本身。

以下是图像作为数组的"直接"表示。

In [ ]:
Pkg.add(["IndirectArrays", "TestImages"])
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中的每个位置找到最近的"对象"(bw处于真实状态的位置)。

在两个或更多对象处于相同距离的情况下,选择任意对象。

例如,Bw_example[1,1]中最接近的true存在于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中邻居的连通性。 这仅对应于最近邻居,即2d中的4连通性和3d矩阵中的6连通性。 默认值为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

分水岭方法函数使用分水岭变换对图像进行分段。

流域变换形成的每个盆地对应一段。 在标记矩阵中,零表示没有标记。 如果两个标记具有相同的索引,则它们的区域将合并为一个区域。

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

结论

在这个演示中,我们展示了如何使用图像索引来分割单个图像区域。 这些方法可用于从原始图像中提取感兴趣区域。