Search code examples
pythonopencvimage-processingscikit-image

skeletonization (thinning) of small images not giving expected results - python


I am trying to implement a skeletonization of small images. But I am not getting an expected results. I tried also thin() and medial_axis() but nothing seems to work as expected. I am suspicious that this problem occurs because of the small resolutions of images. Here is the code:

import cv2
from numpy import asarray
import numpy as np

# open image
file = "66.png"
img_grey = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
afterMedian = cv2.medianBlur(img_grey, 3)
thresh = 140

# threshold the image
img_binary = cv2.threshold(afterMedian, thresh, 255, cv2.THRESH_BINARY)[1]

# make binary image
arr = asarray(img_binary)
binaryArr = np.zeros(asarray(img_binary).shape)
for i in range(0, arr.shape[0]):
    for j in range(0, arr.shape[1]):
        if arr[i][j] == 255:
            binaryArr[i][j] = 1
        else:
            binaryArr[i][j] = 0

# perform skeletonization
from skimage.morphology import skeletonize

cv2.imshow("binary arr", binaryArr)
backgroundSkeleton = skeletonize(binaryArr)

# convert to non-binary image
bSkeleton = np.zeros(arr.shape)
for i in range(0, arr.shape[0]):
    for j in range(0, arr.shape[1]):
        if backgroundSkeleton[i][j] == 0:
            bSkeleton[i][j] = 0
        else:
            bSkeleton[i][j] = 255

cv2.imshow("background skeleton", bSkeleton)
cv2.waitKey(0)

The results are:

enter image description here enter image description here

I would expect something more like this:

enter image description here

This applies to similar shapes also:

enter image description here enter image description here

Expectation:

enter image description here

Am I doing something wrong? Or it will truly will not be possible with such small pictures, because I tried skeletonization on bigger images and it worked just fine. Original images:

enter image description here enter image description here


Solution

  • You could try the skeleton in DIPlib (dip.EuclideanSkeleton):

    import numpy as np
    import diplib as dip
    import cv2
    
    file = "66.png"
    img_grey = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
    afterMedian = cv2.medianBlur(img_grey, 3)
    thresh = 140
    
    bin = afterMedian > thresh
    
    sk = dip.EuclideanSkeleton(bin, endPixelCondition='three neighbors')
    
    dip.viewer.Show(bin)
    dip.viewer.Show(sk)
    dip.viewer.Spin()
    

    The endPixelCondition input argument can be used to adjust how many branches are preserved or removed. 'three neighbors' is the option that produces the most branches.

    The code above produces branches also towards the corners of the image. Using 'two neighbors' prevents that, but produces fewer branches towards the object as well. The other way to prevent it is to set edgeCondition='object', but in this case the ring around the object becomes a square on the image boundary.


    To convert the DIPlib image sk back to a NumPy array, do

    sk = np.array(sk)
    

    sk is now a Boolean NumPy array (values True and False). To create an array compatible with OpenCV simply cast to np.uint8 and multiply by 255:

    sk = np.array(sk, dtype=np.uint8)
    sk *= 255
    

    Note that, when dealing with NumPy arrays, you generally don't need to loop over all pixels. In fact, it's worth trying to avoid doing so, as loops in Python are extremely slow.