Быстрый способ нарезать изображение на перекрывающиеся патчи и слить патчи к изображению
Попытка обрезать изображение в оттенках серого размером 100x100 в патчи размером 39x39, которые перекрываются, с шагом 1. Это означает, что следующий патч, который запускает один пиксель справа/или ниже, отличается от предыдущего патча в один дополнительный столбец/или ряд.
Грубая схема кода: сначала вычислите индексы для каждого патча, чтобы иметь возможность создавать 2D-массив патчей с изображения и иметь возможность создавать изображение из патчей:
patches = imgFlat[ind]
"patches" - это 2D-массив с каждым столбцом, содержащим патч в векторной форме.
Эти исправления обрабатываются, каждый патч индивидуально, а затем снова объединяются вместе с изображением с предварительно вычисленными индексами.
img = np.sum(patchesWithColFlat[ind],axis=2)
По мере перекрытия патчей необходимо в конце умножить img на предварительно рассчитанные веса:
imgOut = weights*imgOut
Мой код очень медленный, и скорость - это очень важная проблема, так как это должно быть сделано на ок. 10 ^ 8 патчей.
Функции get_indices_for_un_patchify и weights_unpatchify могут быть предварительно вычислены один раз, поэтому скорость - это всего лишь проблема для исправления и повторной проверки.
Спасибо за любые подсказки.
Carlos
import numpy as np
import scipy
import collections
import random as rand
def get_indices_for_un_patchify(sImg,sP,step):
''' creates indices for fast patchifying and unpatchifying
INPUTS:
sx image size
sp patch size
step offset between two patches (default == [1,1])
OUTPUTS:
patchInd collection with indices
patchInd.img2patch patchifying indices
patch = img(patchInd.img2patch);
patchInd.patch2img unpatchifying indices
NOTE: * for unpatchifying necessary to add a 0 column to the patch matrix
* matrices are constructed row by row, as normally there are less rows than columns in the
patchMtx
'''
lImg = np.prod(sImg)
indImg = np.reshape(range(lImg), sImg)
# no. of patches which fit into the image
sB = (sImg - sP + step) / step
lb = np.prod(sB)
lp = np.prod(sP)
indImg2Patch = np.zeros([lp, lb])
indPatch = np.reshape(range(lp*lb), [lp, lb])
indPatch2Img = np.ones([sImg[0],sImg[1],lp])*(lp*lb+1)
# default value should be last column
iRow = 0;
for jCol in range(sP[1]):
for jRow in range(sP[0]):
tmp1 = np.array(range(0, sImg[0]-sP[0]+1, step[0]))
tmp2 = np.array(range(0, sImg[1]-sP[1]+1, step[1]))
sel1 = jRow + tmp1
sel2 = jCol + tmp2
tmpIndImg2Patch = indImg[sel1,:]
# do not know how to combine following 2 lines in python
tmpIndImg2Patch = tmpIndImg2Patch[:,sel2]
indImg2Patch[iRow, :] = tmpIndImg2Patch.flatten()
# next line not nice, but do not know how to implement it better
indPatch2Img[min(sel1):max(sel1)+1, min(sel2):max(sel2)+1, iRow] = np.reshape(indPatch[iRow, :, np.newaxis], sB)
iRow += 1
pInd = collections.namedtuple
pInd.patch2img = indPatch2Img
pInd.img2patch = indImg2Patch
return pInd
def weights_unpatchify(sImg,pInd):
weights = 1./unpatchify(patchify(np.ones(sImg), pInd), pInd)
return weights
# @profile
def patchify(img,pInd):
imgFlat = img.flat
# imgFlat = img.flatten()
ind = pInd.img2patch.tolist()
patches = imgFlat[ind]
return patches
# @profile
def unpatchify(patches,pInd):
# add a row of zeros to the patches matrix
h,w = patches.shape
patchesWithCol = np.zeros([h+1,w])
patchesWithCol[:-1,:] = patches
patchesWithColFlat = patchesWithCol.flat
# patchesWithColFlat = patchesWithCol.flatten()
ind = pInd.patch2img.tolist()
img = np.sum(patchesWithColFlat[ind],axis=2)
return img
Я называю эти функции, например, со случайным изображением
if __name__ =='__main__':
img = np.random.randint(255,size=[100,100])
sImg = img.shape
sP = np.array([39,39]) # size of patch
step = np.array([1,1]) # sliding window step size
pInd = get_indices_for_un_patchify(sImg,sP,step)
patches = patchify(img,pInd)
imgOut = unpatchify(patches,pInd)
weights = weights_unpatchify(sImg,pInd)
imgOut = weights*imgOut
print 'Difference of img and imgOut = %.7f' %sum(img.flatten() - imgOut.flatten())
Ответы
Ответ 1
Эффективный способ "патфицировать" массив, т.е. Получить массив окон в исходном массиве, - это создать представление с пользовательскими шагами, количество байтов для перехода к следующему элементу. Может быть полезно подумать о массиве numpy как (прославленном) фрагменте памяти, а затем шагами - способ сопоставления индексов по адресу памяти.
Например, в
a = np.arange(10).reshape(2, 5)
a.itemsize
равно 4 (т.е. 4 байта или 32 бита для каждого элемента), а a.strides
- (20, 4)
(5 элементов, 1 элемент), так что a[1,2]
ссылается на элемент, который равен 1*20 + 2*4
байта (или 1*5 + 2
элемента) после первого:
0 1 2 3 4
5 6 7 x x
Фактически, элементы помещаются в память один за другим, 0 1 2 3 4 5 6 7 xx
но шаги позволяют нам индексировать его как 2D-массив.
Основываясь на этой концепции, мы можем переписать patchify
следующим образом
def patchify(img, patch_shape):
img = np.ascontiguousarray(img) # won't make a copy if not needed
X, Y = img.shape
x, y = patch_shape
shape = ((X-x+1), (Y-y+1), x, y) # number of patches, patch_shape
# The right strides can be thought by:
# 1) Thinking of 'img' as a chunk of memory in C order
# 2) Asking how many items through that chunk of memory are needed when indices
# i,j,k,l are incremented by one
strides = img.itemsize*np.array([Y, 1, Y, 1])
return np.lib.stride_tricks.as_strided(img, shape=shape, strides=strides)
Эта функция возвращает представление img
, поэтому память не выделяется и выполняется всего за несколько десятков микросекунд. Форма вывода не совсем то, что вы хотите, и на самом деле ее нужно скопировать, чтобы получить эту форму.
Нужно быть осторожным, имея дело с представлениями массива, которые намного больше, чем базовый массив, потому что операции могут инициировать копию, которая должна будет выделять много памяти. В вашем случае, поскольку массивы не слишком велики и не так много патчей, все должно быть хорошо.
Наконец, мы можем немного разбить массив патчей:
patches = patchify(img, (39,39))
contiguous_patches = np.ascontiguousarray(patches)
contiguous_patches.shape = (-1, 39**2)
Это не воспроизводит выходные данные вашей функции исправления, потому что вы разрабатываете исправления в порядке Fortran. Я рекомендую вам использовать это вместо этого
-
Это приводит к более естественному индексированию позже (т.е. Первый патч - патчи [0] вместо патчей [:, 0] для вашего решения).
-
Также проще в numpy использовать C-порядок везде, потому что вам нужно меньше набирать текст (вы избегаете таких вещей, как order = 'F', массивы создаются по заказу C по умолчанию...).
"Подсказки", если вы настаиваете: strides = img.itemsize * np.array([1, Y, Y, 1])
, используйте .reshape(..., order='F')
на contiguous_patches
и, наконец, транспонируйте его .T