Leptonica 1.85.0
Image processing and image analysis suite
Loading...
Searching...
No Matches
readbarcode.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
27
83#ifdef HAVE_CONFIG_H
84#include <config_auto.h>
85#endif /* HAVE_CONFIG_H */
86
87#include <string.h>
88#include "allheaders.h"
89#include "readbarcode.h"
90
91 /* Parameters for pixGenerateBarcodeMask() */
92static const l_int32 MAX_SPACE_WIDTH = 19; /* was 15 */
93static const l_int32 MAX_NOISE_WIDTH = 50; /* smaller than barcode width */
94static const l_int32 MAX_NOISE_HEIGHT = 30; /* smaller than barcode height */
95
96 /* Minimum barcode image size */
97static const l_int32 MIN_BC_WIDTH = 50;
98static const l_int32 MIN_BC_HEIGHT = 50;
99
100 /* Static functions */
101static PIX *pixGenerateBarcodeMask(PIX *pixs, l_int32 maxspace,
102 l_int32 nwidth, l_int32 nheight);
103static NUMA *pixAverageRasterScans(PIX *pixs, l_int32 nscans);
104static l_int32 numaGetCrossingDistances(NUMA *nas, NUMA **pnaedist,
105 NUMA **pnaodist, l_float32 *pmindist,
106 l_float32 *pmaxdist);
107static NUMA *numaLocatePeakRanges(NUMA *nas, l_float32 minfirst,
108 l_float32 minsep, l_float32 maxmin);
109static NUMA *numaGetPeakCentroids(NUMA *nahist, NUMA *narange);
110static NUMA *numaGetPeakWidthLUT(NUMA *narange, NUMA *nacent);
111static l_int32 numaEvalBestWidthAndShift(NUMA *nas, l_int32 nwidth,
112 l_int32 nshift, l_float32 minwidth,
113 l_float32 maxwidth,
114 l_float32 *pbestwidth,
115 l_float32 *pbestshift,
116 l_float32 *pbestscore);
117static l_int32 numaEvalSyncError(NUMA *nas, l_int32 ifirst, l_int32 ilast,
118 l_float32 width, l_float32 shift,
119 l_float32 *pscore, NUMA **pnad);
120
121
122#ifndef NO_CONSOLE_IO
123#define DEBUG_DESKEW 0
124#define DEBUG_WIDTHS 0
125#endif /* ~NO_CONSOLE_IO */
126
127
128/*------------------------------------------------------------------------*
129 * Top level *
130 *------------------------------------------------------------------------*/
141SARRAY *
143 l_int32 format,
144 l_int32 method,
145 SARRAY **psaw,
146 l_int32 debugflag)
147{
148PIX *pixg;
149PIXA *pixa;
150SARRAY *sad;
151
152 if (psaw) *psaw = NULL;
153 if (!pixs)
154 return (SARRAY *)ERROR_PTR("pixs not defined", __func__, NULL);
155 if (format != L_BF_ANY && !barcodeFormatIsSupported(format))
156 return (SARRAY *)ERROR_PTR("unsupported format", __func__, NULL);
157 if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
158 return (SARRAY *)ERROR_PTR("invalid method", __func__, NULL);
159
160 /* Get an 8 bpp image, no cmap */
161 if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs))
162 pixg = pixClone(pixs);
163 else
164 pixg = pixConvertTo8(pixs, 0);
165
166 pixa = pixExtractBarcodes(pixg, debugflag);
167 pixDestroy(&pixg);
168 if (!pixa)
169 return (SARRAY *)ERROR_PTR("no barcode(s) found", __func__, NULL);
170
171 sad = pixReadBarcodes(pixa, format, method, psaw, debugflag);
172 pixaDestroy(&pixa);
173 return sad;
174}
175
176
185PIXA *
187 l_int32 debugflag)
188{
189l_int32 i, n;
190l_float32 angle, conf;
191BOX *box;
192BOXA *boxa;
193PIX *pix1, *pix2, *pix3;
194PIXA *pixa;
195
196 if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
197 return (PIXA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
198
199 /* Locate them; use small threshold for edges. */
200 boxa = pixLocateBarcodes(pixs, 20, &pix2, &pix1);
201 n = boxaGetCount(boxa);
202 L_INFO("%d possible barcode(s) found\n", __func__, n);
203 if (n == 0) {
204 boxaDestroy(&boxa);
205 pixDestroy(&pix2);
206 pixDestroy(&pix1);
207 return NULL;
208 }
209
210 if (debugflag) {
211 boxaWriteStderr(boxa);
212 pixDisplay(pix2, 100, 100);
213 pixDisplay(pix1, 800, 100);
214 }
215 pixDestroy(&pix1);
216
217 /* Deskew each barcode individually */
218 pixa = pixaCreate(n);
219 for (i = 0; i < n; i++) {
220 box = boxaGetBox(boxa, i, L_CLONE);
221 pix3 = pixDeskewBarcode(pixs, pix2, box, 15, 20, &angle, &conf);
222 if (!pix3) conf = 0.0; /* don't use */
223 L_INFO("angle = %6.2f, conf = %6.2f\n", __func__, angle, conf);
224 if (conf > 5.0) {
225 pixaAddPix(pixa, pix3, L_INSERT);
226 pixaAddBox(pixa, box, L_INSERT);
227 } else {
228 pixDestroy(&pix3);
229 boxDestroy(&box);
230 }
231 }
232 pixDestroy(&pix2);
233 boxaDestroy(&boxa);
234
235#if DEBUG_DESKEW
236 pix3 = pixaDisplayTiledInRows(pixa, 8, 1000, 1.0, 0, 30, 2);
237 pixWrite("/tmp/lept/pix3.png", pix3, IFF_PNG);
238 pixDestroy(&pix3);
239#endif /* DEBUG_DESKEW */
240
241 return pixa;
242}
243
244
256SARRAY *
258 l_int32 format,
259 l_int32 method,
260 SARRAY **psaw,
261 l_int32 debugflag)
262{
263char *barstr, *data;
264char emptystring[] = "";
265l_int32 w, h, i, j, n, nbars, ival;
266NUMA *na;
267PIX *pix1;
268SARRAY *saw, *sad;
269
270 if (psaw) *psaw = NULL;
271 if (!pixa)
272 return (SARRAY *)ERROR_PTR("pixa not defined", __func__, NULL);
273 if (format != L_BF_ANY && !barcodeFormatIsSupported(format))
274 return (SARRAY *)ERROR_PTR("unsupported format", __func__, NULL);
275 if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
276 return (SARRAY *)ERROR_PTR("invalid method", __func__, NULL);
277
278 n = pixaGetCount(pixa);
279 saw = sarrayCreate(n);
280 sad = sarrayCreate(n);
281 for (i = 0; i < n; i++) {
282 /* Extract the widths of the lines in each barcode */
283 pix1 = pixaGetPix(pixa, i, L_CLONE);
284 pixGetDimensions(pix1, &w, &h, NULL);
285 if (w < MIN_BC_WIDTH || h < MIN_BC_HEIGHT) {
286 L_ERROR("pix is too small: w = %d, h = %d\n", __func__, w, h);
287 pixDestroy(&pix1);
288 continue;
289 }
290 na = pixReadBarcodeWidths(pix1, method, debugflag);
291 pixDestroy(&pix1);
292 if (!na) {
293 ERROR_INT("valid barcode widths not returned", __func__, 1);
294 continue;
295 }
296
297 /* Save the widths as a string */
298 nbars = numaGetCount(na);
299 barstr = (char *)LEPT_CALLOC(nbars + 1, sizeof(char));
300 for (j = 0; j < nbars; j++) {
301 numaGetIValue(na, j, &ival);
302 barstr[j] = 0x30 + ival;
303 }
304 sarrayAddString(saw, barstr, L_INSERT);
305 numaDestroy(&na);
306
307 /* Decode the width strings */
308 data = barcodeDispatchDecoder(barstr, format, debugflag);
309 if (!data) {
310 ERROR_INT("barcode not decoded", __func__, 1);
311 sarrayAddString(sad, emptystring, L_COPY);
312 continue;
313 }
314 sarrayAddString(sad, data, L_INSERT);
315 }
316
317 /* If nothing found, clean up */
318 if (sarrayGetCount(saw) == 0) {
319 sarrayDestroy(&saw);
320 sarrayDestroy(&sad);
321 return (SARRAY *)ERROR_PTR("no valid barcode data", __func__, NULL);
322 }
323
324 if (psaw)
325 *psaw = saw;
326 else
327 sarrayDestroy(&saw);
328 return sad;
329}
330
331
340NUMA *
342 l_int32 method,
343 l_int32 debugflag)
344{
345l_float32 winwidth;
346NUMA *na;
347
348 if (!pixs)
349 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
350 if (pixGetDepth(pixs) != 8)
351 return (NUMA *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
352 if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
353 return (NUMA *)ERROR_PTR("invalid method", __func__, NULL);
354
355 /* Extract the widths of the lines in each barcode */
356 if (method == L_USE_WIDTHS)
357 na = pixExtractBarcodeWidths1(pixs, 120, 0.25, NULL, NULL,
358 debugflag);
359 else /* method == L_USE_WINDOWS */
360 na = pixExtractBarcodeWidths2(pixs, 120, &winwidth,
361 NULL, debugflag);
362#if DEBUG_WIDTHS
363 if (method == L_USE_WINDOWS)
364 lept_stderr("Window width for barcode: %7.3f\n", winwidth);
365 numaWriteStderr(na);
366#endif /* DEBUG_WIDTHS */
367
368 if (!na)
369 return (NUMA *)ERROR_PTR("barcode widths invalid", __func__, NULL);
370
371 return na;
372}
373
374
375/*------------------------------------------------------------------------*
376 * Locate barcode in image *
377 *------------------------------------------------------------------------*/
387BOXA *
389 l_int32 thresh,
390 PIX **ppixb,
391 PIX **ppixm)
392{
393BOXA *boxa;
394PIX *pix8, *pixe, *pixb, *pixm;
395
396 if (!pixs)
397 return (BOXA *)ERROR_PTR("pixs not defined", __func__, NULL);
398
399 /* Get an 8 bpp image, no cmap */
400 if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs))
401 pix8 = pixClone(pixs);
402 else
403 pix8 = pixConvertTo8(pixs, 0);
404
405 /* Get a 1 bpp image of the edges */
406 pixe = pixSobelEdgeFilter(pix8, L_ALL_EDGES);
407 pixb = pixThresholdToBinary(pixe, thresh);
408 pixInvert(pixb, pixb);
409 pixDestroy(&pix8);
410 pixDestroy(&pixe);
411
412 pixm = pixGenerateBarcodeMask(pixb, MAX_SPACE_WIDTH, MAX_NOISE_WIDTH,
413 MAX_NOISE_HEIGHT);
414 boxa = pixConnComp(pixm, NULL, 8);
415
416 if (ppixb)
417 *ppixb = pixb;
418 else
419 pixDestroy(&pixb);
420 if (ppixm)
421 *ppixm = pixm;
422 else
423 pixDestroy(&pixm);
424
425 return boxa;
426}
427
428
445static PIX *
447 l_int32 maxspace,
448 l_int32 nwidth,
449 l_int32 nheight)
450{
451PIX *pixt1, *pixt2, *pixd;
452
453 if (!pixs || pixGetDepth(pixs) != 1)
454 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
455
456 /* Identify horizontal barcodes */
457 pixt1 = pixCloseBrick(NULL, pixs, maxspace + 1, 1);
458 pixt2 = pixOpenBrick(NULL, pixs, maxspace + 1, 1);
459 pixXor(pixt2, pixt2, pixt1);
460 pixOpenBrick(pixt2, pixt2, nwidth, nheight);
461 pixDestroy(&pixt1);
462
463 /* Identify vertical barcodes */
464 pixt1 = pixCloseBrick(NULL, pixs, 1, maxspace + 1);
465 pixd = pixOpenBrick(NULL, pixs, 1, maxspace + 1);
466 pixXor(pixd, pixd, pixt1);
467 pixOpenBrick(pixd, pixd, nheight, nwidth);
468 pixDestroy(&pixt1);
469
470 /* Combine to get all barcodes */
471 pixOr(pixd, pixd, pixt2);
472 pixDestroy(&pixt2);
473
474 return pixd;
475}
476
477
478/*------------------------------------------------------------------------*
479 * Extract and deskew barcode *
480 *------------------------------------------------------------------------*/
499PIX *
501 PIX *pixb,
502 BOX *box,
503 l_int32 margin,
504 l_int32 threshold,
505 l_float32 *pangle,
506 l_float32 *pconf)
507{
508l_int32 x, y, w, h, n;
509l_float32 angle, angle1, angle2, conf, conf1, conf2, score1, score2, deg2rad;
510BOX *box1, *box2;
511BOXA *boxa1, *boxa2;
512PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pixd;
513
514 if (pangle) *pangle = 0.0;
515 if (pconf) *pconf = 0.0;
516 if (!pixs || pixGetDepth(pixs) != 8)
517 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
518 if (!pixb || pixGetDepth(pixb) != 1)
519 return (PIX *)ERROR_PTR("pixb undefined or not 1 bpp", __func__, NULL);
520 if (!box)
521 return (PIX *)ERROR_PTR("box not defined or 1 bpp", __func__, NULL);
522
523 /* Clip out */
524 deg2rad = 3.1415926535f / 180.f;
525 boxGetGeometry(box, &x, &y, &w, &h);
526 box2 = boxCreate(x - 25, y - 25, w + 51, h + 51);
527 pix1 = pixClipRectangle(pixb, box2, NULL);
528 pix2 = pixClipRectangle(pixs, box2, NULL);
529 boxDestroy(&box2);
530
531 /* Deskew, looking at all possible orientations over 180 degrees */
532 pix3 = pixRotateOrth(pix1, 1); /* look for vertical bar lines */
533 pix4 = pixClone(pix1); /* look for horizontal bar lines */
534 pixFindSkewSweepAndSearchScore(pix3, &angle1, &conf1, &score1,
535 1, 1, 0.0f, 45.0f, 2.5f, 0.01f);
536 pixFindSkewSweepAndSearchScore(pix4, &angle2, &conf2, &score2,
537 1, 1, 0.0f, 45.0f, 2.5f, 0.01f);
538 pixDestroy(&pix1);
539 pixDestroy(&pix3);
540 pixDestroy(&pix4);
541
542 /* Because we're using the boundary pixels of the barcodes,
543 * the peak can be sharper (and the confidence ratio higher)
544 * from the signal across the top and bottom of the barcode.
545 * However, the max score, which is the magnitude of the signal
546 * at the optimum skew angle, will be smaller, so we use the
547 * max score as the primary indicator of orientation. */
548 if (score1 >= score2) {
549 conf = conf1;
550 if (conf1 > 6.0 && L_ABS(angle1) > 0.1) {
551 angle = angle1;
552 pix5 = pixRotate(pix2, deg2rad * angle1, L_ROTATE_AREA_MAP,
553 L_BRING_IN_WHITE, 0, 0);
554 } else {
555 angle = 0.0;
556 pix5 = pixClone(pix2);
557 }
558 } else { /* score2 > score1 */
559 conf = conf2;
560 pix6 = pixRotateOrth(pix2, 1);
561 if (conf2 > 6.0 && L_ABS(angle2) > 0.1) {
562 angle = 90.0 + angle2;
563 pix5 = pixRotate(pix6, deg2rad * angle2, L_ROTATE_AREA_MAP,
564 L_BRING_IN_WHITE, 0, 0);
565 } else {
566 angle = 90.0;
567 pix5 = pixClone(pix6);
568 }
569 pixDestroy(&pix6);
570 }
571 pixDestroy(&pix2);
572
573 /* Extract barcode plus a margin around it */
574 boxa1 = pixLocateBarcodes(pix5, threshold, 0, 0);
575 if ((n = boxaGetCount(boxa1)) != 1) {
576 L_WARNING("barcode mask in %d components\n", __func__, n);
577 boxa2 = boxaSort(boxa1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
578 } else {
579 boxa2 = boxaCopy(boxa1, L_CLONE);
580 }
581 box1 = boxaGetBox(boxa2, 0, L_CLONE);
582 boxGetGeometry(box1, &x, &y, &w, &h);
583 box2 = boxCreate(x - margin, y - margin, w + 2 * margin,
584 h + 2 * margin);
585 pixd = pixClipRectangle(pix5, box2, NULL);
586 boxDestroy(&box1);
587 boxDestroy(&box2);
588 boxaDestroy(&boxa1);
589 boxaDestroy(&boxa2);
590 pixDestroy(&pix5);
591
592 if (pangle) *pangle = angle;
593 if (pconf) *pconf = conf;
594
595 if (!pixd)
596 L_ERROR("pixd not made\n", __func__);
597 return pixd;
598}
599
600
601/*------------------------------------------------------------------------*
602 * Process to get line widths *
603 *------------------------------------------------------------------------*/
627NUMA *
629 l_float32 thresh,
630 l_float32 binfract,
631 NUMA **pnaehist,
632 NUMA **pnaohist,
633 l_int32 debugflag)
634{
635NUMA *nac, *nad;
636
637 if (pnaehist) *pnaehist = NULL;
638 if (pnaohist) *pnaohist = NULL;
639 if (!pixs || pixGetDepth(pixs) != 8)
640 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
641
642 /* Get the best estimate of the crossings, in pixel units */
643 if ((nac = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL)
644 return (NUMA *)ERROR_PTR("nac not made", __func__, NULL);
645
646 /* Get the array of bar widths, starting with a black bar */
647 nad = numaQuantizeCrossingsByWidth(nac, binfract, pnaehist,
648 pnaohist, debugflag);
649
650 numaDestroy(&nac);
651 return nad;
652}
653
654
681NUMA *
683 l_float32 thresh,
684 l_float32 *pwidth,
685 NUMA **pnac,
686 l_int32 debugflag)
687{
688NUMA *nacp, *nad;
689
690 if (pwidth) *pwidth = 0;
691 if (pnac) *pnac = NULL;
692 if (!pixs || pixGetDepth(pixs) != 8)
693 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
694
695 /* Get the best estimate of the crossings, in pixel units */
696 if ((nacp = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL)
697 return (NUMA *)ERROR_PTR("nacp not made", __func__, NULL);
698
699 /* Quantize the crossings to get actual windowed data */
700 nad = numaQuantizeCrossingsByWindow(nacp, 2.0, pwidth, NULL,
701 pnac, debugflag);
702 numaDestroy(&nacp);
703 return nad;
704}
705
706
721NUMA *
723 l_float32 thresh,
724 l_int32 debugflag)
725{
726l_int32 w;
727l_float32 bestthresh;
728GPLOT *gplot;
729NUMA *nas, *nax, *nay, *nad;
730
731 if (!pixs || pixGetDepth(pixs) != 8)
732 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
733
734 /* Scan pixels horizontally and average results */
735 if ((nas = pixAverageRasterScans(pixs, 50)) == NULL)
736 return (NUMA *)ERROR_PTR("nas not made", __func__, NULL);
737
738 /* Interpolate to get 4x the number of values */
739 w = pixGetWidth(pixs);
740 numaInterpolateEqxInterval(0.0, 1.0, nas, L_QUADRATIC_INTERP, 0.0,
741 (l_float32)(w - 1), 4 * w + 1, &nax, &nay);
742
743 if (debugflag) {
744 lept_mkdir("lept/barcode");
745 gplot = gplotCreate("/tmp/lept/barcode/signal", GPLOT_PNG,
746 "Pixel values", "dist in pixels", "value");
747 gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "plot 1");
748 gplotMakeOutput(gplot);
749 gplotDestroy(&gplot);
750 }
751
752 /* Locate the crossings. Run multiple times with different
753 * thresholds, and choose a threshold in the center of the
754 * run of thresholds that all give the maximum number of crossings. */
755 numaSelectCrossingThreshold(nax, nay, thresh, &bestthresh);
756
757 /* Get the crossings with the best threshold. */
758 nad = numaCrossingsByThreshold(nax, nay, bestthresh);
759 numaDestroy(&nas);
760 numaDestroy(&nax);
761 numaDestroy(&nay);
762
763 if (numaGetCount(nad) < 10) {
764 L_ERROR("Only %d crossings; failure\n", __func__, numaGetCount(nad));
765 numaDestroy(&nad);
766 }
767 return nad;
768}
769
770
771/*------------------------------------------------------------------------*
772 * Average adjacent rasters *
773 *------------------------------------------------------------------------*/
781static NUMA *
783 l_int32 nscans)
784{
785l_int32 w, h, first, last, i, j, wpl, val;
786l_uint32 *line, *data;
787l_float32 *array;
788NUMA *nad;
789
790 if (!pixs || pixGetDepth(pixs) != 8)
791 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
792
793 pixGetDimensions(pixs, &w, &h, NULL);
794 if (nscans > h) {
795 first = 0;
796 last = h - 1;
797 nscans = h;
798 } else {
799 first = (h - nscans) / 2;
800 last = first + nscans - 1;
801 }
802
803 nad = numaCreate(w);
804 numaSetCount(nad, w);
805 array = numaGetFArray(nad, L_NOCOPY);
806 wpl = pixGetWpl(pixs);
807 data = pixGetData(pixs);
808 for (j = 0; j < w; j++) {
809 for (i = first; i <= last; i++) {
810 line = data + i * wpl;
811 val = GET_DATA_BYTE(line, j);
812 array[j] += val;
813 }
814 array[j] = array[j] / (l_float32)nscans;
815 }
816
817 return nad;
818}
819
820
821/*------------------------------------------------------------------------*
822 * Signal processing for barcode widths *
823 *------------------------------------------------------------------------*/
847NUMA *
849 l_float32 binfract,
850 NUMA **pnaehist,
851 NUMA **pnaohist,
852 l_int32 debugflag)
853{
854l_int32 i, n, ret, ned, nod, iw, width;
855l_float32 val, minsize, maxsize, factor;
856GPLOT *gplot;
857NUMA *naedist, *naodist, *naehist, *naohist, *naecent, *naocent;
858NUMA *naerange, *naorange, *naelut, *naolut, *nad;
859
860 if (pnaehist) *pnaehist = NULL;
861 if (pnaohist) *pnaohist = NULL;
862 if (!nas)
863 return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL);
864 n = numaGetCount(nas);
865 if (n < 10)
866 return (NUMA *)ERROR_PTR("n < 10", __func__, NULL);
867 if (binfract <= 0.0)
868 return (NUMA *)ERROR_PTR("binfract <= 0.0", __func__, NULL);
869
870 /* Get even and odd crossing distances, and determine the rank
871 * widths for rank 0.1 (minsize) and 0.9 (maxsize). */
872 ret = numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize);
873 if (ret || minsize < 1.0 || maxsize / minsize > 8.0) {
874 L_ERROR("bad data, or minsize = %5.2f < 1.0 or max/min = %f > 4.0\n",
875 __func__, minsize, maxsize / minsize);
876 numaDestroy(&naedist);
877 numaDestroy(&naodist);
878 return NULL;
879 }
880
881 /* Bin the spans in units of binfract * minsize. These
882 * units are convenient because they scale to make at least
883 * 1/binfract bins in the smallest span (width). We want this
884 * number to be large enough to clearly separate the
885 * widths, but small enough so that the histogram peaks
886 * have very few if any holes (zeroes) within them. */
887 naehist = numaMakeHistogramClipped(naedist, binfract * minsize,
888 (1.25 / binfract) * maxsize);
889 naohist = numaMakeHistogramClipped(naodist, binfract * minsize,
890 (1.25 / binfract) * maxsize);
891
892 if (debugflag) {
893 lept_mkdir("lept/barcode");
894 gplot = gplotCreate("/tmp/lept/barcode/histw", GPLOT_PNG,
895 "Raw width histogram", "Width", "Number");
896 gplotAddPlot(gplot, NULL, naehist, GPLOT_LINES, "plot black");
897 gplotAddPlot(gplot, NULL, naohist, GPLOT_LINES, "plot white");
898 gplotMakeOutput(gplot);
899 gplotDestroy(&gplot);
900 }
901
902 /* Compute the peak ranges, still in units of binfract * minsize. */
903 naerange = numaLocatePeakRanges(naehist, 1.0 / binfract,
904 1.0 / binfract, 0.0);
905 naorange = numaLocatePeakRanges(naohist, 1.0 / binfract,
906 1.0 / binfract, 0.0);
907
908 /* Find the centroid values of each peak */
909 naecent = numaGetPeakCentroids(naehist, naerange);
910 naocent = numaGetPeakCentroids(naohist, naorange);
911
912 /* Generate the lookup tables that map from the bar width, in
913 * units of (binfract * minsize), to the integerized barcode
914 * units (1, 2, 3, 4), which are the output integer widths
915 * between transitions. */
916 naelut = numaGetPeakWidthLUT(naerange, naecent);
917 naolut = numaGetPeakWidthLUT(naorange, naocent);
918
919 /* Get the widths. Because the LUT accepts our funny units,
920 * we first must convert the pixel widths to these units,
921 * which is what 'factor' does. */
922 nad = numaCreate(0);
923 ned = numaGetCount(naedist);
924 nod = numaGetCount(naodist);
925 if (nod != ned - 1)
926 L_WARNING("ned != nod + 1\n", __func__);
927 factor = 1.0 / (binfract * minsize); /* for converting units */
928 for (i = 0; i < ned - 1; i++) {
929 numaGetFValue(naedist, i, &val);
930 width = (l_int32)(factor * val);
931 numaGetIValue(naelut, width, &iw);
932 numaAddNumber(nad, iw);
933/* lept_stderr("even: val = %7.3f, width = %d, iw = %d\n",
934 val, width, iw); */
935 numaGetFValue(naodist, i, &val);
936 width = (l_int32)(factor * val);
937 numaGetIValue(naolut, width, &iw);
938 numaAddNumber(nad, iw);
939/* lept_stderr("odd: val = %7.3f, width = %d, iw = %d\n",
940 val, width, iw); */
941 }
942 numaGetFValue(naedist, ned - 1, &val);
943 width = (l_int32)(factor * val);
944 numaGetIValue(naelut, width, &iw);
945 numaAddNumber(nad, iw);
946
947 if (debugflag) {
948 lept_stderr(" ---- Black bar widths (pixels) ------ \n");
949 numaWriteStderr(naedist);
950 lept_stderr(" ---- Histogram of black bar widths ------ \n");
951 numaWriteStderr(naehist);
952 lept_stderr(" ---- Peak ranges in black bar histogram bins --- \n");
953 numaWriteStderr(naerange);
954 lept_stderr(" ---- Peak black bar centroid width values ------ \n");
955 numaWriteStderr(naecent);
956 lept_stderr(" ---- Black bar lookup table ------ \n");
957 numaWriteStderr(naelut);
958 lept_stderr(" ---- White bar widths (pixels) ------ \n");
959 numaWriteStderr(naodist);
960 lept_stderr(" ---- Histogram of white bar widths ------ \n");
961 numaWriteStderr(naohist);
962 lept_stderr(" ---- Peak ranges in white bar histogram bins --- \n");
963 numaWriteStderr(naorange);
964 lept_stderr(" ---- Peak white bar centroid width values ------ \n");
965 numaWriteStderr(naocent);
966 lept_stderr(" ---- White bar lookup table ------ \n");
967 numaWriteStderr(naolut);
968 }
969
970 numaDestroy(&naedist);
971 numaDestroy(&naodist);
972 numaDestroy(&naerange);
973 numaDestroy(&naorange);
974 numaDestroy(&naecent);
975 numaDestroy(&naocent);
976 numaDestroy(&naelut);
977 numaDestroy(&naolut);
978 if (pnaehist)
979 *pnaehist = naehist;
980 else
981 numaDestroy(&naehist);
982 if (pnaohist)
983 *pnaohist = naohist;
984 else
985 numaDestroy(&naohist);
986 return nad;
987}
988
989
1000static l_int32
1002 NUMA **pnaedist,
1003 NUMA **pnaodist,
1004 l_float32 *pmindist,
1005 l_float32 *pmaxdist)
1006{
1007l_int32 i, n;
1008l_float32 val, newval, mindist, maxdist;
1009NUMA *na1, *na2, *naedist, *naodist;
1010
1011 if (pnaedist) *pnaedist = NULL;
1012 if (pnaodist) *pnaodist = NULL;
1013 if (pmindist) *pmindist = 0.0;
1014 if (pmaxdist) *pmaxdist = 0.0;
1015 if (!nas)
1016 return ERROR_INT("nas not defined", __func__, 1);
1017 if ((n = numaGetCount(nas)) < 2)
1018 return ERROR_INT("n < 2", __func__, 1);
1019
1020 /* Get numas of distances between crossings. Separate these
1021 * into even (e.g., black) and odd (e.g., white) spans.
1022 * For barcodes, the black spans are 0, 2, etc. These
1023 * distances are in pixel units. */
1024 naedist = numaCreate(n / 2 + 1);
1025 naodist = numaCreate(n / 2);
1026 numaGetFValue(nas, 0, &val);
1027 for (i = 1; i < n; i++) {
1028 numaGetFValue(nas, i, &newval);
1029 if (i % 2)
1030 numaAddNumber(naedist, newval - val);
1031 else
1032 numaAddNumber(naodist, newval - val);
1033 val = newval;
1034 }
1035
1036 /* The min and max rank distances of the spans are in pixel units. */
1037 na1 = numaCopy(naedist);
1038 numaJoin(na1, naodist, 0, -1); /* use both bars and spaces */
1039 na2 = numaMakeHistogram(na1, 100, NULL, NULL);
1040 numaHistogramGetValFromRank(na2, 0.1f, &mindist);
1041 numaHistogramGetValFromRank(na2, 0.9f, &maxdist);
1042 numaDestroy(&na1);
1043 numaDestroy(&na2);
1044 L_INFO("mindist = %7.3f, maxdist = %7.3f\n", __func__, mindist, maxdist);
1045
1046 if (pnaedist)
1047 *pnaedist = naedist;
1048 else
1049 numaDestroy(&naedist);
1050 if (pnaodist)
1051 *pnaodist = naodist;
1052 else
1053 numaDestroy(&naodist);
1054 if (pmindist) *pmindist = mindist;
1055 if (pmaxdist) *pmaxdist = maxdist;
1056 return 0;
1057}
1058
1059
1088static NUMA *
1090 l_float32 minfirst,
1091 l_float32 minsep,
1092 l_float32 maxmin)
1093{
1094l_int32 i, n, inpeak, left;
1095l_float32 center, prevcenter, val;
1096NUMA *nad;
1097
1098 if (!nas)
1099 return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL);
1100 n = numaGetCount(nas);
1101 nad = numaCreate(0);
1102
1103 inpeak = FALSE;
1104 prevcenter = minfirst - minsep - 1.0;
1105 for (i = 0; i < n; i++) {
1106 numaGetFValue(nas, i, &val);
1107 if (inpeak == FALSE && val > maxmin) {
1108 inpeak = TRUE;
1109 left = i;
1110 } else if (inpeak == TRUE && val <= maxmin) { /* end peak */
1111 center = (left + i - 1.0) / 2.0;
1112 if (center - prevcenter >= minsep) { /* save new peak */
1113 inpeak = FALSE;
1114 numaAddNumber(nad, left);
1115 numaAddNumber(nad, i - 1);
1116 prevcenter = center;
1117 } else { /* attach to previous peak; revise the right edge */
1118 numaSetValue(nad, numaGetCount(nad) - 1, i - 1);
1119 }
1120 }
1121 }
1122 if (inpeak == TRUE) { /* save the last peak */
1123 numaAddNumber(nad, left);
1124 numaAddNumber(nad, n - 1);
1125 }
1126
1127 return nad;
1128}
1129
1130
1139static NUMA *
1141 NUMA *narange)
1142{
1143l_int32 i, j, nr, low, high;
1144l_float32 cent, sum, val;
1145NUMA *nad;
1146
1147 if (!nahist)
1148 return (NUMA *)ERROR_PTR("nahist not defined", __func__, NULL);
1149 if (!narange)
1150 return (NUMA *)ERROR_PTR("narange not defined", __func__, NULL);
1151 nr = numaGetCount(narange) / 2;
1152
1153 nad = numaCreate(4);
1154 for (i = 0; i < nr; i++) {
1155 numaGetIValue(narange, 2 * i, &low);
1156 numaGetIValue(narange, 2 * i + 1, &high);
1157 cent = 0.0;
1158 sum = 0.0;
1159 for (j = low; j <= high; j++) {
1160 numaGetFValue(nahist, j, &val);
1161 cent += j * val;
1162 sum += val;
1163 }
1164 numaAddNumber(nad, cent / sum);
1165 }
1166
1167 return nad;
1168}
1169
1170
1188static NUMA *
1190 NUMA *nacent)
1191{
1192l_int32 i, j, nc, low, high, imax;
1193l_int32 assign[4];
1194l_float32 *warray;
1195l_float32 max, rat21, rat32, rat42;
1196NUMA *nalut;
1197
1198 if (!narange)
1199 return (NUMA *)ERROR_PTR("narange not defined", __func__, NULL);
1200 if (!nacent)
1201 return (NUMA *)ERROR_PTR("nacent not defined", __func__, NULL);
1202 nc = numaGetCount(nacent); /* half the size of narange */
1203 if (nc < 1 || nc > 4)
1204 return (NUMA *)ERROR_PTR("nc must be 1, 2, 3, or 4", __func__, NULL);
1205
1206 /* Check the peak centroids for consistency with bar widths.
1207 * The third peak can correspond to a width of either 3 or 4.
1208 * Use ratios 3/2 and 4/2 instead of 3/1 and 4/1 because the
1209 * former are more stable and closer to the expected ratio. */
1210 if (nc > 1) {
1211 warray = numaGetFArray(nacent, L_NOCOPY);
1212 if (warray[0] == 0)
1213 return (NUMA *)ERROR_PTR("first peak has width 0.0",
1214 __func__, NULL);
1215 rat21 = warray[1] / warray[0];
1216 if (rat21 < 1.5 || rat21 > 2.6)
1217 L_WARNING("width ratio 2/1 = %f\n", __func__, rat21);
1218 if (nc > 2) {
1219 rat32 = warray[2] / warray[1];
1220 if (rat32 < 1.3 || rat32 > 2.25)
1221 L_WARNING("width ratio 3/2 = %f\n", __func__, rat32);
1222 }
1223 if (nc == 4) {
1224 rat42 = warray[3] / warray[1];
1225 if (rat42 < 1.7 || rat42 > 2.3)
1226 L_WARNING("width ratio 4/2 = %f\n", __func__, rat42);
1227 }
1228 }
1229
1230 /* Set width assignments.
1231 * The only possible ambiguity is with nc = 3 */
1232 for (i = 0; i < 4; i++)
1233 assign[i] = i + 1;
1234 if (nc == 3) {
1235 if (rat32 > 1.75)
1236 assign[2] = 4;
1237 }
1238
1239 /* Put widths into the LUT */
1240 numaGetMax(narange, &max, NULL);
1241 imax = (l_int32)max;
1242 nalut = numaCreate(imax + 1);
1243 numaSetCount(nalut, imax + 1); /* fill the array with zeroes */
1244 for (i = 0; i < nc; i++) {
1245 numaGetIValue(narange, 2 * i, &low);
1246 if (i == 0) low--; /* catch smallest width */
1247 numaGetIValue(narange, 2 * i + 1, &high);
1248 for (j = low; j <= high; j++)
1249 numaSetValue(nalut, j, assign[i]);
1250 }
1251
1252 return nalut;
1253}
1254
1255
1278NUMA *
1280 l_float32 ratio,
1281 l_float32 *pwidth,
1282 l_float32 *pfirstloc,
1283 NUMA **pnac,
1284 l_int32 debugflag)
1285{
1286l_int32 i, nw, started, count, trans;
1287l_float32 minsize, minwidth, minshift, xfirst;
1288NUMA *nac, *nad;
1289
1290 if (!nas)
1291 return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL);
1292 if (numaGetCount(nas) < 2)
1293 return (NUMA *)ERROR_PTR("nas size < 2", __func__, NULL);
1294
1295 /* Get the minsize, which is needed for the search for
1296 * the window width (ultimately found as 'minwidth') */
1297 numaGetCrossingDistances(nas, NULL, NULL, &minsize, NULL);
1298
1299 /* Compute the width and shift increments; start at minsize
1300 * and go up to ratio * minsize */
1301 numaEvalBestWidthAndShift(nas, 100, 10, minsize, ratio * minsize,
1302 &minwidth, &minshift, NULL);
1303
1304 /* Refine width and shift calculation */
1305 numaEvalBestWidthAndShift(nas, 100, 10, 0.98 * minwidth, 1.02 * minwidth,
1306 &minwidth, &minshift, NULL);
1307
1308 L_INFO("best width = %7.3f, best shift = %7.3f\n",
1309 __func__, minwidth, minshift);
1310
1311 /* Get the crossing array (0,1,2) for the best window width and shift */
1312 numaEvalSyncError(nas, 0, 0, minwidth, minshift, NULL, &nac);
1313 if (pwidth) *pwidth = minwidth;
1314 if (pfirstloc) {
1315 numaGetFValue(nas, 0, &xfirst);
1316 *pfirstloc = xfirst + minshift;
1317 }
1318
1319 /* Get the array of bar widths, starting with a black bar */
1320 nad = numaCreate(0);
1321 nw = numaGetCount(nac); /* number of window measurements */
1322 started = FALSE;
1323 count = 0; /* unnecessary init */
1324 for (i = 0; i < nw; i++) {
1325 numaGetIValue(nac, i, &trans);
1326 if (trans > 2)
1327 L_WARNING("trans = %d > 2 !!!\n", __func__, trans);
1328 if (started) {
1329 if (trans > 1) { /* i.e., when trans == 2 */
1330 numaAddNumber(nad, count);
1331 trans--;
1332 count = 1;
1333 }
1334 if (trans == 1) {
1335 numaAddNumber(nad, count);
1336 count = 1;
1337 } else {
1338 count++;
1339 }
1340 }
1341 if (!started && trans) {
1342 started = TRUE;
1343 if (trans == 2) /* a whole bar in this window */
1344 numaAddNumber(nad, 1);
1345 count = 1;
1346 }
1347 }
1348
1349 if (pnac)
1350 *pnac = nac;
1351 else
1352 numaDestroy(&nac);
1353 return nad;
1354}
1355
1356
1378static l_int32
1380 l_int32 nwidth,
1381 l_int32 nshift,
1382 l_float32 minwidth,
1383 l_float32 maxwidth,
1384 l_float32 *pbestwidth,
1385 l_float32 *pbestshift,
1386 l_float32 *pbestscore)
1387{
1388l_int32 i, j;
1389l_float32 delwidth, delshift, width, shift, score;
1390l_float32 bestwidth, bestshift, bestscore;
1391
1392 if (!nas)
1393 return ERROR_INT("nas not defined", __func__, 1);
1394 if (!pbestwidth || !pbestshift)
1395 return ERROR_INT("&bestwidth and &bestshift not defined", __func__, 1);
1396
1397 bestwidth = 0.0f;
1398 bestshift = 0.0f;
1399 bestscore = 1.0;
1400 delwidth = (maxwidth - minwidth) / (nwidth - 1.0);
1401 for (i = 0; i < nwidth; i++) {
1402 width = minwidth + delwidth * i;
1403 delshift = width / (l_float32)(nshift);
1404 for (j = 0; j < nshift; j++) {
1405 shift = -0.5 * (width - delshift) + j * delshift;
1406 numaEvalSyncError(nas, 0, 0, width, shift, &score, NULL);
1407 if (score < bestscore) {
1408 bestscore = score;
1409 bestwidth = width;
1410 bestshift = shift;
1411#if DEBUG_FREQUENCY
1412 lept_stderr("width = %7.3f, shift = %7.3f, score = %7.3f\n",
1413 width, shift, score);
1414#endif /* DEBUG_FREQUENCY */
1415 }
1416 }
1417 }
1418
1419 *pbestwidth = bestwidth;
1420 *pbestshift = bestshift;
1421 if (pbestscore)
1422 *pbestscore = bestscore;
1423 return 0;
1424}
1425
1426
1450static l_int32
1452 l_int32 ifirst,
1453 l_int32 ilast,
1454 l_float32 width,
1455 l_float32 shift,
1456 l_float32 *pscore,
1457 NUMA **pnad)
1458{
1459l_int32 i, n, nc, nw, ival;
1460l_int32 iw; /* cell in which transition occurs */
1461l_float32 score, xfirst, xlast, xleft, xc, xwc;
1462NUMA *nad;
1463
1464 if (!nas)
1465 return ERROR_INT("nas not defined", __func__, 1);
1466 if ((n = numaGetCount(nas)) < 2)
1467 return ERROR_INT("nas size < 2", __func__, 1);
1468 if (ifirst < 0) ifirst = 0;
1469 if (ilast <= 0) ilast = n - 1;
1470 if (ifirst >= ilast)
1471 return ERROR_INT("ifirst not < ilast", __func__, 1);
1472 nc = ilast - ifirst + 1;
1473
1474 /* Set up an array corresponding to the (shifted) windows,
1475 * and fill in the crossings. */
1476 score = 0.0;
1477 numaGetFValue(nas, ifirst, &xfirst);
1478 numaGetFValue(nas, ilast, &xlast);
1479 nw = (l_int32) ((xlast - xfirst + 2.0 * width) / width);
1480 nad = numaCreate(nw);
1481 numaSetCount(nad, nw); /* init to all 0.0 */
1482 xleft = xfirst - width / 2.0 + shift; /* left edge of first window */
1483 for (i = ifirst; i <= ilast; i++) {
1484 numaGetFValue(nas, i, &xc);
1485 iw = (l_int32)((xc - xleft) / width);
1486 xwc = xleft + (iw + 0.5) * width; /* center of cell iw */
1487 score += (xwc - xc) * (xwc - xc);
1488 numaGetIValue(nad, iw, &ival);
1489 numaSetValue(nad, iw, ival + 1);
1490 }
1491
1492 if (pscore)
1493 *pscore = 4.0 * score / (width * width * (l_float32)nc);
1494 if (pnad)
1495 *pnad = nad;
1496 else
1497 numaDestroy(&nad);
1498
1499 return 0;
1500}
@ L_QUADRATIC_INTERP
Definition array.h:93
#define GET_DATA_BYTE(pdata, n)
@ L_SORT_BY_AREA
Definition pix.h:537
@ L_COPY
Definition pix.h:505
@ L_CLONE
Definition pix.h:506
@ L_NOCOPY
Definition pix.h:503
@ L_INSERT
Definition pix.h:504
@ L_SORT_DECREASING
Definition pix.h:523
@ L_BRING_IN_WHITE
Definition pix.h:662
@ L_ROTATE_AREA_MAP
Definition pix.h:655
@ L_ALL_EDGES
Definition pix.h:798
static l_int32 numaGetCrossingDistances(NUMA *nas, NUMA **pnaedist, NUMA **pnaodist, l_float32 *pmindist, l_float32 *pmaxdist)
numaGetCrossingDistances()
static NUMA * pixAverageRasterScans(PIX *pixs, l_int32 nscans)
pixAverageRasterScans()
BOXA * pixLocateBarcodes(PIX *pixs, l_int32 thresh, PIX **ppixb, PIX **ppixm)
pixLocateBarcodes()
static l_int32 numaEvalBestWidthAndShift(NUMA *nas, l_int32 nwidth, l_int32 nshift, l_float32 minwidth, l_float32 maxwidth, l_float32 *pbestwidth, l_float32 *pbestshift, l_float32 *pbestscore)
numaEvalBestWidthAndShift()
NUMA * pixExtractBarcodeWidths2(PIX *pixs, l_float32 thresh, l_float32 *pwidth, NUMA **pnac, l_int32 debugflag)
pixExtractBarcodeWidths2()
NUMA * pixReadBarcodeWidths(PIX *pixs, l_int32 method, l_int32 debugflag)
pixReadBarcodeWidths()
PIX * pixDeskewBarcode(PIX *pixs, PIX *pixb, BOX *box, l_int32 margin, l_int32 threshold, l_float32 *pangle, l_float32 *pconf)
pixDeskewBarcode()
PIXA * pixExtractBarcodes(PIX *pixs, l_int32 debugflag)
pixExtractBarcodes()
static NUMA * numaLocatePeakRanges(NUMA *nas, l_float32 minfirst, l_float32 minsep, l_float32 maxmin)
numaLocatePeakRanges()
SARRAY * pixReadBarcodes(PIXA *pixa, l_int32 format, l_int32 method, SARRAY **psaw, l_int32 debugflag)
pixReadBarcodes()
static PIX * pixGenerateBarcodeMask(PIX *pixs, l_int32 maxspace, l_int32 nwidth, l_int32 nheight)
pixGenerateBarcodeMask()
static NUMA * numaGetPeakCentroids(NUMA *nahist, NUMA *narange)
numaGetPeakCentroids()
NUMA * pixExtractBarcodeWidths1(PIX *pixs, l_float32 thresh, l_float32 binfract, NUMA **pnaehist, NUMA **pnaohist, l_int32 debugflag)
pixExtractBarcodeWidths1()
static NUMA * numaGetPeakWidthLUT(NUMA *narange, NUMA *nacent)
numaGetPeakWidthLUT()
static l_int32 numaEvalSyncError(NUMA *nas, l_int32 ifirst, l_int32 ilast, l_float32 width, l_float32 shift, l_float32 *pscore, NUMA **pnad)
numaEvalSyncError()
NUMA * numaQuantizeCrossingsByWidth(NUMA *nas, l_float32 binfract, NUMA **pnaehist, NUMA **pnaohist, l_int32 debugflag)
numaQuantizeCrossingsByWidth()
NUMA * pixExtractBarcodeCrossings(PIX *pixs, l_float32 thresh, l_int32 debugflag)
pixExtractBarcodeCrossings()
SARRAY * pixProcessBarcodes(PIX *pixs, l_int32 format, l_int32 method, SARRAY **psaw, l_int32 debugflag)
pixProcessBarcodes()
NUMA * numaQuantizeCrossingsByWindow(NUMA *nas, l_float32 ratio, l_float32 *pwidth, l_float32 *pfirstloc, NUMA **pnac, l_int32 debugflag)
numaQuantizeCrossingsByWindow()
Definition gplot.h:77