Вот один из подходов к решению этой проблемы.Основная идея заключается в том, чтобы использовать итеративное решение, которое принимает 2D свертку матрицы и набор фильтров на каждом шаге для обнаружения и заполнения ячеек, попадающих в ограничивающую рамку.
Это будет намного понятнее с примером.Скажем, у нас есть следующие ndarray
:
a = np.array([[0,0,0,0],
[0,0,0,0],
[1,0,0,0],
[1,1,1,0]])
Идея этого метода заключается в обнаружении ячеек, которые имеют как минимум двух ортогональных соседей (на расстоянии 1 ячейка), которыепод углом 90 ° между собой с ненулевыми значениями в них.
Итеративно находя эти ячейки и заполняя их ячейками, мы получим ожидаемый результат.Таким образом, для этого примера выходные данные после первой итерации будут:
a = np.array([[0,0,0,0],
[0,0,0,0],
[1,1,0,0],
[1,1,1,0]])
И на следующей итерации:
a = np.array([[0,0,0,0],
[0,0,0,0],
[1,1,1,0],
[1,1,1,0]])
Как эти ячейки могут быть обнаружены?
Один из способов - взять двумерную свертку ndarray
с набором предопределенных фильтров, специально предназначенных для обнаружения интересующих ячеек.Для этой цели мы можем использовать scipy's convolve2D
.
2D свертка по существу берется путем сдвига 2D-фильтра через ndarray
и вычисления на каждом шагесумма поэлементного умножения.Это может быть более интуитивно понятно с помощью следующей анимации ( изображение из ):
Так что будет необходимопридумать какой-то фильтр, чтобы обнаружить интересующие клетки.Один из подходов может быть следующим:
array([[0, 1, 0],
[1, 0, 1],
[0, 1, 0]])
На первый взгляд этот фильтр может выполнить задачу, учитывая, что он обнаружит соседних соседей.Однако этот фильтр также будет учитывать выборки, которые находятся на расстоянии двух ячеек, поэтому, например, он будет складывать значения в первой и последней строке фильтра, и, как упоминалось ранее, мы хотим найти соседей, которые находятся под углом90 ° друг от друга.Таким образом, мы могли бы применить последовательность фильтров, рассматривающих все возможности такого случая:
2-мерные фильтры для применения
[0, 1, 0] [0, 1, 0] [0, 0, 0] [0, 0, 0]
[0, 0, 1] , [1, 0, 0] , [1, 0, 0] , [0, 0, 1]
[0, 0, 0] [0, 0, 0] [0, 1, 0] [0, 1, 0]
Применяя каждый из этихПо фильтрам мы могли определить, какие ячейки имеют хотя бы двух соседей с упомянутыми требованиями, и заполнить их.
Общее решение
def fill_bounding_boxes(a):
'''
Detects contiguous non-zero values in a 2D array
and fills with ones all missing values in the
minimal rectangular boundaries that enclose all
non-zero entries, or "Minimal Bounding Boxes"
----
a: np.array
2D array. All values > 0 are considered to define
the bounding boxes
----
Returns:
2D array with missing values filled
'''
import numpy as np
from scipy.signal import convolve2d
# Copy of the original array so it remains unmodified
x = np.copy(a).clip(0,1)
# Indicator. Set to false when no additional
# changes in x are found
is_diff = True
# Filter to be used for the 2D convolution
# The other filters are obtained by rotating this one
f = np.array([[0,1,0], [0,0,1], [0,0,0]])
# Runs while indicator is True
while is_diff:
x_ = np.copy(x)
# Convolution between x and the filters
# Only values with sums > 1 are kept, as it will mean
# that they had minimum 2 non-zero neighbours
# All filters are applied by rotating the initial filter
x += sum((convolve2d(x, np.rot90(f, i), mode='same') > 1)
for i in range(4))
# Clip values between 0 and 1
x = x.clip(0,1)
# Set indicator to false if matrix x is unmodified
if (x == x_).all():
is_diff = False
return x
Примеры
Давайте посмотрим на результат с помощью предложенного примера:
print(a)
array([[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, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 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, 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],
[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, 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, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]])
fill_bounding_boxes(a)
array([[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, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 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, 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, 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, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0]])
И для этого другого примера:
print(a)
array([[0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 1, 1, 0, 0, 1],
[0, 0, 0, 0, 1, 0]])
fill_bounding_boxes(a)
array([[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 1, 1],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0],
[0, 1, 1, 0, 1, 1],
[0, 0, 0, 0, 1, 1]])