/* * pixFindSkewOrthogonalRange() * * Input: pixs (1 bpp) * &angle (<return> angle required to deskew; in degrees cw) * &conf (<return> confidence given by ratio of max/min score) * redsweep (sweep reduction factor = 1, 2, 4 or 8) * redsearch (binary search reduction factor = 1, 2, 4 or 8; * and must not exceed redsweep) * sweeprange (half the full range in each orthogonal * direction, taken about 0, in degrees) * sweepdelta (angle increment of sweep; in degrees) * minbsdelta (min binary search increment angle; in degrees) * confprior (amount by which confidence of 90 degree rotated * result is reduced when comparing with unrotated * confidence value) * Return: 0 if OK, 1 on error or if angle measurment not valid * * Notes: * (1) This searches for the skew angle, first in the range * [-sweeprange, sweeprange], and then in * [90 - sweeprange, 90 + sweeprange], with angles measured * clockwise. For exploring the full range of possibilities, * suggest using sweeprange = 47.0 degrees, giving some overlap * at 45 and 135 degrees. From these results, and discounting * the the second confidence by %confprior, it selects the * angle for maximal differential variance. If the angle * is larger than pi/4, the angle found after 90 degree rotation * is selected. * (2) The larger the confidence value, the greater the probability * that the proper alignment is given by the angle that maximizes * variance. It should be compared to a threshold, which depends * on the application. Values between 3.0 and 6.0 are common. * (3) Allowing for both portrait and landscape searches is more * difficult, because if the signal from the text lines is weak, * a signal from vertical rules can be larger! * The most difficult documents to deskew have some or all of: * (a) Multiple columns, not aligned * (b) Black lines along the vertical edges * (c) Text from two pages, and at different angles * Rule of thumb for resolution: * (a) If the margins are clean, you can work at 75 ppi, * although 100 ppi is safer. * (b) If there are vertical lines in the margins, do not * work below 150 ppi. The signal from the text lines must * exceed that from the margin lines. * (4) Choosing the %confprior parameter depends on knowing something * about the source of image. However, we're not using * real probabilities here, so its use is qualitative. * If landscape and portrait are equally likely, use * %confprior = 0.0. If the likelihood of portrait (non-rotated) * is 100 times higher than that of landscape, we want to reduce * the chance that we rotate to landscape in a situation where * the landscape signal is accidentally larger than the * portrait signal. To do this use a positive value of * %confprior; say 1.5. */ l_int32 pixFindSkewOrthogonalRange(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_float32 confprior) { l_float32 angle1, conf1, score1, angle2, conf2, score2; PIX *pixr; PROCNAME("pixFindSkewOrthogonalRange"); if (pangle) *pangle = 0.0; if (pconf) *pconf = 0.0; if (!pangle || !pconf) return ERROR_INT("&angle and/or &conf not defined", procName, 1); if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); pixFindSkewSweepAndSearchScorePivot(pixs, &angle1, &conf1, &score1, redsweep, redsearch, 0.0, sweeprange, sweepdelta, minbsdelta, L_SHEAR_ABOUT_CORNER); pixr = pixRotateOrth(pixs, 1); pixFindSkewSweepAndSearchScorePivot(pixr, &angle2, &conf2, &score2, redsweep, redsearch, 0.0, sweeprange, sweepdelta, minbsdelta, L_SHEAR_ABOUT_CORNER); pixDestroy(&pixr); if (conf1 > conf2 - confprior) { *pangle = angle1; *pconf = conf1; } else { *pangle = -90.0 + angle2; *pconf = conf2; } #if DEBUG_PRINT_ORTH fprintf(stderr, " About 0: angle1 = %7.3f, conf1 = %7.3f, score1 = %f\n", angle1, conf1, score1); fprintf(stderr, " About 90: angle2 = %7.3f, conf2 = %7.3f, score2 = %f\n", angle2, conf2, score2); fprintf(stderr, " Final: angle = %7.3f, conf = %7.3f\n", *pangle, *pconf); #endif /* DEBUG_PRINT_ORTH */ return 0; }
/*! * pixFindSkewSweepAndSearchScore() * * Input: pixs (1 bpp) * &angle (<return> angle required to deskew; in degrees) * &conf (<return> confidence given by ratio of max/min score) * &endscore (<optional return> max score; use NULL to ignore) * redsweep (sweep reduction factor = 1, 2, 4 or 8) * redsearch (binary search reduction factor = 1, 2, 4 or 8; * and must not exceed redsweep) * sweepcenter (angle about which sweep is performed; in degrees) * sweeprange (half the full range, taken about sweepcenter; * in degrees) * sweepdelta (angle increment of sweep; in degrees) * minbsdelta (min binary search increment angle; in degrees) * Return: 0 if OK, 1 on error or if angle measurment not valid * * Notes: * (1) This finds the skew angle, doing first a sweep through a set * of equal angles, and then doing a binary search until convergence. * (2) There are two built-in constants that determine if the * returned confidence is nonzero: * - MIN_VALID_MAXSCORE (minimum allowed maxscore) * - MINSCORE_THRESHOLD_CONSTANT (determines minimum allowed * minscore, by multiplying by (height * width^2) * If either of these conditions is not satisfied, the returned * confidence value will be zero. The maxscore is optionally * returned in this function to allow evaluation of the * resulting angle by a method that is independent of the * returned confidence value. * (3) The larger the confidence value, the greater the probability * that the proper alignment is given by the angle that maximizes * variance. It should be compared to a threshold, which depends * on the application. Values between 3.0 and 6.0 are common. * (4) By default, the shear is about the UL corner. */ l_int32 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) { return pixFindSkewSweepAndSearchScorePivot(pixs, pangle, pconf, pendscore, redsweep, redsearch, 0.0, sweeprange, sweepdelta, minbsdelta, L_SHEAR_ABOUT_CORNER); }
int main(int argc, char **argv) { l_int32 w, h, wd, hd; l_float32 deg2rad, angle, conf; PIX *pixs, *pixb1, *pixb2, *pixr, *pixf, *pixd, *pixc; PIXA *pixa; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; deg2rad = 3.1415926535 / 180.; pixa = pixaCreate(0); pixs = pixRead("feyn.tif"); pixSetOrClearBorder(pixs, 100, 250, 100, 0, PIX_CLR); pixb1 = pixReduceRankBinaryCascade(pixs, 2, 2, 0, 0); regTestWritePixAndCheck(rp, pixb1, IFF_PNG); /* 0 */ pixDisplayWithTitle(pixb1, 0, 100, NULL, rp->display); /* Add a border and locate and deskew a 40 degree rotation */ pixb2 = pixAddBorder(pixb1, BORDER, 0); pixGetDimensions(pixb2, &w, &h, NULL); pixSaveTiled(pixb2, pixa, 0.5, 1, 20, 8); pixr = pixRotateBySampling(pixb2, w / 2, h / 2, deg2rad * 40., L_BRING_IN_WHITE); regTestWritePixAndCheck(rp, pixr, IFF_PNG); /* 1 */ pixSaveTiled(pixr, pixa, 0.5, 0, 20, 0); pixFindSkewSweepAndSearchScorePivot(pixr, &angle, &conf, NULL, 1, 1, 0.0, 45.0, 2.0, 0.03, L_SHEAR_ABOUT_CENTER); fprintf(stderr, "Should be 40 degrees: angle = %7.3f, conf = %7.3f\n", angle, conf); pixf = pixRotateBySampling(pixr, w / 2, h / 2, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRemoveBorder(pixf, BORDER); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 */ pixSaveTiled(pixd, pixa, 0.5, 0, 20, 0); pixDestroy(&pixr); pixDestroy(&pixf); pixDestroy(&pixd); /* Do a rotation larger than 90 degrees using embedding; * Use 2 sets of measurements at 90 degrees to scan the * full range of possible rotation angles. */ pixGetDimensions(pixb1, &w, &h, NULL); pixr = pixRotate(pixb1, deg2rad * 37., L_ROTATE_SAMPLING, L_BRING_IN_WHITE, w, h); regTestWritePixAndCheck(rp, pixr, IFF_PNG); /* 3 */ pixSaveTiled(pixr, pixa, 0.5, 1, 20, 0); startTimer(); pixFindSkewOrthogonalRange(pixr, &angle, &conf, 2, 1, 47.0, 1.0, 0.03, 0.0); fprintf(stderr, "Orth search time: %7.3f sec\n", stopTimer()); fprintf(stderr, "Should be about -128 degrees: angle = %7.3f\n", angle); pixd = pixRotate(pixr, deg2rad * angle, L_ROTATE_SAMPLING, L_BRING_IN_WHITE, w, h); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 4 */ pixGetDimensions(pixd, &wd, &hd, NULL); pixc = pixCreate(w, h, 1); pixRasterop(pixc, 0, 0, w, h, PIX_SRC, pixd, (wd - w) / 2, (hd - h) / 2); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 5 */ pixSaveTiled(pixc, pixa, 0.5, 0, 20, 0); pixDestroy(&pixr); pixDestroy(&pixf); pixDestroy(&pixd); pixDestroy(&pixc); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 6 */ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display); pixDestroy(&pixd); pixDestroy(&pixs); pixDestroy(&pixb1); pixDestroy(&pixb2); pixaDestroy(&pixa); return regTestCleanup(rp); }
int main(int argc, char **argv) { char *filein, *fileout; l_int32 ret; l_float32 deg2rad; l_float32 angle, conf, score; PIX *pix, *pixs, *pixd; static char mainName[] = "skewtest"; if (argc != 3) return ERROR_INT(" Syntax: skewtest filein fileout", mainName, 1); filein = argv[1]; fileout = argv[2]; setLeptDebugOK(1); pixd = NULL; deg2rad = 3.1415926535 / 180.; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); /* Find the skew angle various ways */ pix = pixConvertTo1(pixs, 130); pixWrite("/tmp/binarized.tif", pix, IFF_TIFF_G4); pixFindSkew(pix, &angle, &conf); fprintf(stderr, "pixFindSkew():\n" " conf = %5.3f, angle = %7.3f degrees\n", conf, angle); pixFindSkewSweepAndSearchScorePivot(pix, &angle, &conf, &score, SWEEP_REDUCTION2, SEARCH_REDUCTION, 0.0, SWEEP_RANGE2, SWEEP_DELTA2, SEARCH_MIN_DELTA, L_SHEAR_ABOUT_CORNER); fprintf(stderr, "pixFind...Pivot(about corner):\n" " conf = %5.3f, angle = %7.3f degrees, score = %f\n", conf, angle, score); pixFindSkewSweepAndSearchScorePivot(pix, &angle, &conf, &score, SWEEP_REDUCTION2, SEARCH_REDUCTION, 0.0, SWEEP_RANGE2, SWEEP_DELTA2, SEARCH_MIN_DELTA, L_SHEAR_ABOUT_CENTER); fprintf(stderr, "pixFind...Pivot(about center):\n" " conf = %5.3f, angle = %7.3f degrees, score = %f\n", conf, angle, score); /* Use top-level */ pixd = pixDeskew(pixs, 0); pixWriteImpliedFormat(fileout, pixd, 0, 0); #if 0 /* Do it piecemeal; fails if outside the range */ if (pixGetDepth(pixs) == 1) { pixd = pixDeskew(pix, DESKEW_REDUCTION); pixWrite(fileout, pixd, IFF_PNG); } else { ret = pixFindSkewSweepAndSearch(pix, &angle, &conf, SWEEP_REDUCTION2, SEARCH_REDUCTION, SWEEP_RANGE2, SWEEP_DELTA2, SEARCH_MIN_DELTA); if (ret) L_WARNING("skew angle not valid\n", mainName); else { fprintf(stderr, "conf = %5.3f, angle = %7.3f degrees\n", conf, angle); if (conf > 2.5) pixd = pixRotate(pixs, angle * deg2rad, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0); else pixd = pixClone(pixs); pixWrite(fileout, pixd, IFF_PNG); pixDestroy(&pixd); } } #endif pixDestroy(&pixs); pixDestroy(&pix); pixDestroy(&pixd); return 0; }