实时笔记本

你可以在 live session Binder 中运行这个笔记本,或者在 Github 上查看它。

图像处理

欢迎使用 dask-image 的快速入门指南。

  1. 设置你的环境

  2. 导入 dask-image

  3. 获取示例数据

  4. 读取图像数据

    1. 读取单张图像

    2. 读取多张图像

  5. 将您自己的自定义函数应用于图像

    1. 令人尴尬的并行问题

  6. 将部分图像拼接在一起

  7. 分割分析流程

    1. 过滤

    2. 分段

    3. 分析

  8. 下一步

  9. 清理临时目录和文件

设置你的环境

安装额外依赖项

我们首先安装 scikit-image 库,以便更容易访问那里的示例图像数据。

如果你在自己的电脑上运行这个笔记本,而不是在mybinder环境中,你还需要确保你的Python环境包含以下内容:* dask * dask-image * python-graphviz * scikit-image * matplotlib * numpy

您可以参考 dask-examples 仓库使用的完整依赖列表,该列表可在 `binder/environment.yml 文件中找到 <https://github.com/dask/dask-examples/blob/main/binder/environment.yml>`__ (请注意,nomkl 包对 Windows 用户不可用):https://github.com/dask/dask-examples/blob/main/binder/environment.yml

导入 dask-image

当你导入 dask-image 时,请确保在两个单词之间使用下划线而不是连字符。

[ ]:
import dask_image.imread
import dask_image.ndfilters
import dask_image.ndmeasure
import dask.array as da

我们还将使用 matplotlib 在这个笔记本中显示图像结果。

[ ]:
import matplotlib.pyplot as plt
%matplotlib inline

获取示例数据

在本教程中,我们将使用来自 scikit-image 库的一些示例图像数据。这些图像非常小,但将允许我们演示 dask-image 的功能。

让我们下载并保存一张宇航员Eileen Collins的公有领域图片到一个临时目录。这张图片最初是从NASA的Great Images数据库https://flic.kr/p/r9qvLn下载的,但我们将使用scikit-image的``data.astronaut()``方法来访问它。

[ ]:
!mkdir temp
[ ]:
import os
from skimage import data, io

output_filename = os.path.join('temp', 'astronaut.png')
io.imsave(output_filename, data.astronaut())

非常大的数据集通常无法将所有数据放入单个文件中,因此我们将把这张图片分割成四个部分,并将图像块保存到第二个临时目录中。这将让你更好地了解如何在实际数据集上使用 dask-image。

[ ]:
!mkdir temp-tiles
[ ]:
io.imsave(os.path.join('temp-tiles', 'image-00.png'), data.astronaut()[:256, :256, :])  # top left
io.imsave(os.path.join('temp-tiles', 'image-01.png'), data.astronaut()[:256, 256:, :])  # top right
io.imsave(os.path.join('temp-tiles', 'image-10.png'), data.astronaut()[256:, :256, :])  # bottom left
io.imsave(os.path.join('temp-tiles', 'image-11.png'), data.astronaut()[256:, 256:, :])  # bottom right

现在我们已经保存了一些数据,让我们练习使用 dask-image 读取文件并处理我们的图像。

读取图像数据

读取单张图像

让我们使用 dask-image 的 imread() 加载一张宇航员 Eileen Collins 的公共领域图片。这张图片最初从 NASA 的 Great Images 数据库下载,链接为 https://flic.kr/p/r9qvLn

[ ]:
import os
filename = os.path.join('temp', 'astronaut.png')
print(filename)
[ ]:
astronaut = dask_image.imread.imread(filename)
print(astronaut)
plt.imshow(astronaut[0, ...])  # display the first (and only) frame of the image

这创建了一个 shape=(1, 512, 512, 3) 的 dask 数组。这意味着它包含一个图像帧,具有 512 行、512 列和 3 个颜色通道。

由于图像相对较小,它完全适合一个 dask-image 块,其中 chunksize=(1, 512, 512, 3)

读取多张图像

在许多情况下,您可能在磁盘上存储了多张图片,例如:image_00.pngimage_01.png、… image_NN.png。这些可以作为多个图像帧读入dask数组。

这里我们将宇航员图像分割成四个不重叠的图块:* image_00.png = 左上图像(索引 0,0)* image_01.png = 右上图像(索引 0,1)* image_10.png = 左下图像(索引 1,0)* image_11.png = 右下图像(索引 1,1)

