2

I will want to plot some images using Opencv, and for this I would like to glue images together.

Imagine I have 4 pictures. The best way would be to glue them in a 2x2 image matrix.

a = img; a.shape == (48, 48)
b = img; b.shape == (48, 48)
c = img; c.shape == (48, 48)
d = img; d.shape == (48, 48)

I now use the np.reshape which takes a list such as [a,b,c,d], and then I manually put the dimensions to get the following:

np.reshape([a,b,c,d], (a.shape*2, a.shape*2)).shape == (96, 96)

The issue starts when I have 3 pictures. I kind of figured that I can take the square root of the length of the list and then the ceiling value which will yield the square matrix dimension of 2 (np.ceil(sqrt(len([a,b,c]))) == 2). I would then have to add a white image with the dimension of the first element to the list and there we go. But I imagine there must be an easier way to accomplish this for plotting, most likely already defined somewhere.

So, how to easily combine any amount of square matrices into one big square matrix?

EDIT:

I came up with the following:

def plotimgs(ls):
    shp = ls[0].shape[0]         # the image's dimension
    dim = np.ceil(sqrt(len(ls))) # the amount of pictures per row AND column
    emptyimg = (ls[1]*0 + 1)*255 # used to add to the list to allow square matrix
    for i in range(int(dim*dim - len(ls))):
        ls.append(emptyimg)
    enddim = int(shp*dim)        # enddim by enddim is the final matrix dimension
    # Convert to 600x600 in the end to resize the pictures to fit the screen
    newimg = cv2.resize(np.reshape(ls, (enddim, enddim)), (600, 600))
    cv2.imshow("frame", newimg)
    cv2.waitKey(10)

 plotimgs([a,b,d])

Somehow, even though the dimensions are okay, it actually clones some pictures more:

When I give 4 pictures, I get 8 pictures.
When I give 9 pictures, I get 27 pictures.
When I give 16 pictures, I get 64 pictures.

So in fact rather than squared, I get to the third power of images somehow. Though, e.g.

plotimg([a]*9) gives a picture with dimensions of 44*3 x 44*3 = 144x144 which should be correct for 9 images?

Cœur
  • 37,241
  • 25
  • 195
  • 267
PascalVKooten
  • 20,643
  • 17
  • 103
  • 160

2 Answers2

0

Here's a snippet that I use for doing this sort of thing:

import numpy as np

def montage(imgarray, nrows=None, border=5, border_val=np.nan):
    """
    Returns an array of regularly spaced images in a regular grid, separated
    by a border

    imgarray: 
        3D array of 2D images (n_images, rows, cols)
    nrows:  
        the number of rows of images in the output array. if 
        unspecified, nrows = ceil(sqrt(n_images))
    border: 
        the border size separating images (px)
    border_val:
        the value of the border regions of the output array (np.nan
        renders as transparent with imshow)
    """

    dims = (imgarray.shape[0], imgarray.shape[1]+2*border,
        imgarray.shape[2] + 2*border)

    X = np.ones(dims, dtype=imgarray.dtype) * border_val
    X[:,border:-border,border:-border] = imgarray

    # array dims should be [imageno,r,c]
    count, m, n = X.shape

    if nrows != None:
        mm = nrows
        nn = int(np.ceil(count/nrows))
    else:
        mm = int(np.ceil(np.sqrt(count)))
        nn = mm

    M = np.ones((nn * n, mm * m)) * np.nan

    image_id = 0
    for j in xrange(mm):
        for k in xrange(nn):
            if image_id >= count: 
                break
            sliceM, sliceN = j * m, k * n
            img = X[image_id,:, :].T
            M[sliceN:(sliceN + n), sliceM:(sliceM + m)] = img
            image_id += 1

    return np.flipud(np.rot90(M))

Example:

from scipy.misc import lena
from matplotlib import pyplot as plt

img = lena().astype(np.float32)
img -= img.min()
img /= img.max()
imgarray = np.sin(np.linspace(0, 2*np.pi, 25)[:, None, None] + img)

m = montage(imgarray)
plt.imshow(m, cmap=plt.cm.jet)

enter image description here

ali_m
  • 71,714
  • 23
  • 223
  • 298
  • `imgarray = np.reshape(ls, (ls[0].shape[0], ls[0].shape[1], len(ls)))`, does not work, where ls is `[a,b,c,d]` in my case. -- Actually, it does work. – PascalVKooten Jan 15 '14 at 11:38
  • Ok so the montage creates from the input of `(300, 300, 4)`, where 4 represents the amount of pictures. It returns an image of size `(5580, 252)`. How does that work? – PascalVKooten Jan 15 '14 at 11:41
  • The first dimension of the input should be the number of images – ali_m Jan 15 '14 at 12:00
0

Reusing chunks from How do you split a list into evenly sized chunks? :

def chunks(l, n):
    """ Yield successive n-sized chunks from l.
    """
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

Rewriting your function:

def plotimgs(ls):
    shp = ls[0].shape[0]         # the image's dimension
    dim = int(np.ceil(sqrt(len(ls)))) # the amount of pictures per row AND column
    emptyimg = (ls[1]*0 + 1)*255 # used to add to the list to allow square matrix
    ls.extend((dim **2 - ls) * [emptyimg]) # filling the list with missing images
    newimg = np.concatenate([np.concatenate(c, axis=0) for c in chunks(ls, dim)], axis=1)
    cv2.imshow("frame", newimg)
    cv2.waitKey(10)

 plotimgs([a,b,d])

Community
  • 1
  • 1
Nicolas Barbey
  • 6,639
  • 4
  • 28
  • 34