图像处理
内容
实时笔记本
你可以在 live session 中运行这个笔记本,或者在 Github 上查看它。
图像处理¶
欢迎使用 dask-image 的快速入门指南。
设置你的环境
导入 dask-image
获取示例数据
读取图像数据
读取单张图像
读取多张图像
将您自己的自定义函数应用于图像
令人尴尬的并行问题
将部分图像拼接在一起
分割分析流程
过滤
分段
分析
下一步
清理临时目录和文件
设置你的环境¶
安装额外依赖项¶
我们首先安装 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.png
、image_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