此文件名模式可以用正则表达式匹配:image-*.png

[ ]:
!ls temp-tiles
[ ]:
import os
filename_pattern = os.path.join('temp-tiles', 'image-*.png')
tiled_astronaut_images = dask_image.imread.imread(filename_pattern)
print(tiled_astronaut_images)

这创建了一个 shape=(4, 256, 256, 3) 的 dask 数组。这意味着它包含四个图像帧;每个图像帧有 256 行、256 列和 3 个颜色通道。

在这种情况下有四个块。这里的每个图像帧都是一个单独的块,使用 chunksize=(1, 256, 256, 3)

[ ]:
fig, ax = plt.subplots(nrows=2, ncols=2)
ax[0,0].imshow(tiled_astronaut_images[0])
ax[0,1].imshow(tiled_astronaut_images[1])
ax[1,0].imshow(tiled_astronaut_images[2])
ax[1,1].imshow(tiled_astronaut_images[3])
plt.show()

将您自己的自定义函数应用于图像

接下来,您可能希望进行一些图像处理,并将某个函数应用于您的图像。

我们将使用一个非常简单的例子:将RGB图像转换为灰度图像。但你也可以使用这种方法将任意函数应用于dask图像。要将我们的图像转换为灰度图像,我们将使用计算亮度(参考pdf)的公式:

Y = 0.2125 R + 0.7154 G + 0.0721 B

我们将按以下方式编写此方程的函数:

[ ]:
def grayscale(rgb):
    result = ((rgb[..., 0] * 0.2125) +
              (rgb[..., 1] * 0.7154) +
              (rgb[..., 2] * 0.0721))
    return result

让我们将这个函数应用于我们作为单个文件读入的宇航员图像,并可视化计算图。

(大多数情况下不需要可视化计算图,但了解dask在后台的操作是有帮助的,它对于调试问题也非常有用。)

[ ]:
single_image_result = grayscale(astronaut)
print(single_image_result)
single_image_result.visualize()

我们还看到结果的形状中不再有三个颜色通道,并且输出图像如预期所示。

[ ]:
print("Original image dimensions: ", astronaut.shape)
print("Processed image dimensions:", single_image_result.shape)

fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)
ax0.imshow(astronaut[0, ...])            # display the first (and only) frame of the image
ax1.imshow(single_image_result[0, ...], cmap='gray')  # display the first (and only) frame of the image

# Subplot headings
ax0.set_title('Original image')
ax1.set_title('Processed image')

# Don't display axes
ax0.axis('off')
ax1.axis('off')

# Display images
plt.show(fig)

令人尴尬的并行问题

语法与将函数应用于多个图像或dask块相同。这是一个令人尴尬的并行问题的示例,我们看到dask自动为每个块创建了一个计算图。

[ ]:
result = grayscale(tiled_astronaut_images)
print(result)
result.visualize()

让我们来看看结果。

[ ]:
fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2)
ax0.imshow(result[0, ...], cmap='gray')
ax1.imshow(result[1, ...], cmap='gray')
ax2.imshow(result[2, ...], cmap='gray')
ax3.imshow(result[3, ...], cmap='gray')

# Subplot headings
ax0.set_title('First chunk')
ax1.set_title('Second chunk')
ax2.set_title('Thurd chunk')
ax3.set_title('Fourth chunk')

# Don't display axes
ax0.axis('off')
ax1.axis('off')
ax2.axis('off')
ax3.axis('off')

# Display images
plt.show(fig)

将部分图像拼接在一起

好的,事情看起来很不错!但我们如何将这些图像块拼接在一起呢?

到目前为止,我们还没有需要来自相邻像素的信息来进行计算。但是有很多函数(比如 dask-image ndfilters 中的那些)确实需要这些信息以获得准确的结果。如果你不告诉 dask 你的图像应该如何连接,你可能会遇到不想要的边缘效应。

Dask 有几种将块连接在一起的方法:堆叠、连接和块

Block 非常多功能,因此我们将在下一个示例中使用它。你只需传入一个列表(或列表的列表)来告诉 dask 图像块之间的空间关系。

[ ]:
data = [[result[0, ...], result[1, ...]],
        [result[2, ...], result[3, ...]]]
combined_image = da.block(data)
print(combined_image.shape)
plt.imshow(combined_image, cmap='gray')

分割分析流程

