如何找到中轴的关节和端点

How to find the joints and endpoints of medial axis

我能够使用

获得中轴
image = Image.open('path/to/img.png')
image = (np.array(image) / 255).astype(np.uint8)
medial = skeletonize(image)
medial_x, medial_y = np.where(medial == 1)

但是我如何使用 python 找到中轴的端点和关节,因为我从 medial_x, medial_y = np.where(medial == 1) 获得的坐标没有按照我可以轻松获得端点或关节的方式排序?

原剪影附在下方

我把骨架看成一个图,并构造它的邻接矩阵。使用邻接矩阵,您可以非常轻松地找到端点。图中具有一个邻居的节点是端点,而具有两个以上邻居的节点就是您所说的“关节”。我喜欢称它们为十字路口。 我创建邻接矩阵的方法是,如果从节点 i 到节点 j 的欧几里德距离小于 2,则我用零启动一个 NxN 数组并将数组中的 [i, j] 值设置为 1,这意味着它们是相邻像素在图像中。

# create an adjacency matrix 
xx, yy = np.where(skeleton)
n = len(xx)

dist_mat = ((xx[:, None]-xx[None, :])**2 + (yy[:, None]-yy[None, :])**2)
adj_mat  = (dist_mat <= 2).astype(int)
# joints will have more than 2 neighbors and endpoints
# will have only one.
ends = np.where(adj_mat.sum(1)==1)[0]
intersections = np.where(adj_mat.sum(1)>2)[0]

最后,变量 'ends' 将保存端点的索引,这样端点 k 在图像中的 [x, y] 位置将是 [xx[ends[k]], yy[ends[k]]]。交叉路口也是如此。

我使用 wand 完成此操作 - 它源自 ImageMagick。它有一个 skeletonise 方法和 “Hit and Miss Morphology” 用于查找特定形状,例如线端或连接点。

Anthony Thyssen here 进行了精彩的讨论,但如果我可以总结一下,您在寻找线端时正在寻找以下形状:

寻找路口时的这个:

黑色方块意味着图像在该位置必须是黑色的,并且在代码的内核中表示为零。白色方块意味着图像在那个位置必须是白色的,并且在代码中表示为内核中的那些。空白方块意味着我们“不关心”那个位置是什么,在代码中表示为破折号(减号)。

代码如下所示:

#!/usr/bin/env python3

import numpy as np
from wand.image import Image

# Use 'wand' to:
# 1 skeletonize
# 2 find line-ends using Top-Hat Morphology
# 3 find line-junctions using Top-Hat Morphology

with Image(filename='Q4J0l.png') as img:
    # Skeletonize
    img.morphology(method='thinning',
                   kernel='skeleton',
                   iterations=-1)
    img.save(filename='DEBUG-skeleton.png')

    # Find line-ends using Top-Hat Morphology
    # There are two kernels here, separated by a semi-colon
    # Each is rotated through 90 degress to form all 4 orientations
    # The first 3x3 kernel is the one tinted red in the diagram above.
    # The second 3x3 kernel is the one tinted green in the diagram above
    lineEnds = """
    3>:
        0,0,-
        0,1,1
        0,0,-;
    3>:
        0,0,0
        0,1,0
        0,0,1
    """
    # Clone the original image as we are about to destroy it
    with img.clone() as endsImage:
        endsImage.morphology(method='hit_and_miss', kernel=lineEnds)
        endsImage.save(filename='DEBUG-ends.png')

    # Find line-junctions using Top-Hat Morphology
    # There are three kernels here, separated by a semi-colon
    # Each is rotated through 90 degress to form all 4 orientations
    # The first 3x3 kernel is the one tinted yellow in the diagram above
    # The second 3x3 kernel is the one tinted magenta in the diagram above 
    # The third 3x3 kernel is the one tinted cyan in the diagram above
    lineJunctions = """
    3>:
        1,-,1
        -,1,-
        -,1,-;
    3>:
        -,1,-
        -,1,1
        1,-,-;
    3>:
        1,-,-
        -,1,-
        1,-,1
    """
    # Clone the original image as we are about to destroy it
    with img.clone() as junctionsImage:
        junctionsImage.morphology(method='hit_and_miss', kernel=lineJunctions)
        junctionsImage.save(filename='DEBUG-junctions.png')

DEBUG-images如下:

调试骨架

DEBUG-lineends

调试连接点


使用 ImageMagick:

在终端中实际上要简单得多
magick Q4J0l.png -morphology Thinning:-1 Skeleton skeleton.png
magick skeleton.png -morphology HMT lineends ends.png
magick skeleton.png -morphology HMT linejunctions junctions.png

或者您可以在一个命令中生成所有 3 个图像:

magick Q4J0l.png \
    -morphology Thinning:-1 Skeleton -write S.png              \
    \( +clone -morphology HMT lineends -write E.png +delete \) \
    -morphology HMT linejunctions J.png