найти ближайшего соседа к каждому пикселю на карте - PullRequest
0 голосов
/ 30 сентября 2018

Я пытаюсь найти простой метод в python, в котором для каждого пикселя в 2dim маске я могу получить индексы ближайшего ненулевого соседа.В Matlab есть bwdist, который возвращает именно это.например: если мой ввод:

array [[0 0 0 0 0 0 0]
       [0 1 0 0 0 0 0]
       [0 0 0 0 0 1 0]
       [0 0 0 0 0 0 0]]

мой вывод должен быть:

array [[(1,1) (1,1) (1,1) (1,1) (2,5) (2,5) (2,5)]
       [(1,1) (1,1) (1,1) (1,1) (2,5) (2,5) (2,5)]
       [(1,1) (1,1) (1,1) (2,5) (2,5) (2,5) (2,5)]
       [(1,1) (1,1) (1,1) (2,5) (2,5) (2,5) (2,5)]]

Функция также может возвращать абсолютный индекс (для массива 1 дим), как bwdist в Matlab.

Спасибо!

РЕДАКТИРОВАТЬ: до сих пор я пробовал некоторые потенциальные решения, связанные со scipy, такие как distance_transform_edt, но он находит только расстояние до ближайшего пикселя, а не сам пиксель.Я также использую OpenCV и VLfeat в других местах моего кода, если это уместно.

Ответы [ 2 ]

0 голосов
/ 31 января 2019

Это на самом деле однострочник при использовании scipy.

Если ваша входная матрица mat, координаты ближайшего ненулевого значения определяются как:

import scipy.ndimage

nearest_neighbor = scipy.ndimage.morphology.distance_transform_edt(
    mat==0, return_distances=False, return_indices=True)

Дляматрица, приведенная в вопросе, приводит к следующей индексной матрице, которая является правильным ответом:

[[[1 1 1 1 2 2 2]
  [1 1 1 1 2 2 2]
  [1 1 1 2 2 2 2]
  [1 1 1 2 2 2 2]]

 [[1 1 1 1 5 5 5]
  [1 1 1 1 5 5 5]
  [1 1 1 5 5 5 5]
  [1 1 1 5 5 5 5]]]
0 голосов
/ 30 сентября 2018

Из документов:

OpenCV имеет функции distanceTransform() и distanceTransformWithLabels(), которые работают примерно так же, но есть некоторые отличия от этой функции Matlab.Из документов Matlab для bwdist:

D = bwdist(BW) вычисляется евклидово преобразование расстояния двоичного изображения BW.Для каждого пикселя в BW преобразование расстояния назначает число, которое является расстоянием между этим пикселем и ближайшим ненулевым пикселем BW.

Сравните это с документами OpenCV дляdistanceTransformWithLabels():

Вычисляет расстояние до ближайшего нулевого пикселя для каждого пикселя исходного изображения.

Таким образом, Matlab дает расстояние до ближайшего не- нулевой пиксель, в то время как OpenCV дает расстояние до ближайшего нулевого пикселя.Так что вам нужно будет инвертировать изображение для OpenCV.Кроме того, дополнительный вывод для Matlab с метками дает линейный индекс, соответствующий этому ближайшему пикселю:

[D,idx] = bwdist(BW) также вычисляет карту ближайшего пикселя в виде массива индекса, idx,Каждый элемент idx содержит линейный индекс ближайшего ненулевого пикселя BW.Карта ближайших пикселей также называется картой объектов, преобразованием объектов или преобразованием ближайшего соседа.

В OpenCV метка, которая получает выходные данные, не является ни координатой изображения, ни индексом.Вместо этого это просто числовая метка, похожая на метку подключенного компонента, которая вообще не связана с расположением / индексом пикселя.

Этот вариант функции рассчитывает не только минимальное расстояние для каждогоpixel (x, y) , но также идентифицирует ближайший подключенный компонент, состоящий из нулевых пикселей (labelType==DIST_LABEL_CCOMP) или ближайшего нулевого пикселя (labelType==DIST_LABEL_PIXEL).

Это означает, чточто вам нужно будет использовать это изображение с меткой, чтобы замаскировать ввод и найти пиксель, соответствующий этой метке (насколько я знаю, это лучший способ сделать это, по крайней мере).

Решение:

Итак, просто чтобы понять, как добраться туда, куда мы хотим, давайте посмотрим, куда нас доставляет эта функция (с инвертированным изображением в качестве ввода, как указано выше):

In [138]: img
Out[138]:
array([[  0,   0,   0,   0,   0,   0,   0],
       [  0, 255,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0, 255,   0],
       [  0,   0,   0,   0,   0,   0,   0]], dtype=uint8)

In [139]: dist, labels = cv2.distanceTransformWithLabels(~a, distanceType=cv2.DIST_L2, maskSize=3)

