简体   繁体   中英

Faster way than concatenate of combining multiple cv2 images (numpy arrays) in python?

I have 100 small images which I want to combine into one large (10x10) grid image for display with imshow. Each image (as a numpy array) is within the variable of a cell object. At the moment I'm using concatenate to first create vertical strips then using concatenate to connect all of those strips but it seems kinda clunky. Is there a better way to do this? I feel like I should be able to just create a numpy array the size of the final image (800 x 600) then plop each image in, but it seems to be beyond my ability to wrap my head around the syntax.

def stackImages(im1, im2, axisToStack):
    newImage = np.concatenate((im1, im2), axis = axisToStack)
    return newImage

def compileCells():
    #Make a list of strips
    strips = [(np.zeros((0,cellWidth,3), np.uint8)) for i in range(numberOfCells)]

    for x in range(numberOfCells):
        for i in range(numberOfCells):
            strips[x] = stackImages(cellArray[i+(x*numberOfCells)].image, strips[x], 0)

    display = strips[0]
    for c in range(1,numberOfCells):
        display = stackImages(strips[c], display, 1)
    return display

Copying arrays can be a real speed killer when working with NumPy. Every time np.concatenate is called, space for a new array is allocated, and all the old data is copied into the new array. A way to make your code faster is to reduce the amount of copying.

So as you suggested, the faster way is to allocate space for the final array, display from the very the beginning:

display = np.empty((cellHeight*nrows, cellWidth*ncols, 3), dtype=np.uint8)

and then copy the data from cellArray into display just once:

for i, j in IT.product(range(nrows), range(ncols)):
    arr = cellArray[i*ncols+j].image  
    x, y = i*cellHeight, j*cellWidth
    display[x:x+cellHeight, y:y+cellWidth, :] = arr

For example,

import numpy as np
import matplotlib.pyplot as plt
import itertools as IT

def compileCells(cellArray, nrows, ncols, cellHeight, cellWidth):
    display = np.empty((cellHeight*nrows, cellWidth*ncols, 3), dtype=np.uint8)
    for i, j in IT.product(range(nrows), range(ncols)):
        # arr = cellArray[i*ncols+j].image  # you may need this
        arr = cellArray[i*ncols+j]          # my simplified cellArray uses this
        x, y = i*cellHeight, j*cellWidth
        display[x:x+cellHeight, y:y+cellWidth, :] = arr
    return display

cellHeight, cellWidth = 80, 60
nrows = ncols = numberOfCells = 10

cellArray = [np.full((cellHeight, cellWidth, 3), i) 
             for i in np.linspace(0, 255, nrows*ncols)]
display = compileCells(cellArray, nrows, ncols, cellHeight, cellWidth)
plt.imshow(display)
plt.show()

yields

在此处输入图片说明

Note, your code implies that cellArray is a list of objects whose image attributes are NumPy arrays. To make the example code above runnable and simple(r), I've defined cellArray above to be a list of NumPy arrays. You may need to uncomment

# arr = cellArray[i*ncols+j].image  

and comment out

arr = cellArray[i*ncols+j] 

to fit your definition of cellArray .


Let's compare the amount of copying done by the two approaches:

Using the original approach, if we say that an image array has size 1, then to build a strip requires allocating arrays of size 1, 2, ..., 10. So one strip requires allocating arrays of total size 1+2+...+10 = 10(11)/2 = 55. To build display requires allocating arrays of total size 55(1+2+..+10) = 55*55 = 3025. Each allocation of space is accompanied by a copy operation. The amount of copying grows quadratically with the number of cells in the final array.

In contrast, if we allocate space for the final display just once, then we only need to allocate of total size 10*10 = 100. Here, the amount of copying grows linearly with the number of cells.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM