Divida una matriz 2D en matrices 2D más pequeñas
¿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.
Hubo otra pregunta hace un par de meses que me dio una pista sobre la idea de usar reshape
y swapaxes
. Tiene h//nrows
sentido ya que esto mantiene juntas las filas del primer bloque. También tiene sentido que necesites nrows
ser ncols
parte de la forma. -1
le 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 reshape
y 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 unblockshaped
aquí 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.
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.hsplit
y numpy.vsplit
también están disponibles.
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]