使用numpy重采样表示图像的数组

90

我想知道如何将代表图像数据的numpy数组在新大小下重新采样,最好可以选择插值方法(最近邻、双线性等)。我知道有

scipy.misc.imresize

通过包装PIL的resize函数,它确切地做到了这一点。唯一的问题是,由于它使用PIL,numpy数组必须符合图像格式,最多只能有4个“颜色”通道。

我想能够调整任意数量的“颜色”通道的任意图像大小。我想知道在scipy / numpy中是否有简单的方法来实现这一点,或者我需要自己编写代码。

我有两个构思:

  • 对每个通道单独运行scipy.misc.imresize的函数
  • 使用scipy.ndimage.interpolation.affine_transform创建自己的函数

第一个方法可能会因为数据量大而较慢,而第二个方法似乎除了样条插值之外没有提供其他插值方法。


你看过 scipy.interpolate.griddata 吗? 链接 - Isaac
看起来是一个很棒的函数,但它适用于完全非结构化的数据,这将运行比我需要的更耗时的算法。我已经查看了interp2d,但不仅极其有错,而且我甚至不确定它是否会正确地对数据进行下采样。 - Gustav Larsson
7个回答

129
根据您的描述,您需要scipy.ndimage.zoom
双线性插值为order=1,最近邻为order=0,立方插值为默认值(order=3)。 zoom专门用于规则网格数据的重新采样到新分辨率。
以下是一个快速示例:
import numpy as np
import scipy.ndimage

x = np.arange(9).reshape(3,3)

print 'Original array:'
print x

print 'Resampled by a factor of 2 with nearest interpolation:'
print scipy.ndimage.zoom(x, 2, order=0)


print 'Resampled by a factor of 2 with bilinear interpolation:'
print scipy.ndimage.zoom(x, 2, order=1)


print 'Resampled by a factor of 2 with cubic interpolation:'
print scipy.ndimage.zoom(x, 2, order=3)

并且结果是:

Original array:
[[0 1 2]
 [3 4 5]
 [6 7 8]]
Resampled by a factor of 2 with nearest interpolation:
[[0 0 1 1 2 2]
 [0 0 1 1 2 2]
 [3 3 4 4 5 5]
 [3 3 4 4 5 5]
 [6 6 7 7 8 8]
 [6 6 7 7 8 8]]
Resampled by a factor of 2 with bilinear interpolation:
[[0 0 1 1 2 2]
 [1 2 2 2 3 3]
 [2 3 3 4 4 4]
 [4 4 4 5 5 6]
 [5 5 6 6 6 7]
 [6 6 7 7 8 8]]
Resampled by a factor of 2 with cubic interpolation:
[[0 0 1 1 2 2]
 [1 1 1 2 2 3]
 [2 2 3 3 4 4]
 [4 4 5 5 6 6]
 [5 6 6 7 7 7]
 [6 6 7 7 8 8]]

编辑:正如Matt S.指出的那样,缩放多波段图像有一些注意事项。我几乎完全把下面的部分从我的早期答案中复制过来:

缩放也适用于3D(和nD)数组。然而,请注意,如果您以2倍缩放,您将沿着所有轴缩放。

data = np.arange(27).reshape(3,3,3)
print 'Original:\n', data
print 'Zoomed by 2x gives an array of shape:', ndimage.zoom(data, 2).shape

这将产生:

Original:
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]]

 [[ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]]]
Zoomed by 2x gives an array of shape: (6, 6, 6)

在多波段图像的情况下,通常不希望沿着“z”轴进行插值,从而创建新的波段。
如果您有一个类似于3波段的RGB图像,并且想要进行缩放,可以通过指定元组序列作为缩放系数来实现:
print 'Zoomed by 2x along the last two axes:'
print ndimage.zoom(data, (1, 2, 2))

这将产生:

Zoomed by 2x along the last two axes:
[[[ 0  0  1  1  2  2]
  [ 1  1  1  2  2  3]
  [ 2  2  3  3  4  4]
  [ 4  4  5  5  6  6]
  [ 5  6  6  7  7  7]
  [ 6  6  7  7  8  8]]

 [[ 9  9 10 10 11 11]
  [10 10 10 11 11 12]
  [11 11 12 12 13 13]
  [13 13 14 14 15 15]
  [14 15 15 16 16 16]
  [15 15 16 16 17 17]]

 [[18 18 19 19 20 20]
  [19 19 19 20 20 21]
  [20 20 21 21 22 22]
  [22 22 23 23 24 24]
  [23 24 24 25 25 25]
  [24 24 25 25 26 26]]]

