Из документов:
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]]