How to find the joints and endpoints of medial axis

I was able to get the medial axis using

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)

But how can I find the endpoints and the joints of the medial axis using python as the coordinates I get from medial_x, medial_y = np.where(medial == 1) are not ordered in a way that I can easily get endpoints or joints?

enter image description here

The original silhouette is attached below.

enter image description here

Answer

I did this using wand – which is derived from ImageMagick. It has a skeletonise method and “Hit and Miss Morphology” for looking for specific shapes such as line-ends or junctions.

There is an excellent discussion by Anthony Thyssen here, but if I may summarise, you are looking for the following shapes when trying to find line-ends:

enter image description here

and this when looking for junctions:

enter image description here

The black squares mean the image must be black at that location and are represented as zeroes in the kernel in the code. The white squares mean the image must be white at that location and are represented as ones in the kernel in the code. The blank squares mean we “don’t care” what is at that location and are represented as dashes (minus signs) in the code.

The code looks like this:

#!/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')

The DEBUG-images are as follows:

DEBUG-skeleton

enter image description here

DEBUG-lineends

enter image description here

DEBUG-junctions

enter image description here


It’s actually all a lot simpler in the Terminal with 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

Or you can produce all 3 images in a single command:

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