Как я могу эффективно обрабатывать массив numpy в блоках, подобных функции Matlab blkproc (blockproc)
Я ищу хороший подход для эффективного разделения изображения на небольшие регионы, обработки каждого региона отдельно, а затем повторной сборки результатов из каждого процесса в одно обработанное изображение. У Matlab был инструмент для этого blkproc (заменен на blockproc
в новых версиях Matlab).
В идеальном мире функция или класс будут поддерживать перекрытие между делениями во входной матрице. В справке Matlab blkproc определяется как:
B = blkproc (A, [m n], [mborder nborder], fun,...)
- A - ваша матрица ввода,
- [m n] - размер блока
- [mborder, nborder] - размер области вашей границы (необязательно)
- fun - это функция, применяемая к каждому блоку
Я объединил подход, но он кажется мне неуклюжим, и я уверен, что там намного лучший способ. В опасности моего собственного смущения, здесь мой код:
import numpy as np
def segmented_process(M, blk_size=(16,16), overlap=(0,0), fun=None):
rows = []
for i in range(0, M.shape[0], blk_size[0]):
cols = []
for j in range(0, M.shape[1], blk_size[1]):
cols.append(fun(M[i:i+blk_size[0], j:j+blk_size[1]]))
rows.append(np.concatenate(cols, axis=1))
return np.concatenate(rows, axis=0)
R = np.random.rand(128,128)
passthrough = lambda(x):x
Rprime = segmented_process(R, blk_size=(16,16),
overlap=(0,0),
fun=passthrough)
np.all(R==Rprime)
Ответы
Ответ 1
Вот несколько примеров другого (без цикла) способа работы с блоками:
import numpy as np
from numpy.lib.stride_tricks import as_strided as ast
A= np.arange(36).reshape(6, 6)
print A
#[[ 0 1 2 3 4 5]
# [ 6 7 8 9 10 11]
# ...
# [30 31 32 33 34 35]]
# 2x2 block view
B= ast(A, shape= (3, 3, 2, 2), strides= (48, 8, 24, 4))
print B[1, 1]
#[[14 15]
# [20 21]]
# for preserving original shape
B[:, :]= np.dot(B[:, :], np.array([[0, 1], [1, 0]]))
print A
#[[ 1 0 3 2 5 4]
# [ 7 6 9 8 11 10]
# ...
# [31 30 33 32 35 34]]
print B[1, 1]
#[[15 14]
# [21 20]]
# for reducing shape, processing in 3D is enough
C= B.reshape(3, 3, -1)
print C.sum(-1)
#[[ 14 22 30]
# [ 62 70 78]
# [110 118 126]]
Таким образом, просто попытка просто скопировать функциональность matlab
на numpy
- это далеко не все пути для продолжения. Иногда требуется мышление "от шляпы".
Caveat:
В целом, реализации, основанные на шаговых трюках, могут (но не обязательно нужно) страдать от некоторых штрафов за производительность. Поэтому будьте готовы ко всем способам оценить свою эффективность. В любом случае целесообразно сначала проверить, была ли вся необходимая функциональность (или достаточно подобная, чтобы легко адаптироваться) в numpy
или scipy
.
Обновление:
Обратите внимание, что здесь нет magic
реального magic
, поэтому я предоставил бы простую функцию для получения block_view
любого подходящего 2D numpy
-array. Итак, идем:
from numpy.lib.stride_tricks import as_strided as ast
def block_view(A, block= (3, 3)):
"""Provide a 2D block view to 2D array. No error checking made.
Therefore meaningful (as implemented) only for blocks strictly
compatible with the shape of A."""
# simple shape and strides computations may seem at first strange
# unless one is able to recognize the 'tuple additions' involved ;-)
shape= (A.shape[0]/ block[0], A.shape[1]/ block[1])+ block
strides= (block[0]* A.strides[0], block[1]* A.strides[1])+ A.strides
return ast(A, shape= shape, strides= strides)
if __name__ == '__main__':
from numpy import arange
A= arange(144).reshape(12, 12)
print block_view(A)[0, 0]
#[[ 0 1 2]
# [12 13 14]
# [24 25 26]]
print block_view(A, (2, 6))[0, 0]
#[[ 0 1 2 3 4 5]
# [12 13 14 15 16 17]]
print block_view(A, (3, 12))[0, 0]
#[[ 0 1 2 3 4 5 6 7 8 9 10 11]
# [12 13 14 15 16 17 18 19 20 21 22 23]
# [24 25 26 27 28 29 30 31 32 33 34 35]]
Ответ 2
Обработка фрагментами/представлениями. Конкатенация очень дорого.
for x in xrange(0, 160, 16):
for y in xrange(0, 160, 16):
view = A[x:x+16, y:y+16]
view[:,:] = fun(view)
Ответ 3
Я взял оба входа, а также мой оригинальный подход и сравнил результаты. Как правильно указывает @eat, результаты зависят от характера ваших входных данных. Удивительно, что в нескольких случаях объединяет обработку просмотра битов. Каждый метод имеет сладкое пятно. Вот мой тестовый код:
import numpy as np
from itertools import product
def segment_and_concatenate(M, fun=None, blk_size=(16,16), overlap=(0,0)):
# truncate M to a multiple of blk_size
M = M[:M.shape[0]-M.shape[0]%blk_size[0],
:M.shape[1]-M.shape[1]%blk_size[1]]
rows = []
for i in range(0, M.shape[0], blk_size[0]):
cols = []
for j in range(0, M.shape[1], blk_size[1]):
max_ndx = (min(i+blk_size[0], M.shape[0]),
min(j+blk_size[1], M.shape[1]))
cols.append(fun(M[i:max_ndx[0], j:max_ndx[1]]))
rows.append(np.concatenate(cols, axis=1))
return np.concatenate(rows, axis=0)
from numpy.lib.stride_tricks import as_strided
def block_view(A, block= (3, 3)):
"""Provide a 2D block view to 2D array. No error checking made.
Therefore meaningful (as implemented) only for blocks strictly
compatible with the shape of A."""
# simple shape and strides computations may seem at first strange
# unless one is able to recognize the 'tuple additions' involved ;-)
shape= (A.shape[0]/ block[0], A.shape[1]/ block[1])+ block
strides= (block[0]* A.strides[0], block[1]* A.strides[1])+ A.strides
return as_strided(A, shape= shape, strides= strides)
def segmented_stride(M, fun, blk_size=(3,3), overlap=(0,0)):
# This is some complex function of blk_size and M.shape
stride = blk_size
output = np.zeros(M.shape)
B = block_view(M, block=blk_size)
O = block_view(output, block=blk_size)
for b,o in zip(B, O):
o[:,:] = fun(b);
return output
def view_process(M, fun=None, blk_size=(16,16), overlap=None):
# truncate M to a multiple of blk_size
from itertools import product
output = np.zeros(M.shape)
dz = np.asarray(blk_size)
shape = M.shape - (np.mod(np.asarray(M.shape),
blk_size))
for indices in product(*[range(0, stop, step)
for stop,step in zip(shape, blk_size)]):
# Don't overrun the end of the array.
#max_ndx = np.min((np.asarray(indices) + dz, M.shape), axis=0)
#slices = [slice(s, s + f, None) for s,f in zip(indices, dz)]
output[indices[0]:indices[0]+dz[0],
indices[1]:indices[1]+dz[1]][:,:] = fun(M[indices[0]:indices[0]+dz[0],
indices[1]:indices[1]+dz[1]])
return output
if __name__ == "__main__":
R = np.random.rand(128,128)
squareit = lambda(x):x*2
from timeit import timeit
t ={}
kn = np.array(list(product((8,16,64,128),
(128, 512, 2048, 4096)) ) )
methods = ("segment_and_concatenate",
"view_process",
"segmented_stride")
t = np.zeros((kn.shape[0], len(methods)))
for i, (k, N) in enumerate(kn):
for j, method in enumerate(methods):
t[i,j] = timeit("""Rprime = %s(R, blk_size=(%d,%d),
overlap = (0,0),
fun = squareit)""" % (method, k, k),
setup="""
from segmented_processing import %s
import numpy as np
R = np.random.rand(%d,%d)
squareit = lambda(x):x**2""" % (method, N, N),
number=5
)
print "k =", k, "N =", N #, "time:", t[i]
print (" Speed up (view vs. concat, stride vs. concat): %0.4f, %0.4f" % (
t[i][0]/t[i][1],
t[i][0]/t[i][2]))
И вот результаты:
Обратите внимание, что метод сегментированных шагов выигрывает 3-4 раза для небольших размеров блоков. Только при больших размерах блоков (128 x 128) и очень больших матрицах (2048 x 2048 и выше) выигрывает вид обработки просмотра, а затем только на небольшой процент. Основываясь на выпечке, похоже, что @eat получает контрольный знак! Спасибо вам за хорошие примеры!
Ответ 4
Бит поздно в игре, но это будет перекрывать блоки. Я не сделал этого здесь, но вы можете легко адаптироваться к размерам шага для переключения окна, я думаю:
from numpy.lib.stride_tricks import as_strided
def rolling_block(A, block=(3, 3)):
shape = (A.shape[0] - block[0] + 1, A.shape[1] - block[1] + 1) + block
strides = (A.strides[0], A.strides[1]) + A.strides
return as_strided(A, shape=shape, strides=strides)
Ответ 5
Даже позже в игре.
Существует пакет обработки изображений Swiss Bob, доступный по адресу:
https://www.idiap.ch/software/bob/docs/releases/last/sphinx/html/index.html
Он имеет команду python bob.ip.block, описанную в:
https://www.idiap.ch/software/bob/docs/releases/last/sphinx/html/ip/generated/bob.ip.block.html#bob.ip.block
который, кажется, делает все, что делает команда "blockproc" Matlab.
Я не тестировал его.
Также есть интересная команда bob.ip.DCTFeatures, которая включает возможности "block" для извлечения или изменения DCT-коэффициентов изображения.
Ответ 6
Я нашел этот учебник - окончательный исходный код обеспечивает именно нужные функции!
Он даже должен работать для любой размерности (я не тестировал его)
http://www.johnvinyard.com/blog/?p=268
Хотя опция "сгладить" в самом конце исходного кода выглядит немного ошибкой. Тем не менее, очень приятная часть программного обеспечения!