5. 图像数据类型及其含义#

skimage 中,图像仅仅是 numpy 数组,它支持各种数据类型 [1]“dtypes”。为了避免扭曲图像强度(参见 重新缩放强度值),我们假设图像使用以下 dtype 范围

数据类型

范围

uint8

0 到 255

uint16

0 到 65535

uint32

0 到 232 - 1

float

-1 到 1 或 0 到 1

int8

-128 到 127

int16

-32768 到 32767

int32

-231 到 231 - 1

请注意,即使浮点类型本身可以超过此范围,浮点图像也应限制在 -1 到 1 的范围内;另一方面,所有整数 dtype 的像素强度都可以跨越整个数据类型范围。除少数例外情况外,不支持 64 位 (u)int 图像

skimage 中的函数设计为可以接受任何这些 dtype,但为了提高效率,可能会返回不同 dtype 的图像(参见 输出类型)。如果您需要特定的 dtype,skimage 提供了实用程序函数来转换 dtype 并正确重新缩放图像强度(参见 输入类型)。您永远不要对图像使用 astype,因为它违反了关于 dtype 范围的这些假设

>>> import numpy as np
>>> import skimage as ski
>>> image = np.arange(0, 50, 10, dtype=np.uint8)
>>> print(image.astype(float)) # These float values are out of range.
[  0.  10.  20.  30.  40.]
>>> print(ski.util.img_as_float(image))
[ 0.          0.03921569  0.07843137  0.11764706  0.15686275]

5.1. 输入类型#

虽然我们的目标是保留输入图像的数据范围和类型,但函数可能只支持这些数据类型的一部分。在这种情况下,输入将转换为所需的类型(如果可能),如果需要内存复制,则会向日志打印警告消息。类型要求应在文档字符串中注明。

以下主包中的实用程序函数可供开发人员和用户使用

函数名称

描述

img_as_float

转换为浮点数(整数类型变为 64 位浮点数)

img_as_ubyte

转换为 8 位 uint。

img_as_uint

转换为 16 位 uint。

img_as_int

转换为 16 位 int。

这些函数将图像转换为所需的 dtype 并正确地重新缩放其值

>>> import skimage as ski
>>> image = np.array([0, 0.5, 1], dtype=float)
>>> ski.util.img_as_ubyte(image)
array([  0, 128, 255], dtype=uint8)

小心!这些转换可能会导致精度损失,因为 8 位无法容纳与 64 位相同的信息量

>>> image = np.array([0, 0.5, 0.503, 1], dtype=float)
>>> ski.util.img_as_ubyte(image)
array([  0, 128, 128, 255], dtype=uint8)

请注意,skimage.util.img_as_float() 将保留浮点类型的精度,并且不会自动重新缩放浮点输入的范围。

此外,某些函数采用 preserve_range 参数,在该参数中,范围转换很方便但不是必需的。例如,skimage.transform.warp() 中的插值需要类型为 float 的图像,该图像的范围应在 [0, 1] 中。因此,默认情况下,输入图像将重新缩放至此范围。但是,在某些情况下,图像值表示物理测量值,例如温度或降雨量值,用户不希望对其进行重新缩放。使用 preserve_range=True,将保留数据的原始范围,即使输出是 float 图像。然后,用户必须确保此非标准图像由下游函数正确处理,这些函数可能期望 [0, 1] 中的图像。通常,除非函数具有 preserve_range=False 关键字参数,否则不会自动重新缩放浮点输入。

>>> image = ski.data.coins()
>>> image.dtype, image.min(), image.max(), image.shape
(dtype('uint8'), 1, 252, (303, 384))
>>> rescaled = ski.transform.rescale(image, 0.5)
>>> (rescaled.dtype, np.round(rescaled.min(), 4),
...  np.round(rescaled.max(), 4), rescaled.shape)
(dtype('float64'), 0.0147, 0.9456, (152, 192))
>>> rescaled = ski.transform.rescale(image, 0.5, preserve_range=True)
>>> (rescaled.dtype, np.round(rescaled.min()),
...  np.round(rescaled.max()), rescaled.shape)
(dtype('float64'), 4.0, 241.0, (152, 192))

5.2. 输出类型#

函数的输出类型由函数作者确定,并记录供用户使用。虽然这要求用户显式地将输出转换为所需的任何格式,但它确保不会发生任何不必要的复制。