我们将通过一个简单的图像分割和分析流程,分为三个步骤:1. 过滤 1. 分割 1. 分析

过滤

大多数分析流程都需要一定程度的图像预处理。dask-image 提供了一些内置滤镜,可通过 dask-image ndfilters 获得。

通常,在分割之前可以使用高斯滤波器来平滑图像。这会导致图像中的一些锐度损失,但可以提高依赖于图像阈值的方法的分割质量。

[ ]:
smoothed_image = dask_image.ndfilters.gaussian_filter(combined_image, sigma=[1, 1])

我们在平滑后的图像中看到了少量的模糊。

[ ]:
fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)
ax0.imshow(smoothed_image, cmap='gray')
ax1.imshow(smoothed_image - combined_image, cmap='gray')

# Subplot headings
ax0.set_title('Smoothed image')
ax1.set_title('Difference from original')

# Don't display axes
ax0.axis('off')
ax1.axis('off')

# Display images
plt.show(fig)

由于高斯滤波器使用相邻像素的信息,计算图看起来比我们之前看到的要复杂得多。这不再是令人尴尬的并行。在可能的情况下,dask 保持每个四个图像块的计算分开,但必须在边缘附近组合来自不同块的信息。

[ ]:
smoothed_image.visualize()

分段

在图像预处理之后,我们从数据中分割出感兴趣的区域。我们将使用一个简单的任意阈值作为截止点,设定为平滑图像最大强度的75%。

[ ]:
threshold_value = 0.75 * da.max(smoothed_image).compute()
print(threshold_value)
[ ]:
threshold_image = smoothed_image > threshold_value
plt.imshow(threshold_image, cmap='gray')

接下来,我们标记每个高于阈值的连通像素区域。为此,我们使用 dask-image ndmeasure 中的 label 函数。这将返回标签图像和标签的数量。

[ ]:
label_image, num_labels = dask_image.ndmeasure.label(threshold_image)
[ ]:
print("Number of labels:", int(num_labels))
plt.imshow(label_image, cmap='viridis')

分析

dask-image ndmeasure 中有许多内置函数,适用于定量分析。

我们将使用 dask_image.ndmeasure.mean()dask_image.ndmeasure.standard_deviation() 函数,并通过 dask_image.ndmeasure.labeled_comprehension() 将它们应用于每个标签区域。

[ ]:
index = list(range(int(num_labels)))  # Note that we're including the background label=0 here, too.
out_dtype = float  # The data type we want to use for our results.
default = None     # The value to return if an element of index does not exist in the label image.
mean_values = dask_image.ndmeasure.labeled_comprehension(combined_image, label_image, index, dask_image.ndmeasure.mean, out_dtype, default, pass_positions=False)
print(mean_values.compute())

由于我们在索引中包含了标签0,第一个平均值远低于其他值并不奇怪——它是我们分割阈值以下的背景区域。

我们还可以计算灰度图像中像素值的标准差。

[ ]:
stdev_values = dask_image.ndmeasure.labeled_comprehension(combined_image, label_image, index, dask_image.ndmeasure.standard_deviation, out_dtype, default, pass_positions=False)

最后,让我们将分析结果加载到一个 pandas 表格中,然后将其保存为 csv 文件。

[ ]:
import pandas as pd

df = pd.DataFrame()
df['label'] = index
df['mean'] = mean_values.compute()
df['standard_deviation'] = stdev_values.compute()

df.head()
[ ]:
df.to_csv('example_analysis_results.csv')
print('Saved example_analysis_results.csv')

下一步

我希望本指南能帮助你开始使用 dask-image。

文档

你可以在 dask-image 文档API 参考 中阅读更多关于 dask-image 的内容。dask 的文档在这里

dask-examples 仓库中有许多其他示例笔记本:https://github.com/dask/dask-examples

使用 dask distributed 进行扩展

如果你想将dask任务发送到计算集群进行分布式处理,你应该看看 dask distributed 。还有一个 快速入门指南 可供参考。

使用 zarr 保存图像数据

在某些情况下,图像处理后可能需要保存大量数据,zarr 是一个你可能觉得有用的 Python 库。

清理临时目录和文件

你记得我们把一些示例数据保存到了目录 temp/temp-tiles/ 中。要删除这些内容,请运行以下命令:

[ ]:
!rm -r temp
[ ]:
!rm -r temp-tiles