Atualmente tenho uma imagem binária, armazenado como uma matriz 2D de [col] [linha]. A imagem é dividida em duas por uma linha e que deseja definir todos os valores da matriz acima da linha para uma.
Actualmente, eu percorrer as colunas da matriz, looping através das linhas de coluna (de baixo para cima) para encontrar a primeira linha, onde o valor da matriz [col] [row] é 1. Em seguida, a partir de quebrar o ciclo através linhas e definir todos os valores acima da linha que rompeu a partir de uma coluna para que.
Infelizmente para uma imagem de 1920x1080, este leva cerca de 3 segundos. Como isso pode ser alcançado de forma mais eficiente?
for x in range(len(image)):
col = image[x]
minY= -1
for y in range(len(col)):
if image[x][-y] != 0:
minY = y
break
if minY != -1:
under = [0] * minY
over = [1] * (len(col) - minY)
newCol = over + under
image[x] = newCol
Antes e depois fotos abaixo ...
Aqui um par de métodos que exigem uma entrada 2D. Pode ter de ser ajustada para garantir que eles estão trabalhando no lado correto e sobre a dimensão correta, mas a idéia central detém.
- usando
np.where()
e looping sobre um dim com corte:
import numpy as np
def side_fill_np(arr, value, flag):
rows, cols = arr.shape
idx = np.where(arr == flag)
for i in range(cols):
arr[idx[0][i]:, idx[1][i]] = value
return arr
- usando looping completo explícita (semelhante ao seu, mas de alguma forma mais limpa), mas acelerou com
numba
import numba as nb
@nb.jit
def side_fill_nb(arr, value, flag):
rows, cols = arr.shape
for j in range(cols):
found = False
for i in range(rows):
if found:
arr[i, j] = value
elif arr[i, j] == flag:
found = True
Para testar se estamos recebendo o resultado correto, geramos alguma entrada usando:
def gen_data(shape):
rows, cols = shape
arr = np.zeros(shape, dtype=bool)
indexes = (np.random.randint(0, rows - 1, cols), np.arange(cols))
arr[indexes] = True
return arr
eo teste lê:
np.random.seed(0) # for reproducible results
arr = gen_data((10, 20))
print(arr.astype(int))
# [[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
# [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0]
# [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0]
# [0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
# [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
# [1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1]
# [0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0]
# [0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0]
# [0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0]
# [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
side_fill_np(arr, True, True)
print(arr.astype(int))
# [[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
# [0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0]
# [0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0]
# [0 1 1 1 0 1 0 1 0 0 0 0 0 1 0 0 0 0 1 0]
# [0 1 1 1 0 1 0 1 1 0 0 0 0 1 0 0 0 0 1 0]
# [1 1 1 1 0 1 1 1 1 0 0 0 0 1 0 0 0 0 1 1]
# [1 1 1 1 0 1 1 1 1 0 1 0 0 1 1 0 0 0 1 1]
# [1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 1 1]
# [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
# [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
Agora, para os horários para aprox. o seu tamanho de entrada:
arr = gen_data((2000, 2000))
%timeit side_fill(arr, True, True)
# 1 loop, best of 3: 2.48 s per loop
%timeit side_fill_np(arr, True, True)
# 10 loops, best of 3: 52.6 ms per loop
%timeit side_fill_nb(arr, True, True)
# 100 loops, best of 3: 6.14 ms per loop
Como você pode ver, com a abordagem NumPy (que é tanto vetorizado como eu poderia pensar), obtém aprox. 2 ordens de velocidade-up magnitude, com o Numba acelerado código que você obter aprox. 3 ordens de magnitude velocidade-up.