1
提醒其他人:如果您有多通道的图像数据,请针对每个“通道切片”调用此函数,以避免不必要的“通道扩展”。例如:如果您有一个像素宽度为10,高度为5,然后有3个通道(比如RGB的每个通道),在您调用此函数进行7.0倍缩放后,您将得到一个“70x35”像素的数组,但其中包含21个通道。"scipy.ndimage.zoom(np.ones( 1053).reshape( 10, 5, 3), 7.0, order=0).shape"将给出元组:'(70, 35, 21)' PS. 不相关的提示:它可以优雅地处理浮点缩放因子,如“0.37”或“6.1”。 - Matt S.
8
不需要像你描述的那样为每个波段单独采用它。只需将一个元组作为缩放因子进行指定即可。例如:scipy.ndimage.zoom(data, (3,3,1))会将一个3D数组在x和y维度上的缩放因子设为3,同时保持第三个维度不变。请注意不要改变原来的意思。 - Joe Kington
1
@MattS. -(回复您已删除的评论)好建议!抱歉我没有早些回复!我添加了有关缩放多频带图像的警告。 - Joe Kington
1
scipy.ndimage.zoom 实际上是否与 scipy.misc.imresize 处理矩阵边缘方式不同?当使用值 10 进行缩放时,边缘只有5个值宽(使用 imresize10)。 - Chris
1
@Kevin,该问题已在1.6.0版本中得到解决。 - Tom

15
如果您想重新采样,可以查看Scipy的菜谱rebinning。特别是在结尾定义的congrid函数将支持重新采样或插值(相当于IDL中具有相同名称的函数)。如果您不需要插值,则这应该是最快的选项。
您还可以直接使用scipy.ndimage.map_coordinates,它将为任何类型的重新采样(包括非结构化网格)进行样条插值。我发现对于大数组(nx,ny> 200),map_coordinates速度较慢。
对于结构化网格上的插值,我倾向于使用scipy.interpolate.RectBivariateSpline。您可以选择样条的阶数(线性、二次、三次等),甚至可以分别为每个轴选择。一个例子:
    import scipy.interpolate as interp
    f = interp.RectBivariateSpline(x, y, im, kx=1, ky=1)
    new_im = f(new_x, new_y)

在这种情况下,您正在进行双线性插值(kx = ky = 1)。不支持“nearest”插值类型,因为所有这些都只是在矩形网格上进行样条插值。这也不是最快的方法。
如果您想要双线性或双三次插值,则通常更快的方法是进行两个一维插值:
    f = interp.interp1d(y, im, kind='linear')
    temp = f(new_y)
    f = interp.interp1d(x, temp.T, kind='linear')
    new_im = f(new_x).T

您还可以使用kind='nearest',但在这种情况下,需要摆脱横向数组。


“rebinning”链接已经过时了 - 我们能否获取一个有效的链接? - N. Jonas Figge

10
你看过 Scikit-image 吗?它的 transform.pyramid_* 函数可能对你有用。

8

我最近发现了scipy.ndimage.interpolation.zoom的一个问题,我已经将其作为错误报告提交:https://github.com/scipy/scipy/issues/3203

作为替代方案(至少对我而言),我发现scikit-image的skimage.transform.resize可以正常工作:http://scikit-image.org/docs/dev/api/skimage.transform.html#skimage.transform.resize

但是它与scipy的interpolation.zoom的工作方式不同-您需要指定所需的输出形状,而不是指定倍数。这适用于2D和3D图像。

对于仅为2D图像,您可以使用transform.rescale并指定乘数或比例,就像使用interpolation.zoom一样。


谢谢,我也注意到使用 zoom 时会出现奇怪的输出。我会记住使用 skimage 的 resize,谢谢! - Gustav Larsson
旧的线程,但是 resize 函数是否保留数组(图像)中数值的大小?我第一次尝试使用它,对于一个16位灰度图像,它没有保留;原始数组的中位数约为32000,而调整大小后的图像中位数在0到1之间。 - Evan

3
你可以使用 interpolate.interp2d。例如,对于用numpy数组arr表示的图像,你可以按照以下方式将其调整为任意高度和宽度:
W, H = arr.shape[:2]
new_W, new_H = (600,300)
xrange = lambda x: np.linspace(0, 1, x)

f = interp2d(xrange(W), xrange(H), arr, kind="linear")
new_arr = f(xrange(new_W), xrange(new_H))

当然,如果您的图像包含多个通道,则需要对每个通道执行插值。

1
最近的插值可以通过numpy.repeat来实现。
import numpy as np

a = np.array([[1, 2, 3], 
              [4, 5, 6],
              [7, 8, 9]])

size = 2
a = np.repeat(a, size, axis=0)
a = np.repeat(a, size, axis=1)

[[1, 1, 2, 2, 3, 3],
 [1, 1, 2, 2, 3, 3],
 [4, 4, 5, 5, 6, 6],
 [4, 4, 5, 5, 6, 6],
 [7, 7, 8, 8, 9, 9],
 [7, 7, 8, 8, 9, 9]]

对于RGB图像,我们还需要重复通道维度。

0

这个解决方案可以在不影响RGB通道的情况下,缩放所提供图像的X和Y轴:

import numpy as np
import scipy.ndimage

matplotlib.pyplot.imshow(scipy.ndimage.zoom(image_np_array, zoom = (7,7,1), order = 1))

希望这对你有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接