Leptonica  1.77.0
Image processing and image analysis suite
skew.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 
97 #include <math.h>
98 #include "allheaders.h"
99 
100  /* Default sweep angle parameters for pixFindSkew() */
101 static const l_float32 DEFAULT_SWEEP_RANGE = 7.; /* degrees */
102 static const l_float32 DEFAULT_SWEEP_DELTA = 1.; /* degrees */
103 
104  /* Default final angle difference parameter for binary
105  * search in pixFindSkew(). The expected accuracy is
106  * not better than the inverse image width in pixels,
107  * say, 1/2000 radians, or about 0.03 degrees. */
108 static const l_float32 DEFAULT_MINBS_DELTA = 0.01; /* degrees */
109 
110  /* Default scale factors for pixFindSkew() */
111 static const l_int32 DEFAULT_SWEEP_REDUCTION = 4; /* sweep part; 4 is good */
112 static const l_int32 DEFAULT_BS_REDUCTION = 2; /* binary search part */
113 
114  /* Minimum angle for deskewing in pixDeskew() */
115 static const l_float32 MIN_DESKEW_ANGLE = 0.1; /* degree */
116 
117  /* Minimum allowed confidence (ratio) for deskewing in pixDeskew() */
118 static const l_float32 MIN_ALLOWED_CONFIDENCE = 3.0;
119 
120  /* Minimum allowed maxscore to give nonzero confidence */
121 static const l_int32 MIN_VALID_MAXSCORE = 10000;
122 
123  /* Constant setting threshold for minimum allowed minscore
124  * to give nonzero confidence; multiply this constant by
125  * (height * width^2) */
126 static const l_float32 MINSCORE_THRESHOLD_CONSTANT = 0.000002;
127 
128  /* Default binarization threshold value */
129 static const l_int32 DEFAULT_BINARY_THRESHOLD = 130;
130 
131 #ifndef NO_CONSOLE_IO
132 #define DEBUG_PRINT_SCORES 0
133 #define DEBUG_PRINT_SWEEP 0
134 #define DEBUG_PRINT_BINARY 0
135 #define DEBUG_PRINT_ORTH 0
136 #define DEBUG_THRESHOLD 0
137 #define DEBUG_PLOT_SCORES 0 /* requires the gnuplot executable */
138 #endif /* ~NO_CONSOLE_IO */
139 
140 
141 
142 /*-----------------------------------------------------------------------*
143  * Top-level deskew interfaces *
144  *-----------------------------------------------------------------------*/
161 PIX *
163  l_int32 redsearch)
164 {
165 PIX *pix1, *pix2, *pix3, *pix4;
166 
167  PROCNAME("pixDeskewBoth");
168 
169  if (!pixs)
170  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
171  if (redsearch == 0)
172  redsearch = DEFAULT_BS_REDUCTION;
173  else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
174  return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
175 
176  pix1 = pixDeskew(pixs, redsearch);
177  pix2 = pixRotate90(pix1, 1);
178  pix3 = pixDeskew(pix2, redsearch);
179  pix4 = pixRotate90(pix3, -1);
180  pixDestroy(&pix1);
181  pixDestroy(&pix2);
182  pixDestroy(&pix3);
183  return pix4;
184 }
185 
186 
204 PIX *
206  l_int32 redsearch)
207 {
208  PROCNAME("pixDeskew");
209 
210  if (!pixs)
211  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
212  if (redsearch == 0)
213  redsearch = DEFAULT_BS_REDUCTION;
214  else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
215  return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
216 
217  return pixDeskewGeneral(pixs, 0, 0.0, 0.0, redsearch, 0, NULL, NULL);
218 }
219 
220 
240 PIX *
242  l_int32 redsearch,
243  l_float32 *pangle,
244  l_float32 *pconf)
245 {
246  PROCNAME("pixFindSkewAndDeskew");
247 
248  if (!pixs)
249  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
250  if (redsearch == 0)
251  redsearch = DEFAULT_BS_REDUCTION;
252  else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
253  return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
254 
255  return pixDeskewGeneral(pixs, 0, 0.0, 0.0, redsearch, 0, pangle, pconf);
256 }
257 
258 
284 PIX *
286  l_int32 redsweep,
287  l_float32 sweeprange,
288  l_float32 sweepdelta,
289  l_int32 redsearch,
290  l_int32 thresh,
291  l_float32 *pangle,
292  l_float32 *pconf)
293 {
294 l_int32 ret, depth;
295 l_float32 angle, conf, deg2rad;
296 PIX *pixb, *pixd;
297 
298  PROCNAME("pixDeskewGeneral");
299 
300  if (pangle) *pangle = 0.0;
301  if (pconf) *pconf = 0.0;
302  if (!pixs)
303  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
304  if (redsweep == 0)
305  redsweep = DEFAULT_SWEEP_REDUCTION;
306  else if (redsweep != 1 && redsweep != 2 && redsweep != 4)
307  return (PIX *)ERROR_PTR("redsweep not in {1,2,4}", procName, NULL);
308  if (sweeprange == 0.0)
309  sweeprange = DEFAULT_SWEEP_RANGE;
310  if (sweepdelta == 0.0)
311  sweepdelta = DEFAULT_SWEEP_DELTA;
312  if (redsearch == 0)
313  redsearch = DEFAULT_BS_REDUCTION;
314  else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
315  return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
316  if (thresh == 0)
317  thresh = DEFAULT_BINARY_THRESHOLD;
318 
319  deg2rad = 3.1415926535 / 180.;
320 
321  /* Binarize if necessary */
322  depth = pixGetDepth(pixs);
323  if (depth == 1)
324  pixb = pixClone(pixs);
325  else
326  pixb = pixConvertTo1(pixs, thresh);
327 
328  /* Use the 1 bpp image to find the skew */
329  ret = pixFindSkewSweepAndSearch(pixb, &angle, &conf, redsweep, redsearch,
330  sweeprange, sweepdelta,
331  DEFAULT_MINBS_DELTA);
332  pixDestroy(&pixb);
333  if (pangle) *pangle = angle;
334  if (pconf) *pconf = conf;
335  if (ret)
336  return pixClone(pixs);
337 
338  if (L_ABS(angle) < MIN_DESKEW_ANGLE || conf < MIN_ALLOWED_CONFIDENCE)
339  return pixClone(pixs);
340 
341  if ((pixd = pixRotate(pixs, deg2rad * angle, L_ROTATE_AREA_MAP,
342  L_BRING_IN_WHITE, 0, 0)) == NULL)
343  return pixClone(pixs);
344  else
345  return pixd;
346 }
347 
348 
349 /*-----------------------------------------------------------------------*
350  * Simple top-level angle-finding interface *
351  *-----------------------------------------------------------------------*/
369 l_ok
371  l_float32 *pangle,
372  l_float32 *pconf)
373 {
374  PROCNAME("pixFindSkew");
375 
376  if (pangle) *pangle = 0.0;
377  if (pconf) *pconf = 0.0;
378  if (!pangle || !pconf)
379  return ERROR_INT("&angle and/or &conf not defined", procName, 1);
380  if (!pixs)
381  return ERROR_INT("pixs not defined", procName, 1);
382  if (pixGetDepth(pixs) != 1)
383  return ERROR_INT("pixs not 1 bpp", procName, 1);
384 
385  return pixFindSkewSweepAndSearch(pixs, pangle, pconf,
386  DEFAULT_SWEEP_REDUCTION,
387  DEFAULT_BS_REDUCTION,
388  DEFAULT_SWEEP_RANGE,
389  DEFAULT_SWEEP_DELTA,
390  DEFAULT_MINBS_DELTA);
391 }
392 
393 
394 /*-----------------------------------------------------------------------*
395  * Basic angle-finding functions *
396  *-----------------------------------------------------------------------*/
413 l_ok
415  l_float32 *pangle,
416  l_int32 reduction,
417  l_float32 sweeprange,
418  l_float32 sweepdelta)
419 {
420 l_int32 ret, bzero, i, nangles;
421 l_float32 deg2rad, theta;
422 l_float32 sum, maxscore, maxangle;
423 NUMA *natheta, *nascore;
424 PIX *pix, *pixt;
425 
426  PROCNAME("pixFindSkewSweep");
427 
428  if (!pangle)
429  return ERROR_INT("&angle not defined", procName, 1);
430  *pangle = 0.0;
431  if (!pixs)
432  return ERROR_INT("pixs not defined", procName, 1);
433  if (pixGetDepth(pixs) != 1)
434  return ERROR_INT("pixs not 1 bpp", procName, 1);
435  if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
436  return ERROR_INT("reduction must be in {1,2,4,8}", procName, 1);
437 
438  deg2rad = 3.1415926535 / 180.;
439  ret = 0;
440 
441  /* Generate reduced image, if requested */
442  if (reduction == 1)
443  pix = pixClone(pixs);
444  else if (reduction == 2)
445  pix = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
446  else if (reduction == 4)
447  pix = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
448  else /* reduction == 8 */
449  pix = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0);
450 
451  pixZero(pix, &bzero);
452  if (bzero) {
453  pixDestroy(&pix);
454  return 1;
455  }
456 
457  nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1);
458  natheta = numaCreate(nangles);
459  nascore = numaCreate(nangles);
460  pixt = pixCreateTemplate(pix);
461 
462  if (!pix || !pixt) {
463  ret = ERROR_INT("pix and pixt not both made", procName, 1);
464  goto cleanup;
465  }
466  if (!natheta || !nascore) {
467  ret = ERROR_INT("natheta and nascore not both made", procName, 1);
468  goto cleanup;
469  }
470 
471  for (i = 0; i < nangles; i++) {
472  theta = -sweeprange + i * sweepdelta; /* degrees */
473 
474  /* Shear pix about the UL corner and put the result in pixt */
475  pixVShearCorner(pixt, pix, deg2rad * theta, L_BRING_IN_WHITE);
476 
477  /* Get score */
478  pixFindDifferentialSquareSum(pixt, &sum);
479 
480 #if DEBUG_PRINT_SCORES
481  L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum);
482 #endif /* DEBUG_PRINT_SCORES */
483 
484  /* Save the result in the output arrays */
485  numaAddNumber(nascore, sum);
486  numaAddNumber(natheta, theta);
487  }
488 
489  /* Find the location of the maximum (i.e., the skew angle)
490  * by fitting the largest data point and its two neighbors
491  * to a quadratic, using lagrangian interpolation. */
492  numaFitMax(nascore, &maxscore, natheta, &maxangle);
493  *pangle = maxangle;
494 
495 #if DEBUG_PRINT_SWEEP
496  L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName,
497  maxangle, maxscore);
498 #endif /* DEBUG_PRINT_SWEEP */
499 
500 #if DEBUG_PLOT_SCORES
501  /* Plot the result -- the scores versus rotation angle --
502  * using gnuplot with GPLOT_LINES (lines connecting data points).
503  * The GPLOT data structure is first created, with the
504  * appropriate data incorporated from the two input NUMAs,
505  * and then the function gplotMakeOutput() uses gnuplot to
506  * generate the output plot. This can be either a .png file
507  * or a .ps file, depending on whether you use GPLOT_PNG
508  * or GPLOT_PS. */
509  {GPLOT *gplot;
510  gplot = gplotCreate("sweep_output", GPLOT_PNG,
511  "Sweep. Variance of difference of ON pixels vs. angle",
512  "angle (deg)", "score");
513  gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1");
514  gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2");
515  gplotMakeOutput(gplot);
516  gplotDestroy(&gplot);
517  }
518 #endif /* DEBUG_PLOT_SCORES */
519 
520 cleanup:
521  pixDestroy(&pix);
522  pixDestroy(&pixt);
523  numaDestroy(&nascore);
524  numaDestroy(&natheta);
525  return ret;
526 }
527 
528 
557 l_ok
559  l_float32 *pangle,
560  l_float32 *pconf,
561  l_int32 redsweep,
562  l_int32 redsearch,
563  l_float32 sweeprange,
564  l_float32 sweepdelta,
565  l_float32 minbsdelta)
566 {
567  return pixFindSkewSweepAndSearchScore(pixs, pangle, pconf, NULL,
568  redsweep, redsearch, 0.0, sweeprange,
569  sweepdelta, minbsdelta);
570 }
571 
572 
611 l_ok
613  l_float32 *pangle,
614  l_float32 *pconf,
615  l_float32 *pendscore,
616  l_int32 redsweep,
617  l_int32 redsearch,
618  l_float32 sweepcenter,
619  l_float32 sweeprange,
620  l_float32 sweepdelta,
621  l_float32 minbsdelta)
622 {
623  return pixFindSkewSweepAndSearchScorePivot(pixs, pangle, pconf, pendscore,
624  redsweep, redsearch, 0.0,
625  sweeprange, sweepdelta,
626  minbsdelta,
628 }
629 
630 
660 l_ok
662  l_float32 *pangle,
663  l_float32 *pconf,
664  l_float32 *pendscore,
665  l_int32 redsweep,
666  l_int32 redsearch,
667  l_float32 sweepcenter,
668  l_float32 sweeprange,
669  l_float32 sweepdelta,
670  l_float32 minbsdelta,
671  l_int32 pivot)
672 {
673 l_int32 ret, bzero, i, nangles, n, ratio, maxindex, minloc;
674 l_int32 width, height;
675 l_float32 deg2rad, theta, delta;
676 l_float32 sum, maxscore, maxangle;
677 l_float32 centerangle, leftcenterangle, rightcenterangle;
678 l_float32 lefttemp, righttemp;
679 l_float32 bsearchscore[5];
680 l_float32 minscore, minthresh;
681 l_float32 rangeleft;
682 NUMA *natheta, *nascore;
683 PIX *pixsw, *pixsch, *pixt1, *pixt2;
684 
685  PROCNAME("pixFindSkewSweepAndSearchScorePivot");
686 
687  if (pendscore) *pendscore = 0.0;
688  if (pangle) *pangle = 0.0;
689  if (pconf) *pconf = 0.0;
690  if (!pangle || !pconf)
691  return ERROR_INT("&angle and/or &conf not defined", procName, 1);
692  if (!pixs || pixGetDepth(pixs) != 1)
693  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
694  if (redsweep != 1 && redsweep != 2 && redsweep != 4 && redsweep != 8)
695  return ERROR_INT("redsweep must be in {1,2,4,8}", procName, 1);
696  if (redsearch != 1 && redsearch != 2 && redsearch != 4 && redsearch != 8)
697  return ERROR_INT("redsearch must be in {1,2,4,8}", procName, 1);
698  if (redsearch > redsweep)
699  return ERROR_INT("redsearch must not exceed redsweep", procName, 1);
700  if (pivot != L_SHEAR_ABOUT_CORNER && pivot != L_SHEAR_ABOUT_CENTER)
701  return ERROR_INT("invalid pivot", procName, 1);
702 
703  deg2rad = 3.1415926535 / 180.;
704  ret = 0;
705 
706  /* Generate reduced image for binary search, if requested */
707  if (redsearch == 1)
708  pixsch = pixClone(pixs);
709  else if (redsearch == 2)
710  pixsch = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
711  else if (redsearch == 4)
712  pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
713  else /* redsearch == 8 */
714  pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0);
715 
716  pixZero(pixsch, &bzero);
717  if (bzero) {
718  pixDestroy(&pixsch);
719  return 1;
720  }
721 
722  /* Generate reduced image for sweep, if requested */
723  ratio = redsweep / redsearch;
724  if (ratio == 1) {
725  pixsw = pixClone(pixsch);
726  } else { /* ratio > 1 */
727  if (ratio == 2)
728  pixsw = pixReduceRankBinaryCascade(pixsch, 1, 0, 0, 0);
729  else if (ratio == 4)
730  pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 0, 0);
731  else /* ratio == 8 */
732  pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 2, 0);
733  }
734 
735  pixt1 = pixCreateTemplate(pixsw);
736  if (ratio == 1)
737  pixt2 = pixClone(pixt1);
738  else
739  pixt2 = pixCreateTemplate(pixsch);
740 
741  nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1);
742  natheta = numaCreate(nangles);
743  nascore = numaCreate(nangles);
744 
745  if (!pixsch || !pixsw) {
746  ret = ERROR_INT("pixsch and pixsw not both made", procName, 1);
747  goto cleanup;
748  }
749  if (!pixt1 || !pixt2) {
750  ret = ERROR_INT("pixt1 and pixt2 not both made", procName, 1);
751  goto cleanup;
752  }
753  if (!natheta || !nascore) {
754  ret = ERROR_INT("natheta and nascore not both made", procName, 1);
755  goto cleanup;
756  }
757 
758  /* Do sweep */
759  rangeleft = sweepcenter - sweeprange;
760  for (i = 0; i < nangles; i++) {
761  theta = rangeleft + i * sweepdelta; /* degrees */
762 
763  /* Shear pix and put the result in pixt1 */
764  if (pivot == L_SHEAR_ABOUT_CORNER)
765  pixVShearCorner(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE);
766  else
767  pixVShearCenter(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE);
768 
769  /* Get score */
770  pixFindDifferentialSquareSum(pixt1, &sum);
771 
772 #if DEBUG_PRINT_SCORES
773  L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum);
774 #endif /* DEBUG_PRINT_SCORES */
775 
776  /* Save the result in the output arrays */
777  numaAddNumber(nascore, sum);
778  numaAddNumber(natheta, theta);
779  }
780 
781  /* Find the largest of the set (maxscore at maxangle) */
782  numaGetMax(nascore, &maxscore, &maxindex);
783  numaGetFValue(natheta, maxindex, &maxangle);
784 
785 #if DEBUG_PRINT_SWEEP
786  L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName,
787  maxangle, maxscore);
788 #endif /* DEBUG_PRINT_SWEEP */
789 
790 #if DEBUG_PLOT_SCORES
791  /* Plot the sweep result -- the scores versus rotation angle --
792  * using gnuplot with GPLOT_LINES (lines connecting data points). */
793  {GPLOT *gplot;
794  gplot = gplotCreate("sweep_output", GPLOT_PNG,
795  "Sweep. Variance of difference of ON pixels vs. angle",
796  "angle (deg)", "score");
797  gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1");
798  gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2");
799  gplotMakeOutput(gplot);
800  gplotDestroy(&gplot);
801  }
802 #endif /* DEBUG_PLOT_SCORES */
803 
804  /* Check if the max is at the end of the sweep. */
805  n = numaGetCount(natheta);
806  if (maxindex == 0 || maxindex == n - 1) {
807  L_WARNING("max found at sweep edge\n", procName);
808  goto cleanup;
809  }
810 
811  /* Empty the numas for re-use */
812  numaEmpty(nascore);
813  numaEmpty(natheta);
814 
815  /* Do binary search to find skew angle.
816  * First, set up initial three points. */
817  centerangle = maxangle;
818  if (pivot == L_SHEAR_ABOUT_CORNER) {
819  pixVShearCorner(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE);
820  pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]);
821  pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle - sweepdelta),
823  pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]);
824  pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle + sweepdelta),
826  pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]);
827  } else {
828  pixVShearCenter(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE);
829  pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]);
830  pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle - sweepdelta),
832  pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]);
833  pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle + sweepdelta),
835  pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]);
836  }
837 
838  numaAddNumber(nascore, bsearchscore[2]);
839  numaAddNumber(natheta, centerangle);
840  numaAddNumber(nascore, bsearchscore[0]);
841  numaAddNumber(natheta, centerangle - sweepdelta);
842  numaAddNumber(nascore, bsearchscore[4]);
843  numaAddNumber(natheta, centerangle + sweepdelta);
844 
845  /* Start the search */
846  delta = 0.5 * sweepdelta;
847  while (delta >= minbsdelta)
848  {
849  /* Get the left intermediate score */
850  leftcenterangle = centerangle - delta;
851  if (pivot == L_SHEAR_ABOUT_CORNER)
852  pixVShearCorner(pixt2, pixsch, deg2rad * leftcenterangle,
854  else
855  pixVShearCenter(pixt2, pixsch, deg2rad * leftcenterangle,
857  pixFindDifferentialSquareSum(pixt2, &bsearchscore[1]);
858  numaAddNumber(nascore, bsearchscore[1]);
859  numaAddNumber(natheta, leftcenterangle);
860 
861  /* Get the right intermediate score */
862  rightcenterangle = centerangle + delta;
863  if (pivot == L_SHEAR_ABOUT_CORNER)
864  pixVShearCorner(pixt2, pixsch, deg2rad * rightcenterangle,
866  else
867  pixVShearCenter(pixt2, pixsch, deg2rad * rightcenterangle,
869  pixFindDifferentialSquareSum(pixt2, &bsearchscore[3]);
870  numaAddNumber(nascore, bsearchscore[3]);
871  numaAddNumber(natheta, rightcenterangle);
872 
873  /* Find the maximum of the five scores and its location.
874  * Note that the maximum must be in the center
875  * three values, not in the end two. */
876  maxscore = bsearchscore[1];
877  maxindex = 1;
878  for (i = 2; i < 4; i++) {
879  if (bsearchscore[i] > maxscore) {
880  maxscore = bsearchscore[i];
881  maxindex = i;
882  }
883  }
884 
885  /* Set up score array to interpolate for the next iteration */
886  lefttemp = bsearchscore[maxindex - 1];
887  righttemp = bsearchscore[maxindex + 1];
888  bsearchscore[2] = maxscore;
889  bsearchscore[0] = lefttemp;
890  bsearchscore[4] = righttemp;
891 
892  /* Get new center angle and delta for next iteration */
893  centerangle = centerangle + delta * (maxindex - 2);
894  delta = 0.5 * delta;
895  }
896  *pangle = centerangle;
897 
898 #if DEBUG_PRINT_SCORES
899  L_INFO(" Binary search score = %7.3f\n", procName, bsearchscore[2]);
900 #endif /* DEBUG_PRINT_SCORES */
901 
902  if (pendscore) /* save if requested */
903  *pendscore = bsearchscore[2];
904 
905  /* Return the ratio of Max score over Min score
906  * as a confidence value. Don't trust if the Min score
907  * is too small, which can happen if the image is all black
908  * with only a few white pixels interspersed. In that case,
909  * we get a contribution from the top and bottom edges when
910  * vertically sheared, but this contribution becomes zero when
911  * the shear angle is zero. For zero shear angle, the only
912  * contribution will be from the white pixels. We expect that
913  * the signal goes as the product of the (height * width^2),
914  * so we compute a (hopefully) normalized minimum threshold as
915  * a function of these dimensions. */
916  numaGetMin(nascore, &minscore, &minloc);
917  width = pixGetWidth(pixsch);
918  height = pixGetHeight(pixsch);
919  minthresh = MINSCORE_THRESHOLD_CONSTANT * width * width * height;
920 
921 #if DEBUG_THRESHOLD
922  L_INFO(" minthresh = %10.2f, minscore = %10.2f\n", procName,
923  minthresh, minscore);
924  L_INFO(" maxscore = %10.2f\n", procName, maxscore);
925 #endif /* DEBUG_THRESHOLD */
926 
927  if (minscore > minthresh)
928  *pconf = maxscore / minscore;
929  else
930  *pconf = 0.0;
931 
932  /* Don't trust it if too close to the edge of the sweep
933  * range or if maxscore is small */
934  if ((centerangle > rangeleft + 2 * sweeprange - sweepdelta) ||
935  (centerangle < rangeleft + sweepdelta) ||
936  (maxscore < MIN_VALID_MAXSCORE))
937  *pconf = 0.0;
938 
939 #if DEBUG_PRINT_BINARY
940  fprintf(stderr, "Binary search: angle = %7.3f, score ratio = %6.2f\n",
941  *pangle, *pconf);
942  fprintf(stderr, " max score = %8.0f\n", maxscore);
943 #endif /* DEBUG_PRINT_BINARY */
944 
945 #if DEBUG_PLOT_SCORES
946  /* Plot the result -- the scores versus rotation angle --
947  * using gnuplot with GPLOT_POINTS. Because the data
948  * points are not ordered by theta (increasing or decreasing),
949  * using GPLOT_LINES would be confusing! */
950  {GPLOT *gplot;
951  gplot = gplotCreate("search_output", GPLOT_PNG,
952  "Binary search. Variance of difference of ON pixels vs. angle",
953  "angle (deg)", "score");
954  gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot1");
955  gplotMakeOutput(gplot);
956  gplotDestroy(&gplot);
957  }
958 #endif /* DEBUG_PLOT_SCORES */
959 
960 cleanup:
961  pixDestroy(&pixsw);
962  pixDestroy(&pixsch);
963  pixDestroy(&pixt1);
964  pixDestroy(&pixt2);
965  numaDestroy(&nascore);
966  numaDestroy(&natheta);
967  return ret;
968 }
969 
970 
971 /*---------------------------------------------------------------------*
972  * Search over arbitrary range of angles in orthogonal directions *
973  *---------------------------------------------------------------------*/
974 /*
975  * pixFindSkewOrthogonalRange()
976  *
977  * Input: pixs (1 bpp)
978  * &angle (<return> angle required to deskew; in degrees cw)
979  * &conf (<return> confidence given by ratio of max/min score)
980  * redsweep (sweep reduction factor = 1, 2, 4 or 8)
981  * redsearch (binary search reduction factor = 1, 2, 4 or 8;
982  * and must not exceed redsweep)
983  * sweeprange (half the full range in each orthogonal
984  * direction, taken about 0, in degrees)
985  * sweepdelta (angle increment of sweep; in degrees)
986  * minbsdelta (min binary search increment angle; in degrees)
987  * confprior (amount by which confidence of 90 degree rotated
988  * result is reduced when comparing with unrotated
989  * confidence value)
990  * Return: 0 if OK, 1 on error or if angle measurement not valid
991  *
992  * Notes:
993  * (1) This searches for the skew angle, first in the range
994  * [-sweeprange, sweeprange], and then in
995  * [90 - sweeprange, 90 + sweeprange], with angles measured
996  * clockwise. For exploring the full range of possibilities,
997  * suggest using sweeprange = 47.0 degrees, giving some overlap
998  * at 45 and 135 degrees. From these results, and discounting
999  * the the second confidence by %confprior, it selects the
1000  * angle for maximal differential variance. If the angle
1001  * is larger than pi/4, the angle found after 90 degree rotation
1002  * is selected.
1003  * (2) The larger the confidence value, the greater the probability
1004  * that the proper alignment is given by the angle that maximizes
1005  * variance. It should be compared to a threshold, which depends
1006  * on the application. Values between 3.0 and 6.0 are common.
1007  * (3) Allowing for both portrait and landscape searches is more
1008  * difficult, because if the signal from the text lines is weak,
1009  * a signal from vertical rules can be larger!
1010  * The most difficult documents to deskew have some or all of:
1011  * (a) Multiple columns, not aligned
1012  * (b) Black lines along the vertical edges
1013  * (c) Text from two pages, and at different angles
1014  * Rule of thumb for resolution:
1015  * (a) If the margins are clean, you can work at 75 ppi,
1016  * although 100 ppi is safer.
1017  * (b) If there are vertical lines in the margins, do not
1018  * work below 150 ppi. The signal from the text lines must
1019  * exceed that from the margin lines.
1020  * (4) Choosing the %confprior parameter depends on knowing something
1021  * about the source of image. However, we're not using
1022  * real probabilities here, so its use is qualitative.
1023  * If landscape and portrait are equally likely, use
1024  * %confprior = 0.0. If the likelihood of portrait (non-rotated)
1025  * is 100 times higher than that of landscape, we want to reduce
1026  * the chance that we rotate to landscape in a situation where
1027  * the landscape signal is accidentally larger than the
1028  * portrait signal. To do this use a positive value of
1029  * %confprior; say 1.5.
1030  */
1031 l_int32
1032 pixFindSkewOrthogonalRange(PIX *pixs,
1033  l_float32 *pangle,
1034  l_float32 *pconf,
1035  l_int32 redsweep,
1036  l_int32 redsearch,
1037  l_float32 sweeprange,
1038  l_float32 sweepdelta,
1039  l_float32 minbsdelta,
1040  l_float32 confprior)
1041 {
1042 l_float32 angle1, conf1, score1, angle2, conf2, score2;
1043 PIX *pixr;
1044 
1045  PROCNAME("pixFindSkewOrthogonalRange");
1046 
1047  if (pangle) *pangle = 0.0;
1048  if (pconf) *pconf = 0.0;
1049  if (!pangle || !pconf)
1050  return ERROR_INT("&angle and/or &conf not defined", procName, 1);
1051  if (!pixs || pixGetDepth(pixs) != 1)
1052  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1053 
1054  pixFindSkewSweepAndSearchScorePivot(pixs, &angle1, &conf1, &score1,
1055  redsweep, redsearch, 0.0,
1056  sweeprange, sweepdelta, minbsdelta,
1058  pixr = pixRotateOrth(pixs, 1);
1059  pixFindSkewSweepAndSearchScorePivot(pixr, &angle2, &conf2, &score2,
1060  redsweep, redsearch, 0.0,
1061  sweeprange, sweepdelta, minbsdelta,
1063  pixDestroy(&pixr);
1064 
1065  if (conf1 > conf2 - confprior) {
1066  *pangle = angle1;
1067  *pconf = conf1;
1068  } else {
1069  *pangle = -90.0 + angle2;
1070  *pconf = conf2;
1071  }
1072 
1073 #if DEBUG_PRINT_ORTH
1074  fprintf(stderr, " About 0: angle1 = %7.3f, conf1 = %7.3f, score1 = %f\n",
1075  angle1, conf1, score1);
1076  fprintf(stderr, " About 90: angle2 = %7.3f, conf2 = %7.3f, score2 = %f\n",
1077  angle2, conf2, score2);
1078  fprintf(stderr, " Final: angle = %7.3f, conf = %7.3f\n", *pangle, *pconf);
1079 #endif /* DEBUG_PRINT_ORTH */
1080 
1081  return 0;
1082 }
1083 
1084 
1085 
1086 /*----------------------------------------------------------------*
1087  * Differential square sum function *
1088  *----------------------------------------------------------------*/
1104 l_ok
1106  l_float32 *psum)
1107 {
1108 l_int32 i, n;
1109 l_int32 w, h, skiph, skip, nskip;
1110 l_float32 val1, val2, diff, sum;
1111 NUMA *na;
1112 
1113  PROCNAME("pixFindDifferentialSquareSum");
1114 
1115  if (!psum)
1116  return ERROR_INT("&sum not defined", procName, 1);
1117  *psum = 0.0;
1118  if (!pixs)
1119  return ERROR_INT("pixs not defined", procName, 1);
1120 
1121  /* Generate a number array consisting of the sum
1122  * of pixels in each row of pixs */
1123  if ((na = pixCountPixelsByRow(pixs, NULL)) == NULL)
1124  return ERROR_INT("na not made", procName, 1);
1125 
1126  /* Compute the number of rows at top and bottom to omit.
1127  * We omit these to avoid getting a spurious signal from
1128  * the top and bottom of a (nearly) all black image. */
1129  w = pixGetWidth(pixs);
1130  h = pixGetHeight(pixs);
1131  skiph = (l_int32)(0.05 * w); /* skip for max shear of 0.025 radians */
1132  skip = L_MIN(h / 10, skiph); /* don't remove more than 10% of image */
1133  nskip = L_MAX(skip / 2, 1); /* at top & bot; skip at least one line */
1134 
1135  /* Sum the squares of differential row sums, on the
1136  * allowed rows. Note that nskip must be >= 1. */
1137  n = numaGetCount(na);
1138  sum = 0.0;
1139  for (i = nskip; i < n - nskip; i++) {
1140  numaGetFValue(na, i - 1, &val1);
1141  numaGetFValue(na, i, &val2);
1142  diff = val2 - val1;
1143  sum += diff * diff;
1144  }
1145  numaDestroy(&na);
1146  *psum = sum;
1147  return 0;
1148 }
1149 
1150 
1151 /*----------------------------------------------------------------*
1152  * Normalized square sum *
1153  *----------------------------------------------------------------*/
1177 l_ok
1179  l_float32 *phratio,
1180  l_float32 *pvratio,
1181  l_float32 *pfract)
1182 {
1183 l_int32 i, w, h, empty;
1184 l_float32 sum, sumsq, uniform, val;
1185 NUMA *na;
1186 PIX *pixt;
1187 
1188  PROCNAME("pixFindNormalizedSquareSum");
1189 
1190  if (phratio) *phratio = 0.0;
1191  if (pvratio) *pvratio = 0.0;
1192  if (pfract) *pfract = 0.0;
1193  if (!phratio && !pvratio)
1194  return ERROR_INT("nothing to do", procName, 1);
1195  if (!pixs || pixGetDepth(pixs) != 1)
1196  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1197  pixGetDimensions(pixs, &w, &h, NULL);
1198 
1199  empty = 0;
1200  if (phratio) {
1201  na = pixCountPixelsByRow(pixs, NULL);
1202  numaGetSum(na, &sum); /* fg pixels */
1203  if (pfract) *pfract = sum / (l_float32)(w * h);
1204  if (sum != 0.0) {
1205  uniform = sum * sum / h; /* h*(sum / h)^2 */
1206  sumsq = 0.0;
1207  for (i = 0; i < h; i++) {
1208  numaGetFValue(na, i, &val);
1209  sumsq += val * val;
1210  }
1211  *phratio = sumsq / uniform;
1212  } else {
1213  empty = 1;
1214  }
1215  numaDestroy(&na);
1216  }
1217 
1218  if (pvratio) {
1219  if (empty == 1) return 1;
1220  pixt = pixRotateOrth(pixs, 1);
1221  na = pixCountPixelsByRow(pixt, NULL);
1222  numaGetSum(na, &sum);
1223  if (pfract) *pfract = sum / (l_float32)(w * h);
1224  if (sum != 0.0) {
1225  uniform = sum * sum / w;
1226  sumsq = 0.0;
1227  for (i = 0; i < w; i++) {
1228  numaGetFValue(na, i, &val);
1229  sumsq += val * val;
1230  }
1231  *pvratio = sumsq / uniform;
1232  } else {
1233  empty = 1;
1234  }
1235  pixDestroy(&pixt);
1236  numaDestroy(&na);
1237  }
1238 
1239  return empty;
1240 }
l_ok numaGetSum(NUMA *na, l_float32 *psum)
numaGetSum()
Definition: numafunc1.c:527
void gplotDestroy(GPLOT **pgplot)
gplotDestroy()
Definition: gplot.c:197
NUMA * pixCountPixelsByRow(PIX *pix, l_int32 *tab8)
pixCountPixelsByRow()
Definition: pix3.c:2029
PIX * pixConvertTo1(PIX *pixs, l_int32 threshold)
pixConvertTo1()
Definition: pixconv.c:2933
l_ok numaGetFValue(NUMA *na, l_int32 index, l_float32 *pval)
numaGetFValue()
Definition: numabasic.c:692
l_ok numaFitMax(NUMA *na, l_float32 *pmaxval, NUMA *naloc, l_float32 *pmaxloc)
numaFitMax()
Definition: numafunc1.c:2077
PIX * pixDeskew(PIX *pixs, l_int32 redsearch)
pixDeskew()
Definition: skew.c:205
l_ok gplotAddPlot(GPLOT *gplot, NUMA *nax, NUMA *nay, l_int32 plotstyle, const char *plottitle)
gplotAddPlot()
Definition: gplot.c:263
l_ok pixFindDifferentialSquareSum(PIX *pixs, l_float32 *psum)
pixFindDifferentialSquareSum()
Definition: skew.c:1105
l_ok numaAddNumber(NUMA *na, l_float32 val)
numaAddNumber()
Definition: numabasic.c:473
l_ok gplotMakeOutput(GPLOT *gplot)
gplotMakeOutput()
Definition: gplot.c:379
GPLOT * gplotCreate(const char *rootname, l_int32 outformat, const char *title, const char *xlabel, const char *ylabel)
gplotCreate()
Definition: gplot.c:138
PIX * pixDeskewBoth(PIX *pixs, l_int32 redsearch)
pixDeskewBoth()
Definition: skew.c:162
l_ok pixFindSkewSweep(PIX *pixs, l_float32 *pangle, l_int32 reduction, l_float32 sweeprange, l_float32 sweepdelta)
pixFindSkewSweep()
Definition: skew.c:414
l_ok pixFindSkewSweepAndSearchScore(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta)
pixFindSkewSweepAndSearchScore()
Definition: skew.c:612
PIX * pixFindSkewAndDeskew(PIX *pixs, l_int32 redsearch, l_float32 *pangle, l_float32 *pconf)
pixFindSkewAndDeskew()
Definition: skew.c:241
l_ok pixFindSkew(PIX *pixs, l_float32 *pangle, l_float32 *pconf)
pixFindSkew()
Definition: skew.c:370
NUMA * numaCreate(l_int32 n)
numaCreate()
Definition: numabasic.c:187
PIX * pixCreateTemplate(PIX *pixs)
pixCreateTemplate()
Definition: pix1.c:367
PIX * pixVShearCenter(PIX *pixd, PIX *pixs, l_float32 radang, l_int32 incolor)
pixVShearCenter()
Definition: shear.c:421
PIX * pixVShearCorner(PIX *pixd, PIX *pixs, l_float32 radang, l_int32 incolor)
pixVShearCorner()
Definition: shear.c:359
Definition: array.h:59
l_ok pixFindSkewSweepAndSearchScorePivot(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_int32 pivot)
pixFindSkewSweepAndSearchScorePivot()
Definition: skew.c:661
l_int32 numaGetCount(NUMA *na)
numaGetCount()
Definition: numabasic.c:631
l_ok pixFindNormalizedSquareSum(PIX *pixs, l_float32 *phratio, l_float32 *pvratio, l_float32 *pfract)
pixFindNormalizedSquareSum()
Definition: skew.c:1178
PIX * pixDeskewGeneral(PIX *pixs, l_int32 redsweep, l_float32 sweeprange, l_float32 sweepdelta, l_int32 redsearch, l_int32 thresh, l_float32 *pangle, l_float32 *pconf)
pixDeskewGeneral()
Definition: skew.c:285
Definition: gplot.h:75
PIX * pixClone(PIX *pixs)
pixClone()
Definition: pix1.c:515
l_ok numaEmpty(NUMA *na)
numaEmpty()
Definition: numabasic.c:449
void pixDestroy(PIX **ppix)
pixDestroy()
Definition: pix1.c:543
PIX * pixRotateOrth(PIX *pixs, l_int32 quads)
pixRotateOrth()
Definition: rotateorth.c:72
void numaDestroy(NUMA **pna)
numaDestroy()
Definition: numabasic.c:360
l_ok pixGetDimensions(const PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd)
pixGetDimensions()
Definition: pix1.c:1065
l_ok numaGetMin(NUMA *na, l_float32 *pminval, l_int32 *piminloc)
numaGetMin()
Definition: numafunc1.c:444
Definition: pix.h:134
l_ok pixZero(PIX *pix, l_int32 *pempty)
pixZero()
Definition: pix3.c:1701
l_ok pixFindSkewSweepAndSearch(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta)
pixFindSkewSweepAndSearch()
Definition: skew.c:558
PIX * pixRotate90(PIX *pixs, l_int32 direction)
pixRotate90()
Definition: rotateorth.c:163
l_ok numaGetMax(NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc)
numaGetMax()
Definition: numafunc1.c:486
PIX * pixReduceRankBinaryCascade(PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4)
pixReduceRankBinaryCascade()
Definition: binreduce.c:148
PIX * pixRotate(PIX *pixs, l_float32 angle, l_int32 type, l_int32 incolor, l_int32 width, l_int32 height)
pixRotate()
Definition: rotate.c:99