Leptonica 1.85.0
Image processing and image analysis suite
Loading...
Searching...
No Matches
binarize.c
Go to the documentation of this file.
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
87#ifdef HAVE_CONFIG_H
88#include <config_auto.h>
89#endif /* HAVE_CONFIG_H */
90
91#include <math.h>
92#include "allheaders.h"
93
94static PIX *pixSauvolaGetThreshold(PIX *pixm, PIX *pixms, l_float32 factor,
95 PIX **ppixsd);
96static PIX *pixApplyLocalThreshold(PIX *pixs, PIX *pixth);
97
98/*------------------------------------------------------------------*
99 * Adaptive Otsu-based thresholding *
100 *------------------------------------------------------------------*/
156l_ok
158 l_int32 sx,
159 l_int32 sy,
160 l_int32 smoothx,
161 l_int32 smoothy,
162 l_float32 scorefract,
163 PIX **ppixth,
164 PIX **ppixd)
165{
166l_int32 w, h, nx, ny, i, j, thresh;
167l_uint32 val;
168PIX *pixt, *pixb, *pixthresh, *pixth, *pixd;
169PIXTILING *pt;
170
171 if (!ppixth && !ppixd)
172 return ERROR_INT("neither &pixth nor &pixd defined", __func__, 1);
173 if (ppixth) *ppixth = NULL;
174 if (ppixd) *ppixd = NULL;
175 if (!pixs || pixGetDepth(pixs) != 8)
176 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
177 if (sx < 16 || sy < 16)
178 return ERROR_INT("sx and sy must be >= 16", __func__, 1);
179
180 /* Compute the threshold array for the tiles */
181 pixGetDimensions(pixs, &w, &h, NULL);
182 nx = L_MAX(1, w / sx);
183 ny = L_MAX(1, h / sy);
184 smoothx = L_MIN(smoothx, (nx - 1) / 2);
185 smoothy = L_MIN(smoothy, (ny - 1) / 2);
186 pt = pixTilingCreate(pixs, nx, ny, 0, 0, 0, 0);
187 pixthresh = pixCreate(nx, ny, 8);
188 for (i = 0; i < ny; i++) {
189 for (j = 0; j < nx; j++) {
190 pixt = pixTilingGetTile(pt, i, j);
191 pixSplitDistributionFgBg(pixt, scorefract, 1, &thresh,
192 NULL, NULL, NULL);
193 pixSetPixel(pixthresh, j, i, thresh); /* see note (4) */
194 pixDestroy(&pixt);
195 }
196 }
197
198 /* Optionally smooth the threshold array */
199 if (smoothx > 0 || smoothy > 0)
200 pixth = pixBlockconv(pixthresh, smoothx, smoothy);
201 else
202 pixth = pixClone(pixthresh);
203 pixDestroy(&pixthresh);
204
205 /* Optionally apply the threshold array to binarize pixs */
206 if (ppixd) {
207 pixd = pixCreate(w, h, 1);
208 pixCopyResolution(pixd, pixs);
209 for (i = 0; i < ny; i++) {
210 for (j = 0; j < nx; j++) {
211 pixt = pixTilingGetTile(pt, i, j);
212 pixGetPixel(pixth, j, i, &val);
213 pixb = pixThresholdToBinary(pixt, val);
214 pixTilingPaintTile(pixd, i, j, pixb, pt);
215 pixDestroy(&pixt);
216 pixDestroy(&pixb);
217 }
218 }
219 *ppixd = pixd;
220 }
221
222 if (ppixth)
223 *ppixth = pixth;
224 else
225 pixDestroy(&pixth);
226
227 pixTilingDestroy(&pt);
228 return 0;
229}
230
231
232/*------------------------------------------------------------------*
233 * Otsu thresholding on adaptive background normalization *
234 *------------------------------------------------------------------*/
270PIX *
272 PIX *pixim,
273 l_int32 sx,
274 l_int32 sy,
275 l_int32 thresh,
276 l_int32 mincount,
277 l_int32 bgval,
278 l_int32 smoothx,
279 l_int32 smoothy,
280 l_float32 scorefract,
281 l_int32 *pthresh)
282{
283l_int32 w, h;
284l_uint32 val;
285PIX *pixn, *pixt, *pixd;
286
287 if (pthresh) *pthresh = 0;
288 if (!pixs || pixGetDepth(pixs) != 8)
289 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
290 if (pixGetColormap(pixs))
291 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, NULL);
292 if (sx < 4 || sy < 4)
293 return (PIX *)ERROR_PTR("sx and sy must be >= 4", __func__, NULL);
294 if (mincount > sx * sy) {
295 L_WARNING("mincount too large for tile size\n", __func__);
296 mincount = (sx * sy) / 3;
297 }
298
299 pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh,
300 mincount, bgval, smoothx, smoothy);
301 if (!pixn)
302 return (PIX *)ERROR_PTR("pixn not made", __func__, NULL);
303
304 /* Just use 1 tile for a global threshold, which is stored
305 * as a single pixel in pixt. */
306 pixGetDimensions(pixn, &w, &h, NULL);
307 pixOtsuAdaptiveThreshold(pixn, w, h, 0, 0, scorefract, &pixt, &pixd);
308 pixDestroy(&pixn);
309
310 if (pixt && pthresh) {
311 pixGetPixel(pixt, 0, 0, &val);
312 *pthresh = val;
313 }
314 pixDestroy(&pixt);
315
316 if (!pixd)
317 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
318 else
319 return pixd;
320}
321
322
323
324/*----------------------------------------------------------------------*
325 * Masking and Otsu estimate on adaptive background normalization *
326 *----------------------------------------------------------------------*/
366PIX *
368 PIX *pixim,
369 l_int32 sx,
370 l_int32 sy,
371 l_int32 thresh,
372 l_int32 mincount,
373 l_int32 smoothx,
374 l_int32 smoothy,
375 l_float32 scorefract,
376 l_int32 *pthresh)
377{
378l_int32 w, h, highthresh;
379l_uint32 val;
380PIX *pixn, *pixm, *pixd, *pix1, *pix2, *pix3, *pix4;
381
382 if (pthresh) *pthresh = 0;
383 if (!pixs || pixGetDepth(pixs) != 8)
384 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
385 if (pixGetColormap(pixs))
386 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, NULL);
387 if (sx < 4 || sy < 4)
388 return (PIX *)ERROR_PTR("sx and sy must be >= 4", __func__, NULL);
389 if (mincount > sx * sy) {
390 L_WARNING("mincount too large for tile size\n", __func__);
391 mincount = (sx * sy) / 3;
392 }
393
394 /* Standard background normalization */
395 pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh,
396 mincount, 255, smoothx, smoothy);
397 if (!pixn)
398 return (PIX *)ERROR_PTR("pixn not made", __func__, NULL);
399
400 /* Special background normalization for adaptation to quickly
401 * varying background. Threshold on the very light parts,
402 * which tend to be near significant edges, and dilate to
403 * form a mask over regions that are typically text. The
404 * dilation size is chosen to cover the text completely,
405 * except for very thick fonts. */
406 pix1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 20);
407 pix2 = pixThresholdToBinary(pix1, 240);
408 pixInvert(pix2, pix2);
409 pixm = pixMorphSequence(pix2, "d21.21", 0);
410 pixDestroy(&pix1);
411 pixDestroy(&pix2);
412
413 /* Use Otsu to get a global threshold estimate for the image,
414 * which is stored as a single pixel in pix3. */
415 pixGetDimensions(pixs, &w, &h, NULL);
416 pixOtsuAdaptiveThreshold(pixs, w, h, 0, 0, scorefract, &pix3, NULL);
417 pixGetPixel(pix3, 0, 0, &val);
418 if (pthresh) *pthresh = val;
419 pixDestroy(&pix3);
420
421 /* Threshold the background normalized images differentially,
422 * using a high value correlated with the background normalization
423 * for the part of the image under the mask (i.e., near the
424 * darker, thicker foreground), and a value that depends on the Otsu
425 * threshold for the rest of the image. This gives a solid
426 * (high) thresholding for the foreground parts of the image,
427 * while allowing the background and light foreground to be
428 * reasonably well cleaned using a threshold adapted to the
429 * input image. */
430 highthresh = L_MIN(256, val + 30);
431 pixd = pixThresholdToBinary(pixn, highthresh); /* for bg and light fg */
432 pix4 = pixThresholdToBinary(pixn, 190); /* for heavier fg */
433 pixCombineMasked(pixd, pix4, pixm);
434 pixDestroy(&pix4);
435 pixDestroy(&pixm);
436 pixDestroy(&pixn);
437
438 if (!pixd)
439 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
440 else
441 return pixd;
442}
443
444
445/*----------------------------------------------------------------------*
446 * Sauvola binarization *
447 *----------------------------------------------------------------------*/
477l_ok
479 l_int32 whsize,
480 l_float32 factor,
481 l_int32 nx,
482 l_int32 ny,
483 PIX **ppixth,
484 PIX **ppixd)
485{
486l_int32 i, j, w, h, xrat, yrat;
487PIX *pixth = NULL, *pixd = NULL, *tileth = NULL, *tiled = NULL, *pixt;
488PIX **ptileth, **ptiled;
489PIXTILING *pt;
490
491 if (!ppixth && !ppixd)
492 return ERROR_INT("no outputs", __func__, 1);
493 if (ppixth) *ppixth = NULL;
494 if (ppixd) *ppixd = NULL;
495 if (!pixs || pixGetDepth(pixs) != 8)
496 return ERROR_INT("pixs undefined or not 8 bpp", __func__, 1);
497 if (pixGetColormap(pixs))
498 return ERROR_INT("pixs is cmapped", __func__, 1);
499 pixGetDimensions(pixs, &w, &h, NULL);
500 if (whsize < 2)
501 return ERROR_INT("whsize must be >= 2", __func__, 1);
502 if (w < 2 * whsize + 3 || h < 2 * whsize + 3)
503 return ERROR_INT("whsize too large for image", __func__, 1);
504 if (factor < 0.0)
505 return ERROR_INT("factor must be >= 0", __func__, 1);
506
507 if (nx <= 1 && ny <= 1)
508 return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL,
509 ppixth, ppixd);
510
511 /* Test to see if the tiles are too small. The required
512 * condition is that the tile dimensions must be at least
513 * (whsize + 2) x (whsize + 2). */
514 xrat = w / nx;
515 yrat = h / ny;
516 if (xrat < whsize + 2) {
517 nx = w / (whsize + 2);
518 L_WARNING("tile width too small; nx reduced to %d\n", __func__, nx);
519 }
520 if (yrat < whsize + 2) {
521 ny = h / (whsize + 2);
522 L_WARNING("tile height too small; ny reduced to %d\n", __func__, ny);
523 }
524 if (nx <= 1 && ny <= 1)
525 return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL,
526 ppixth, ppixd);
527
528 /* We can use pixtiling for painting both outputs, if requested */
529 if (ppixth) {
530 pixth = pixCreate(w, h, 8);
531 *ppixth = pixth;
532 }
533 if (ppixd) {
534 pixd = pixCreate(w, h, 1);
535 *ppixd = pixd;
536 }
537 pt = pixTilingCreate(pixs, nx, ny, 0, 0, whsize + 1, whsize + 1);
538 pixTilingNoStripOnPaint(pt); /* pixSauvolaBinarize() does the stripping */
539
540 for (i = 0; i < ny; i++) {
541 for (j = 0; j < nx; j++) {
542 pixt = pixTilingGetTile(pt, i, j);
543 ptileth = (ppixth) ? &tileth : NULL;
544 ptiled = (ppixd) ? &tiled : NULL;
545 pixSauvolaBinarize(pixt, whsize, factor, 0, NULL, NULL,
546 ptileth, ptiled);
547 if (ppixth) { /* do not strip */
548 pixTilingPaintTile(pixth, i, j, tileth, pt);
549 pixDestroy(&tileth);
550 }
551 if (ppixd) {
552 pixTilingPaintTile(pixd, i, j, tiled, pt);
553 pixDestroy(&tiled);
554 }
555 pixDestroy(&pixt);
556 }
557 }
558
559 pixTilingDestroy(&pt);
560 return 0;
561}
562
563
602l_ok
604 l_int32 whsize,
605 l_float32 factor,
606 l_int32 addborder,
607 PIX **ppixm,
608 PIX **ppixsd,
609 PIX **ppixth,
610 PIX **ppixd)
611{
612l_int32 w, h;
613PIX *pixg, *pixsc, *pixm = NULL, *pixms = NULL, *pixth = NULL, *pixd = NULL;
614
615 if (ppixm) *ppixm = NULL;
616 if (ppixsd) *ppixsd = NULL;
617 if (ppixth) *ppixth = NULL;
618 if (ppixd) *ppixd = NULL;
619 if (!ppixm && !ppixsd && !ppixth && !ppixd)
620 return ERROR_INT("no outputs", __func__, 1);
621 if (!pixs || pixGetDepth(pixs) != 8)
622 return ERROR_INT("pixs undefined or not 8 bpp", __func__, 1);
623 if (pixGetColormap(pixs))
624 return ERROR_INT("pixs is cmapped", __func__, 1);
625 pixGetDimensions(pixs, &w, &h, NULL);
626 if (whsize < 2)
627 return ERROR_INT("whsize must be >= 2", __func__, 1);
628 if (w < 2 * whsize + 3 || h < 2 * whsize + 3)
629 return ERROR_INT("whsize too large for image", __func__, 1);
630 if (factor < 0.0)
631 return ERROR_INT("factor must be >= 0", __func__, 1);
632
633 if (addborder) {
634 pixg = pixAddMirroredBorder(pixs, whsize + 1, whsize + 1,
635 whsize + 1, whsize + 1);
636 pixsc = pixClone(pixs);
637 } else {
638 pixg = pixClone(pixs);
639 pixsc = pixRemoveBorder(pixs, whsize + 1);
640 }
641 if (!pixg || !pixsc)
642 return ERROR_INT("pixg and pixsc not made", __func__, 1);
643
644 /* All these functions strip off the border pixels. */
645 if (ppixm || ppixth || ppixd)
646 pixm = pixWindowedMean(pixg, whsize, whsize, 1, 1);
647 if (ppixsd || ppixth || ppixd)
648 pixms = pixWindowedMeanSquare(pixg, whsize, whsize, 1);
649 if (ppixth || ppixd)
650 pixth = pixSauvolaGetThreshold(pixm, pixms, factor, ppixsd);
651 if (ppixd) {
652 pixd = pixApplyLocalThreshold(pixsc, pixth);
653 pixCopyResolution(pixd, pixs);
654 }
655
656 if (ppixm)
657 *ppixm = pixm;
658 else
659 pixDestroy(&pixm);
660 pixDestroy(&pixms);
661 if (ppixth)
662 *ppixth = pixth;
663 else
664 pixDestroy(&pixth);
665 if (ppixd)
666 *ppixd = pixd;
667 pixDestroy(&pixg);
668 pixDestroy(&pixsc);
669 return 0;
670}
671
672
710static PIX *
712 PIX *pixms,
713 l_float32 factor,
714 PIX **ppixsd)
715{
716l_int32 i, j, w, h, tabsize, wplm, wplms, wplsd, wpld, usetab;
717l_int32 mv, ms, var, thresh;
718l_uint32 *datam, *datams, *datasd = NULL, *datad;
719l_uint32 *linem, *linems, *linesd = NULL, *lined;
720l_float32 sd;
721l_float32 *tab = NULL; /* of 2^16 square roots */
722PIX *pixsd = NULL, *pixd = NULL;
723
724 if (ppixsd) *ppixsd = NULL;
725 if (!pixm || pixGetDepth(pixm) != 8)
726 return (PIX *)ERROR_PTR("pixm undefined or not 8 bpp", __func__, NULL);
727 if (pixGetColormap(pixm))
728 return (PIX *)ERROR_PTR("pixm is colormapped", __func__, NULL);
729 if (!pixms || pixGetDepth(pixms) != 32)
730 return (PIX *)ERROR_PTR("pixms undefined or not 32 bpp",
731 __func__, NULL);
732 if (factor < 0.0)
733 return (PIX *)ERROR_PTR("factor must be >= 0", __func__, NULL);
734
735 /* Only make a table of 2^16 square roots if there
736 * are enough pixels to justify it. */
737 pixGetDimensions(pixm, &w, &h, NULL);
738 usetab = (w * h > 100000) ? 1 : 0;
739 if (usetab) {
740 tabsize = 1 << 16;
741 tab = (l_float32 *)LEPT_CALLOC(tabsize, sizeof(l_float32));
742 for (i = 0; i < tabsize; i++)
743 tab[i] = sqrtf((l_float32)i);
744 }
745
746 pixd = pixCreate(w, h, 8);
747 if (ppixsd) {
748 pixsd = pixCreate(w, h, 8);
749 *ppixsd = pixsd;
750 }
751 datam = pixGetData(pixm);
752 datams = pixGetData(pixms);
753 if (ppixsd) datasd = pixGetData(pixsd);
754 datad = pixGetData(pixd);
755 wplm = pixGetWpl(pixm);
756 wplms = pixGetWpl(pixms);
757 if (ppixsd) wplsd = pixGetWpl(pixsd);
758 wpld = pixGetWpl(pixd);
759 for (i = 0; i < h; i++) {
760 linem = datam + i * wplm;
761 linems = datams + i * wplms;
762 if (ppixsd) linesd = datasd + i * wplsd;
763 lined = datad + i * wpld;
764 for (j = 0; j < w; j++) {
765 mv = GET_DATA_BYTE(linem, j);
766 ms = linems[j];
767 var = ms - mv * mv;
768 if (usetab)
769 sd = tab[var];
770 else
771 sd = sqrtf((l_float32)var);
772 if (ppixsd) SET_DATA_BYTE(linesd, j, (l_int32)sd);
773 thresh = (l_int32)(mv * (1.0 - factor * (1.0 - sd / 128.)));
774 SET_DATA_BYTE(lined, j, thresh);
775 }
776 }
777
778 if (usetab) LEPT_FREE(tab);
779 return pixd;
780}
781
782
790static PIX *
792 PIX *pixth)
793{
794l_int32 i, j, w, h, wpls, wplt, wpld, vals, valt;
795l_uint32 *datas, *datat, *datad, *lines, *linet, *lined;
796PIX *pixd;
797
798 if (!pixs || pixGetDepth(pixs) != 8)
799 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
800 if (pixGetColormap(pixs))
801 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, NULL);
802 if (!pixth || pixGetDepth(pixth) != 8)
803 return (PIX *)ERROR_PTR("pixth undefined or not 8 bpp", __func__, NULL);
804
805 pixGetDimensions(pixs, &w, &h, NULL);
806 pixd = pixCreate(w, h, 1);
807 datas = pixGetData(pixs);
808 datat = pixGetData(pixth);
809 datad = pixGetData(pixd);
810 wpls = pixGetWpl(pixs);
811 wplt = pixGetWpl(pixth);
812 wpld = pixGetWpl(pixd);
813 for (i = 0; i < h; i++) {
814 lines = datas + i * wpls;
815 linet = datat + i * wplt;
816 lined = datad + i * wpld;
817 for (j = 0; j < w; j++) {
818 vals = GET_DATA_BYTE(lines, j);
819 valt = GET_DATA_BYTE(linet, j);
820 if (vals < valt)
821 SET_DATA_BIT(lined, j);
822 }
823 }
824
825 return pixd;
826}
827
828
829/*----------------------------------------------------------------------*
830 * Contrast normalization followed by Sauvola binarization *
831 *----------------------------------------------------------------------*/
849PIX *
851 l_int32 mindiff,
852 PIX **ppixn,
853 PIX **ppixth)
854{
855l_int32 w, h, d, nx, ny;
856PIX *pixg, *pix1, *pixd;
857
858 if (ppixn) *ppixn = NULL;
859 if (ppixth) *ppixth = NULL;
860 if (!pixs || (d = pixGetDepth(pixs)) < 8)
861 return (PIX *)ERROR_PTR("pixs undefined or d < 8 bpp", __func__, NULL);
862 if (d == 32)
863 pixg = pixConvertRGBToGray(pixs, 0.3f, 0.4f, 0.3f);
864 else
865 pixg = pixConvertTo8(pixs, 0);
866
867 pix1 = pixContrastNorm(NULL, pixg, 50, 50, mindiff, 2, 2);
868
869 /* Use tiles of size approximately 250 x 250 */
870 pixGetDimensions(pix1, &w, &h, NULL);
871 nx = L_MAX(1, (w + 125) / 250);
872 ny = L_MAX(1, (h + 125) / 250);
873 pixSauvolaBinarizeTiled(pix1, 25, 0.40f, nx, ny, ppixth, &pixd);
874 pixDestroy(&pixg);
875 if (ppixn)
876 *ppixn = pix1;
877 else
878 pixDestroy(&pix1);
879 return pixd;
880}
881
882
883/*----------------------------------------------------------------------*
884 * Contrast normalization followed by background normalization *
885 * and thresholding *
886 *----------------------------------------------------------------------*/
903PIX *
905 l_int32 mindiff)
906{
907l_int32 d, ival;
908l_uint32 val;
909PIX *pixg, *pix1, *pixd;
910
911 if (!pixs || (d = pixGetDepth(pixs)) < 8)
912 return (PIX *)ERROR_PTR("pixs undefined or d < 8 bpp", __func__, NULL);
913 if (d == 32)
914 pixg = pixConvertRGBToGray(pixs, 0.3f, 0.4f, 0.3f);
915 else
916 pixg = pixConvertTo8(pixs, 0);
917
918 /* Use the entire image for the estimate; pix1 is 1x1 */
919 pixOtsuAdaptiveThreshold(pixg, 5000, 5000, 0, 0, 0.1f, &pix1, NULL);
920 pixGetPixel(pix1, 0, 0, &val);
921 ival = (l_int32)val;
922 ival = L_MIN(ival, 110);
923 pixDestroy(&pix1);
924
925 /* Double normalization */
926 pixContrastNorm(pixg, pixg, 50, 50, mindiff, 2, 2);
927 pix1 = pixBackgroundNormSimple(pixg, NULL, NULL);
928 pixDestroy(&pixg);
929
930/* lept_stderr("ival = %d\n", ival); */
931 pixd = pixThresholdToBinary(pix1, ival);
932 pixDestroy(&pix1);
933 return pixd;
934}
935
936
937/*----------------------------------------------------------------------*
938 * Global thresholding using connected components *
939 *----------------------------------------------------------------------*/
994l_ok
996 PIX *pixm,
997 l_int32 start,
998 l_int32 end,
999 l_int32 incr,
1000 l_float32 thresh48,
1001 l_float32 threshdiff,
1002 l_int32 *pglobthresh,
1003 PIX **ppixd,
1004 l_int32 debugflag)
1005{
1006l_int32 i, thresh, n, n4, n8, mincounts, found, globthresh;
1007l_float32 count4, count8, firstcount4, prevcount4, diff48, diff4;
1008GPLOT *gplot;
1009NUMA *na4, *na8;
1010PIX *pix1, *pix2, *pix3;
1011
1012 if (pglobthresh) *pglobthresh = 0;
1013 if (ppixd) *ppixd = NULL;
1014 if (!pixs || pixGetDepth(pixs) == 1)
1015 return ERROR_INT("pixs undefined or 1 bpp", __func__, 1);
1016 if (pixm && pixGetDepth(pixm) != 1)
1017 return ERROR_INT("pixm must be 1 bpp", __func__, 1);
1018
1019 /* Assign default values if requested */
1020 if (start <= 0) start = 80;
1021 if (end <= 0) end = 200;
1022 if (incr <= 0) incr = 10;
1023 if (thresh48 <= 0.0) thresh48 = 0.01f;
1024 if (threshdiff <= 0.0) threshdiff = 0.01f;
1025 if (start > end)
1026 return ERROR_INT("invalid start,end", __func__, 1);
1027
1028 /* Make 8 bpp, using the max component if color. */
1029 if (pixGetColormap(pixs))
1030 pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
1031 else
1032 pix1 = pixClone(pixs);
1033 if (pixGetDepth(pix1) == 32)
1034 pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX);
1035 else
1036 pix2 = pixConvertTo8(pix1, 0);
1037 pixDestroy(&pix1);
1038
1039 /* Mask out any non-text regions. Do this in-place, because pix2
1040 * can never be the same pix as pixs. */
1041 if (pixm)
1042 pixSetMasked(pix2, pixm, 255);
1043
1044 /* Make sure there are enough components to get a valid signal */
1045 pix3 = pixConvertTo1(pix2, start);
1046 pixCountConnComp(pix3, 4, &n4);
1047 pixDestroy(&pix3);
1048 mincounts = 500;
1049 if (n4 < mincounts) {
1050 L_INFO("Insufficient component count: %d\n", __func__, n4);
1051 pixDestroy(&pix2);
1052 return 1;
1053 }
1054
1055 /* Compute the c.c. data */
1056 na4 = numaCreate(0);
1057 na8 = numaCreate(0);
1058 numaSetParameters(na4, start, incr);
1059 numaSetParameters(na8, start, incr);
1060 for (thresh = start, i = 0; thresh <= end; thresh += incr, i++) {
1061 pix3 = pixConvertTo1(pix2, thresh);
1062 pixCountConnComp(pix3, 4, &n4);
1063 pixCountConnComp(pix3, 8, &n8);
1064 numaAddNumber(na4, n4);
1065 numaAddNumber(na8, n8);
1066 pixDestroy(&pix3);
1067 }
1068 if (debugflag) {
1069 lept_mkdir("lept/binarize");
1070 gplot = gplotCreate("/tmp/lept/binarize", GPLOT_PNG,
1071 "number of cc vs. threshold",
1072 "threshold", "number of cc");
1073 gplotAddPlot(gplot, NULL, na4, GPLOT_LINES, "plot 4cc");
1074 gplotAddPlot(gplot, NULL, na8, GPLOT_LINES, "plot 8cc");
1075 gplotMakeOutput(gplot);
1076 gplotDestroy(&gplot);
1077 }
1078
1079 n = numaGetCount(na4);
1080 found = FALSE;
1081 for (i = 0; i < n; i++) {
1082 if (i == 0) {
1083 numaGetFValue(na4, i, &firstcount4);
1084 prevcount4 = firstcount4;
1085 } else {
1086 numaGetFValue(na4, i, &count4);
1087 numaGetFValue(na8, i, &count8);
1088 diff48 = (count4 - count8) / firstcount4;
1089 diff4 = L_ABS(prevcount4 - count4) / firstcount4;
1090 if (debugflag) {
1091 lept_stderr("diff48 = %7.3f, diff4 = %7.3f\n",
1092 diff48, diff4);
1093 }
1094 if (diff48 < thresh48 && diff4 < threshdiff) {
1095 found = TRUE;
1096 break;
1097 }
1098 prevcount4 = count4;
1099 }
1100 }
1101 numaDestroy(&na4);
1102 numaDestroy(&na8);
1103
1104 if (found) {
1105 globthresh = start + i * incr;
1106 if (pglobthresh) *pglobthresh = globthresh;
1107 if (ppixd) {
1108 *ppixd = pixConvertTo1(pix2, globthresh);
1109 pixCopyResolution(*ppixd, pixs);
1110 }
1111 if (debugflag) lept_stderr("global threshold = %d\n", globthresh);
1112 pixDestroy(&pix2);
1113 return 0;
1114 }
1115
1116 if (debugflag) lept_stderr("no global threshold found\n");
1117 pixDestroy(&pix2);
1118 return 1;
1119}
1120
1121/*----------------------------------------------------------------------*
1122 * Global thresholding by histogram *
1123 *----------------------------------------------------------------------*/
1150l_ok
1152 l_int32 factor,
1153 l_int32 halfw,
1154 l_int32 skip,
1155 l_int32 *pthresh,
1156 PIX **ppixd,
1157 NUMA **pnahisto,
1158 PIX **ppixhisto)
1159{
1160l_float32 maxval, fract;
1161NUMA *na1, *na2, *na3;
1162
1163 if (ppixd) *ppixd = NULL;
1164 if (pnahisto) *pnahisto = NULL;
1165 if (ppixhisto) *ppixhisto = NULL;
1166 if (!pthresh)
1167 return ERROR_INT("&thresh not defined", __func__, 1);
1168 *pthresh = 0;
1169 if (!pixs || pixGetDepth(pixs) != 8)
1170 return ERROR_INT("pixs undefined or not 8 bpp", __func__, 1);
1171 if (pixGetColormap(pixs))
1172 return ERROR_INT("pixs has colormap", __func__, 1);
1173 if (factor < 1)
1174 return ERROR_INT("sampling must be >= 1", __func__, 1);
1175 if (halfw <= 0) halfw = 20;
1176 if (skip <= 0) skip = 20;
1177
1178 /* Make a histogram of pixel values where the largest peak
1179 * is normalized to a value of 1.0. */
1180 na1 = pixGetGrayHistogram(pixs, factor);
1181 na2 = numaWindowedMean(na1, halfw); /* smoothing */
1182 numaGetMax(na2, &maxval, NULL);
1183 na3 = numaTransform(na2, 0.0, 1.0 / maxval); /* rescale to max of 1.0 */
1184 numaDestroy(&na1);
1185 numaDestroy(&na2);
1186
1187 if (numaFindLocForThreshold(na3, skip, pthresh, &fract) == 1) {
1188 numaDestroy(&na3);
1189 return ERROR_INT("failure to find threshold", __func__, 1);
1190 }
1191 L_INFO("fractional area under first peak: %5.3f\n", __func__, fract);
1192
1193 if (ppixhisto) {
1194 lept_mkdir("lept/histo");
1195 gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/histo/histo", NULL);
1196 *ppixhisto = pixRead("/tmp/lept/histo/histo.png");
1197 }
1198 if (pnahisto)
1199 *pnahisto = na3;
1200 else
1201 numaDestroy(&na3);
1202 if (*pthresh > 0 && ppixd)
1203 *ppixd = pixThresholdToBinary(pixs, *pthresh);
1204 return 0;
1205}
1206
PIX * pixBackgroundNormFlex(PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_int32 delta)
pixBackgroundNormFlex()
Definition adaptmap.c:2485
PIX * pixBackgroundNormSimple(PIX *pixs, PIX *pixim, PIX *pixg)
pixBackgroundNormSimple()
Definition adaptmap.c:263
PIX * pixContrastNorm(PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy, l_int32 mindiff, l_int32 smoothx, l_int32 smoothy)
pixContrastNorm()
Definition adaptmap.c:2578
PIX * pixBackgroundNorm(PIX *pixs, PIX *pixim, PIX *pixg, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy)
pixBackgroundNorm()
Definition adaptmap.c:338
#define SET_DATA_BIT(pdata, n)
#define GET_DATA_BYTE(pdata, n)
#define SET_DATA_BYTE(pdata, n, val)
static PIX * pixApplyLocalThreshold(PIX *pixs, PIX *pixth)
pixApplyLocalThreshold()
Definition binarize.c:791
l_ok pixSauvolaBinarizeTiled(PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 nx, l_int32 ny, PIX **ppixth, PIX **ppixd)
pixSauvolaBinarizeTiled()
Definition binarize.c:478
l_ok pixThresholdByConnComp(PIX *pixs, PIX *pixm, l_int32 start, l_int32 end, l_int32 incr, l_float32 thresh48, l_float32 threshdiff, l_int32 *pglobthresh, PIX **ppixd, l_int32 debugflag)
pixThresholdByConnComp()
Definition binarize.c:995
static PIX * pixSauvolaGetThreshold(PIX *pixm, PIX *pixms, l_float32 factor, PIX **ppixsd)
pixSauvolaGetThreshold()
Definition binarize.c:711
PIX * pixOtsuThreshOnBackgroundNorm(PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh)
pixOtsuThreshOnBackgroundNorm()
Definition binarize.c:271
PIX * pixSauvolaOnContrastNorm(PIX *pixs, l_int32 mindiff, PIX **ppixn, PIX **ppixth)
pixSauvolaOnContrastNorm()
Definition binarize.c:850
l_ok pixSauvolaBinarize(PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 addborder, PIX **ppixm, PIX **ppixsd, PIX **ppixth, PIX **ppixd)
pixSauvolaBinarize()
Definition binarize.c:603
PIX * pixThreshOnDoubleNorm(PIX *pixs, l_int32 mindiff)
pixThreshOnDoubleNorm()
Definition binarize.c:904
l_ok pixOtsuAdaptiveThreshold(PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, PIX **ppixth, PIX **ppixd)
pixOtsuAdaptiveThreshold()
Definition binarize.c:157
l_ok pixThresholdByHisto(PIX *pixs, l_int32 factor, l_int32 halfw, l_int32 skip, l_int32 *pthresh, PIX **ppixd, NUMA **pnahisto, PIX **ppixhisto)
pixThresholdByHisto()
Definition binarize.c:1151
PIX * pixMaskedThreshOnBackgroundNorm(PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh)
pixMaskedThreshOnBackgroundNorm()
Definition binarize.c:367
@ REMOVE_CMAP_BASED_ON_SRC
Definition pix.h:384
Definition gplot.h:77