3.3. Scikit-image: image processing

作者Emmanuelle Gouillart

scikit-image是一个专用于图像处理的Python包,并且使用本机NumPy数组作为图像对象。本章介绍如何在各种图像处理任务中使用scikit-image,并坚持与其他科学Python模块(如NumPy和SciPy)的链接。

也可以看看

对于基本图像处理,例如图像裁剪或简单过滤,可以仅用NumPy和SciPy实现大量简单的操作。请参阅Image manipulation and processing using Numpy and Scipy的图像处理和处理。

请注意,在阅读前一章的内容之前,您应该熟悉前一章的内容,因为基本操作(如掩蔽和标记)是前提条件。

3.3.1. 介绍和概念

图像是NumPy的数组np.ndarray

图片:np.ndarray
像素:数组值:a [2, 3]
频道:数组维度
图像编码:dtypenp.uint8np.uint16np.float
过滤器:函数(numpyskimagescipy
>>> import numpy as np
>>> check = np.zeros((9, 9))
>>> check[::2, 1::2] = 1
>>> check[1::2, ::2] = 1
>>> import matplotlib.pyplot as plt
>>> plt.imshow(check, cmap='gray', interpolation='nearest')
../../_images/sphx_glr_plot_check_001.png

3.3.1.1. scikit-imageSciPy生态系统

scikit-image的最新版本封装在大多数科学Python分发版中,例如Anaconda或Enthought Canopy。它也为Ubuntu / Debian打包。

>>> import skimage
>>> from skimage import data # most functions are in subpackages

大多数scikit-image函数将NumPy ndarrays作为参数

>>> camera = data.camera()
>>> camera.dtype
dtype('uint8')
>>> camera.shape
(512, 512)
>>> from skimage import restoration
>>> filtered_camera = restoration.denoise_bilateral(camera)
>>> type(filtered_camera)
<type 'numpy.ndarray'>

其他Python包可用于图像处理和使用NumPy数组:

此外,强大的图像处理库具有Python绑定:

  • OpenCV(计算机视觉)
  • ITK(3D图像和注册)
  • 和许多其他

(但它们较少Pythonic和NumPy友好,可变程度)。

3.3.1.2. 在scikit-image 中找到的内容

不同类型的函数,从样板效用函数到高级别的最近算法。

  • 过滤器:将图像转换为其他图像的函数。

    • NumPy机械
    • 常用过滤算法
  • 数据简化功能:图像直方图的计算,局部最大值的位置,角点等。

  • 其他操作:I / O,可视化等

3.3.2. 输入/输出,数据类型和颜色空间

I / O:skimage.io

>>> from skimage import io

从文件读取:skimage.io.imread()

>>> import os
>>> filename = os.path.join(skimage.data_dir, 'camera.png')
>>> camera = io.imread(filename)
../../_images/sphx_glr_plot_camera_001.png

适用于Python成像库(或使用plugin关键字参数提供给imread的任何其他I / O插件)支持的所有数据格式。

也适用于URL图像路径:

>>> logo = io.imread('http://scikit-image.org/_static/img/logo.png')

保存到文件:

>>> io.imsave('local_logo.png', logo)

imsave也使用外部插件,例如PIL)

3.3.2.1. 数据类型

../../_images/sphx_glr_plot_camera_uint_001.png

图像ndarrays可以由整数(有符号或无符号)或浮点数表示。

仔细使用带有整数数据类型的溢出

>>> camera = data.camera()
>>> camera.dtype
dtype('uint8')
>>> camera_multiply = 3 * camera

不同的整数大小是可能的:8位,16位或32字节,有符号或无符号。

警告

一个重要的(如果有问题的话)skimage 约定:浮点图像应该位于[-1,1](为了对所有浮点图像具有可比较的对比度)

>>> from skimage import img_as_float
>>> camera_float = img_as_float(camera)
>>> camera.max(), camera_float.max()
(255, 1.0)

