The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
Thinning reduces a binary shape to a one-pixel-wide centerline that
preserves topology. Several algorithms exist; they differ in how
aggressively they erode pixels, how well they preserve corners, and how
fast they run. This vignette gives a short guide to choosing among the
algorithms thinr provides.
| Method | Reference | Approach |
|---|---|---|
zhang_suen |
Zhang & Suen (1984) | 2 sub-iterations, mixed-direction products |
guo_hall |
Guo & Hall (1989) | 2 sub-iterations, conditions tuned for diagonals |
lee |
Lee, Kashyap & Chu (1994), 2-D | 4 directional sub-iterations + Euler-invariance |
k3m |
Saeed et al. (2010) | 6 phases of progressively permissive removal |
hilditch |
parallel form (Rutovitz-style) | Single pass with look-ahead crossing-number check |
opta |
Naccache & Shinghal (1984) | Safe-point thinning (SPTA) |
holt |
Holt, Stewart, Clint & Perrott (1987) | One subcycle with edge information on neighbours |
zhang_suen is the default and matches
EBImage::thinImage() behavior. The thinImage()
wrapper is provided as a drop-in replacement.
lee is a 2-D adaptation of Lee’s original 3-D algorithm.
The 3-D case (volumetric thinning) is not implemented in this
release.
The hilditch method implements the parallel
form commonly cited as “Hilditch” in modern image-processing
surveys; Hilditch’s 1969 paper actually describes a sequential algorithm
with within-pass deletion tracking and a different crossing-number
definition. See Lam, Lee & Suen (1992) for the relationship between
the two.
The K3M lookup tables A_0 … A_5 are reproduced from
Saeed et al. (2010), Section 3.3, page 327. OPTA’s safe-point boolean
expression and Holt’s condition H are taken from the
original papers; the Lam-Lee-Suen 1992 survey was used as
cross-reference and one transcription error in Holt’s middle clause
(north vs east) was corrected against the original.
# A 'V' shape — exercises diagonal preservation
make_v <- function() {
m <- matrix(0L, 11, 11)
for (k in seq(0, 5)) {
m[2 + k, 2 + k] <- 1L
m[2 + k, 10 - k] <- 1L
m[3 + k, 2 + k] <- 1L
m[3 + k, 10 - k] <- 1L
}
m
}
v <- make_v()
v
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 1 0 0 0 0 0 0 0 1 0
#> [3,] 0 1 1 0 0 0 0 0 1 1 0
#> [4,] 0 0 1 1 0 0 0 1 1 0 0
#> [5,] 0 0 0 1 1 0 1 1 0 0 0
#> [6,] 0 0 0 0 1 1 1 0 0 0 0
#> [7,] 0 0 0 0 1 1 1 0 0 0 0
#> [8,] 0 0 0 0 1 0 1 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
#> [10,] 0 0 0 0 0 0 0 0 0 0 0
#> [11,] 0 0 0 0 0 0 0 0 0 0 0thin(v, method = "zhang_suen")
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 0 0 0 0 0 0 0 0 0
#> [4,] 0 0 0 0 0 0 0 0 0 0 0
#> [5,] 0 0 0 0 0 0 0 0 0 0 0
#> [6,] 0 0 0 0 1 1 1 0 0 0 0
#> [7,] 0 0 0 0 0 0 0 0 0 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
#> [10,] 0 0 0 0 0 0 0 0 0 0 0
#> [11,] 0 0 0 0 0 0 0 0 0 0 0thin(v, method = "guo_hall")
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 1 0 0 0 0 0 0 0 1 0
#> [3,] 0 1 0 0 0 0 0 0 1 0 0
#> [4,] 0 0 1 0 0 0 0 1 0 0 0
#> [5,] 0 0 0 1 0 0 1 0 0 0 0
#> [6,] 0 0 0 0 1 1 0 0 0 0 0
#> [7,] 0 0 0 0 0 1 0 0 0 0 0
#> [8,] 0 0 0 0 1 0 1 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
#> [10,] 0 0 0 0 0 0 0 0 0 0 0
#> [11,] 0 0 0 0 0 0 0 0 0 0 0thin(v, method = "hilditch")
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 0 0 0 0 0 0 0 0 0
#> [4,] 0 0 0 0 0 0 0 0 0 0 0
#> [5,] 0 0 0 0 0 0 0 0 0 0 0
#> [6,] 0 0 0 0 1 1 1 0 0 0 0
#> [7,] 0 0 0 0 0 0 0 0 0 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
#> [10,] 0 0 0 0 0 0 0 0 0 0 0
#> [11,] 0 0 0 0 0 0 0 0 0 0 0thin(v, method = "k3m")
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 0 0 0 0 0 0 0 0 0
#> [4,] 0 0 0 0 0 0 0 0 0 0 0
#> [5,] 0 0 0 0 1 0 1 0 0 0 0
#> [6,] 0 0 0 0 1 0 1 0 0 0 0
#> [7,] 0 0 0 0 1 1 1 0 0 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
#> [10,] 0 0 0 0 0 0 0 0 0 0 0
#> [11,] 0 0 0 0 0 0 0 0 0 0 0thin(v, method = "holt")
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 0 0 0 0 0 0 0 0 0
#> [4,] 0 0 0 0 0 0 0 0 0 0 0
#> [5,] 0 0 0 0 0 0 0 0 0 0 0
#> [6,] 0 0 0 0 1 1 1 0 0 0 0
#> [7,] 0 0 0 0 0 0 0 0 0 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
#> [10,] 0 0 0 0 0 0 0 0 0 0 0
#> [11,] 0 0 0 0 0 0 0 0 0 0 0The thinning algorithms produce broadly similar skeletons on this V — they all collapse the two diagonal strokes to single lines and meet at the bottom. Differences show up on more complex shapes:
zhang_suen is the most aggressive on simple
shapes.guo_hall and k3m preserve corners
marginally better.hilditch has the look-ahead crossing-number check,
which gives different connectivity properties at junctions.holt uses edge information about neighbouring pixels
rather than a crossing-number check on the central pixel; it is
specifically designed to preserve 2-pixel-wide lines.zhang_suen — the default. Most
predictable behavior. Use for general purpose thinning and for
compatibility with code that previously used
EBImage::thinImage().guo_hall — try this if your skeletons
have lots of diagonal features and Zhang-Suen is breaking them at
corners.lee — when you want directional
processing (four sub-iterations per pass, one per cardinal direction).
Sometimes produces cleaner skeletons on asymmetric inputs.k3m — strongest corner preservation in
published comparative studies, at the cost of being slower (six phases
per outer iteration vs. two for Zhang-Suen).hilditch — well-cited historical
algorithm; the look-ahead crossing-number check makes its connectivity
slightly different from the other parallel algorithms.opta — one-pass safe-point algorithm.
Its N2 condition protects two-4-adjacent-pixel diagonal
patterns, which can leave stray pixels at bar corners (a documented
property of SPTA).holt — when 2-pixel-wide lines should
be preserved. The algorithm uses edge information from neighbouring
pixels in a 5x5 window, allowing a single subcycle.The thinning algorithms above all produce binary 1-pixel-wide
skeletons without width information. For tasks where local thickness
matters, use medial_axis():
m <- matrix(0L, 9, 11)
m[3:7, 3:9] <- 1L # 5x7 solid rectangle
medial_axis(m)
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 1 1 0 0 0 1 1 0 0
#> [4,] 0 0 1 1 1 0 1 1 1 0 0
#> [5,] 0 0 0 1 1 1 1 1 0 0 0
#> [6,] 0 0 1 1 1 0 1 1 1 0 0
#> [7,] 0 0 1 1 0 0 0 1 1 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0result <- medial_axis(m, return_distance = TRUE)
result$skeleton
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 1 1 0 0 0 1 1 0 0
#> [4,] 0 0 1 1 1 0 1 1 1 0 0
#> [5,] 0 0 0 1 1 1 1 1 0 0 0
#> [6,] 0 0 1 1 1 0 1 1 1 0 0
#> [7,] 0 0 1 1 0 0 0 1 1 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0
round(result$distance, 3)
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#> [1,] 0 0 0 0 0 0 0 0 0 0 0
#> [2,] 0 0 0 0 0 0 0 0 0 0 0
#> [3,] 0 0 1 1 1 1 1 1 1 0 0
#> [4,] 0 0 1 2 2 2 2 2 1 0 0
#> [5,] 0 0 1 2 3 3 3 2 1 0 0
#> [6,] 0 0 1 2 2 2 2 2 1 0 0
#> [7,] 0 0 1 1 1 1 1 1 1 0 0
#> [8,] 0 0 0 0 0 0 0 0 0 0 0
#> [9,] 0 0 0 0 0 0 0 0 0 0 0The distance is the Euclidean distance from each foreground pixel to the nearest background pixel.
distance_transform() exposes the distance transform
itself as a stand-alone utility, with a choice of metric:
m <- matrix(1L, 5, 5)
m[1, 1] <- 0L
distance_transform(m, metric = "manhattan")
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 0 1 2 3 4
#> [2,] 1 2 3 4 5
#> [3,] 2 3 4 5 6
#> [4,] 3 4 5 6 7
#> [5,] 4 5 6 7 8
distance_transform(m, metric = "chessboard")
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 0 1 2 3 4
#> [2,] 1 1 2 3 4
#> [3,] 2 2 2 3 4
#> [4,] 3 3 3 3 4
#> [5,] 4 4 4 4 4
round(distance_transform(m, metric = "euclidean"), 3)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 0 1.000 2.000 3.000 4.000
#> [2,] 1 1.414 2.236 3.162 4.123
#> [3,] 2 2.236 2.828 3.606 4.472
#> [4,] 3 3.162 3.606 4.243 5.000
#> [5,] 4 4.123 4.472 5.000 5.657The Euclidean implementation uses the linear-time separable algorithm of Felzenszwalb and Huttenlocher (2012); the others use the classical Rosenfeld and Pfaltz (1968) two-pass sweep.
thin(), medial_axis(), and
thinImage() accept logical, integer, and numeric matrices.
Non-zero values are foreground. The return matrix matches the storage
mode of the input — logical in, logical out; integer in, integer out;
numeric in, numeric out.
distance_transform() always returns a numeric
matrix.
Higher-dimensional arrays (e.g. 3-D volumes) are not yet supported.
A simple bench::mark() comparison on a moderate image
(illustrative):
library(bench)
m <- matrix(0L, 200, 200)
m[50:150, 50:150] <- 1L # solid square
bench::mark(
zs = thin(m, method = "zhang_suen"),
gh = thin(m, method = "guo_hall"),
hild = thin(m, method = "hilditch"),
k3m = thin(m, method = "k3m"),
ma = medial_axis(m),
dt_eucl = distance_transform(m, metric = "euclidean"),
iterations = 5,
check = FALSE
)All thinning algorithms are O(iterations × pixels).
Constant factors are smallest for zhang_suen and
guo_hall; holt and k3m are the
most expensive because of their look-aheads. The Euclidean distance
transform is O(pixels) via Felzenszwalb-Huttenlocher;
medial axis is O(pixels) since it just adds a single linear
pass over the DT.
For 200×200 images all algorithms finish in a few milliseconds on modern hardware.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.