In [140]: print(dist)
[[1.3999939 1.        1.3999939 2.1968994 2.1968994 2.        2.1968994]
 [1.        0.        1.        2.        1.3999939 1.        1.3999939]
 [1.3999939 1.        1.3999939 2.        1.        0.        1.       ]
 [2.1968994 2.        2.1968994 2.1968994 1.3999939 1.        1.3999939]]

In [141]: print(labels)
[[1 1 1 1 2 2 2]
 [1 1 1 1 2 2 2]
 [1 1 1 2 2 2 2]
 [1 1 1 2 2 2 2]]

Итак, если мы просто пройдемся по уникальным значениям в метках, создадим маску для каждого из них, замаскируем оригинальное изображение ... и затем найдем белый пиксель внутри этой маркированной области, у нас будут индексы:

In [146]: for l in np.unique(labels):
     ...:     mask = label == l
     ...:     i = np.where(img * mask)
     ...:     print(i)
     ...:
(array([1]), array([1]))
(array([2]), array([5]))

Это не точный вывод, который вы запрашивали, но это список индексов, и у вас есть ярлыки.Так что теперь нам просто нужно отобразить это.Что я сделаю, это создаю пустую двухканальную матрицу для хранения значений индекса, а затем заполняю ее, основываясь на маске из меток:

In [177]: index_img = np.zeros((*img.shape, 2), dtype=np.intp)

In [178]: for l in np.unique(labels):
     ...:     mask = label == l
     ...:     index_img[mask] = np.dstack(np.where(img * mask))

И это двухканальный массив сИнформация, которую вы хотите.Структура немного отличается (без использования кортежей для каждой записи), но обычно это структура, которую вы хотите для других функций OpenCV (двухканальный массив):

In [204]: index_img[:, :, 0]
Out[204]:
array([[1, 1, 1, 1, 2, 2, 2],
       [1, 1, 1, 1, 2, 2, 2],
       [1, 1, 1, 2, 2, 2, 2],
       [1, 1, 1, 2, 2, 2, 2]])

In [205]: index_img[:, :, 1]
Out[205]:
array([[1, 1, 1, 1, 5, 5, 5],
       [1, 1, 1, 1, 5, 5, 5],
       [1, 1, 1, 5, 5, 5, 5],
       [1, 1, 1, 5, 5, 5, 5]])

Собираем все вместе

Вот функция, которая делает это, и имеет опцию для выделения этого двухканального выхода или только линейного выхода, как это делает Matlab:

def bwdist(img, metric=cv2.DIST_L2, dist_mask=cv2.DIST_MASK_5, label_type=cv2.DIST_LABEL_CCOMP, ravel=True):
    """Mimics Matlab's bwdist function.

    Available metrics:
        https://docs.opencv.org/3.4/d7/d1b/group__imgproc__misc.html#gaa2bfbebbc5c320526897996aafa1d8eb
    Available distance masks:
        https://docs.opencv.org/3.4/d7/d1b/group__imgproc__misc.html#gaaa68392323ccf7fad87570e41259b497
    Available label types:
        https://docs.opencv.org/3.4/d7/d1b/group__imgproc__misc.html#ga3fe343d63844c40318ee627bd1c1c42f
    """
    flip = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV)[1]
    dist, labeled = cv2.distanceTransformWithLabels(flip, metric, dist_mask)

    # return linear indices if ravel == True (default)
    if ravel:  
        idx = np.zeros(img.shape, dtype=np.intp)  # np.intp type is for indices
        for l in np.unique(labeled):
            mask = labeled == l
            idx[mask] = np.flatnonzero(img * mask)
        return dist, idx

    # return two-channel indices if ravel == False
    idx = np.zeros((*img.shape, 2), dtype=np.intp)  
    for l in np.unique(labeled):
        mask = labeled == l
        idx[mask] = np.dstack(np.where(img * mask))
    return dist, idx

И с примером, который Matlab дает в документации:

In [241]: bw = np.zeros((5, 5), dtype=np.uint8)
     ...: bw[1, 1] = 1
     ...: bw[3, 3] = 1
     ...: print(bw)
     ...:
[[0 0 0 0 0]
 [0 1 0 0 0]
 [0 0 0 0 0]
 [0 0 0 1 0]
 [0 0 0 0 0]]

In [244]: d, idx = bwdist(bw)

In [245]: print(d)
[[1.3999939 1.        1.3999939 2.1968994 3.1968994]
 [1.        0.        1.        2.        2.1968994]
 [1.3999939 1.        1.3999939 1.        1.3999939]
 [2.1968994 2.        1.        0.        1.       ]
 [3.1968994 2.1968994 1.3999939 1.        1.3999939]]

In [246]: print(idx)
[[ 6  6  6  6 18]
 [ 6  6  6  6 18]
 [ 6  6  6 18 18]
 [ 6  6 18 18 18]
 [ 6 18 18 18 18]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...