一些图像处理例程需要与浮点数组一起工作,并且因此可以输出具有不同类型和来自输入数组的数据范围的数组

>>> try:
... from skimage import filters
... except ImportError:
... from skimage import filter as filters
>>> camera_sobel = filters.sobel(camera)
>>> camera_sobel.max()
0.591502...

警告

在上面的示例中,我们使用scikit-image的filters子模块,它已从filter重命名为版本0.10和0.11之间的filters以避免与Python的内置名称filter冲突。

根据skimage的约定,skimage中提供了效用函数来转换dtype和数据范围:util.img_as_floatutil.img_as_ubyte等。

有关详细信息,请参阅用户指南

3.3.2.2. 颜色空间

彩色图像具有形状(N,M,3)或(N,M,4)(当α通道编码透明度时)

>>> face = scipy.misc.face()
>>> face.shape
(768, 1024, 3)

在不同颜色空间之间转换的程序(RGB,HSV,LAB等)可在skimage.colorcolor.rgb2hsvcolor.lab2rgb等中使用。检查docstring输入图像的预期dtype(和数据范围)。

3D图像

skimage的大多数功能都可以将3D图像作为输入参数。检查文档字符串,以了解是否可以在3D图像(例如MRI或CT图像)上使用函数。

行使

在NumPy阵列上打开磁盘上的彩色图像。

查找skimage函数计算图像的直方图,并绘制每个颜色通道的直方图

将图像转换为灰度并绘制其直方图。

3.3.3. 图像预处理/增强

目标:去噪,特征(边缘)提取,...

3.3.3.1. 本地过滤器

局部滤波器通过相邻像素的值的函数来替换像素的值。该函数可以是线性或非线性的。

邻域:正方形(选择大小),磁盘或更复杂的结构元素

../../_images/kernels1.png

示例:水平Sobel滤波器

>>> text = data.text()
>>> hsobel_text = filters.hsobel(text)

使用以下线性内核来计算水平梯度:

1   2   1
0 0 0
-1 -2 -1
../../_images/sphx_glr_plot_sobel_001.png

3.3.3.2. 非本地过滤器

非局部过滤器使用图像的大区域(或所有图像)来变换一个像素的值:

>>> from skimage import exposure
>>> camera = data.camera()
>>> camera_equalized = exposure.equalize_hist(camera)

增强大均匀区域的对比度。

../../_images/sphx_glr_plot_equalize_hist_001.png

3.3.3.3. 数学形态

有关数学形态学的介绍,请参见维基百科

探测具有简单形状的图像(结构元素),并根据形状在本地适合或错过图像来修改此图像。

默认结构元素:4-像素的连通性

>>> from skimage import morphology
>>> morphology.diamond(1)
array([[0, 1, 0],
[1, 1, 1],
[0, 1, 0]], dtype=uint8)
../../_images/diamond_kernel1.png

侵蚀 =最小过滤器。将像素的值替换为结构化元素覆盖的最小值。

>>> a = np.zeros((7,7), dtype=np.int)
>>> a[1:6, 2:5] = 1
>>> a
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
>>> morphology.binary_erosion(a, morphology.diamond(1)).astype(np.uint8)
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
>>> #Erosion removes objects smaller than the structure
>>> morphology.binary_erosion(a, morphology.diamond(2)).astype(np.uint8)
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)

扩张:最大过滤器:

>>> a = np.zeros((5, 5))
>>> a[2, 2] = 1
>>> a
array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
>>> morphology.binary_dilation(a, morphology.diamond(1)).astype(np.uint8)
array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype=uint8)

打开:erosion + dilation:

>>> a = np.zeros((5,5), dtype=np.int)
>>> a[1:4, 1:4] = 1; a[4, 4] = 1
>>> a
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 1]])
>>> morphology.binary_opening(a, morphology.diamond(1)).astype(np.uint8)
array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype=uint8)

