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