| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * ccthin.c |
| * |
| * PIX *pixThin() |
| * PIX *pixThinGeneral() |
| * PIX *pixThinExamples() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "allheaders.h" |
| |
| |
| /* ------------------------------------------------------------ |
| * These sels (and their rotated counterparts) are the useful |
| * 3x3 Sels for thinning. The notation is based on |
| * "Connectivity-preserving morphological image transformations," |
| * a version of which can be found at |
| * http://www.leptonica.com/papers/conn.pdf |
| * ------------------------------------------------------------ */ |
| |
| /* Sels for 4-connected thinning */ |
| static const char *sel_4_1 = " x" |
| "oCx" |
| " x"; |
| |
| static const char *sel_4_2 = " x" |
| "oCx" |
| " o "; |
| |
| static const char *sel_4_3 = " o " |
| "oCx" |
| " x"; |
| |
| static const char *sel_4_4 = " o " |
| "oCx" |
| " o "; |
| |
| static const char *sel_4_5 = " ox" |
| "oCx" |
| " o "; |
| |
| static const char *sel_4_6 = " o " |
| "oCx" |
| " ox"; |
| |
| static const char *sel_4_7 = " xx" |
| "oCx" |
| " o "; |
| |
| static const char *sel_4_8 = " x" |
| "oCx" |
| "o x"; |
| |
| static const char *sel_4_9 = "o x" |
| "oCx" |
| " x"; |
| |
| /* Sels for 8-connected thinning */ |
| static const char *sel_8_1 = " x " |
| "oCx" |
| " x "; |
| |
| static const char *sel_8_2 = " x " |
| "oCx" |
| "o "; |
| |
| static const char *sel_8_3 = "o " |
| "oCx" |
| " x "; |
| |
| static const char *sel_8_4 = "o " |
| "oCx" |
| "o "; |
| |
| static const char *sel_8_5 = "o x" |
| "oCx" |
| "o "; |
| |
| static const char *sel_8_6 = "o " |
| "oCx" |
| "o x"; |
| |
| static const char *sel_8_7 = " x " |
| "oCx" |
| "oo "; |
| |
| static const char *sel_8_8 = " x " |
| "oCx" |
| "ox "; |
| |
| static const char *sel_8_9 = "ox " |
| "oCx" |
| " x "; |
| |
| /* Sels for both 4 and 8-connected thinning */ |
| static const char *sel_48_1 = " xx" |
| "oCx" |
| "oo "; |
| |
| static const char *sel_48_2 = "o x" |
| "oCx" |
| "o x"; |
| |
| #ifndef NO_CONSOLE_IO |
| #define DEBUG_SELS 0 |
| #endif /* ~NO_CONSOLE_IO */ |
| |
| |
| /*----------------------------------------------------------------* |
| * CC-preserving thinning * |
| *----------------------------------------------------------------*/ |
| /*! |
| * pixThin() |
| * |
| * Input: pixs (1 bpp) |
| * type (L_THIN_FG, L_THIN_BG) |
| * connectivity (4 or 8) |
| * maxiters (max number of iters allowed; use 0 to iterate |
| * until completion) |
| * Return: pixd, or null on error |
| * |
| * Notes: |
| * (1) See "Connectivity-preserving morphological image transformations," |
| * Dan S. Bloomberg, in SPIE Visual Communications and Image |
| * Processing, Conference 1606, pp. 320-334, November 1991, |
| * Boston, MA. A web version is available at |
| * http://www.leptonica.com/papers/conn.pdf |
| * (2) We implement here two of the best iterative |
| * morphological thinning algorithms, for 4 c.c and 8 c.c. |
| * Each iteration uses a mixture of parallel operations |
| * (using several different 3x3 Sels) and serial operations. |
| * Specifically, each thinning iteration consists of |
| * four sequential thinnings from each of four directions. |
| * Each of these thinnings is a parallel composite |
| * operation, where the union of a set of HMTs are set |
| * subtracted from the input. For 4-cc thinning, we |
| * use 3 HMTs in parallel, and for 8-cc thinning we use 4 HMTs. |
| * (3) A "good" thinning algorithm is one that generates a skeleton |
| * that is near the medial axis and has neither pruned |
| * real branches nor left extra dendritic branches. |
| * (4) To thin the foreground, which is the usual situation, |
| * use type == L_THIN_FG. Thickening the foreground is equivalent |
| * to thinning the background (type == L_THIN_BG), where the |
| * opposite connectivity gets preserved. For example, to thicken |
| * the fg using 4-connectivity, we thin the bg using Sels that |
| * preserve 8-connectivity. |
| */ |
| PIX * |
| pixThin(PIX *pixs, |
| l_int32 type, |
| l_int32 connectivity, |
| l_int32 maxiters) |
| { |
| PIX *pixd; |
| SEL *sel; |
| SELA *sela; |
| |
| PROCNAME("pixThin"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 1) |
| return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); |
| if (type != L_THIN_FG && type != L_THIN_BG) |
| return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); |
| if (connectivity != 4 && connectivity != 8) |
| return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); |
| if (maxiters == 0) maxiters = 10000; |
| |
| sela = selaCreate(4); |
| if (connectivity == 4) { |
| sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); |
| selaAddSel(sela, sel, NULL, 0); |
| } else { /* connectivity == 8 */ |
| sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); |
| selaAddSel(sela, sel, NULL, 0); |
| } |
| |
| pixd = pixThinGeneral(pixs, type, sela, maxiters); |
| |
| selaDestroy(&sela); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixThinGeneral() |
| * |
| * Input: pixs (1 bpp) |
| * type (L_THIN_FG, L_THIN_BG) |
| * sela (of Sels for parallel composite HMTs) |
| * maxiters (max number of iters allowed; use 0 to iterate |
| * until completion) |
| * Return: pixd, or null on error |
| * |
| * Notes: |
| * (1) See notes in pixThin(). That function chooses among |
| * the best of the Sels for thinning. |
| * (2) This is a general function that takes a Sela of HMTs |
| * that are used in parallel for thinning from each |
| * of four directions. One iteration consists of four |
| * such parallel thins. |
| */ |
| PIX * |
| pixThinGeneral(PIX *pixs, |
| l_int32 type, |
| SELA *sela, |
| l_int32 maxiters) |
| { |
| l_int32 i, j, r, nsels, same; |
| PIXA *pixahmt; |
| PIX **pixhmt; /* array owned by pixahmt; do not destroy! */ |
| PIX *pixd, *pixt; |
| SEL *sel, *selr; |
| |
| PROCNAME("pixThinGeneral"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 1) |
| return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); |
| if (type != L_THIN_FG && type != L_THIN_BG) |
| return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); |
| if (!sela) |
| return (PIX *)ERROR_PTR("sela not defined", procName, NULL); |
| if (maxiters == 0) maxiters = 10000; |
| |
| /* Set up array of temp pix to hold hmts */ |
| nsels = selaGetCount(sela); |
| pixahmt = pixaCreate(nsels); |
| for (i = 0; i < nsels; i++) { |
| pixt = pixCreateTemplate(pixs); |
| pixaAddPix(pixahmt, pixt, L_INSERT); |
| } |
| pixhmt = pixaGetPixArray(pixahmt); |
| if (!pixhmt) |
| return (PIX *)ERROR_PTR("pixhmt array not made", procName, NULL); |
| |
| #if DEBUG_SELS |
| pixt = selaDisplayInPix(sela, 35, 3, 15, 4); |
| pixDisplayWithTitle(pixt, 100, 100, "allsels", 1); |
| pixDestroy(&pixt); |
| #endif /* DEBUG_SELS */ |
| |
| /* Set up initial image for fg thinning */ |
| if (type == L_THIN_FG) |
| pixd = pixCopy(NULL, pixs); |
| else /* bg thinning */ |
| pixd = pixInvert(NULL, pixs); |
| |
| /* Thin the fg, with up to maxiters iterations */ |
| for (i = 0; i < maxiters; i++) { |
| pixt = pixCopy(NULL, pixd); /* test for completion */ |
| for (r = 0; r < 4; r++) { /* over 90 degree rotations of Sels */ |
| for (j = 0; j < nsels; j++) { /* over individual sels in sela */ |
| sel = selaGetSel(sela, j); /* not a copy */ |
| selr = selRotateOrth(sel, r); |
| pixHMT(pixhmt[j], pixd, selr); |
| selDestroy(&selr); |
| if (j > 0) |
| pixOr(pixhmt[0], pixhmt[0], pixhmt[j]); /* accum result */ |
| } |
| pixSubtract(pixd, pixd, pixhmt[0]); /* remove result */ |
| } |
| pixEqual(pixd, pixt, &same); |
| pixDestroy(&pixt); |
| if (same) { |
| L_INFO_INT("%d iterations to completion", procName, i); |
| break; |
| } |
| } |
| |
| if (type == L_THIN_BG) |
| pixInvert(pixd, pixd); |
| |
| pixaDestroy(&pixahmt); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixThinExamples() |
| * |
| * Input: pixs (1 bpp) |
| * type (L_THIN_FG, L_THIN_BG) |
| * index (into specific examples; valid 1-9; see notes) |
| * maxiters (max number of iters allowed; use 0 to iterate |
| * until completion) |
| * selfile (<optional> filename for output sel display) |
| * Return: pixd, or null on error |
| * |
| * Notes: |
| * (1) See notes in pixThin(). The examples are taken from |
| * the paper referenced there. |
| * (2) Here we allow specific sets of HMTs to be used in |
| * parallel for thinning from each of four directions. |
| * One iteration consists of four such parallel thins. |
| * (3) The examples are indexed as follows: |
| * Thinning (e.g., run to completion): |
| * index = 1 sel_4_1, sel_4_5, sel_4_6 |
| * index = 2 sel_4_1, sel_4_7, sel_4_7_rot |
| * index = 3 sel_48_1, sel_48_1_rot, sel_48_2 |
| * index = 4 sel_8_2, sel_8_3, sel_48_2 |
| * index = 5 sel_8_1, sel_8_5, sel_8_6 |
| * index = 6 sel_8_2, sel_8_3, sel_8_8, sel_8_9 |
| * index = 7 sel_8_5, sel_8_6, sel_8_7, sel_8_7_rot |
| * Thickening: |
| * index = 8 sel_4_2, sel_4_3 (e.g,, do just a few iterations) |
| * index = 9 sel_8_4 (e.g., do just a few iterations) |
| */ |
| PIX * |
| pixThinExamples(PIX *pixs, |
| l_int32 type, |
| l_int32 index, |
| l_int32 maxiters, |
| const char *selfile) |
| { |
| PIX *pixd, *pixt; |
| SEL *sel; |
| SELA *sela; |
| |
| PROCNAME("pixThinExamples"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 1) |
| return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); |
| if (type != L_THIN_FG && type != L_THIN_BG) |
| return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); |
| if (index < 1 || index > 9) |
| return (PIX *)ERROR_PTR("invalid index", procName, NULL); |
| if (maxiters == 0) maxiters = 10000; |
| |
| switch(index) |
| { |
| case 1: |
| sela = selaCreate(3); |
| sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_4_5, 3, 3, "sel_4_5"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_4_6, 3, 3, "sel_4_6"); |
| selaAddSel(sela, sel, NULL, 0); |
| break; |
| case 2: |
| sela = selaCreate(3); |
| sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_4_7, 3, 3, "sel_4_7"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selRotateOrth(sel, 1); |
| selaAddSel(sela, sel, "sel_4_7_rot", 0); |
| break; |
| case 3: |
| sela = selaCreate(3); |
| sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selRotateOrth(sel, 1); |
| selaAddSel(sela, sel, "sel_48_1_rot", 0); |
| sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| break; |
| case 4: |
| sela = selaCreate(3); |
| sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| break; |
| case 5: |
| sela = selaCreate(3); |
| sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); |
| selaAddSel(sela, sel, NULL, 0); |
| break; |
| case 6: |
| sela = selaCreate(4); |
| sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_8, 3, 3, "sel_8_8"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_9, 3, 3, "sel_8_9"); |
| selaAddSel(sela, sel, NULL, 0); |
| break; |
| case 7: |
| sela = selaCreate(4); |
| sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selRotateOrth(sel, 1); |
| selaAddSel(sela, sel, "sel_8_7_rot", 0); |
| break; |
| case 8: /* thicken for this one; just a few iterations */ |
| sela = selaCreate(2); |
| sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); |
| selaAddSel(sela, sel, NULL, 0); |
| sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); |
| selaAddSel(sela, sel, NULL, 0); |
| pixt = pixThinGeneral(pixs, type, sela, maxiters); |
| pixd = pixRemoveBorderConnComps(pixt, 4); |
| pixDestroy(&pixt); |
| break; |
| case 9: /* thicken for this one; just a few iterations */ |
| sela = selaCreate(1); |
| sel = selCreateFromString(sel_8_4, 3, 3, "sel_8_4"); |
| selaAddSel(sela, sel, NULL, 0); |
| pixt = pixThinGeneral(pixs, type, sela, maxiters); |
| pixd = pixRemoveBorderConnComps(pixt, 4); |
| pixDestroy(&pixt); |
| break; |
| default: |
| return (PIX *)ERROR_PTR("invalid index", procName, NULL); |
| } |
| |
| if (index <= 7) |
| pixd = pixThinGeneral(pixs, type, sela, maxiters); |
| |
| /* Optionally display the sels */ |
| if (selfile) { |
| pixt = selaDisplayInPix(sela, 35, 3, 15, 4); |
| pixWrite(selfile, pixt, IFF_PNG); |
| pixDestroy(&pixt); |
| } |
| |
| selaDestroy(&sela); |
| return pixd; |
| } |
| |
| |