Actualmente tengo una imagen binaria, almacenado como un array 2D de [col] [fila]. La imagen se divide en 2 por una línea y Quiero configurar todos los valores de la matriz por encima de la línea a 1.
Actualmente, bucle a través de las columnas de la matriz, bucle a través de las filas de la columna (de abajo a arriba) para encontrar la primera fila donde el valor de la matriz [col] [fila] es 1. entonces descanso de bucle a través de la filas y establecer todos los valores por encima de la fila que rompí de a 1 para esa columna.
Por desgracia para una imagen de 1920x1080, esto toma alrededor de 3 segundos. ¿Cómo podría lograrse de manera más 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 y después de fotos de abajo ...
Aquí un par de métodos que requieren una entrada de 2D. Puede ser necesario ajustar para asegurar que están trabajando en el lado correcto y en la dimensión correcta, pero la idea central sostiene.
- el uso
np.where()
y la colocación sobre una tenue con rebanado:
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 un bucle completo explícita (similar a la suya, pero de alguna manera más limpia), pero se aceleró con
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 la prueba de que estamos consiguiendo el resultado correcto, generamos alguna 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
y la prueba se lee:
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]]
Ahora, para los tiempos de aprox. el tamaño 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 se puede ver, con el enfoque NumPy (que es tanto como vectorizado lo que podía pensar) se obtiene aprox. 2 órdenes de magnitud de aceleración, con la Numba aceleraron código se obtiene aprox. 3 órdenes de magnitud de aceleración.