首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

A crash course on NumPy for images

scikit-image的图像是NumPy数组。因此很大一部分图像操作与NumPy相关:

代码语言:javascript
复制
>>> from skimage import data
>>> camera = data.camera()
>>> type(camera)
<type 'numpy.ndarray'>

检索图像的几何图形和像素数量:

代码语言:javascript
复制
>>> camera.shape
(512, 512)
>>> camera.size
262144

检索有关灰度值的统计信息:

代码语言:javascript
复制
>>> camera.min(), camera.max()
(0, 255)
>>> camera.mean()
118.31400299072266

表示图像的NumPy数组可以是不同的浮点数字类型的整数。有关这些类型的更多信息以及scikit-image如何处理它们,请参阅图像数据类型及其含义。

NumPy索引

NumPy索引既可用于查看像素值,也可用于修改像素值:

代码语言:javascript
复制
>>> # Get the value of the pixel on the 10th row and 20th column
>>> camera[10, 20]
153
>>> # Set to black the pixel on the 3rd row and 10th column
>>> camera[3, 10] = 0

注意:在NumPy索引中,第一个维(camera.shape[0])对应于行,而第二个(camera.shape[1])对应于列,而camera[0, 0]左上角的origin()。这匹配矩阵/线性代数符号,但与笛卡尔(x,y)坐标相反。有关更多详细信息,请参阅下面的坐标约定。

除了单个像素之外,还可以使用NumPy的不同索引可能性来访问/修改整组像素的值。

切片:

代码语言:javascript
复制
>>> # Set to black the ten first lines
>>> camera[:10] = 0

掩蔽(用布尔值掩码索引):

代码语言:javascript
复制
>>> mask = camera < 87
>>> # Set to "white" (255) pixels where mask is True
>>> camera[mask] = 255

花式索引(索引与索引)

代码语言:javascript
复制
>>> inds_r = np.arange(len(camera))
>>> inds_c = 4 * inds_r % len(camera)
>>> camera[inds_r, inds_c] = 0

尤其是,使用蒙版选择一组要执行进一步操作的像素非常有用。掩码可以是任何与图像形状相同的布尔阵列(或可以广播到图像形状的形状)。这对定义一个感兴趣的区域很有用,例如磁盘:

代码语言:javascript
复制
>>> nrows, ncols = camera.shape
>>> row, col = np.ogrid[:nrows, :ncols]
>>> cnt_row, cnt_col = nrows / 2, ncols / 2
>>> outer_disk_mask = ((row - cnt_row)**2 + (col - cnt_col)**2 >
...                    (nrows / 2)**2)
>>> camera[outer_disk_mask] = 0

布尔运算可用于定义更复杂的掩码:

代码语言:javascript
复制
>>> lower_half = row > cnt_row
>>> lower_half_disk = np.logical_and(lower_half, outer_disk_mask)
>>> camera = data.camera()
>>> camera[lower_half_disk] = 0

彩色图像

以上所有内容对于彩色图像也是如此:彩色图像是一个NumPy数组,并且为通道添加了一个附加的拖尾维度:

代码语言:javascript
复制
>>> cat = data.chelsea()
>>> type(cat)
<type 'numpy.ndarray'>
>>> cat.shape
(300, 451, 3)

这显示了cat具有三个通道(红色,绿色和蓝色)的300x451像素图像。和以前一样,我们可以获取并设置像素值:

代码语言:javascript
复制
>>> cat[10, 20]
array([151, 129, 115], dtype=uint8)
>>> # set the pixel at row 50, column 60 to black
>>> cat[50, 60] = 0
>>> # set the pixel at row 50, column 61 to green
>>> cat[50, 61] = [0, 255, 0] # [red, green, blue]

我们也可以使用二维布尔模板来渲染二维彩色图像,就像我们上面的灰度图一样:

在2D彩色图像上使用2D蒙版

代码语言:javascript
复制
>>> from skimage import data
>>> cat = data.chelsea()
>>> reddish = cat[:, :, 0] > 160
>>> cat[reddish] = [0, 255, 0]
>>> plt.imshow(cat)

(源代码,png,pdf)

协调惯例

