Leptonica 1.68
C Image Processing Library

skew.c

Go to the documentation of this file.
00001 #/*====================================================================*
00002 # -  Copyright (C) 2001 Leptonica.  All rights reserved.
00003 # -  This software is distributed in the hope that it will be
00004 # -  useful, but with NO WARRANTY OF ANY KIND.
00005 # -  No author or distributor accepts responsibility to anyone for the
00006 # -  consequences of using this software, or for whether it serves any
00007 # -  particular purpose or works at all, unless he or she says so in
00008 # -  writing.  Everyone is granted permission to copy, modify and
00009 # -  redistribute this source code, for commercial or non-commercial
00010 # -  purposes, with the following restrictions: (1) the origin of this
00011 # -  source code must not be misrepresented; (2) modified versions must
00012 # -  be plainly marked as such; and (3) this notice may not be removed
00013 # -  or altered from any source or modified source distribution.
00014 # *====================================================================*/
00015 
00016 /*
00017  *  skew.c
00018  *
00019  *      Top-level deskew interfaces
00020  *          PIX       *pixDeskew()
00021  *          PIX       *pixFindSkewAndDeskew()
00022  *          PIX       *pixDeskewGeneral()
00023  *
00024  *      Top-level angle-finding interface
00025  *          l_int32    pixFindSkew()
00026  *
00027  *      Basic angle-finding functions
00028  *          l_int32    pixFindSkewSweep()
00029  *          l_int32    pixFindSkewSweepAndSearch()
00030  *          l_int32    pixFindSkewSweepAndSearchScore()
00031  *          l_int32    pixFindSkewSweepAndSearchScorePivot()
00032  *
00033  *      Search over arbitrary range of angles in orthogonal directions
00034  *          l_int32    pixFindSkewOrthogonalRange()
00035  *
00036  *      Differential square sum function for scoring
00037  *          l_int32    pixFindDifferentialSquareSum()
00038  *
00039  *      Measures of variance of row sums
00040  *          l_int32    pixFindNormalizedSquareSum()
00041  *
00042  *
00043  *      ==============================================================    
00044  *      Page skew detection
00045  *
00046  *      Skew is determined by pixel profiles, which are computed
00047  *      as pixel sums along the raster line for each line in the
00048  *      image.  By vertically shearing the image by a given angle,
00049  *      the sums can be computed quickly along the raster lines
00050  *      rather than along lines at that angle.  The score is
00051  *      computed from these line sums by taking the square of
00052  *      the DIFFERENCE between adjacent line sums, summed over
00053  *      all lines.  The skew angle is then found as the angle
00054  *      that maximizes the score.  The actual computation for
00055  *      any sheared image is done in the function
00056  *      pixFindDifferentialSquareSum().
00057  *
00058  *      The search for the angle that maximizes this score is
00059  *      most efficiently performed by first sweeping coarsely
00060  *      over angles, using a significantly reduced image (say, 4x
00061  *      reduction), to find the approximate maximum within a half
00062  *      degree or so, and then doing an interval-halving binary
00063  *      search at higher resolution to get the skew angle to
00064  *      within 1/20 degree or better.
00065  *
00066  *      The differential signal is used (rather than just using
00067  *      that variance of line sums) because it rejects the
00068  *      background noise due to total number of black pixels,
00069  *      and has maximum contributions from the baselines and
00070  *      x-height lines of text when the textlines are aligned
00071  *      with the raster lines.  It also works well in multicolumn
00072  *      pages where the textlines do not line up across columns.
00073  *
00074  *      The method is fast, accurate to within an angle (in radians)
00075  *      of approximately the inverse width in pixels of the image,
00076  *      and will work on a surprisingly small amount of text data
00077  *      (just a couple of text lines).  Consequently, it can
00078  *      also be used to find local skew if the skew were to vary
00079  *      significantly over the page.  Local skew determination
00080  *      is not very important except for locating lines of
00081  *      handwritten text that may be mixed with printed text.
00082  */
00083 
00084 #include <math.h>
00085 #include "allheaders.h"
00086 
00087     /* Default sweep angle parameters for pixFindSkew() */
00088 static const l_float32  DEFAULT_SWEEP_RANGE = 7.;    /* degrees */
00089 static const l_float32  DEFAULT_SWEEP_DELTA = 1.;    /* degrees */
00090 
00091     /* Default final angle difference parameter for binary
00092      * search in pixFindSkew().  The expected accuracy is
00093      * not better than the inverse image width in pixels,
00094      * say, 1/2000 radians, or about 0.03 degrees. */
00095 static const l_float32  DEFAULT_MINBS_DELTA = 0.01;  /* degrees */
00096 
00097     /* Default scale factors for pixFindSkew() */
00098 static const l_int32  DEFAULT_SWEEP_REDUCTION = 4;  /* sweep part; 4 is good */
00099 static const l_int32  DEFAULT_BS_REDUCTION = 2;  /* binary search part */
00100 
00101     /* Minimum angle for deskewing in pixDeskew() */
00102 static const l_float32  MIN_DESKEW_ANGLE = 0.1;  /* degree */
00103 
00104     /* Minimum allowed confidence (ratio) for deskewing in pixDeskew() */
00105 static const l_float32  MIN_ALLOWED_CONFIDENCE = 3.0;
00106 
00107     /* Minimum allowed maxscore to give nonzero confidence */
00108 static const l_int32  MIN_VALID_MAXSCORE = 10000;
00109 
00110     /* Constant setting threshold for minimum allowed minscore
00111      * to give nonzero confidence; multiply this constant by
00112      *  (height * width^2) */
00113 static const l_float32  MINSCORE_THRESHOLD_CONSTANT = 0.000002;
00114 
00115     /* Default binarization threshold value */
00116 static const l_int32  DEFAULT_BINARY_THRESHOLD = 130;
00117 
00118 #ifndef  NO_CONSOLE_IO
00119 #define  DEBUG_PRINT_SCORES     0
00120 #define  DEBUG_PRINT_SWEEP      0
00121 #define  DEBUG_PRINT_BINARY     0
00122 #define  DEBUG_PRINT_ORTH       0
00123 #define  DEBUG_THRESHOLD        0
00124 #define  DEBUG_PLOT_SCORES      0
00125 #endif  /* ~NO_CONSOLE_IO */
00126 
00127 
00128 
00129 /*-----------------------------------------------------------------------*
00130  *                       Top-level deskew interfaces                     *
00131  *-----------------------------------------------------------------------*/
00132 /*!
00133  *  pixDeskew()
00134  *
00135  *      Input:  pixs (any depth)
00136  *              redsearch (for binary search: reduction factor = 1, 2 or 4;
00137  *                         use 0 for default)
00138  *      Return: pixd (deskewed pix), or null on error
00139  *
00140  *  Notes:
00141  *      (1) This binarizes if necessary and finds the skew angle.  If the
00142  *          angle is large enough and there is sufficient confidence,
00143  *          it returns a deskewed image; otherwise, it returns a clone.
00144  */
00145 PIX *
00146 pixDeskew(PIX     *pixs,
00147           l_int32  redsearch)
00148 {
00149     PROCNAME("pixDeskew");
00150 
00151     if (!pixs)
00152         return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
00153     if (redsearch == 0)
00154         redsearch = DEFAULT_BS_REDUCTION;
00155     else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
00156         return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
00157 
00158     return pixDeskewGeneral(pixs, 0, 0.0, 0.0, redsearch, 0, NULL, NULL);
00159 }
00160 
00161 
00162 /*!
00163  *  pixFindSkewAndDeskew()
00164  *
00165  *      Input:  pixs (any depth)
00166  *              redsearch (for binary search: reduction factor = 1, 2 or 4;
00167  *                         use 0 for default)
00168  *              &angle   (<optional return> angle required to deskew,
00169  *                        in degrees; use NULL to skip)
00170  *              &conf    (<optional return> conf value is ratio
00171  *                        of max/min scores; use NULL to skip)
00172  *      Return: pixd (deskewed pix), or null on error
00173  *
00174  *  Notes:
00175  *      (1) This binarizes if necessary and finds the skew angle.  If the
00176  *          angle is large enough and there is sufficient confidence,
00177  *          it returns a deskewed image; otherwise, it returns a clone.
00178  */
00179 PIX *
00180 pixFindSkewAndDeskew(PIX        *pixs,
00181                      l_int32     redsearch,
00182                      l_float32  *pangle,
00183                      l_float32  *pconf)
00184 {
00185     PROCNAME("pixFindSkewAndDeskew");
00186 
00187     if (!pixs)
00188         return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
00189     if (redsearch == 0)
00190         redsearch = DEFAULT_BS_REDUCTION;
00191     else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
00192         return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
00193 
00194     return pixDeskewGeneral(pixs, 0, 0.0, 0.0, redsearch, 0, pangle, pconf);
00195 }
00196 
00197 
00198 /*!
00199  *  pixDeskewGeneral()
00200  *
00201  *      Input:  pixs  (any depth)
00202  *              redsweep  (for linear search: reduction factor = 1, 2 or 4;
00203  *                         use 0 for default)
00204  *              sweeprange (in degrees in each direction from 0;
00205  *                          use 0.0 for default)
00206  *              sweepdelta (in degrees; use 0.0 for default)
00207  *              redsearch  (for binary search: reduction factor = 1, 2 or 4;
00208  *                          use 0 for default;)
00209  *              thresh (for binarizing the image; use 0 for default)
00210  *              &angle   (<optional return> angle required to deskew,
00211  *                        in degrees; use NULL to skip)
00212  *              &conf    (<optional return> conf value is ratio
00213  *                        of max/min scores; use NULL to skip)
00214  *      Return: pixd (deskewed pix), or null on error
00215  *
00216  *  Notes:
00217  *      (1) This binarizes if necessary and finds the skew angle.  If the
00218  *          angle is large enough and there is sufficient confidence,
00219  *          it returns a deskewed image; otherwise, it returns a clone.
00220  */
00221 PIX *
00222 pixDeskewGeneral(PIX        *pixs,
00223                  l_int32     redsweep,
00224                  l_float32   sweeprange,
00225                  l_float32   sweepdelta,
00226                  l_int32     redsearch,
00227                  l_int32     thresh,
00228                  l_float32  *pangle,
00229                  l_float32  *pconf)
00230 {
00231 l_int32    ret, depth;
00232 l_float32  angle, conf, deg2rad;
00233 PIX       *pixb, *pixd;
00234 
00235     PROCNAME("pixDeskewGeneral");
00236 
00237     if (!pixs)
00238         return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
00239     if (redsweep == 0)
00240         redsweep = DEFAULT_SWEEP_REDUCTION;
00241     else if (redsweep != 1 && redsweep != 2 && redsweep != 4)
00242         return (PIX *)ERROR_PTR("redsweep not in {1,2,4}", procName, NULL);
00243     if (sweeprange == 0.0)
00244         sweeprange = DEFAULT_SWEEP_RANGE;
00245     if (sweepdelta == 0.0)
00246         sweepdelta = DEFAULT_SWEEP_DELTA;
00247     if (redsearch == 0)
00248         redsearch = DEFAULT_BS_REDUCTION;
00249     else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
00250         return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
00251     if (thresh == 0)
00252         thresh = DEFAULT_BINARY_THRESHOLD;
00253 
00254     deg2rad = 3.1415926535 / 180.;
00255 
00256         /* Binarize if necessary */
00257     depth = pixGetDepth(pixs);
00258     if (depth == 1)
00259         pixb = pixClone(pixs);
00260     else
00261         pixb = pixConvertTo1(pixs, thresh);
00262 
00263         /* Use the 1 bpp image to find the skew */
00264     ret = pixFindSkewSweepAndSearch(pixb, &angle, &conf, redsweep, redsearch,
00265                                     sweeprange, sweepdelta,
00266                                     DEFAULT_MINBS_DELTA);
00267     pixDestroy(&pixb);
00268     if (pangle)
00269         *pangle = angle;
00270     if (pconf)
00271         *pconf = conf;
00272     if (ret)
00273         return pixClone(pixs);
00274 
00275     if (L_ABS(angle) < MIN_DESKEW_ANGLE || conf < MIN_ALLOWED_CONFIDENCE)
00276         return pixClone(pixs);
00277 
00278     if ((pixd = pixRotate(pixs, deg2rad * angle, L_ROTATE_AREA_MAP,
00279                           L_BRING_IN_WHITE, 0, 0)) == NULL)
00280         return pixClone(pixs);
00281     else
00282         return pixd;
00283 }
00284 
00285 
00286 /*-----------------------------------------------------------------------*
00287  *                  Simple top-level angle-finding interface             *
00288  *-----------------------------------------------------------------------*/
00289 /*!
00290  *  pixFindSkew()
00291  *
00292  *      Input:  pixs  (1 bpp)
00293  *              &angle   (<return> angle required to deskew, in degrees)
00294  *              &conf    (<return> confidence value is ratio max/min scores)
00295  *      Return: 0 if OK, 1 on error or if angle measurment not valid
00296  *
00297  *  Notes:
00298  *      (1) This is a simple high-level interface, that uses default
00299  *          values of the parameters for reasonable speed and accuracy.
00300  *      (2) The angle returned is the negative of the skew angle of
00301  *          the image.  It is the angle required for deskew.
00302  *          Clockwise rotations are positive angles.
00303  */
00304 l_int32
00305 pixFindSkew(PIX        *pixs,
00306             l_float32  *pangle,
00307             l_float32  *pconf)
00308 {
00309     PROCNAME("pixFindSkew");
00310 
00311     if (!pixs)
00312         return ERROR_INT("pixs not defined", procName, 1);
00313     if (pixGetDepth(pixs) != 1)
00314         return ERROR_INT("pixs not 1 bpp", procName, 1);
00315     if (!pangle)
00316         return ERROR_INT("&angle not defined", procName, 1);
00317     if (!pconf)
00318         return ERROR_INT("&conf not defined", procName, 1);
00319 
00320     return pixFindSkewSweepAndSearch(pixs, pangle, pconf,
00321                                      DEFAULT_SWEEP_REDUCTION,
00322                                      DEFAULT_BS_REDUCTION,
00323                                      DEFAULT_SWEEP_RANGE,
00324                                      DEFAULT_SWEEP_DELTA,
00325                                      DEFAULT_MINBS_DELTA);
00326 }
00327 
00328 
00329 /*-----------------------------------------------------------------------*
00330  *                       Basic angle-finding functions                   *
00331  *-----------------------------------------------------------------------*/
00332 /*!
00333  *  pixFindSkewSweep()
00334  *
00335  *      Input:  pixs  (1 bpp)
00336  *              &angle   (<return> angle required to deskew, in degrees)
00337  *              reduction  (factor = 1, 2, 4 or 8)
00338  *              sweeprange   (half the full range; assumed about 0; in degrees)
00339  *              sweepdelta   (angle increment of sweep; in degrees)
00340  *      Return: 0 if OK, 1 on error or if angle measurment not valid
00341  *
00342  *  Notes:
00343  *      (1) This examines the 'score' for skew angles with equal intervals.
00344  *      (2) Caller must check the return value for validity of the result.
00345  */
00346 l_int32
00347 pixFindSkewSweep(PIX        *pixs,
00348                  l_float32  *pangle,
00349                  l_int32     reduction,
00350                  l_float32   sweeprange,
00351                  l_float32   sweepdelta)
00352 {
00353 l_int32    ret, bzero, i, nangles;
00354 l_float32  deg2rad, theta;
00355 l_float32  sum, maxscore, maxangle;
00356 NUMA      *natheta, *nascore;
00357 PIX       *pix, *pixt;
00358 
00359     PROCNAME("pixFindSkewSweep");
00360 
00361     if (!pixs)
00362         return ERROR_INT("pixs not defined", procName, 1);
00363     if (pixGetDepth(pixs) != 1)
00364         return ERROR_INT("pixs not 1 bpp", procName, 1);
00365     if (!pangle)
00366         return ERROR_INT("&angle not defined", procName, 1);
00367     if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
00368         return ERROR_INT("reduction must be in {1,2,4,8}", procName, 1);
00369 
00370     *pangle = 0.0;  /* init */
00371     deg2rad = 3.1415926535 / 180.;
00372     ret = 0;
00373 
00374         /* Generate reduced image, if requested */
00375     if (reduction == 1)
00376         pix = pixClone(pixs);
00377     else if (reduction == 2)
00378         pix = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
00379     else if (reduction == 4)
00380         pix = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
00381     else /* reduction == 8 */
00382         pix = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0);
00383 
00384     pixZero(pix, &bzero);
00385     if (bzero) {
00386         pixDestroy(&pix);
00387         return 1;
00388     }
00389 
00390     nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1);
00391     natheta = numaCreate(nangles);
00392     nascore = numaCreate(nangles);
00393     pixt = pixCreateTemplate(pix);
00394 
00395     if (!pix || !pixt) {
00396         ret = ERROR_INT("pix and pixt not both made", procName, 1);
00397         goto cleanup;
00398     }
00399     if (!natheta || !nascore) {
00400         ret = ERROR_INT("natheta and nascore not both made", procName, 1);
00401         goto cleanup;
00402     }
00403 
00404     for (i = 0; i < nangles; i++) {
00405         theta = -sweeprange + i * sweepdelta;   /* degrees */
00406 
00407             /* Shear pix about the UL corner and put the result in pixt */
00408         pixVShearCorner(pixt, pix, deg2rad * theta, L_BRING_IN_WHITE);
00409 
00410             /* Get score */
00411         pixFindDifferentialSquareSum(pixt, &sum);
00412 
00413 #if  DEBUG_PRINT_SCORES
00414         L_INFO_FLOAT2("sum(%7.2f) = %7.0f", procName, theta, sum);
00415 #endif  /* DEBUG_PRINT_SCORES */
00416 
00417             /* Save the result in the output arrays */
00418         numaAddNumber(nascore, sum);
00419         numaAddNumber(natheta, theta);
00420     }
00421 
00422         /* Find the location of the maximum (i.e., the skew angle)
00423          * by fitting the largest data point and its two neighbors
00424          * to a quadratic, using lagrangian interpolation.  */
00425     numaFitMax(nascore, &maxscore, natheta, &maxangle);
00426     *pangle = maxangle;
00427 
00428 #if  DEBUG_PRINT_SWEEP
00429     L_INFO_FLOAT2(" From sweep: angle = %7.3f, score = %7.3f", procName,
00430                   maxangle, maxscore);
00431 #endif  /* DEBUG_PRINT_SWEEP */
00432 
00433 #if  DEBUG_PLOT_SCORES
00434         /* Plot the result -- the scores versus rotation angle --
00435          * using gnuplot with GPLOT_LINES (lines connecting data points).
00436          * The GPLOT data structure is first created, with the
00437          * appropriate data incorporated from the two input NUMAs,
00438          * and then the function gplotMakeOutput() uses gnuplot to
00439          * generate the output plot.  This can be either a .png file
00440          * or a .ps file, depending on whether you use GPLOT_PNG
00441          * or GPLOT_PS.  */
00442     {GPLOT  *gplot;
00443         gplot = gplotCreate("sweep_output", GPLOT_PNG,
00444                     "Sweep. Variance of difference of ON pixels vs. angle",
00445                     "angle (deg)", "score");
00446         gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1");
00447         gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2");
00448         gplotMakeOutput(gplot);
00449         gplotDestroy(&gplot);
00450     }
00451 #endif  /* DEBUG_PLOT_SCORES */
00452 
00453 cleanup:
00454     pixDestroy(&pix);
00455     pixDestroy(&pixt);
00456     numaDestroy(&nascore);
00457     numaDestroy(&natheta);
00458     return 0;
00459 }
00460 
00461 
00462 /*!
00463  *  pixFindSkewSweepAndSearch()
00464  *
00465  *      Input:  pixs  (1 bpp)
00466  *              &angle   (<return> angle required to deskew; in degrees)
00467  *              &conf    (<return> confidence given by ratio of max/min score)
00468  *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
00469  *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
00470  *                          and must not exceed redsweep)
00471  *              sweeprange   (half the full range, assumed about 0; in degrees)
00472  *              sweepdelta   (angle increment of sweep; in degrees)
00473  *              minbsdelta   (min binary search increment angle; in degrees)
00474  *      Return: 0 if OK, 1 on error or if angle measurment not valid
00475  *
00476  *  Notes:
00477  *      (1) This finds the skew angle, doing first a sweep through a set
00478  *          of equal angles, and then doing a binary search until
00479  *          convergence.
00480  *      (2) Caller must check the return value for validity of the result.
00481  *      (3) In computing the differential line sum variance score, we sum
00482  *          the result over scanlines, but we always skip:
00483  *           - at least one scanline
00484  *           - not more than 10% of the image height
00485  *           - not more than 5% of the image width
00486  *      (4) See also notes in pixFindSkewSweepAndSearchScore()
00487  */
00488 l_int32
00489 pixFindSkewSweepAndSearch(PIX        *pixs,
00490                           l_float32  *pangle,
00491                           l_float32  *pconf,
00492                           l_int32     redsweep,
00493                           l_int32     redsearch,
00494                           l_float32   sweeprange,
00495                           l_float32   sweepdelta,
00496                           l_float32   minbsdelta)
00497 {
00498     return pixFindSkewSweepAndSearchScore(pixs, pangle, pconf, NULL,
00499                                           redsweep, redsearch, 0.0, sweeprange, 
00500                                           sweepdelta, minbsdelta);
00501 }
00502 
00503 
00504 /*!
00505  *  pixFindSkewSweepAndSearchScore()
00506  *
00507  *      Input:  pixs  (1 bpp)
00508  *              &angle   (<return> angle required to deskew; in degrees)
00509  *              &conf    (<return> confidence given by ratio of max/min score)
00510  *              &endscore (<optional return> max score; use NULL to ignore)
00511  *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
00512  *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
00513  *                          and must not exceed redsweep)
00514  *              sweepcenter  (angle about which sweep is performed; in degrees)
00515  *              sweeprange   (half the full range, taken about sweepcenter;
00516  *                            in degrees)
00517  *              sweepdelta   (angle increment of sweep; in degrees)
00518  *              minbsdelta   (min binary search increment angle; in degrees)
00519  *      Return: 0 if OK, 1 on error or if angle measurment not valid
00520  *
00521  *  Notes:
00522  *      (1) This finds the skew angle, doing first a sweep through a set
00523  *          of equal angles, and then doing a binary search until convergence.
00524  *      (2) There are two built-in constants that determine if the 
00525  *          returned confidence is nonzero:
00526  *            - MIN_VALID_MAXSCORE (minimum allowed maxscore)
00527  *            - MINSCORE_THRESHOLD_CONSTANT (determines minimum allowed
00528  *                 minscore, by multiplying by (height * width^2)
00529  *          If either of these conditions is not satisfied, the returned
00530  *          confidence value will be zero.  The maxscore is optionally
00531  *          returned in this function to allow evaluation of the
00532  *          resulting angle by a method that is independent of the
00533  *          returned confidence value.
00534  *      (3) The larger the confidence value, the greater the probability
00535  *          that the proper alignment is given by the angle that maximizes
00536  *          variance.  It should be compared to a threshold, which depends
00537  *          on the application.  Values between 3.0 and 6.0 are common.
00538  *      (4) By default, the shear is about the UL corner.
00539  */
00540 l_int32
00541 pixFindSkewSweepAndSearchScore(PIX        *pixs,
00542                                l_float32  *pangle,
00543                                l_float32  *pconf,
00544                                l_float32  *pendscore,
00545                                l_int32     redsweep,
00546                                l_int32     redsearch,
00547                                l_float32   sweepcenter,
00548                                l_float32   sweeprange,
00549                                l_float32   sweepdelta,
00550                                l_float32   minbsdelta)
00551 {
00552     return pixFindSkewSweepAndSearchScorePivot(pixs, pangle, pconf, pendscore,
00553                                                redsweep, redsearch, 0.0,
00554                                                sweeprange, sweepdelta,
00555                                                minbsdelta,
00556                                                L_SHEAR_ABOUT_CORNER);
00557 }
00558 
00559 
00560 /*!
00561  *  pixFindSkewSweepAndSearchScorePivot()
00562  *
00563  *      Input:  pixs  (1 bpp)
00564  *              &angle   (<return> angle required to deskew; in degrees)
00565  *              &conf    (<return> confidence given by ratio of max/min score)
00566  *              &endscore (<optional return> max score; use NULL to ignore)
00567  *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
00568  *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
00569  *                          and must not exceed redsweep)
00570  *              sweepcenter  (angle about which sweep is performed; in degrees)
00571  *              sweeprange   (half the full range, taken about sweepcenter;
00572  *                            in degrees)
00573  *              sweepdelta   (angle increment of sweep; in degrees)
00574  *              minbsdelta   (min binary search increment angle; in degrees)
00575  *              pivot  (L_SHEAR_ABOUT_CORNER, L_SHEAR_ABOUT_CENTER)
00576  *      Return: 0 if OK, 1 on error or if angle measurment not valid
00577  *
00578  *  Notes:
00579  *      (1) See notes in pixFindSkewSweepAndSearchScore().
00580  *      (2) This allows choice of shear pivoting from either the UL corner
00581  *          or the center.  For small angles, the ability to discriminate
00582  *          angles is better with shearing from the UL corner.  However,
00583  *          for large angles (say, greater than 20 degrees), it is better
00584  *          to shear about the center because a shear from the UL corner
00585  *          loses too much of the image.
00586  */
00587 l_int32
00588 pixFindSkewSweepAndSearchScorePivot(PIX        *pixs,
00589                                     l_float32  *pangle,
00590                                     l_float32  *pconf,
00591                                     l_float32  *pendscore,
00592                                     l_int32     redsweep,
00593                                     l_int32     redsearch,
00594                                     l_float32   sweepcenter,
00595                                     l_float32   sweeprange,
00596                                     l_float32   sweepdelta,
00597                                     l_float32   minbsdelta,
00598                                     l_int32     pivot)
00599 {
00600 l_int32    ret, bzero, i, nangles, n, ratio, maxindex, minloc;
00601 l_int32    width, height;
00602 l_float32  deg2rad, theta, delta;
00603 l_float32  sum, maxscore, maxangle;
00604 l_float32  centerangle, leftcenterangle, rightcenterangle;
00605 l_float32  lefttemp, righttemp;
00606 l_float32  bsearchscore[5];
00607 l_float32  minscore, minthresh;
00608 l_float32  rangeleft;
00609 NUMA      *natheta, *nascore;
00610 PIX       *pixsw, *pixsch, *pixt1, *pixt2;
00611 
00612     PROCNAME("pixFindSkewSweepAndSearchScorePivot");
00613 
00614     if (!pixs)
00615         return ERROR_INT("pixs not defined", procName, 1);
00616     if (pixGetDepth(pixs) != 1)
00617         return ERROR_INT("pixs not 1 bpp", procName, 1);
00618     if (!pangle)
00619         return ERROR_INT("&angle not defined", procName, 1);
00620     if (!pconf)
00621         return ERROR_INT("&conf not defined", procName, 1);
00622     if (redsweep != 1 && redsweep != 2 && redsweep != 4 && redsweep != 8)
00623         return ERROR_INT("redsweep must be in {1,2,4,8}", procName, 1);
00624     if (redsearch != 1 && redsearch != 2 && redsearch != 4 && redsearch != 8)
00625         return ERROR_INT("redsearch must be in {1,2,4,8}", procName, 1);
00626     if (redsearch > redsweep)
00627         return ERROR_INT("redsearch must not exceed redsweep", procName, 1);
00628     if (pivot != L_SHEAR_ABOUT_CORNER && pivot != L_SHEAR_ABOUT_CENTER)
00629         return ERROR_INT("invalid pivot", procName, 1);
00630 
00631     *pangle = 0.0;
00632     *pconf = 0.0;
00633     deg2rad = 3.1415926535 / 180.;
00634     ret = 0;
00635 
00636         /* Generate reduced image for binary search, if requested */
00637     if (redsearch == 1)
00638         pixsch = pixClone(pixs);
00639     else if (redsearch == 2)
00640         pixsch = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
00641     else if (redsearch == 4)
00642         pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
00643     else  /* redsearch == 8 */
00644         pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0);
00645 
00646     pixZero(pixsch, &bzero);
00647     if (bzero) {
00648         pixDestroy(&pixsch);
00649         return 1;
00650     }
00651 
00652         /* Generate reduced image for sweep, if requested */
00653     ratio = redsweep / redsearch;
00654     if (ratio == 1)
00655         pixsw = pixClone(pixsch);
00656     else {  /* ratio > 1 */
00657         if (ratio == 2)
00658             pixsw = pixReduceRankBinaryCascade(pixsch, 1, 0, 0, 0);
00659         else if (ratio == 4)
00660             pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 0, 0);
00661         else  /* ratio == 8 */
00662             pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 2, 0);
00663     }
00664 
00665     pixt1 = pixCreateTemplate(pixsw);
00666     if (ratio == 1)
00667         pixt2 = pixClone(pixt1);
00668     else
00669         pixt2 = pixCreateTemplate(pixsch);
00670 
00671     nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1);
00672     natheta = numaCreate(nangles);
00673     nascore = numaCreate(nangles);
00674 
00675     if (!pixsch || !pixsw) {
00676         ret = ERROR_INT("pixsch and pixsw not both made", procName, 1);
00677         goto cleanup;
00678     }
00679     if (!pixt1 || !pixt2) {
00680         ret = ERROR_INT("pixt1 and pixt2 not both made", procName, 1);
00681         goto cleanup;
00682     }
00683     if (!natheta || !nascore) {
00684         ret = ERROR_INT("natheta and nascore not both made", procName, 1);
00685         goto cleanup;
00686     }
00687 
00688         /* Do sweep */
00689     rangeleft = sweepcenter - sweeprange;
00690     for (i = 0; i < nangles; i++) {
00691         theta = rangeleft + i * sweepdelta;   /* degrees */
00692 
00693             /* Shear pix and put the result in pixt1 */
00694         if (pivot == L_SHEAR_ABOUT_CORNER)
00695             pixVShearCorner(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE);
00696         else
00697             pixVShearCenter(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE);
00698 
00699             /* Get score */
00700         pixFindDifferentialSquareSum(pixt1, &sum);
00701 
00702 #if  DEBUG_PRINT_SCORES
00703         L_INFO_FLOAT2("sum(%7.2f) = %7.0f", procName, theta, sum);
00704 #endif  /* DEBUG_PRINT_SCORES */
00705 
00706             /* Save the result in the output arrays */
00707         numaAddNumber(nascore, sum);
00708         numaAddNumber(natheta, theta);
00709     }
00710 
00711         /* Find the largest of the set (maxscore at maxangle) */
00712     numaGetMax(nascore, &maxscore, &maxindex);
00713     numaGetFValue(natheta, maxindex, &maxangle);
00714 
00715 #if  DEBUG_PRINT_SWEEP
00716     L_INFO_FLOAT2(" From sweep: angle = %7.3f, score = %7.3f", procName,
00717                   maxangle, maxscore);
00718 #endif  /* DEBUG_PRINT_SWEEP */
00719 
00720 #if  DEBUG_PLOT_SCORES
00721         /* Plot the sweep result -- the scores versus rotation angle --
00722          * using gnuplot with GPLOT_LINES (lines connecting data points). */
00723     {GPLOT  *gplot;
00724         gplot = gplotCreate("sweep_output", GPLOT_PNG,
00725                     "Sweep. Variance of difference of ON pixels vs. angle",
00726                     "angle (deg)", "score");
00727         gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1");
00728         gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2");
00729         gplotMakeOutput(gplot);
00730         gplotDestroy(&gplot);
00731     }
00732 #endif  /* DEBUG_PLOT_SCORES */
00733 
00734         /* Check if the max is at the end of the sweep. */
00735     n = numaGetCount(natheta);
00736     if (maxindex == 0 || maxindex == n - 1) {
00737         L_WARNING("max found at sweep edge", procName);
00738         goto cleanup;
00739     }
00740 
00741         /* Empty the numas for re-use */
00742     numaEmpty(nascore);
00743     numaEmpty(natheta);
00744 
00745         /* Do binary search to find skew angle.
00746          * First, set up initial three points. */
00747     centerangle = maxangle;
00748     if (pivot == L_SHEAR_ABOUT_CORNER) {
00749         pixVShearCorner(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE);
00750         pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]);
00751         pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle - sweepdelta),
00752                         L_BRING_IN_WHITE);
00753         pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]);
00754         pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle + sweepdelta),
00755                         L_BRING_IN_WHITE);
00756         pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]);
00757     }
00758     else {
00759         pixVShearCenter(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE);
00760         pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]);
00761         pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle - sweepdelta),
00762                         L_BRING_IN_WHITE);
00763         pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]);
00764         pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle + sweepdelta),
00765                         L_BRING_IN_WHITE);
00766         pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]);
00767     }
00768 
00769     numaAddNumber(nascore, bsearchscore[2]);
00770     numaAddNumber(natheta, centerangle);
00771     numaAddNumber(nascore, bsearchscore[0]);
00772     numaAddNumber(natheta, centerangle - sweepdelta);
00773     numaAddNumber(nascore, bsearchscore[4]);
00774     numaAddNumber(natheta, centerangle + sweepdelta);
00775 
00776         /* Start the search */
00777     delta = 0.5 * sweepdelta;
00778     while (delta >= minbsdelta)
00779     {
00780             /* Get the left intermediate score */
00781         leftcenterangle = centerangle - delta;
00782         if (pivot == L_SHEAR_ABOUT_CORNER)
00783             pixVShearCorner(pixt2, pixsch, deg2rad * leftcenterangle,
00784                             L_BRING_IN_WHITE);
00785         else
00786             pixVShearCenter(pixt2, pixsch, deg2rad * leftcenterangle,
00787                             L_BRING_IN_WHITE);
00788         pixFindDifferentialSquareSum(pixt2, &bsearchscore[1]);
00789         numaAddNumber(nascore, bsearchscore[1]);
00790         numaAddNumber(natheta, leftcenterangle);
00791         
00792             /* Get the right intermediate score */
00793         rightcenterangle = centerangle + delta;
00794         if (pivot == L_SHEAR_ABOUT_CORNER)
00795             pixVShearCorner(pixt2, pixsch, deg2rad * rightcenterangle,
00796                             L_BRING_IN_WHITE);
00797         else
00798             pixVShearCenter(pixt2, pixsch, deg2rad * rightcenterangle,
00799                             L_BRING_IN_WHITE);
00800         pixFindDifferentialSquareSum(pixt2, &bsearchscore[3]);
00801         numaAddNumber(nascore, bsearchscore[3]);
00802         numaAddNumber(natheta, rightcenterangle);
00803         
00804             /* Find the maximum of the five scores and its location.
00805              * Note that the maximum must be in the center
00806              * three values, not in the end two. */
00807         maxscore = bsearchscore[1];
00808         maxindex = 1;
00809         for (i = 2; i < 4; i++) {
00810             if (bsearchscore[i] > maxscore) {
00811                 maxscore = bsearchscore[i];
00812                 maxindex = i;
00813             }
00814         }
00815 
00816             /* Set up score array to interpolate for the next iteration */
00817         lefttemp = bsearchscore[maxindex - 1];
00818         righttemp = bsearchscore[maxindex + 1];
00819         bsearchscore[2] = maxscore;
00820         bsearchscore[0] = lefttemp;
00821         bsearchscore[4] = righttemp;
00822 
00823             /* Get new center angle and delta for next iteration */
00824         centerangle = centerangle + delta * (maxindex - 2);
00825         delta = 0.5 * delta;
00826     }
00827     *pangle = centerangle;
00828 
00829 #if  DEBUG_PRINT_SCORES
00830     L_INFO_FLOAT(" Binary search score = %7.3f", procName, bsearchscore[2]);
00831 #endif  /* DEBUG_PRINT_SCORES */
00832 
00833     if (pendscore)  /* save if requested */
00834         *pendscore = bsearchscore[2];
00835 
00836         /* Return the ratio of Max score over Min score
00837          * as a confidence value.  Don't trust if the Min score
00838          * is too small, which can happen if the image is all black
00839          * with only a few white pixels interspersed.  In that case,
00840          * we get a contribution from the top and bottom edges when
00841          * vertically sheared, but this contribution becomes zero when
00842          * the shear angle is zero.  For zero shear angle, the only
00843          * contribution will be from the white pixels.  We expect that
00844          * the signal goes as the product of the (height * width^2),
00845          * so we compute a (hopefully) normalized minimum threshold as
00846          * a function of these dimensions.  */
00847     numaGetMin(nascore, &minscore, &minloc);
00848     width = pixGetWidth(pixsch);
00849     height = pixGetHeight(pixsch);
00850     minthresh = MINSCORE_THRESHOLD_CONSTANT * width * width * height;
00851 
00852 #if  DEBUG_THRESHOLD
00853     L_INFO_FLOAT2(" minthresh = %10.2f, minscore = %10.2f", procName,
00854             minthresh, minscore);
00855     L_INFO_FLOAT(" maxscore = %10.2f", procName, maxscore);
00856 #endif  /* DEBUG_THRESHOLD */
00857 
00858     if (minscore > minthresh)
00859         *pconf = maxscore / minscore;
00860     else
00861         *pconf = 0.0;
00862 
00863         /* Don't trust it if too close to the edge of the sweep
00864          * range or if maxscore is small */
00865     if ((centerangle > rangeleft + 2 * sweeprange - sweepdelta) ||
00866         (centerangle < rangeleft + sweepdelta) ||
00867         (maxscore < MIN_VALID_MAXSCORE))
00868         *pconf = 0.0;
00869 
00870 #if  DEBUG_PRINT_BINARY
00871     fprintf(stderr, "Binary search: angle = %7.3f, score ratio = %6.2f\n",
00872             *pangle, *pconf);
00873     fprintf(stderr, "               max score = %8.0f\n", maxscore);
00874 #endif  /* DEBUG_PRINT_BINARY */
00875 
00876 #if  DEBUG_PLOT_SCORES
00877         /* Plot the result -- the scores versus rotation angle --
00878          * using gnuplot with GPLOT_POINTS.  Because the data
00879          * points are not ordered by theta (increasing or decreasing),
00880          * using GPLOT_LINES would be confusing! */
00881     {GPLOT  *gplot;
00882         gplot = gplotCreate("search_output", GPLOT_PNG,
00883                 "Binary search.  Variance of difference of ON pixels vs. angle",
00884                 "angle (deg)", "score");
00885         gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot1");
00886         gplotMakeOutput(gplot);
00887         gplotDestroy(&gplot);
00888     }
00889 #endif  /* DEBUG_PLOT_SCORES */
00890 
00891 cleanup:
00892     pixDestroy(&pixsw);
00893     pixDestroy(&pixsch);
00894     pixDestroy(&pixt1);
00895     pixDestroy(&pixt2);
00896     numaDestroy(&nascore);
00897     numaDestroy(&natheta);
00898     return ret;
00899 }
00900 
00901 
00902 /*---------------------------------------------------------------------*
00903  *    Search over arbitrary range of angles in orthogonal directions   *
00904  *---------------------------------------------------------------------*/
00905 /*
00906  *   pixFindSkewOrthogonalRange()
00907  *
00908  *      Input:  pixs  (1 bpp)
00909  *              &angle  (<return> angle required to deskew; in degrees cw)
00910  *              &conf   (<return> confidence given by ratio of max/min score)
00911  *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
00912  *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
00913  *                          and must not exceed redsweep)
00914  *              sweeprange  (half the full range in each orthogonal
00915  *                           direction, taken about 0, in degrees)
00916  *              sweepdelta   (angle increment of sweep; in degrees)
00917  *              minbsdelta   (min binary search increment angle; in degrees)
00918  *              confprior  (amount by which confidence of 90 degree rotated
00919  *                          result is reduced when comparing with unrotated
00920  *                          confidence value)
00921  *      Return: 0 if OK, 1 on error or if angle measurment not valid
00922  *
00923  *  Notes:
00924  *      (1) This searches for the skew angle, first in the range
00925  *          [-sweeprange, sweeprange], and then in
00926  *          [90 - sweeprange, 90 + sweeprange], with angles measured
00927  *          clockwise.  For exploring the full range of possibilities,
00928  *          suggest using sweeprange = 47.0 degrees, giving some overlap
00929  *          at 45 and 135 degrees.  From these results, and discounting
00930  *          the the second confidence by @confprior, it selects the
00931  *          angle for maximal differential variance.  If the angle
00932  *          is larger than pi/4, the angle found after 90 degree rotation
00933  *          is selected.
00934  *      (2) The larger the confidence value, the greater the probability
00935  *          that the proper alignment is given by the angle that maximizes
00936  *          variance.  It should be compared to a threshold, which depends
00937  *          on the application.  Values between 3.0 and 6.0 are common.
00938  *      (3) Allowing for both portrait and landscape searches is more
00939  *          difficult, because if the signal from the text lines is weak,
00940  *          a signal from vertical rules can be larger!
00941  *          The most difficult documents to deskew have some or all of:
00942  *            (a) Multiple columns, not aligned
00943  *            (b) Black lines along the vertical edges
00944  *            (c) Text from two pages, and at different angles
00945  *          Rule of thumb for resolution:
00946  *            (a) If the margins are clean, you can work at 75 ppi,
00947  *                although 100 ppi is safer.
00948  *            (b) If there are vertical lines in the margins, do not
00949  *                work below 150 ppi.  The signal from the text lines must
00950  *                exceed that from the margin lines.
00951  *      (4) Choosing the @confprior parameter depends on knowing something
00952  *          about the source of image.  However, we're not using
00953  *          real probabilities here, so its use is qualitative.
00954  *          If landscape and portrait are equally likely, use
00955  *          @confprior = 0.0.  If the likelihood of portrait (non-rotated)
00956  *          is 100 times higher than that of landscape, we want to reduce
00957  *          the chance that we rotate to landscape in a situation where
00958  *          the landscape signal is accidentally larger than the
00959  *          portrait signal.  To do this use a positive value of
00960  *          @confprior; say 1.5.
00961  */
00962 l_int32
00963 pixFindSkewOrthogonalRange(PIX        *pixs,
00964                            l_float32  *pangle,
00965                            l_float32  *pconf,
00966                            l_int32     redsweep,
00967                            l_int32     redsearch,
00968                            l_float32   sweeprange,
00969                            l_float32   sweepdelta,
00970                            l_float32   minbsdelta,
00971                            l_float32   confprior)
00972 {
00973 l_float32  angle1, conf1, score1, angle2, conf2, score2;
00974 PIX       *pixr;
00975 
00976     PROCNAME("pixFindSkewOrthogonalRange");
00977 
00978     if (!pangle || !pconf)
00979         return ERROR_INT("&angle and &conf not both defined", procName, 1);
00980     *pangle = *pconf = 0.0;
00981     if (!pixs || pixGetDepth(pixs) != 1)
00982         return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
00983 
00984     pixFindSkewSweepAndSearchScorePivot(pixs, &angle1, &conf1, &score1,
00985                                         redsweep, redsearch, 0.0,
00986                                         sweeprange, sweepdelta, minbsdelta,
00987                                         L_SHEAR_ABOUT_CORNER);
00988     pixr = pixRotateOrth(pixs, 1);
00989     pixFindSkewSweepAndSearchScorePivot(pixr, &angle2, &conf2, &score2,
00990                                         redsweep, redsearch, 0.0,
00991                                         sweeprange, sweepdelta, minbsdelta,
00992                                         L_SHEAR_ABOUT_CORNER);
00993     pixDestroy(&pixr);
00994 
00995     if (conf1 > conf2 - confprior) {
00996         *pangle = angle1;
00997         *pconf = conf1;
00998     } else {
00999         *pangle = -90.0 + angle2;
01000         *pconf = conf2;
01001     }
01002 
01003 #if  DEBUG_PRINT_ORTH
01004     fprintf(stderr, " About 0:  angle1 = %7.3f, conf1 = %7.3f, score1 = %f\n",
01005             angle1, conf1, score1);
01006     fprintf(stderr, " About 90: angle2 = %7.3f, conf2 = %7.3f, score2 = %f\n",
01007             angle2, conf2, score2);
01008     fprintf(stderr, " Final:    angle = %7.3f, conf = %7.3f\n", *pangle, *pconf);
01009 #endif  /* DEBUG_PRINT_ORTH */
01010 
01011     return 0;
01012 }
01013 
01014 
01015 
01016 /*----------------------------------------------------------------*
01017  *                  Differential square sum function              *
01018  *----------------------------------------------------------------*/
01019 /*!
01020  *  pixFindDifferentialSquareSum()
01021  *
01022  *      Input:  pixs
01023  *              &sum  (<return> result)
01024  *      Return: 0 if OK, 1 on error
01025  *
01026  *  Notes:
01027  *      (1) At the top and bottom, we skip:
01028  *           - at least one scanline
01029  *           - not more than 10% of the image height
01030  *           - not more than 5% of the image width
01031  */
01032 l_int32
01033 pixFindDifferentialSquareSum(PIX        *pixs,
01034                              l_float32  *psum)
01035 {
01036 l_int32    i, n;
01037 l_int32    w, h, skiph, skip, nskip;
01038 l_float32  val1, val2, diff, sum;
01039 NUMA      *na;
01040 
01041     PROCNAME("pixFindDifferentialSquareSum");
01042 
01043     if (!pixs)
01044         return ERROR_INT("pixs not defined", procName, 1);
01045 
01046         /* Generate a number array consisting of the sum
01047          * of pixels in each row of pixs */
01048     if ((na = pixCountPixelsByRow(pixs, NULL)) == NULL)
01049         return ERROR_INT("na not made", procName, 1);
01050 
01051         /* Compute the number of rows at top and bottom to omit.
01052          * We omit these to avoid getting a spurious signal from
01053          * the top and bottom of a (nearly) all black image. */
01054     w = pixGetWidth(pixs);
01055     h = pixGetHeight(pixs);
01056     skiph = (l_int32)(0.05 * w);  /* skip for max shear of 0.025 radians */
01057     skip = L_MIN(h / 10, skiph);  /* don't remove more than 10% of image */
01058     nskip = L_MAX(skip / 2, 1);  /* at top & bot; skip at least one line */
01059 
01060         /* Sum the squares of differential row sums, on the
01061          * allowed rows.  Note that nskip must be >= 1. */
01062     n = numaGetCount(na);
01063     sum = 0.0;
01064     for (i = nskip; i < n - nskip; i++) {
01065         numaGetFValue(na, i - 1, &val1);
01066         numaGetFValue(na, i, &val2);
01067         diff = val2 - val1;
01068         sum += diff * diff;
01069     }
01070     numaDestroy(&na);
01071     *psum = sum;
01072     return 0;
01073 }
01074 
01075 
01076 /*----------------------------------------------------------------*
01077  *                        Normalized square sum                   *
01078  *----------------------------------------------------------------*/
01079 /*!
01080  *  pixFindNormalizedSquareSum()
01081  *
01082  *      Input:  pixs
01083  *              &hratio (<optional return> ratio of normalized horiz square sum
01084  *                       to result if the pixel distribution were uniform)
01085  *              &vratio (<optional return> ratio of normalized vert square sum
01086  *                       to result if the pixel distribution were uniform)
01087  *              &fract  (<optional return> ratio of fg pixels to total pixels)
01088  *      Return: 0 if OK, 1 on error or if there are no fg pixels
01089  *
01090  *  Notes:
01091  *      (1) Let the image have h scanlines and N fg pixels.
01092  *          If the pixels were uniformly distributed on scanlines,
01093  *          the sum of squares of fg pixels on each scanline would be
01094  *          h * (N / h)^2.  However, if the pixels are not uniformly
01095  *          distributed (e.g., for text), the sum of squares of fg
01096  *          pixels will be larger.  We return in hratio and vratio the
01097  *          ratio of these two values.
01098  *      (2) If there are no fg pixels, hratio and vratio are returned as 0.0.
01099  */
01100 l_int32
01101 pixFindNormalizedSquareSum(PIX        *pixs,
01102                            l_float32  *phratio,
01103                            l_float32  *pvratio,
01104                            l_float32  *pfract)
01105 {
01106 l_int32    i, w, h, empty;
01107 l_float32  sum, sumsq, uniform, val;
01108 NUMA      *na;
01109 PIX       *pixt;
01110 
01111     PROCNAME("pixFindNormalizedSquareSum");
01112 
01113     if (!pixs || pixGetDepth(pixs) != 1)
01114         return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
01115     pixGetDimensions(pixs, &w, &h, NULL);
01116 
01117     if (!phratio && !pvratio)
01118         return ERROR_INT("nothing to do", procName, 1);
01119     if (phratio) *phratio = 0.0;
01120     if (pvratio) *pvratio = 0.0;
01121 
01122     empty = 0;
01123     if (phratio) {
01124         na = pixCountPixelsByRow(pixs, NULL);
01125         numaGetSum(na, &sum);  /* fg pixels */
01126         if (pfract) *pfract = sum / (l_float32)(w * h);
01127         if (sum != 0.0) {
01128             uniform = sum * sum / h;   /*  h*(sum / h)^2  */
01129             sumsq = 0.0;
01130             for (i = 0; i < h; i++) {
01131                 numaGetFValue(na, i, &val);
01132                 sumsq += val * val;
01133             }
01134             *phratio = sumsq / uniform;
01135         }
01136         else
01137             empty = 1;
01138         numaDestroy(&na);
01139     }
01140 
01141     if (pvratio) {
01142         if (empty == 1) return 1;
01143         pixt = pixRotateOrth(pixs, 1);
01144         na = pixCountPixelsByRow(pixt, NULL);
01145         numaGetSum(na, &sum);
01146         if (pfract) *pfract = sum / (l_float32)(w * h);
01147         if (sum != 0.0) {
01148             uniform = sum * sum / w;
01149             sumsq = 0.0;
01150             for (i = 0; i < w; i++) {
01151                 numaGetFValue(na, i, &val);
01152                 sumsq += val * val;
01153             }
01154             *pvratio = sumsq / uniform;
01155         }
01156         else
01157             empty = 1;
01158         pixDestroy(&pixt);
01159         numaDestroy(&na);
01160     }
01161 
01162     return empty;
01163 }
01164 
01165 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines