mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-06 05:16:40 +08:00
122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
"""skeletonize.py - Use an iterative thinning algorithm to find the
|
|
skeletons of binary objects in an image.
|
|
"""
|
|
|
|
import numpy as np
|
|
from scipy.ndimage import correlate
|
|
from .. import util
|
|
|
|
def skeletonize(image):
|
|
"""
|
|
Return a single pixel wide skeleton of all connected components
|
|
in a binary image.
|
|
|
|
The algorithm works by making successive passes of the image,
|
|
removing pixels on object borders. This continues until no
|
|
more pixels can be removed. The image is correlated with a
|
|
mask that assigns each pixel a number in the range [0...255]
|
|
corresponding to each possible pattern of its 8 neighbouring
|
|
pixels. A look up table is then used to assign the pixels a
|
|
value of 0, 1, 2 or 3, which are selectively removed during
|
|
the iterations.
|
|
|
|
Parameters
|
|
----------
|
|
|
|
image: ndarray (2D)
|
|
A binary image containing the objects to be skeletonized. '1'
|
|
represents foreground, and '0' represents background. It
|
|
also accepts arrays of boolean values where True is foreground.
|
|
|
|
Notes
|
|
-----
|
|
|
|
This implementation gives different results than a medial
|
|
axis transformation, which can be can be implemented using
|
|
morphological operations. This implementation is generally much
|
|
faster.
|
|
|
|
Returns
|
|
-------
|
|
|
|
out: ndarray
|
|
A matrix containing the thinned image
|
|
|
|
References
|
|
----------
|
|
A fast parallel algorithm for thinning digital patterns,
|
|
T. Y. ZHANG and C. Y. SUEN, Communications of the ACM,
|
|
March 1984, Volume 27, Number 3
|
|
|
|
|
|
Examples
|
|
--------
|
|
"""
|
|
|
|
# look up table - there is one entry for each of the 2^8=256 possible
|
|
# combinations of 8 binary neighbours. 1's, 2's and 3's are candidates
|
|
# for removal at each iteration of the algorithm.
|
|
lut = [ 0,0,0,1,0,0,1,3,0,0,3,1,1,0,1,3,0,0,0,0,0,0,0,0,2,0,2,0,3,0,3,3,
|
|
0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,3,0,2,2,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
2,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,3,0,2,0,
|
|
0,1,3,1,0,0,1,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
|
|
3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
2,3,1,3,0,0,1,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
2,3,0,1,0,0,0,1,0,0,0,0,0,0,0,0,3,3,0,1,0,0,0,0,2,2,0,0,2,0,0,0]
|
|
|
|
# convert to unsigned int (this should work for boolean values)
|
|
skeleton = np.array(image).astype(np.uint8)
|
|
|
|
# check some properties of the input image:
|
|
# - 2D
|
|
# - binary image with only 0's and 1's
|
|
if skeleton.ndim != 2:
|
|
raise ValueError('Skeletonize requires a 2D array')
|
|
if not np.all(np.in1d(skeleton.flat, (0, 1))):
|
|
raise ValueError('Image contains values other than 0 and 1')
|
|
|
|
# create the mask that will assign a unique value based on the
|
|
# arrangement of neighbouring pixels
|
|
mask = np.array([[ 1, 2, 4],
|
|
[128, 0, 8],
|
|
[ 64, 32, 16]], np.uint8)
|
|
|
|
pixelRemoved = True
|
|
while pixelRemoved:
|
|
pixelRemoved = False;
|
|
|
|
# assign each pixel a unique value based on its foreground neighbours
|
|
neighbours = correlate(skeleton, mask, mode='constant')
|
|
|
|
# ignore background
|
|
neighbours[skeleton == 0] = 0
|
|
|
|
# use LUT to categorize each foreground pixel as a 0, 1, 2 or 3
|
|
codes = np.take(lut, neighbours)
|
|
|
|
# pass 1 - remove the 1's and 3's
|
|
code_mask = (codes == 1)
|
|
if np.any(code_mask):
|
|
pixelRemoved = True
|
|
skeleton[code_mask] = 0
|
|
code_mask = (codes == 3)
|
|
if np.any(code_mask):
|
|
pixelRemoved = True
|
|
skeleton[code_mask] = 0
|
|
|
|
# pass 2 - remove the 2's and 3's
|
|
neighbours = correlate(skeleton, mask, mode='constant')
|
|
neighbours[skeleton == 0] = 0
|
|
codes = np.take(lut, neighbours)
|
|
code_mask = (codes == 2)
|
|
if np.any(code_mask):
|
|
pixelRemoved = True
|
|
skeleton[code_mask] = 0
|
|
code_mask = (codes == 3)
|
|
if np.any(code_mask):
|
|
pixelRemoved = True
|
|
skeleton[code_mask] = 0
|
|
|
|
return skeleton
|