打开可移除小对象并平滑拐角。

灰度数学形态学

数学形态运算也可用于(非二进制)灰度图像(int或浮点数类型)。侵蚀和膨胀对应于最小值最大)过滤器。

提供更高级的数学形态学:tophat,骨架化等

也可以看看

基本数学形态也在scipy.ndimage.morphology中实现。scipy.ndimage实现适用于任意维数组。


过滤器比较示例:图像去噪

>>> from skimage.morphology import disk
>>> coins = data.coins()
>>> coins_zoom = coins[10:80, 300:370]
>>> median_coins = filters.rank.median(coins_zoom, disk(1))
>>> from skimage import restoration
>>> tv_coins = restoration.denoise_tv_chambolle(coins_zoom, weight=0.1)
>>> gaussian_coins = filters.gaussian_filter(coins, sigma=2)
../../_images/sphx_glr_plot_filter_coins_001.png

3.3.4. 图像分割

图像分割是将不同标签归属于图像的不同区域,例如以便提取感兴趣对象的像素。

3.3.4.1. 二进制分割:前景+背景

3.3.4.1.1. Histogram-based method: Otsu thresholding

小费

Otsu方法是一种简单的启发式方法,用于找到将前景与背景分开的阈值。

from skimage import data
from skimage import filters
camera = data.camera()
val = filters.threshold_otsu(camera)
mask = camera < val
../../_images/sphx_glr_plot_threshold_001.png

3.3.4.1.2. Labeling connected components of a discrete image

小费

一旦你已经分离了前景对象,它是用来将它们彼此分离。为此,我们可以为每个分配不同的整数标签。

合成数据:

>>> n = 20
>>> l = 256
>>> im = np.zeros((l, l))
>>> points = l * np.random.random((2, n ** 2))
>>> im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = 1
>>> im = filters.gaussian_filter(im, sigma=l / (4. * n))
>>> blobs = im > im.mean()

标签所有连接的组件:

>>> from skimage import measure
>>> all_labels = measure.label(blobs)

仅标记前台连接组件:

>>> blobs_labels = measure.label(blobs, background=0)
../../_images/sphx_glr_plot_labels_001.png

也可以看看

scipy.ndimage.find_objects()可用于返回图像中对象的切片。

3.3.4.2. 基于标记的方法

如果您在一组区域内有标记,则可以使用这些标记来分割区域。

3.3.4.2.1. Watershed segmentation

流域(skimage.morphology.watershed())是一种区域生长方法,填充图像中的“盆地”

>>> from skimage.morphology import watershed
>>> from skimage.feature import peak_local_max
>>>
>>> # Generate an initial image with two overlapping circles
>>> x, y = np.indices((80, 80))
>>> x1, y1, x2, y2 = 28, 28, 44, 52
>>> r1, r2 = 16, 20
>>> mask_circle1 = (x - x1) ** 2 + (y - y1) ** 2 < r1 ** 2
>>> mask_circle2 = (x - x2) ** 2 + (y - y2) ** 2 < r2 ** 2
>>> image = np.logical_or(mask_circle1, mask_circle2)
>>> # Now we want to separate the two objects in image
>>> # Generate the markers as local maxima of the distance
>>> # to the background
>>> from scipy import ndimage
>>> distance = ndimage.distance_transform_edt(image)
>>> local_maxi = peak_local_max(distance, indices=False, footprint=np.ones((3, 3)), labels=image)
>>> markers = morphology.label(local_maxi)
>>> labels_ws = watershed(-distance, markers, mask=image)

3.3.4.2.2. Random walker segmentation

随机游走算法(skimage.segmentation.random_walker())与Watershed类似,但使用更“概率性”的方法。它是基于标签在图像中的扩散的想法:

