2018-09-30 23:01:58 +01:00
---
id: 594810f028c0303b75339ad7
2020-11-27 19:02:05 +01:00
title: Zhang-Suen thinning algorithm
2018-09-30 23:01:58 +01:00
challengeType: 5
2019-08-05 09:17:33 -07:00
forumTopicId: 302347
2021-01-13 03:31:00 +01:00
dashedName: zhang-suen-thinning-algorithm
2018-09-30 23:01:58 +01:00
---
2020-11-27 19:02:05 +01:00
# --description--
This is an algorithm used to thin a black and white i.e. one bit per pixel images. For example, with an input image of:
2019-07-18 17:32:12 +02:00
<!-- TODO write fully in markdown>
<!-- markdownlint - disable -->
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
< pre >
################# #############
################## ################
################### ##################
######## ####### ###################
###### ####### ####### ######
###### ####### #######
################# #######
################ #######
################# #######
###### ####### #######
###### ####### #######
###### ####### ####### ######
######## ####### ###################
######## ####### ###### ################## ######
######## ####### ###### ################ ######
######## ####### ###### ############# ######
2019-03-18 13:49:02 +05:30
< / pre >
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
It produces the thinned output:
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
< pre >
# ########## #######
## # #### #
# # ##
# # #
# # #
# # #
############ #
# # #
# # #
# # #
# # #
# ##
# ############
### ###
2019-03-18 13:49:02 +05:30
< / pre >
2018-09-30 23:01:58 +01:00
< h2 > Algorithm< / h2 >
2020-11-27 19:02:05 +01:00
Assume black pixels are one and white pixels zero, and that the input image is a rectangular N by M array of ones and zeroes. The algorithm operates on all black pixels P1 that can have eight neighbours. The neighbours are, in order, arranged as:
2018-09-30 23:01:58 +01:00
2019-07-18 17:32:12 +02:00
< table border = "3" >
< tr > < td style = "text-align: center;" > P9< / td > < td style = "text-align: center;" > P2< / td > < td style = "text-align: center;" > P3< / td > < / tr >
< tr > < td style = "text-align: center;" > P8< / td > < td style = "text-align: center;" > < strong > P1< / strong > < / td > < td style = "text-align: center;" > P4< / td > < / tr >
< tr > < td style = "text-align: center;" > P7< / td > < td style = "text-align: center;" > P6< / td > < td style = "text-align: center;" > P5< / td > < / tr >
2019-03-18 13:49:02 +05:30
< / table >
2018-09-30 23:01:58 +01:00
2019-03-18 13:49:02 +05:30
Obviously the boundary pixels of the image cannot have the full eight neighbours.
2020-11-27 19:02:05 +01:00
2019-07-18 17:32:12 +02:00
< ul >
< li > Define $A(P1)$ = the number of transitions from white to black, (0 -> 1) in the sequence P2, P3, P4, P5, P6, P7, P8, P9, P2. (Note the extra P2 at the end - it is circular).< / li >
< li > Define $B(P1)$ = the number of black pixel neighbours of P1. ( = sum(P2 .. P9) )< / li >
< / ul >
2018-09-30 23:01:58 +01:00
2021-01-21 00:11:46 -08:00
**Step 1:**
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
All pixels are tested and pixels satisfying all the following conditions (simultaneously) are just noted at this stage.
2020-11-27 19:02:05 +01:00
2019-03-18 13:49:02 +05:30
< ol >
< li > The pixel is black and has eight neighbours< / li >
< li > $2 < = B(P1) < = 6$< / li >
< li > $A(P1) = 1$< / li >
2019-06-14 20:04:16 +09:00
< li > At least one of < strong > P2, P4 and P6< / strong > is white< / li >
< li > At least one of < strong > P4, P6 and P8< / strong > is white< / li >
2019-03-18 13:49:02 +05:30
< / ol >
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
After iterating over the image and collecting all the pixels satisfying all step 1 conditions, all these condition satisfying pixels are set to white.
2019-03-18 13:49:02 +05:30
2021-01-21 00:11:46 -08:00
**Step 2:**
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
All pixels are again tested and pixels satisfying all the following conditions are just noted at this stage.
2020-11-27 19:02:05 +01:00
2019-03-18 13:49:02 +05:30
< ol >
< li > The pixel is black and has eight neighbours< / li >
< li > $2 < = B(P1) < = 6$< / li >
< li > $A(P1) = 1$< / li >
2019-06-14 20:04:16 +09:00
< li > At least one of < strong > P2, P4 and P8< / strong > is white< / li >
< li > At least one of < strong > P2, P6 and P8< / strong > is white< / li >
2019-03-18 13:49:02 +05:30
< / ol >
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
After iterating over the image and collecting all the pixels satisfying all step 2 conditions, all these condition satisfying pixels are again set to white.
2020-11-27 19:02:05 +01:00
2021-01-21 00:11:46 -08:00
**Iteration:**
2020-11-27 19:02:05 +01:00
2018-09-30 23:01:58 +01:00
If any pixels were set in this round of either step 1 or step 2 then all steps are repeated until no image pixels are so changed.
2020-11-27 19:02:05 +01:00
# --instructions--
2019-03-18 13:49:02 +05:30
Write a routine to perform Zhang-Suen thinning on the provided image matrix.
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
# --hints--
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
`thinImage` should be a function.
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
```js
assert.equal(typeof thinImage, 'function');
```
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
`thinImage` should return an array.
2018-09-30 23:01:58 +01:00
```js
2020-11-27 19:02:05 +01:00
assert(Array.isArray(result));
```
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
`thinImage` should return an array of strings.
2020-09-15 09:57:40 -07:00
2020-11-27 19:02:05 +01:00
```js
assert.equal(typeof result[0], 'string');
2018-09-30 23:01:58 +01:00
```
2020-11-27 19:02:05 +01:00
`thinImage` should return an array of strings.
```js
assert.deepEqual(result, expected);
```
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
# --seed--
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
## --after-user-code--
2018-09-30 23:01:58 +01:00
```js
2018-10-20 21:02:47 +03:00
const imageForTests = [
' ',
' ################# ############# ',
' ################## ################ ',
' ################### ################## ',
' ######## ####### ################### ',
' ###### ####### ####### ###### ',
' ###### ####### ####### ',
' ################# ####### ',
' ################ ####### ',
' ################# ####### ',
' ###### ####### ####### ',
' ###### ####### ####### ',
' ###### ####### ####### ###### ',
' ######## ####### ################### ',
' ######## ####### ###### ################## ###### ',
' ######## ####### ###### ################ ###### ',
' ######## ####### ###### ############# ###### ',
' '];
const expected = [
' ',
' ',
' # ########## ####### ',
' ## # #### # ',
' # # ## ',
' # # # ',
' # # # ',
' # # # ',
' ############ # ',
' # # # ',
' # # # ',
' # # # ',
' # # # ',
' # ## ',
' # ############ ',
' ### ### ',
' ',
' '
];
const result = thinImage(imageForTests);
2018-09-30 23:01:58 +01:00
```
2020-11-27 19:02:05 +01:00
## --seed-contents--
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
```js
const testImage = [
' ',
' ################# ############# ',
' ################## ################ ',
' ################### ################## ',
' ######## ####### ################### ',
' ###### ####### ####### ###### ',
' ###### ####### ####### ',
' ################# ####### ',
' ################ ####### ',
' ################# ####### ',
' ###### ####### ####### ',
' ###### ####### ####### ',
' ###### ####### ####### ###### ',
' ######## ####### ################### ',
' ######## ####### ###### ################## ###### ',
' ######## ####### ###### ################ ###### ',
' ######## ####### ###### ############# ###### ',
' '];
function thinImage(image) {
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
}
```
2018-09-30 23:01:58 +01:00
2020-11-27 19:02:05 +01:00
# --solutions--
2018-09-30 23:01:58 +01:00
```js
function Point(x, y) {
this.x = x;
this.y = y;
}
const ZhangSuen = (function () {
function ZhangSuen() {
}
ZhangSuen.nbrs = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1]];
ZhangSuen.nbrGroups = [[[0, 2, 4], [2, 4, 6]], [[0, 2, 6], [0, 4, 6]]];
ZhangSuen.toWhite = [];
ZhangSuen.main = function (image) {
ZhangSuen.grid = new Array(image);
for (let r = 0; r < image.length ; r + + ) {
2018-10-02 15:02:53 +01:00
ZhangSuen.grid[r] = image[r].split('');
2018-09-30 23:01:58 +01:00
}
ZhangSuen.thinImage();
return ZhangSuen.getResult();
};
ZhangSuen.thinImage = function () {
let firstStep = false;
let hasChanged;
do {
hasChanged = false;
firstStep = !firstStep;
for (let r = 1; r < ZhangSuen.grid.length - 1 ; r + + ) {
for (let c = 1; c < ZhangSuen.grid [ 0 ] . length - 1 ; c + + ) {
if (ZhangSuen.grid[r][c] !== '#') {
continue;
}
const nn = ZhangSuen.numNeighbors(r, c);
if (nn < 2 | | nn > 6) {
continue;
}
if (ZhangSuen.numTransitions(r, c) !== 1) {
continue;
}
if (!ZhangSuen.atLeastOneIsWhite(r, c, firstStep ? 0 : 1)) {
continue;
}
ZhangSuen.toWhite.push(new Point(c, r));
hasChanged = true;
}
}
for (let i = 0; i < ZhangSuen.toWhite.length ; i + + ) {
const p = ZhangSuen.toWhite[i];
ZhangSuen.grid[p.y][p.x] = ' ';
}
ZhangSuen.toWhite = [];
} while ((firstStep || hasChanged));
};
ZhangSuen.numNeighbors = function (r, c) {
let count = 0;
for (let i = 0; i < ZhangSuen.nbrs.length - 1 ; i + + ) {
if (ZhangSuen.grid[r + ZhangSuen.nbrs[i][1]][c + ZhangSuen.nbrs[i][0]] === '#') {
count++;
}
}
return count;
};
ZhangSuen.numTransitions = function (r, c) {
let count = 0;
for (let i = 0; i < ZhangSuen.nbrs.length - 1 ; i + + ) {
if (ZhangSuen.grid[r + ZhangSuen.nbrs[i][1]][c + ZhangSuen.nbrs[i][0]] === ' ') {
if (ZhangSuen.grid[r + ZhangSuen.nbrs[i + 1][1]][c + ZhangSuen.nbrs[i + 1][0]] === '#') {
count++;
}
}
}
return count;
};
ZhangSuen.atLeastOneIsWhite = function (r, c, step) {
let count = 0;
const group = ZhangSuen.nbrGroups[step];
for (let i = 0; i < 2 ; i + + ) {
for (let j = 0; j < group [ i ] . length ; j + + ) {
const nbr = ZhangSuen.nbrs[group[i][j]];
if (ZhangSuen.grid[r + nbr[1]][c + nbr[0]] === ' ') {
count++;
break;
}
}
}
return count > 1;
};
ZhangSuen.getResult = function () {
const result = [];
for (let i = 0; i < ZhangSuen.grid.length ; i + + ) {
2018-10-02 15:02:53 +01:00
const row = ZhangSuen.grid[i].join('');
2018-09-30 23:01:58 +01:00
result.push(row);
}
return result;
};
return ZhangSuen;
}());
function thinImage(image) {
return ZhangSuen.main(image);
}
```