1 /*====================================================================*
2  -  Copyright (C) 2001 Leptonica.  All rights reserved.
3  -
4  -  Redistribution and use in source and binary forms, with or without
5  -  modification, are permitted provided that the following conditions
6  -  are met:
7  -  1. Redistributions of source code must retain the above copyright
8  -     notice, this list of conditions and the following disclaimer.
9  -  2. Redistributions in binary form must reproduce the above
10  -     copyright notice, this list of conditions and the following
11  -     disclaimer in the documentation and/or other materials
12  -     provided with the distribution.
13  -
14  -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18  -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
27 
28 /*!
29  * \file strokes.c
30  * <pre>
31  *
32  *      Operations on 1 bpp images to:
33  *      (1) measure stroke parameters, such as length and average width
34  *      (2) change the average stroke width to a given value by eroding
35  *          or dilating the image.
36  *
37  *      These operations are intended to operate on a single text
38  *      character, to regularize the stroke width. It is expected
39  *      that character matching by correlation, as used in the recog
40  *      application, can often be improved by pre-processing both
41  *      template and character images to a fixed stroke width.
42  *
43  *      Stroke parameter measurement
44  *            l_int32      pixFindStrokeLength()
45  *            l_int32      pixFindStrokeWidth()
46  *            NUMA        *pixaFindStrokeWidth()
47  *
48  *      Stroke width regulation
49  *            PIXA        *pixaModifyStrokeWidth()
50  *            PIX         *pixModifyStrokeWidth()
51  *            PIXA        *pixaSetStrokeWidth()
52  *            PIX         *pixSetStrokeWidth()
53  * </pre>
54  */
55 
56 #include "allheaders.h"
57 
58 /*-----------------------------------------------------------------*
59  *                   Stroke parameter measurement                  *
60  *-----------------------------------------------------------------*/
61 /*!
62  * \brief   pixFindStrokeLength()
63  *
64  * \param[in]    pixs 1 bpp
65  * \param[in]    tab8  [optional] table for counting fg pixels; can be NULL
66  * \param[out]  *plength  estimated length of the strokes
67  * \return  0 if OK, 1 on error
68  *
69  * <pre>
70  * Notes:
71  *      (1) Returns half the number of fg boundary pixels.
72  * </pre>
73  */
74 l_int32
pixFindStrokeLength(PIX * pixs,l_int32 * tab8,l_int32 * plength)75 pixFindStrokeLength(PIX      *pixs,
76                     l_int32  *tab8,
77                     l_int32  *plength)
78 {
79 l_int32   n;
80 l_int32  *tab;
81 PIX      *pix1;
82 
83     PROCNAME("pixFindStrokeLength");
84 
85     if (!plength)
86         return ERROR_INT("&length not defined", procName, 1);
87     *plength = 0;
88     if (!pixs)
89         return ERROR_INT("pixs not defined", procName, 1);
90 
91     pix1 = pixExtractBoundary(pixs, 1);
92     tab = (tab8) ? tab8 : makePixelSumTab8();
93     pixCountPixels(pix1, &n, tab);
94     *plength = n / 2;
95     if (!tab8) LEPT_FREE(tab);
96     pixDestroy(&pix1);
97     return 0;
98 }
99 
100 
101 /*!
102  * \brief   pixFindStrokeWidth()
103  *
104  * \param[in]    pixs 1 bpp
105  * \param[in]    thresh  fractional count threshold relative to distance 1
106  * \param[in]    tab8  [optional] table for counting fg pixels; can be NULL
107  * \param[out]  *pwidth  estimated width of the strokes
108  * \param[out]  *pnahisto  [optional] histo of pixel distances from bg
109  * \return  0 if OK, 1 on error
110  *
111  * <pre>
112  * Notes:
113  *      (1) This uses two methods to estimate the stroke width:
114  *          (a) half the fg boundary length
115  *          (b) a value derived from the histogram of the fg distance transform
116  *      (2) Distance is measured in 8-connected
117  *      (3) %thresh is the minimum fraction N(dist=d)/N(dist=1) of pixels
118  *          required to determine if the pixels at distance d are above
119  *          the noise. It is typically about 0.15.
120  * </pre>
121  */
122 l_int32
pixFindStrokeWidth(PIX * pixs,l_float32 thresh,l_int32 * tab8,l_float32 * pwidth,NUMA ** pnahisto)123 pixFindStrokeWidth(PIX        *pixs,
124                    l_float32   thresh,
125                    l_int32    *tab8,
126                    l_float32  *pwidth,
127                    NUMA      **pnahisto)
128 {
129 l_int32     i, n, count, length, first, last;
130 l_int32    *tab;
131 l_float32   width1, width2, ratio, extra;
132 l_float32  *fa;
133 NUMA       *na1, *na2;
134 PIX        *pix1;
135 
136     PROCNAME("pixFindStrokeWidth");
137 
138     if (!pwidth)
139         return ERROR_INT("&width not defined", procName, 1);
140     *pwidth = 0;
141     if (!pixs)
142         return ERROR_INT("pixs not defined", procName, 1);
143 
144     tab = (tab8) ? tab8 : makePixelSumTab8();
145 
146     /* ------- Method 1: via boundary length ------- */
147         /* The computed stroke length is a bit larger than that actual
148          * length, because of the addition of the 'caps' at the
149          * stroke ends.  Therefore the computed width is a bit
150          * smaller than the average width. */
151     pixFindStrokeLength(pixs, tab8, &length);
152     pixCountPixels(pixs, &count, tab8);
153     width1 = (l_float32)count / (l_float32)length;
154 
155     /* ------- Method 2: via distance transform ------- */
156         /* First get the histogram of distances */
157     pix1 = pixDistanceFunction(pixs, 8, 8, L_BOUNDARY_BG);
158     na1 = pixGetGrayHistogram(pix1, 1);
159     pixDestroy(&pix1);
160     numaGetNonzeroRange(na1, 0.1, &first, &last);
161     na2 = numaClipToInterval(na1, 0, last);
162     numaWriteStream(stderr, na2);
163 
164         /* Find the bucket with the largest distance whose contents
165          * exceed the threshold. */
166     fa = numaGetFArray(na2, L_NOCOPY);
167     n = numaGetCount(na2);
168     for (i = n - 1; i > 0; i--) {
169         ratio = fa[i] / fa[1];
170         if (ratio > thresh) break;
171     }
172         /* Let the last skipped bucket contribute to the stop bucket.
173          * This is the 'extra' term below.  The result may be a slight
174          * over-correction, so the computed width may be a bit larger
175          * than the average width. */
176     extra = (i < n - 1) ? fa[i + 1] / fa[1] : 0;
177     width2 = 2.0 * (i - 1.0 + ratio + extra);
178     fprintf(stderr, "width1 = %5.2f, width2 = %5.2f\n", width1, width2);
179 
180         /* Average the two results */
181     *pwidth = (width1 + width2) / 2.0;
182 
183     if (!tab8) LEPT_FREE(tab);
184     numaDestroy(&na1);
185     if (pnahisto)
186         *pnahisto = na2;
187     else
188         numaDestroy(&na2);
189     return 0;
190 }
191 
192 
193 /*!
194  * \brief   pixaFindStrokeWidth()
195  *
196  * \param[in]    pixa  of 1 bpp images
197  * \param[in]    thresh  fractional count threshold relative to distance 1
198  * \param[in]    tab8  [optional] table for counting fg pixels; can be NULL
199  * \param[in]    debug  1 for debug output; 0 to skip
200  * \return  na  array of stroke widths for each pix in %pixa; NULL on error
201  *
202  * <pre>
203  * Notes:
204  *      (1) See pixFindStrokeWidth() for details.
205  * </pre>
206  */
207 NUMA *
pixaFindStrokeWidth(PIXA * pixa,l_float32 thresh,l_int32 * tab8,l_int32 debug)208 pixaFindStrokeWidth(PIXA     *pixa,
209                    l_float32  thresh,
210                    l_int32   *tab8,
211                    l_int32    debug)
212 {
213 l_int32    i, n, same, maxd;
214 l_int32   *tab;
215 l_float32  width;
216 NUMA      *na;
217 PIX       *pix;
218 
219     PROCNAME("pixaFindStrokeWidth");
220 
221     if (!pixa)
222         return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
223     pixaVerifyDepth(pixa, &same, &maxd);
224     if (maxd > 1)
225         return (NUMA *)ERROR_PTR("pix not all 1 bpp", procName, NULL);
226 
227     tab = (tab8) ? tab8 : makePixelSumTab8();
228 
229     n = pixaGetCount(pixa);
230     na = numaCreate(n);
231     for (i = 0; i < n; i++) {
232         pix = pixaGetPix(pixa, i, L_CLONE);
233         pixFindStrokeWidth(pix, thresh, tab8, &width, NULL);
234         numaAddNumber(na, width);
235         pixDestroy(&pix);
236     }
237 
238     if (!tab8) LEPT_FREE(tab);
239     return na;
240 }
241 
242 
243 /*-----------------------------------------------------------------*
244  *                       Change stroke width                       *
245  *-----------------------------------------------------------------*/
246 /*!
247  * \brief   pixaModifyStrokeWidth()
248  *
249  * \param[in]     pixas  of 1 bpp pix
250  * \param[out]    targetw  desired width for strokes in each pix
251  * \return  pixa  with modified stroke widths, or NULL on error
252  */
253 PIXA *
pixaModifyStrokeWidth(PIXA * pixas,l_float32 targetw)254 pixaModifyStrokeWidth(PIXA      *pixas,
255                       l_float32  targetw)
256 {
257 l_int32    i, n, same, maxd;
258 l_float32  width;
259 NUMA      *na;
260 PIX       *pix1, *pix2;
261 PIXA      *pixad;
262 
263     PROCNAME("pixaModifyStrokeWidth");
264 
265     if (!pixas)
266         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
267     if (targetw < 1)
268         return (PIXA *)ERROR_PTR("target width < 1", procName, NULL);
269     pixaVerifyDepth(pixas, &same, &maxd);
270     if (maxd > 1)
271         return (PIXA *)ERROR_PTR("pix not all 1 bpp", procName, NULL);
272 
273     na = pixaFindStrokeWidth(pixas, 0.1, NULL, 0);
274     n = pixaGetCount(pixas);
275     pixad = pixaCreate(n);
276     for (i = 0; i < n; i++) {
277         pix1 = pixaGetPix(pixas, i, L_CLONE);
278         numaGetFValue(na, i, &width);
279         pix2 = pixModifyStrokeWidth(pix1, width, targetw);
280         pixaAddPix(pixad, pix2, L_INSERT);
281         pixDestroy(&pix1);
282     }
283 
284     numaDestroy(&na);
285     return pixad;
286 }
287 
288 
289 /*!
290  * \brief   pixModifyStrokeWidth()
291  *
292  * \param[in]   pixa  of 1 bpp pix
293  * \param[in]   width  measured average stroke width
294  * \param[in]   targetw  desired stroke width
295  * \return  pix  with modified stroke width, or NULL on error
296  */
297 PIX *
pixModifyStrokeWidth(PIX * pixs,l_float32 width,l_float32 targetw)298 pixModifyStrokeWidth(PIX       *pixs,
299                      l_float32  width,
300                      l_float32  targetw)
301 {
302 char     buf[16];
303 l_int32  diff, size;
304 
305     PROCNAME("pixModifyStrokeWidth");
306 
307     if (!pixs || (pixGetDepth(pixs) != 1))
308         return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
309     if (targetw < 1)
310         return (PIX *)ERROR_PTR("target width < 1", procName, NULL);
311 
312     diff = lept_roundftoi(targetw - width);
313     if (diff == 0) return pixCopy(NULL, pixs);
314 
315     size = L_ABS(diff) + 1;
316     if (diff < 0)  /* erode */
317         snprintf(buf, sizeof(buf), "e%d.%d", size, size);
318     else  /* diff > 0; dilate */
319         snprintf(buf, sizeof(buf), "d%d.%d", size, size);
320     return pixMorphSequence(pixs, buf, 0);
321 }
322 
323 
324 /*!
325  * \brief   pixaSetStrokeWidth()
326  *
327  * \param[in]   pixas  of 1 bpp pix
328  * \param[in]   width  set stroke width to this value, in [1 ... 100].
329  * \param[in]   thinfirst  1 to thin all pix to a skeleton first; 0 to skip
330  * \param[in]   connectivity  4 or 8, to be used if %thinfirst == 1
331  * \return  pixa  with all stroke widths being %width, or NULL on error
332  *
333  * <pre>
334  * Notes:
335  *      (1) If %thinfirst == 1, thin to a skeleton using the specified
336  *          %connectivity.  Use %thinfirst == 0 if all pix in pixas
337  *          have already been thinned as far as possible.
338  *      (2) The image is dilated to the required %width.  This dilation
339  *          is not connectivity preserving, so this is typically
340  *          used in a situation where merging of c.c. in the individual
341  *          pix is not a problem; e.g., where each pix is a single c.c.
342  * </pre>
343  */
344 PIXA *
pixaSetStrokeWidth(PIXA * pixas,l_int32 width,l_int32 thinfirst,l_int32 connectivity)345 pixaSetStrokeWidth(PIXA    *pixas,
346                    l_int32  width,
347                    l_int32  thinfirst,
348                    l_int32  connectivity)
349 {
350 l_int32  i, n, maxd, same;
351 PIX     *pix1, *pix2;
352 PIXA    *pixad;
353 
354     PROCNAME("pixaSetStrokeWidth");
355 
356     if (!pixas)
357         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
358     if (width < 1 || width > 100)
359         return (PIXA *)ERROR_PTR("width not in [1 ... 100]", procName, NULL);
360     if (connectivity != 4 && connectivity != 8)
361         return (PIXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
362     pixaVerifyDepth(pixas, &same, &maxd);
363     if (maxd > 1)
364         return (PIXA *)ERROR_PTR("pix are not all 1 bpp", procName, NULL);
365 
366     n = pixaGetCount(pixas);
367     pixad = pixaCreate(n);
368     for (i = 0; i < n; i++) {
369         pix1 = pixaGetPix(pixas, i, L_CLONE);
370         pix2 = pixSetStrokeWidth(pix1, width, thinfirst, connectivity);
371         pixaAddPix(pixad, pix2, L_INSERT);
372         pixDestroy(&pix1);
373     }
374 
375     return pixad;
376 }
377 
378 
379 /*!
380  * \brief   pixSetStrokeWidth()
381  *
382  * \param[in]   pixs  1 bpp pix
383  * \param[in]   width  set stroke width to this value, in [1 ... 100].
384  * \param[in]   thinfirst  1 to thin all pix to a skeleton first; 0 to skip
385  * \param[in]   connectivity  4 or 8, to be used if %thinfirst == 1
386  * \return  pixd  with stroke width set to %width, or NULL on error
387  *
388  * <pre>
389  * Notes:
390  *      (1) See notes in pixaSetStrokeWidth().
391  *      (2) A white border of sufficient width to avoid boundary
392  *          artifacts in the thickening step is added before thinning.
393  *      (3) %connectivity == 8 usually gives a slightly smoother result.
394  * </pre>
395  */
396 PIX *
pixSetStrokeWidth(PIX * pixs,l_int32 width,l_int32 thinfirst,l_int32 connectivity)397 pixSetStrokeWidth(PIX     *pixs,
398                   l_int32  width,
399                   l_int32  thinfirst,
400                   l_int32  connectivity)
401 {
402 char     buf[16];
403 l_int32  border;
404 PIX     *pix1, *pix2, *pixd;
405 
406     PROCNAME("pixSetStrokeWidth");
407 
408     if (!pixs || (pixGetDepth(pixs) != 1))
409         return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
410     if (width < 1 || width > 100)
411         return (PIX *)ERROR_PTR("width not in [1 ... 100]", procName, NULL);
412     if (connectivity != 4 && connectivity != 8)
413         return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
414 
415     if (!thinfirst && width == 1)  /* nothing to do */
416         return pixCopy(NULL, pixs);
417 
418         /* Add a white border */
419     border = width / 2;
420     pix1 = pixAddBorder(pixs, border, 0);
421 
422         /* Thin to a skeleton */
423     if (thinfirst)
424         pix2 = pixThinConnected(pix1, L_THIN_FG, connectivity, 0);
425     else
426         pix2 = pixClone(pix1);
427     pixDestroy(&pix1);
428 
429         /* Dilate */
430     snprintf(buf, sizeof(buf), "D%d.%d", width, width);
431     pixd = pixMorphSequence(pix2, buf, 0);
432     pixCopyText(pixd, pixs);
433     pixDestroy(&pix2);
434     return pixd;
435 }
436 
437