我试图在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)]]
该函数还可以返回绝对索引(对于1dim数组),如Matlab中的bwdist。
谢谢!
编辑:到目前为止,我已经尝试了一些与scipy有关的潜在解决方案,例如distance_transform_edt,但是它只能找到到最近像素的距离,而不是像素本身。 如果相关,我还将在代码的其他地方使用OpenCV和VLfeat。
答案 0 :(得分:5)
OpenCV具有distanceTransform()
和distanceTransformWithLabels()
函数,它们的工作原理相似,但是与Matlab函数有一些区别。来自Matlab docs for bwdist
:
D = bwdist(BW)
计算二进制图像BW的欧几里德距离变换。对于BW
中的每个像素,距离变换会分配一个数字,该数字是该像素与BW
的最近非零像素之间的距离。
将此与OpenCV docs for distanceTransformWithLabels()
进行比较:
计算源图像中每个像素到最接近的零像素的距离。
因此Matlab给出了到最近的非零像素的距离,而OpenCV给出了到最近的零像素的距离。因此,您需要将图像反转为OpenCV。此外,带有标签的Matlab的可选输出给出了与该最接近像素相对应的线性索引:
[D,idx] = bwdist(BW)
还以索引数组idx
的形式计算最近像素图。 idx的每个元素都包含BW
最近的非零像素的线性索引。最近像素图也称为特征图,特征变换或最近邻变换。
使用OpenCV,获取输出的标签既不是图像的坐标,也不是索引。相反,它只是一个数字标签,类似于连接的组件标签,完全与像素位置/索引无关。
此函数的变体不仅计算每个像素(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]]
答案 1 :(得分:1)
这实际上是使用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]]]