需要特定类型输出(例如,出于显示目的)的用户可以编写

>>> out = ski.util.img_as_uint(sobel(image))
>>> plt.imshow(out)

5.3. 使用 OpenCV#

您可能需要将使用 skimage 创建的图像与 OpenCV 一起使用,反之亦然。可以在 NumPy(以及 scikit-image)中访问(无需复制)OpenCV 图像数据。OpenCV 对彩色图像使用 BGR(而不是 scikit-image 的 RGB),并且其 dtype 默认情况下为 uint8(参见 图像数据类型及其含义)。BGR 代表蓝绿红。

5.3.1. 将 BGR 转换为 RGB 或反之亦然#

skimage 和 OpenCV 中的彩色图像具有 3 个维度:宽度、高度和颜色。RGB 和 BGR 使用相同的颜色空间,除了颜色的顺序相反。

请注意,在 scikit-image 中,我们通常指的是 rowscolumns 而不是宽度和高度(参见 坐标约定)。

对于颜色位于最后一个轴上的图像,以下指令有效地反转了颜色的顺序,而行和列不受影响。

>>> image = image[:, :, ::-1]

5.3.2. 将 OpenCV 中的图像与 skimage 一起使用#

如果 cv_image 是无符号字节数组,skimage 将默认理解它。如果您更喜欢使用浮点图像,则可以使用 img_as_float() 转换图像

>>> import skimage as ski
>>> image = ski.util.img_as_float(any_opencv_image)

5.3.3. skimage 中的图像与 OpenCV 一起使用#

可以使用 img_as_ubyte() 实现反向操作

>>> import skimage as ski
>>> cv_image = ski.util.img_as_ubyte(any_skimage_image)

5.4. 图像处理流程#

这种 dtype 行为允许您将任何 skimage 函数串联在一起,而无需担心图像 dtype。另一方面,如果您想使用需要特定 dtype 的自定义函数,则应调用其中一个 dtype 转换函数(此处,func1func2skimage 函数)

>>> import skimage as ski
>>> image = ski.util.img_as_float(func1(func2(image)))
>>> processed_image = custom_func(image)

更好的是,您可以内部转换图像并使用简化的处理流程

>>> def custom_func(image):
...     image = ski.util.img_as_float(image)
...     # do something
...
>>> processed_image = custom_func(func1(func2(image)))

5.5. 重新缩放强度值#

在可能的情况下,函数应避免盲目地拉伸图像强度(例如,重新缩放浮点图像,使最小和最大强度分别为 0 和 1),因为这可能会严重扭曲图像。例如,如果您正在寻找暗图像中的明亮标记,则可能存在没有标记的图像;将其输入强度拉伸到跨越整个范围将使背景噪声看起来像标记。

但是,有时您会有一些图像应该跨越整个强度范围但没有。例如,某些相机以每个像素 10、12 或 14 位的深度存储图像。如果这些图像存储在 dtype 为 uint16 的数组中,则图像不会扩展到整个强度范围,因此,其显示亮度将低于预期。要解决此问题,您可以使用 rescale_intensity() 函数重新缩放图像,使其使用完整的 dtype 范围

>>> import skimage as ski
>>> image = ski.exposure.rescale_intensity(img10bit, in_range=(0, 2**10 - 1))

此处,in_range 参数设置为 10 位图像的最大范围。默认情况下,rescale_intensity()in_range 的值拉伸以匹配 dtype 的范围。rescale_intensity() 还接受字符串作为 in_rangeout_range 的输入,因此上面的示例也可以写成

>>> image = ski.exposure.rescale_intensity(img10bit, in_range='uint10')

5.6. 关于负值的说明#

人们经常用有符号数据类型表示图像,即使他们只操作图像的正值(例如,在 int8 图像中只使用 0-127)。因此,转换函数**只将有符号数据类型的正值**扩展到无符号数据类型的整个范围。换句话说,从有符号数据类型转换为无符号数据类型时,负值会被截断为 0。(在有符号数据类型之间转换时,负值会保留。)为了防止这种截断行为,您应该事先重新缩放图像。

>>> image = ski.exposure.rescale_intensity(img_int32, out_range=(0, 2**31 - 1))
>>> img_uint8 = ski.util.img_as_ubyte(image)

这种行为是对称的:无符号数据类型中的值仅扩展到有符号数据类型的正数范围。

5.7. 参考文献#