Divida una matriz 2D en matrices 2D más pequeñas

Resuelto TheMeaningfulEngineer asked hace 11 años • 11 respuestas

¿Hay alguna manera de dividir una matriz 2D en numpy en matrices 2D más pequeñas?

Ejemplo

[[1,2,3,4],   ->    [[1,2] [3,4]   
 [5,6,7,8]]          [5,6] [7,8]]

Básicamente, quiero cortar una matriz de 2x4 en 2 matrices de 2x2. Buscando una solución genérica para usar en imágenes.

TheMeaningfulEngineer avatar May 31 '13 19:05 TheMeaningfulEngineer
Aceptado

Hubo otra pregunta hace un par de meses que me dio una pista sobre la idea de usar reshapey swapaxes. Tiene h//nrowssentido ya que esto mantiene juntas las filas del primer bloque. También tiene sentido que necesites nrowsser ncolsparte de la forma. -1le dice a remodelar que complete cualquier número que sea necesario para que la remodelación sea válida. Armado con la forma de la solución, probé cosas hasta que encontré la fórmula que funciona.

Debería poder dividir su matriz en "bloques" usando alguna combinación de reshapey swapaxes:

def blockshaped(arr, nrows, ncols):
    """
    Return an array of shape (n, nrows, ncols) where
    n * nrows * ncols = arr.size

    If arr is a 2D array, the returned array should look like n subblocks with
    each subblock preserving the "physical" layout of arr.
    """
    h, w = arr.shape
    assert h % nrows == 0, f"{h} rows is not evenly divisible by {nrows}"
    assert w % ncols == 0, f"{w} cols is not evenly divisible by {ncols}"
    return (arr.reshape(h//nrows, nrows, -1, ncols)
               .swapaxes(1,2)
               .reshape(-1, nrows, ncols))

vueltasc

np.random.seed(365)
c = np.arange(24).reshape((4, 6))
print(c)

[out]:
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

en

print(blockshaped(c, 2, 3))

[out]:
[[[ 0  1  2]
  [ 6  7  8]]

 [[ 3  4  5]
  [ 9 10 11]]

 [[12 13 14]
  [18 19 20]]

 [[15 16 17]
  [21 22 23]]]

He publicado una función inversa unblockshapedaquí y una generalización N-dimensional aquí . La generalización da una idea un poco más clara del razonamiento detrás de este algoritmo.


Tenga en cuenta que también existe el pez superbat blockwise_view . Organiza los bloques en un formato diferente (usando más ejes) pero tiene la ventaja de (1) siempre devolver una vista y (2) ser capaz de manejar matrices de cualquier dimensión.

unutbu avatar May 31 '2013 13:05 unutbu

Me parece que esta es una tarea paranumpy.split o alguna variante.

p.ej

a = np.arange(30).reshape([5,6])  #a.shape = (5,6)
a1 = np.split(a,3,axis=1) 
#'a1' is a list of 3 arrays of shape (5,2)
a2 = np.split(a, [2,4])
#'a2' is a list of three arrays of shape (2,5), (2,5), (1,5)

Si tiene una imagen NxN, puede crear, por ejemplo, una lista de 2 subimágenes NxN/2 y luego dividirlas a lo largo del otro eje.

numpy.hsplity numpy.vsplittambién están disponibles.

Francesco Montesano avatar May 31 '2013 13:05 Francesco Montesano

Hay algunas otras respuestas que ya parecen adecuadas para su caso específico, pero su pregunta despertó mi interés en la posibilidad de una solución eficiente en memoria utilizable hasta el número máximo de dimensiones que admite numpy, y terminé gastando la mayor parte de la tarde ideando un posible método. (El método en sí es relativamente simple, solo que todavía no he usado la mayoría de las funciones realmente sofisticadas que admite numpy, por lo que pasé la mayor parte del tiempo investigando para ver qué tenía disponible numpy y cuánto podía hacer para que yo no No tengo que hacerlo.)

def blockgen(array, bpa):
    """Creates a generator that yields multidimensional blocks from the given
array(_like); bpa is an array_like consisting of the number of blocks per axis
(minimum of 1, must be a divisor of the corresponding axis size of array). As
the blocks are selected using normal numpy slicing, they will be views rather
than copies; this is good for very large multidimensional arrays that are being
blocked, and for very large blocks, but it also means that the result must be
copied if it is to be modified (unless modifying the original data as well is
intended)."""
    bpa = np.asarray(bpa) # in case bpa wasn't already an ndarray

    # parameter checking
    if array.ndim != bpa.size:         # bpa doesn't match array dimensionality
        raise ValueError("Size of bpa must be equal to the array dimensionality.")
    if (bpa.dtype != np.int            # bpa must be all integers
        or (bpa < 1).any()             # all values in bpa must be >= 1
        or (array.shape % bpa).any()): # % != 0 means not evenly divisible
        raise ValueError("bpa ({0}) must consist of nonzero positive integers "
                         "that evenly divide the corresponding array axis "
                         "size".format(bpa))


    # generate block edge indices
    rgen = (np.r_[:array.shape[i]+1:array.shape[i]//blk_n]
            for i, blk_n in enumerate(bpa))

    # build slice sequences for each axis (unfortunately broadcasting
    # can't be used to make the items easy to operate over
    c = [[np.s_[i:j] for i, j in zip(r[:-1], r[1:])] for r in rgen]

    # Now to get the blocks; this is slightly less efficient than it could be
    # because numpy doesn't like jagged arrays and I didn't feel like writing
    # a ufunc for it.
    for idxs in np.ndindex(*bpa):
        blockbounds = tuple(c[j][idxs[j]] for j in range(bpa.size))

        yield array[blockbounds]
JAB avatar May 31 '2013 20:05 JAB