因为我们用numpy数组表示图像,所以我们的坐标必须相应匹配。二维(2D)灰度图像(例如camera上图)按行和列(缩写为(row, col)(r, c))进行索引,(0, 0)最左边的元素位于左上角。在图书馆的各个部分,您还将看到rrcc参考行列坐标列表。我们将它与(x, y)通常表示笛卡尔坐标的标准区分开来,其中x水平坐标,y垂直坐标和原点在左下角。(例如,Matplotlib使用这个约定。)

在彩色(或多通道)图像的情况下,最后一个维度包含颜色信息并用channel或表示ch

最后,对于诸如视频,磁共振成像(MRI)扫描或共聚焦显微镜之类的3D图像,我们称主要维度为plane,缩写为plnp

这些约定总结如下:

图像类型

坐标

2D灰度

(行,列)

2D多通道(例如RGB)

(row,col,ch)

3D灰度

(pln,row,col)

3D多声道

(pln,row,col,ch)

scikit-image中的许多功能都直接在3D图像上运行:

代码语言:javascript
复制
>>> im3d = np.random.rand(100, 1000, 1000)
>>> from skimage import morphology
>>> from scipy import ndimage as ndi
>>> seeds = ndi.label(im3d < 0.1)[0]
>>> ws = morphology.watershed(im3d, seeds)

在许多情况下,第三个成像维度的分辨率低于其他两个。一些scikit-image函数提供了一个spacing关键字参数来处理这些图像:

代码语言:javascript
复制
>>> from skimage import segmentation
>>> slics = segmentation.slic(im3d, spacing=[5, 1, 1], multichannel=False)

其他时候,处理必须按平面进行。当飞机是主要维度时,我们可以使用以下语法:

代码语言:javascript
复制
>>> from skimage import filters
>>> edges = np.zeros_like(im3d)
>>> for pln, image in enumerate(im3d):
...     # iterate over the leading dimension (planes)
...     edges[pln] = filters.sobel(image)

有关阵列顺序的说明

尽管轴的标记看起来是任意的,但它对操作速度可能有显着影响。这是因为现代处理器从不从内存中检索一个项目,而是从整个相邻项目中取出一个项目。(这称为预取)。因此,即使操作次数相同,在内存中彼此相邻的处理元素也会比以不同顺序处理元素更快。

代码语言:javascript
复制
>>> def in_order_multiply(arr, scalar):
...     for plane in list(range(arr.shape[0])):
...         arr[plane, :, :] *= scalar
...
>>> def out_of_order_multiply(arr, scalar):
...     for plane in list(range(arr.shape[2])):
...         arr[:, :, plane] *= scalar
...
>>> import time
>>> im3d = np.random.rand(100, 1024, 1024)
>>> t0 = time.time(); x = in_order_multiply(im3d, 5); t1 = time.time()
>>> print("%.2f seconds" % (t1 - t0))  
0.14 seconds
>>> im3d_t = np.transpose(im3d).copy() # place "planes" dimension at end
>>> im3d_t.shape
(1024, 1024, 100)
>>> s0 = time.time(); x = out_of_order_multiply(im3d, 5); s1 = time.time()
>>> print("%.2f seconds" % (s1 - s0))  
1.18 seconds
>>> print("Speedup: %.1fx" % ((s1 - s0) / (t1 - t0)))  
Speedup: 8.6x

当您迭代的维度更大时,加速更加戏剧性。编写算法时值得考虑这个数据局部性。特别是,除非另有说明,否则scikit-image会使用C连续数组,因此应该沿着计算最内层循环中的最后/最右边的维度进行迭代。

关于时间的说明

虽然scikit-image目前不提供函数来专门处理随时间变化的3D数据,但我们与numpy数组的兼容性使我们能够非常自然地使用形状的5D阵列(t,pln,row,col,ch ):

代码语言:javascript
复制
>>> for timepoint in image5d:  
...     # each timepoint is a 3D multichannel image
...     do_something_with(timepoint)

然后我们可以补充上述表格如下:

图像类型

坐标

2D彩色视频

(t,row,col,ch)

3D多声道视频

(t,pln,row,col,ch)

扫码关注腾讯云开发者

领取腾讯云代金券