I'm starting off with a numpy array of an image.
In[1]:img = cv2.imread('test.jpg') The shape is what you might expect for a 640x480 RGB image.
In[2]:img.shape Out[2]: (480, 640, 3) However, this image that I have is a frame of a video, which is 100 frames long. Ideally, I would like to have a single array that contains all the data from this video such that img.shape returns (480, 640, 3, 100).
What is the best way to add the next frame -- that is, the next set of image data, another 480 x 640 x 3 array -- to my initial array?
11 Answers
You're asking how to add a dimension to a NumPy array, so that that dimension can then be grown to accommodate new data. A dimension can be added as follows:
image = image[..., np.newaxis] 6Alternatively to
image = image[..., np.newaxis] in @dbliss' answer, you can also use numpy.expand_dims like
image = np.expand_dims(image, <your desired dimension>) For example (taken from the link above):
x = np.array([1, 2]) print(x.shape) # prints (2,) Then
y = np.expand_dims(x, axis=0) yields
array([[1, 2]]) and
y.shape gives
(1, 2) 3You could just create an array of the correct size up-front and fill it:
frames = np.empty((480, 640, 3, 100)) for k in xrange(nframes): frames[:,:,:,k] = cv2.imread('frame_{}.jpg'.format(k)) if the frames were individual jpg file that were named in some particular way (in the example, frame_0.jpg, frame_1.jpg, etc).
Just a note, you might consider using a (nframes, 480,640,3) shaped array, instead.
Pythonic
X = X[:, :, None]
which is equivalent to
X = X[:, :, numpy.newaxis] and X = numpy.expand_dims(X, axis=-1)
But as you are explicitly asking about stacking images, I would recommend going for stacking the list of images np.stack([X1, X2, X3]) that you may have collected in a loop.
If you do not like the order of the dimensions you can rearrange with np.transpose()
You can use np.concatenate() specifying which axis to append, using np.newaxis:
import numpy as np movie = np.concatenate((img1[:,np.newaxis], img2[:,np.newaxis]), axis=3) If you are reading from many files:
import glob movie = np.concatenate([cv2.imread(p)[:,np.newaxis] for p in glob.glob('*.jpg')], axis=3) Consider Approach 1 with reshape method and Approach 2 with np.newaxis method that produce the same outcome:
#Lets suppose, we have: x = [1,2,3,4,5,6,7,8,9] print('I. x',x) xNpArr = np.array(x) print('II. xNpArr',xNpArr) print('III. xNpArr', xNpArr.shape) xNpArr_3x3 = xNpArr.reshape((3,3)) print('IV. xNpArr_3x3.shape', xNpArr_3x3.shape) print('V. xNpArr_3x3', xNpArr_3x3) #Approach 1 with reshape method xNpArrRs_1x3x3x1 = xNpArr_3x3.reshape((1,3,3,1)) print('VI. xNpArrRs_1x3x3x1.shape', xNpArrRs_1x3x3x1.shape) print('VII. xNpArrRs_1x3x3x1', xNpArrRs_1x3x3x1) #Approach 2 with np.newaxis method xNpArrNa_1x3x3x1 = xNpArr_3x3[np.newaxis, ..., np.newaxis] print('VIII. xNpArrNa_1x3x3x1.shape', xNpArrNa_1x3x3x1.shape) print('IX. xNpArrNa_1x3x3x1', xNpArrNa_1x3x3x1) We have as outcome:
I. x [1, 2, 3, 4, 5, 6, 7, 8, 9] II. xNpArr [1 2 3 4 5 6 7 8 9] III. xNpArr (9,) IV. xNpArr_3x3.shape (3, 3) V. xNpArr_3x3 [[1 2 3] [4 5 6] [7 8 9]] VI. xNpArrRs_1x3x3x1.shape (1, 3, 3, 1) VII. xNpArrRs_1x3x3x1 [[[[1] [2] [3]] [[4] [5] [6]] [[7] [8] [9]]]] VIII. xNpArrNa_1x3x3x1.shape (1, 3, 3, 1) IX. xNpArrNa_1x3x3x1 [[[[1] [2] [3]] [[4] [5] [6]] [[7] [8] [9]]]] There is no structure in numpy that allows you to append more data later.
Instead, numpy puts all of your data into a contiguous chunk of numbers (basically; a C array), and any resize requires allocating a new chunk of memory to hold it. Numpy's speed comes from being able to keep all the data in a numpy array in the same chunk of memory; e.g. mathematical operations can be parallelized for speed and you get less cache misses.
So you will have two kinds of solutions:
- Pre-allocate the memory for the numpy array and fill in the values, like in JoshAdel's answer, or
- Keep your data in a normal python list until it's actually needed to put them all together (see below)
images = [] for i in range(100): new_image = # pull image from somewhere images.append(new_image) images = np.stack(images, axis=3) Note that there is no need to expand the dimensions of the individual image arrays first, nor do you need to know how many images you expect ahead of time.
I followed this approach:
import numpy as np import cv2 ls = [] for image in image_paths: ls.append(cv2.imread('test.jpg')) img_np = np.array(ls) # shape (100, 480, 640, 3) img_np = np.rollaxis(img_np, 0, 4) # shape (480, 640, 3, 100). This worked for me:
image = image[..., None] 1You can use stack with the axis parameter:
img.shape # h,w,3 imgs = np.stack([img1,img2,img3,img4], axis=-1) # -1 = new axis is last imgs.shape # h,w,3,nimages For example: to convert grayscale to color:
>>> d = np.zeros((5,4), dtype=int) # 5x4 >>> d[2,3] = 1 >>> d3.shape Out[30]: (5, 4, 3) >>> d3 = np.stack([d,d,d], axis=-2) # 5x4x3 -1=as last axis >>> d3[2,3] Out[32]: array([1, 1, 1]) This will help you add axis anywhere you want
import numpy as np signal = np.array([[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]]) print(signal.shape) #(4,3) print(signal[...,np.newaxis].shape) or signal[...:none] #(4, 3, 1) print(signal[:, np.newaxis, :].shape) or signal[:,none, :] #(4, 1, 3)