>>> from skimage import segmentation
>>> # Transform markers image so that 0-valued pixels are to
>>> # be labelled, and -1-valued pixels represent background
>>> markers[~image] = -1
>>> labels_rw = segmentation.random_walker(image, markers)
../../_images/sphx_glr_plot_segmentations_001.png

后处理标签图片

skimage提供了几个可用于标签图像(即不同离散值标识不同区域的图像)的效用函数。函数名称通常是自解释:skimage.segmentation.clear_border()skimage.segmentation.relabel_from_one()skimage.morphology.remove_small_objects()

行使

  • data子模块加载coins图像。
  • 通过测试几种分割方法,从背景中分离硬币:Otsu阈值,自适应阈值,分水岭或随机游走分割。
  • 如果需要,使用后处理功能来改善硬币/背景分割。

3.3.5. 测量区域的属性

>>> from skimage import measure

示例:计算两个分段区域的大小和周长:

>>> properties = measure.regionprops(labels_rw)
>>> [prop.area for prop in properties]
[770.0, 1168.0]
>>> [prop.perimeter for prop in properties]
[100.91..., 126.81...]

也可以看看

对于一些属性,还可以在scipy.ndimage.measurements中使用不同的API(返回一个列表)来使用函数。

练习(续)

  • 使用前一个练习中硬币和背景的二进制图像。
  • 计算不同硬币的标签图像。
  • 计算所有硬币的大小和偏心率。

3.3.6. 数据可视化和交互

当测试给定的处理流水线时,有意义的可视化是有用的。

一些图像处理操作:

>>> coins = data.coins()
>>> mask = coins > filters.threshold_otsu(coins)
>>> clean_border = segmentation.clear_border(mask)

可视化二进制结果:

>>> plt.figure() 
<matplotlib.figure.Figure object at 0x...>
>>> plt.imshow(clean_border, cmap='gray')
<matplotlib.image.AxesImage object at 0x...>

可视化轮廓

>>> plt.figure() 
<matplotlib.figure.Figure object at 0x...>
>>> plt.imshow(coins, cmap='gray')
<matplotlib.image.AxesImage object at 0x...>
>>> plt.contour(clean_border, [0.5])
<matplotlib.contour.QuadContourSet ...>

使用skimage专用实用功能:

>>> coins_edges = segmentation.mark_boundaries(coins, clean_border.astype(np.int))
../../_images/sphx_glr_plot_boundaries_001.png

(实验性)scikit-image查看器

skimage.viewer =基于matplotlib的画布用于显示图像+实验性的基于Qt的GUI工具包

>>> from skimage import viewer
>>> new_viewer = viewer.ImageViewer(coins)
>>> new_viewer.show()

用于显示像素值。

要进行更多互动,可以将插件添加到查看器:

>>> new_viewer = viewer.ImageViewer(coins) 
>>> from skimage.viewer.plugins import lineprofile
>>> new_viewer += lineprofile.LineProfile()
>>> new_viewer.show()
../../_images/viewer.png

3.3.7. 计算机视觉的特征提取

可以从图像中提取几何或纹理描述符

  • 分类图像的部分(例如天空与建筑物)
  • 匹配不同图像的部分(例如用于对象检测)
  • 以及Computer Vision的许多其他应用程序
>>> from skimage import feature

示例:使用Harris检测器检测拐角

from skimage.feature import corner_harris, corner_subpix, corner_peaks
from skimage.transform import warp, AffineTransform
tform = AffineTransform(scale=(1.3, 1.1), rotation=1, shear=0.7,
translation=(210, 50))
image = warp(data.checkerboard(), tform.inverse, output_shape=(350, 350))
coords = corner_peaks(corner_harris(image), min_distance=5)
coords_subpix = corner_subpix(image, coords, window_size=13)
../../_images/sphx_glr_plot_features_001.png

(此示例取自scikit-image中的plot_corner示例)

然后可以使用诸如角的感兴趣点来匹配不同图像中的对象,如scikit-image的plot_matching示例中所述。