Resampling a numpy array representing an image
Asked Answered
S

7

91

I am looking for how to resample a numpy array representing image data at a new size, preferably having a choice of the interpolation method (nearest, bilinear, etc.). I know there is

scipy.misc.imresize

which does exactly this by wrapping PIL's resize function. The only problem is that since it uses PIL, the numpy array has to conform to image formats, giving me a maximum of 4 "color" channels.

I want to be able to resize arbitrary images, with any number of "color" channels. I was wondering if there is a simple way to do this in scipy/numpy, or if I need to roll my own.

I have two ideas for how to concoct one myself:

  • a function that runs scipy.misc.imresize on every channel separately
  • create my own using scipy.ndimage.interpolation.affine_transform

The first one would probably be slow for large data, and the second one does not seem to offer any other interpolation method except splines.

Schwing answered 5/11, 2012 at 23:56 Comment(2)
Have you looked at scipy.interpolate.griddata? linkUnexpressed
Looks like a great function, but it's for completely unstructured data, which will run a much more time-consuming algorithm than what I need. I have looked at interp2d, but not only is it extremely buggy, but I'm not even sure if it will correctly downsample data.Schwing
T
131

Based on your description, you want scipy.ndimage.zoom.

Bilinear interpolation would be order=1, nearest is order=0, and cubic is the default (order=3).

zoom is specifically for regularly-gridded data that you want to resample to a new resolution.

As a quick example:

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)

And the result:

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]]

Edit: As Matt S. pointed out, there are a couple of caveats for zooming multi-band images. I'm copying the portion below almost verbatim from one of my earlier answers:

Zooming also works for 3D (and nD) arrays. However, be aware that if you zoom by 2x, for example, you'll zoom along all axes.

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

This yields:

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)

In the case of multi-band images, you usually don't want to interpolate along the "z" axis, creating new bands.

If you have something like a 3-band, RGB image that you'd like to zoom, you can do this by specifying a sequence of tuples as the zoom factor:

print 'Zoomed by 2x along the last two axes:'
print ndimage.zoom(data, (1, 2, 2))

This yields:

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]]]
Tacitus answered 12/5, 2013 at 17:19 Comment(5)
FYI for others: If you have multichannel image data, call this with each 'channel slice' to avoid getting unwanted 'channel expansion.' Explained by example: if you an image with a pixel width of 10 and height of 5, and then 3 channels (one for each of RGB say), after you call this to zoom by 7.0 x, you'll get back an array of '70 by 35' pixels, but with 21 channels. "scipy.ndimage.zoom(np.ones( 10*5*3).reshape( 10, 5, 3), 7.0, order=0).shape" will give you the tuple: '(70, 35, 21)' PS. unrelated: it does gracefully handles floating point zoom factors like '0.37' or '6.1'Homorganic
@MattS. - There's no need to adopt it to each band separately, as you describe. Just specify a tuple as the zoom factor. E.g. scipy.ndimage.zoom(data, (3,3,1)) to zoom a 3d array by a factor of 3 along the x and y dimensions while leaving the third dimension alone.Tacitus
@MattS. - (in response to your deleted comment) Good suggestion! Sorry I didn't reply earlier! I added the caveat about zooming multi-band images.Tacitus
Is it just me or is scipy.ndimage.zoom actually handling the edges of the matrix differently to scipy.misc.imresize? When zooming with a value of 10 the sides are only 5 values wide (with imresize it is 10).Haubergeon
@Kevin, that issue has been resolved as of version 1.6.0Brindisi
S
15

If you want to resample, then you should look at Scipy's cookbook for rebinning. In particular, the congrid function defined at the end will support rebinning or interpolation (equivalent to the function in IDL with the same name). This should be the fastest option if you don't want interpolation.

You can also use directly scipy.ndimage.map_coordinates, which will do a spline interpolation for any kind of resampling (including unstructured grids). I find map_coordinates to be slow for large arrays (nx, ny > 200).

For interpolation on structured grids, I tend to use scipy.interpolate.RectBivariateSpline. You can choose the order of the spline (linear, quadratic, cubic, etc) and even independently for each axis. An example:

    import scipy.interpolate as interp
    f = interp.RectBivariateSpline(x, y, im, kx=1, ky=1)
    new_im = f(new_x, new_y)

In this case you're doing a bi-linear interpolation (kx = ky = 1). The 'nearest' kind of interpolation is not supported, as all this does is a spline interpolation over a rectangular mesh. It's also not the fastest method.

If you're after bi-linear or bi-cubic interpolation, it is generally much faster to do two 1D interpolations:

    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

You can also use kind='nearest', but in that case get rid of the transverse arrays.

Schaerbeek answered 6/11, 2012 at 4:10 Comment(1)
The "rebinning" link is not up to date anymore - can we get a working one?Lancer
O
10

Have you looked at Scikit-image? Its transform.pyramid_* functions might be useful for you.

Oringa answered 6/11, 2012 at 12:43 Comment(0)
D
8

I've recently just found an issue with scipy.ndimage.interpolation.zoom, which I've submitted as a bug report: https://github.com/scipy/scipy/issues/3203

As an alternative (or at least for me), I've found that scikit-image's skimage.transform.resize works correctly: http://scikit-image.org/docs/dev/api/skimage.transform.html#skimage.transform.resize

However it works differently to scipy's interpolation.zoom - rather than specifying a mutliplier, you specify the the output shape that you want. This works for 2D and 3D images.

For just 2D images, you can use transform.rescale and specify a multiplier or scale as you would with interpolation.zoom.

Deadradeadweight answered 10/1, 2014 at 16:13 Comment(2)
Thanks, I have also noticed strange outputs using zoom before. I will keep skimage's resize in mind, thanks!Schwing
Old thread, but does resize preserve the magnitude of the values in the array (image)? I have just tried it for the first time, and for a 16-bit grayscale image, it did not; the original array had a median ~32000 and the resized images have medians between 0 and 1.Bruit
A
3

You can use interpolate.interp2d.

For example, considering an image represented by a numpy array arr, you can resize it to an arbitrary height and width as follows:

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))

Of course, if your image has multiple channels, you have to perform the interpolation for each one.

Armidaarmiger answered 26/9, 2019 at 23:57 Comment(0)
L
1

The nearest interpolation can be achieved with 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]]

For RGB images we will also need to repeat channel dimension

Labelle answered 17/9, 2023 at 18:51 Comment(0)
C
0

This solution scales X and Y of the fed image without affecting RGB channels:

import numpy as np
import scipy.ndimage

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

Hope this is useful.

Cardiganshire answered 24/5, 2019 at 16:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.