图像索引和分割¶
本演示将展示如何使用 IndirectArray 处理索引图像,以及如何使用 ImageSegmentation 分割图像。
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)]
或者,我们也可以将其保存为索引图像:
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
现在让我们分析一下使用 IndirectArray 的利弊。
由于要存储两个单独的字段,普通索引图像可能需要更多内存。另一方面,如果我们使用 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 中的每个位置找到最近的 "特征"(bw 为真的位置)。
如果两个或多个对象的距离相同,则会选择一个任意对象。
例如,bw_example[1,1]中最近的 true 位于 CartesianIndex(2, 2),因此它被赋值为 CartesianIndex(2,2)。
bw_transform = feature_transform(bw)
bw_transform_example = feature_transform(bw_example)
距离变换*函数按指定的标签执行变换。
例如,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 上邻居的连通性。这仅对应于最近邻,即 2 维矩阵中的 4 连通性和 3 维矩阵中的 6 连通性。默认值 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 方法使用分水岭变换对图像进行分割。
分水岭变换形成的每个盆地都对应一个片段。在标记矩阵中,0 表示没有标记。如果两个标记的索引相同,它们的区域将合并为一个区域。
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)
输出¶
在本演示中,我们展示了如何使用图像索引来分割单个图像区域。这些技术可用于从原始图像中分离出感兴趣的区域。