/*! * pixRotateAMGrayCorner() * * Input: pixs * angle (radians; clockwise is positive) * grayval (0 to bring in BLACK, 255 for WHITE) * Return: pixd, or null on error * * Notes: * (1) Rotates the image about the UL corner. * (2) A positive angle gives a clockwise rotation. * (3) Specify the grayvalue to be brought in from outside the image. */ PIX * pixRotateAMGrayCorner(PIX *pixs, l_float32 angle, l_uint8 grayval) { l_int32 w, h, wpls, wpld; l_uint32 *datas, *datad; PIX *pixd; PROCNAME("pixRotateAMGrayCorner"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL); if (L_ABS(angle) < VERY_SMALL_ANGLE) return pixClone(pixs); pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); pixd = pixCreateTemplate(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); rotateAMGrayCornerLow(datad, w, h, wpld, datas, wpls, angle, grayval); return pixd; }
/*! * pixRotate3Shear() * * Input: pixs * xcen, ycen (center of rotation) * angle (radians) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); * Return: pixd, or null on error. * * Notes: * (1) This rotates the image about the image center, * using the 3-shear method. It can be used for any angle, and * should be used for angles larger than MAX_2_SHEAR_ANGLE. * (2) A positive angle gives a clockwise rotation. * (3) 3-shear rotation by a specified angle is equivalent * to the sequential transformations * y' = y + tan(angle/2) * (x - xcen) for first y-shear * x' = x + sin(angle) * (y - ycen) for x-shear * y' = y + tan(angle/2) * (x - xcen) for second y-shear * (4) Computation of tan(angle) is performed in the shear operations. * (5) This brings in 'incolor' pixels from outside the image. * (6) The algorithm was published by Alan Paeth: "A Fast Algorithm * for General Raster Rotation," Graphics Interface '86, * pp. 77-81, May 1986. A description of the method, along with * an implementation, can be found in Graphics Gems, p. 179, * edited by Andrew Glassner, published by Academic Press, 1990. */ PIX * pixRotate3Shear(PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor) { l_float32 hangle; PIX *pixt, *pixd; PROCNAME("pixRotate3Shear"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL); if (L_ABS(angle) < VERY_SMALL_ANGLE) return pixClone(pixs); hangle = atan(sin(angle)); if ((pixd = pixVShear(NULL, pixs, xcen, angle / 2., incolor)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if ((pixt = pixHShear(NULL, pixd, ycen, hangle, incolor)) == NULL) return (PIX *)ERROR_PTR("pixt not made", procName, NULL); pixVShear(pixd, pixt, xcen, angle / 2., incolor); pixDestroy(&pixt); return pixd; }
/*! * kernelNormalize() * * Input: kels (source kel, to be normalized) * normsum (desired sum of elements in keld) * Return: keld (normalized version of kels), or null on error * or if sum of elements is very close to 0) * * Notes: * (1) If the sum of kernel elements is close to 0, do not * try to calculate the normalized kernel. Instead, * return a copy of the input kernel, with an error message. */ L_KERNEL * kernelNormalize(L_KERNEL *kels, l_float32 normsum) { l_int32 i, j, sx, sy, cx, cy; l_float32 sum, factor; L_KERNEL *keld; PROCNAME("kernelNormalize"); if (!kels) return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL); kernelGetSum(kels, &sum); if (L_ABS(sum) < 0.01) { L_ERROR("null sum; not normalizing; returning a copy", procName); return kernelCopy(kels); } kernelGetParameters(kels, &sy, &sx, &cy, &cx); if ((keld = kernelCreate(sy, sx)) == NULL) return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL); keld->cy = cy; keld->cx = cx; factor = normsum / sum; for (i = 0; i < sy; i++) for (j = 0; j < sx; j++) keld->data[i][j] = factor * kels->data[i][j]; return keld; }
/*! * pixBilateralGrayExact() * * Input: pixs (8 bpp gray) * spatial_kel (gaussian kernel) * range_kel (<optional> 256 x 1, monotonically decreasing) * Return: pixd (8 bpp bilateral filtered image) * * Notes: * (1) See pixBilateralExact(). */ PIX * pixBilateralGrayExact(PIX *pixs, L_KERNEL *spatial_kel, L_KERNEL *range_kel) { l_int32 i, j, id, jd, k, m, w, h, d, sx, sy, cx, cy, wplt, wpld; l_int32 val, center_val; l_uint32 *datat, *datad, *linet, *lined; l_float32 sum, weight_sum, weight; L_KERNEL *keli; PIX *pixt, *pixd; PROCNAME("pixBilateralGrayExact"); if (!pixs) return (PIX *) ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 8) return (PIX *) ERROR_PTR("pixs must be gray", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (!spatial_kel) return (PIX *) ERROR_PTR("spatial kel not defined", procName, NULL); if (!range_kel) return pixConvolve(pixs, spatial_kel, 8, 1); if (range_kel->sx != 256 || range_kel->sy != 1) return (PIX *) ERROR_PTR("range kel not {256 x 1", procName, NULL); keli = kernelInvert(spatial_kel); kernelGetParameters(keli, &sy, &sx, &cy, &cx); if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL) return (PIX *) ERROR_PTR("pixt not made", procName, NULL); pixd = pixCreate(w, h, 8); datat = pixGetData(pixt); datad = pixGetData(pixd); wplt = pixGetWpl(pixt); wpld = pixGetWpl(pixd); for (i = 0, id = 0; id < h; i++, id++) { lined = datad + id * wpld; for (j = 0, jd = 0; jd < w; j++, jd++) { center_val = GET_DATA_BYTE(datat + (i + cy) * wplt, j + cx); weight_sum = 0.0; sum = 0.0; for (k = 0; k < sy; k++) { linet = datat + (i + k) * wplt; for (m = 0; m < sx; m++) { val = GET_DATA_BYTE(linet, j + m); weight = keli->data[k][m] * range_kel->data[0][L_ABS(center_val - val)]; weight_sum += weight; sum += val * weight; } } SET_DATA_BYTE(lined, jd, (l_int32)(sum / weight_sum + 0.5)); } } kernelDestroy(&keli); pixDestroy(&pixt); return pixd; }
/*! * pixRotateAMColorFast() * * Input: pixs * angle (radians; clockwise is positive) * colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE) * Return: pixd, or null on error * * Notes: * (1) This rotates a color image about the image center. * (2) A positive angle gives a clockwise rotation. * (3) It uses area mapping, dividing each pixel into * 16 subpixels. * (4) It is about 10% to 20% faster than the more accurate linear * interpolation function pixRotateAMColor(), * which uses 256 subpixels. * (5) For some reason it shifts the image center. * No attempt is made to rotate the alpha component. * * *** Warning: implicit assumption about RGB component ordering *** */ PIX * pixRotateAMColorFast(PIX *pixs, l_float32 angle, l_uint32 colorval) { l_int32 w, h, wpls, wpld; l_uint32 *datas, *datad; PIX *pixd; PROCNAME("pixRotateAMColorFast"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); pixd = pixCreateTemplate(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); rotateAMColorFastLow(datad, w, h, wpld, datas, wpls, angle, colorval); return pixd; }
/*! * \brief regTestCompareValues() * * \param[in] rp regtest parameters * \param[in] val1 typ. the golden value * \param[in] val2 typ. the value computed * \param[in] delta allowed max absolute difference * \return 0 if OK, 1 on error a failure in comparison is not an error */ l_int32 regTestCompareValues(L_REGPARAMS *rp, l_float32 val1, l_float32 val2, l_float32 delta) { l_float32 diff; PROCNAME("regTestCompareValues"); if (!rp) return ERROR_INT("rp not defined", procName, 1); rp->index++; diff = L_ABS(val2 - val1); /* Record on failure */ if (diff > delta) { if (rp->fp) { fprintf(rp->fp, "Failure in %s_reg: value comparison for index %d\n" "difference = %f but allowed delta = %f\n", rp->testname, rp->index, diff, delta); } fprintf(stderr, "Failure in %s_reg: value comparison for index %d\n" "difference = %f but allowed delta = %f\n", rp->testname, rp->index, diff, delta); rp->success = FALSE; } return 0; }
/*! * \brief pixRotate3Shear() * * \param[in] pixs * \param[in] xcen, ycen center of rotation * \param[in] angle radians * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK; * \return pixd, or NULL on error. * * <pre> * Notes: * (1) This rotates the image about the given point, using the 3-shear * method. It should only be used for angles smaller than * LIMIT_SHEAR_ANGLE. For larger angles, a warning is issued. * (2) A positive angle gives a clockwise rotation. * (3) 3-shear rotation by a specified angle is equivalent * to the sequential transformations * y' = y + tan(angle/2) * (x - xcen) for first y-shear * x' = x + sin(angle) * (y - ycen) for x-shear * y' = y + tan(angle/2) * (x - xcen) for second y-shear * (4) Computation of tan(angle) is performed in the shear operations. * (5) This brings in 'incolor' pixels from outside the image. * (6) If the image has an alpha layer, it is rotated separately by * two shears. * (7) The algorithm was published by Alan Paeth: "A Fast Algorithm * for General Raster Rotation," Graphics Interface '86, * pp. 77-81, May 1986. A description of the method, along with * an implementation, can be found in Graphics Gems, p. 179, * edited by Andrew Glassner, published by Academic Press, 1990. * </pre> */ PIX * pixRotate3Shear(PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor) { l_float32 hangle; PIX *pix1, *pix2, *pixd; PROCNAME("pixRotate3Shear"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); if (L_ABS(angle) > LIMIT_SHEAR_ANGLE) { L_WARNING("%6.2f radians; large angle for 3-shear rotation\n", procName, L_ABS(angle)); } hangle = atan(sin(angle)); if ((pixd = pixVShear(NULL, pixs, xcen, angle / 2., incolor)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if ((pix1 = pixHShear(NULL, pixd, ycen, hangle, incolor)) == NULL) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("pix1 not made", procName, NULL); } pixVShear(pixd, pix1, xcen, angle / 2., incolor); pixDestroy(&pix1); if (pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4) { pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); /* L_BRING_IN_WHITE brings in opaque for the alpha component */ pix2 = pixRotate3Shear(pix1, xcen, ycen, angle, L_BRING_IN_WHITE); pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); } return pixd; }
/*! * pixRasteropHip() * * Input: pixd (in-place operation) * by (top of horizontal band) * bh (height of horizontal band) * hshift (horizontal shift of band; hshift > 0 is to right) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: 0 if OK; 1 on error * * Notes: * (1) This rasterop translates a horizontal band of the * image either left or right, bringing in either white * or black pixels from outside the image. * (2) The horizontal band extends the full width of pixd. * (3) If a colormap exists, the nearest color to white or black * is brought in. */ l_int32 pixRasteropHip(PIX *pixd, l_int32 by, l_int32 bh, l_int32 hshift, l_int32 incolor) { l_int32 w, h, d, index, op; PIX *pixt; PIXCMAP *cmap; PROCNAME("pixRasteropHip"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return ERROR_INT("invalid value for incolor", procName, 1); if (bh <= 0) return ERROR_INT("bh must be > 0", procName, 1); if (hshift == 0) return 0; pixGetDimensions(pixd, &w, &h, &d); rasteropHipLow(pixGetData(pixd), h, d, pixGetWpl(pixd), by, bh, hshift); cmap = pixGetColormap(pixd); if (!cmap) { if ((d == 1 && incolor == L_BRING_IN_BLACK) || (d > 1 && incolor == L_BRING_IN_WHITE)) op = PIX_SET; else op = PIX_CLR; /* Set the pixels brought in at left or right */ if (hshift > 0) pixRasterop(pixd, 0, by, hshift, bh, op, NULL, 0, 0); else /* hshift < 0 */ pixRasterop(pixd, w + hshift, by, -hshift, bh, op, NULL, 0, 0); return 0; } /* Get the nearest index and fill with that */ if (incolor == L_BRING_IN_BLACK) pixcmapGetRankIntensity(cmap, 0.0, &index); else /* white */ pixcmapGetRankIntensity(cmap, 1.0, &index); pixt = pixCreate(L_ABS(hshift), bh, d); pixSetAllArbitrary(pixt, index); if (hshift > 0) pixRasterop(pixd, 0, by, hshift, bh, PIX_SRC, pixt, 0, 0); else /* hshift < 0 */ pixRasterop(pixd, w + hshift, by, -hshift, bh, PIX_SRC, pixt, 0, 0); pixDestroy(&pixt); return 0; }
/*! * \brief makeBarrelshiftString() */ static char * makeBarrelshiftString(l_int32 delx, /* j - cx */ l_int32 dely) /* i - cy */ { l_int32 absx, absy; char bigbuf[L_BUF_SIZE]; PROCNAME("makeBarrelshiftString"); if (delx < -31 || delx > 31) return (char *)ERROR_PTR("delx out of bounds", procName, NULL); if (dely < -31 || dely > 31) return (char *)ERROR_PTR("dely out of bounds", procName, NULL); absx = L_ABS(delx); absy = L_ABS(dely); if ((delx == 0) && (dely == 0)) sprintf(bigbuf, "(*sptr)"); else if ((delx == 0) && (dely < 0)) sprintf(bigbuf, "(*(sptr %s))", wplstrm[absy - 1]); else if ((delx == 0) && (dely > 0)) sprintf(bigbuf, "(*(sptr %s))", wplstrp[absy - 1]); else if ((delx < 0) && (dely == 0)) sprintf(bigbuf, "((*(sptr) >> %d) | (*(sptr - 1) << %d))", absx, 32 - absx); else if ((delx > 0) && (dely == 0)) sprintf(bigbuf, "((*(sptr) << %d) | (*(sptr + 1) >> %d))", absx, 32 - absx); else if ((delx < 0) && (dely < 0)) sprintf(bigbuf, "((*(sptr %s) >> %d) | (*(sptr %s - 1) << %d))", wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx); else if ((delx > 0) && (dely < 0)) sprintf(bigbuf, "((*(sptr %s) << %d) | (*(sptr %s + 1) >> %d))", wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx); else if ((delx < 0) && (dely > 0)) sprintf(bigbuf, "((*(sptr %s) >> %d) | (*(sptr %s - 1) << %d))", wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx); else /* ((delx > 0) && (dely > 0)) */ sprintf(bigbuf, "((*(sptr %s) << %d) | (*(sptr %s + 1) >> %d))", wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx); return stringNew(bigbuf); }
/*! * dewarpaInfo() * * Input: fp * dewa * Return: 0 if OK, 1 on error */ l_int32 dewarpaInfo(FILE *fp, L_DEWARPA *dewa) { l_int32 i, n, pageno, nnone, nvsuccess, nvvalid, nhsuccess, nhvalid, nref; L_DEWARP *dew; PROCNAME("dewarpaInfo"); if (!fp) return ERROR_INT("dewa not defined", procName, 1); if (!dewa) return ERROR_INT("dewa not defined", procName, 1); fprintf(fp, "\nDewarpaInfo: %p\n", dewa); fprintf(fp, "nalloc = %d, maxpage = %d\n", dewa->nalloc, dewa->maxpage); fprintf(fp, "sampling = %d, redfactor = %d, minlines = %d\n", dewa->sampling, dewa->redfactor, dewa->minlines); fprintf(fp, "maxdist = %d, useboth = %d\n", dewa->maxdist, dewa->useboth); dewarpaModelStats(dewa, &nnone, &nvsuccess, &nvvalid, &nhsuccess, &nhvalid, &nref); n = numaGetCount(dewa->napages); fprintf(stderr, "Total number of pages with a dew = %d\n", n); fprintf(stderr, "Number of pages without any models = %d\n", nnone); fprintf(stderr, "Number of pages with a vert model = %d\n", nvsuccess); fprintf(stderr, "Number of pages with a valid vert model = %d\n", nvvalid); fprintf(stderr, "Number of pages with both models = %d\n", nhsuccess); fprintf(stderr, "Number of pages with both models valid = %d\n", nhvalid); fprintf(stderr, "Number of pages with a ref model = %d\n", nref); for (i = 0; i < n; i++) { numaGetIValue(dewa->napages, i, &pageno); if ((dew = dewarpaGetDewarp(dewa, pageno)) == NULL) continue; fprintf(stderr, "Page: %d\n", dew->pageno); fprintf(stderr, " hasref = %d, refpage = %d\n", dew->hasref, dew->refpage); fprintf(stderr, " nlines = %d\n", dew->nlines); fprintf(stderr, " w = %d, h = %d, nx = %d, ny = %d\n", dew->w, dew->h, dew->nx, dew->ny); if (dew->sampvdispar) fprintf(stderr, " Vertical disparity builds:\n" " (min,max,abs-diff) line curvature = (%d,%d,%d)\n", dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv); if (dew->samphdispar) fprintf(stderr, " Horizontal disparity builds:\n" " left edge slope = %d, right edge slope = %d\n" " (left,right,abs-diff) edge curvature = (%d,%d,%d)\n", dew->leftslope, dew->rightslope, dew->leftcurv, dew->rightcurv, L_ABS(dew->leftcurv - dew->rightcurv)); } return 0; }
/*! * localSearchForBackground() * * Input: &x, &y (starting position for search; return found position) * maxrad (max distance to search from starting location) * Return: 0 if bg pixel found; 1 if not found */ static l_int32 localSearchForBackground(PIX *pix, l_int32 *px, l_int32 *py, l_int32 maxrad) { l_int32 x, y, w, h, r, i, j; l_uint32 val; x = *px; y = *py; pixGetPixel(pix, x, y, &val); if (val == 0) return 0; /* For each value of r, restrict the search to the boundary * pixels in a square centered on (x,y), clipping to the * image boundaries if necessary. */ pixGetDimensions(pix, &w, &h, NULL); for (r = 1; r < maxrad; r++) { for (i = -r; i <= r; i++) { if (y + i < 0 || y + i >= h) continue; for (j = -r; j <= r; j++) { if (x + j < 0 || x + j >= w) continue; if (L_ABS(i) != r && L_ABS(j) != r) /* not on "r ring" */ continue; pixGetPixel(pix, x + j, y + i, &val); if (val == 0) { *px = x + j; *py = y + i; return 0; } } } } return 1; }
/*! * pixRotateShear() * * Input: pixs * xcen (x value for which there is no horizontal shear) * ycen (y value for which there is no vertical shear) * angle (radians) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); * Return: pixd, or null on error. * * Notes: * (1) This rotates an image about the given point, using * either 2 or 3 shears. * (2) A positive angle gives a clockwise rotation. * (3) This brings in 'incolor' pixels from outside the image. */ PIX * pixRotateShear(PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor) { PROCNAME("pixRotateShear"); if (!pixs) return (PIX *)(PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL); if (L_ABS(angle) < VERY_SMALL_ANGLE) return pixClone(pixs); if (L_ABS(angle) <= MAX_2_SHEAR_ANGLE) return pixRotate2Shear(pixs, xcen, ycen, angle, incolor); else return pixRotate3Shear(pixs, xcen, ycen, angle, incolor); }
/** * Test whether b1 and b2 are close enough to be a character pair. */ bool ValidatePairOld(BOX *b1, BOX *b2) { l_int32 max_w = L_MAX(b1->w, b2->w); l_int32 centerx1 = b1->x + b1->w / 2; l_int32 centerx2 = b2->x + b2->w / 2; l_int32 h_dist = L_ABS(centerx1 - centerx2); /* Horizontal distance between centers is * less than twice the wider character */ if (h_dist > max_w * OLDPAIR_MAX_HDIST_RATIO) return false; l_int32 max_h = L_MAX(b1->h, b2->h); l_int32 centery1 = b1->y + b1->h / 2; l_int32 centery2 = b2->y + b2->h / 2; l_int32 v_dist = L_ABS(centery1 - centery2); /* Vertical distance between centers is less than 50% of the taller character */ if (v_dist > max_h * OLDPAIR_MAX_VDIST_RATIO) return false; l_int32 min_h = L_MIN(b1->h, b2->h); l_float32 h_ratio = min_h / (max_h + 1.0); /* Height ratio is between 0.5 and 2 */ if (h_ratio < OLDPAIR_MIN_HPAIR_RATIO) return false; l_int32 min_w = L_MIN(b1->w, b2->w); l_float32 w_ratio = min_w / (max_w + 1.0); /* Width ratio is between 0.1 and 10 */ if (w_ratio < OLDPAIR_MIN_WPAIR_RATIO) return false; return true; }
/*! * pixRotateAM() * * Input: pixs (2, 4, 8 bpp gray or colormapped, or 32 bpp RGB) * angle (radians; clockwise is positive) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: pixd, or null on error * * Notes: * (1) Rotates about image center. * (2) A positive angle gives a clockwise rotation. * (3) Brings in either black or white pixels from the boundary. */ PIX * pixRotateAM(PIX *pixs, l_float32 angle, l_int32 incolor) { l_int32 d; l_uint32 fillval; PIX *pixt1, *pixt2, *pixd; PROCNAME("pixRotateAM"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) == 1) return (PIX *)ERROR_PTR("pixs is 1 bpp", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); /* Remove cmap if it exists, and unpack to 8 bpp if necessary */ pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); d = pixGetDepth(pixt1); if (d < 8) pixt2 = pixConvertTo8(pixt1, FALSE); else pixt2 = pixClone(pixt1); d = pixGetDepth(pixt2); /* Compute actual incoming color */ fillval = 0; if (incolor == L_BRING_IN_WHITE) { if (d == 8) fillval = 255; else /* d == 32 */ fillval = 0xffffff00; } if (d == 8) pixd = pixRotateAMGray(pixt2, angle, fillval); else /* d == 32 */ pixd = pixRotateAMColor(pixt2, angle, fillval); pixDestroy(&pixt1); pixDestroy(&pixt2); return pixd; }
/*! * pixcmapColorToGray() * * Input: cmap * rwt, gwt, bwt (non-negative; these should add to 1.0) * Return: cmap (gray), or null on error * * Notes: * (1) This creates a gray colormap from an arbitrary colormap. * (2) In use, attach the output gray colormap to the pix * (or a copy of it) that provided the input colormap. */ PIXCMAP * pixcmapColorToGray(PIXCMAP *cmaps, l_float32 rwt, l_float32 gwt, l_float32 bwt) { l_int32 i, n, rval, gval, bval, val; l_float32 sum; PIXCMAP *cmapd; PROCNAME("pixcmapColorToGray"); if (!cmaps) return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL); if (rwt < 0.0 || gwt < 0.0 || bwt < 0.0) return (PIXCMAP *)ERROR_PTR("weights not all >= 0.0", procName, NULL); /* Make sure the sum of weights is 1.0; otherwise, you can get * overflow in the gray value. */ sum = rwt + gwt + bwt; if (sum == 0.0) { L_WARNING("all weights zero; setting equal to 1/3", procName); rwt = gwt = bwt = 0.33333; sum = 1.0; } if (L_ABS(sum - 1.0) > 0.0001) { /* maintain ratios with sum == 1.0 */ L_WARNING("weights don't sum to 1; maintaining ratios", procName); rwt = rwt / sum; gwt = gwt / sum; bwt = bwt / sum; } cmapd = pixcmapCopy(cmaps); n = pixcmapGetCount(cmapd); for (i = 0; i < n; i++) { pixcmapGetColor(cmapd, i, &rval, &gval, &bval); val = (l_int32)(rwt * rval + gwt * gval + bwt * bval + 0.5); pixcmapResetColor(cmapd, i, val, val, val); } return cmapd; }
/*! * pixRotateAMColorCorner() * * Input: pixs * angle (radians; clockwise is positive) * colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE) * Return: pixd, or null on error * * Notes: * (1) Rotates the image about the UL corner. * (2) A positive angle gives a clockwise rotation. * (3) Specify the color to be brought in from outside the image. */ PIX * pixRotateAMColorCorner(PIX *pixs, l_float32 angle, l_uint32 fillval) { l_int32 w, h, wpls, wpld; l_uint32 *datas, *datad; PIX *pix1, *pix2, *pixd; PROCNAME("pixRotateAMColorCorner"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); pixd = pixCreateTemplate(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); rotateAMColorCornerLow(datad, w, h, wpld, datas, wpls, angle, fillval); if (pixGetSpp(pixs) == 4) { pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); pix2 = pixRotateAMGrayCorner(pix1, angle, 255); /* bring in opaque */ pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); } return pixd; }
/*! * pixcmapGetNearestGrayIndex() * * Input: cmap * val (gray value to search for; in range [0, ... 255]) * &index (<return> the index of the nearest color) * Return: 0 if OK, 1 on error (caller must check) * * Notes: * (1) This should be used on gray colormaps. It uses only the * green value of the colormap. * (2) Returns the index of the exact color if possible, otherwise the * index of the color closest to the target color. */ l_int32 pixcmapGetNearestGrayIndex(PIXCMAP *cmap, l_int32 val, l_int32 *pindex) { l_int32 i, n, dist, mindist; RGBA_QUAD *cta; PROCNAME("pixcmapGetNearestGrayIndex"); if (!pindex) return ERROR_INT("&index not defined", procName, 1); *pindex = 0; if (!cmap) return ERROR_INT("cmap not defined", procName, 1); if (val < 0 || val > 255) return ERROR_INT("val not in [0 ... 255]", procName, 1); if ((cta = (RGBA_QUAD *)cmap->array) == NULL) return ERROR_INT("cta not defined(!)", procName, 1); n = pixcmapGetCount(cmap); mindist = 256; for (i = 0; i < n; i++) { dist = cta[i].green - val; dist = L_ABS(dist); if (dist < mindist) { *pindex = i; if (dist == 0) break; mindist = dist; } } return 0; }
/*! * \brief numaaCompareImagesByBoxes() * * \param[in] naa1 for image 1, formatted by boxaExtractSortedPattern() * \param[in] naa2 ditto; for image 2 * \param[in] nperline number of box regions to be used in each textline * \param[in] nreq number of complete row matches required * \param[in] maxshiftx max allowed x shift between two patterns, in pixels * \param[in] maxshifty max allowed y shift between two patterns, in pixels * \param[in] delx max allowed difference in x data, after alignment * \param[in] dely max allowed difference in y data, after alignment * \param[out] psame 1 if %nreq row matches are found; 0 otherwise * \param[in] debugflag 1 for debug output * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) Each input numaa describes a set of sorted bounding boxes * (sorted by textline and, within each textline, from * left to right) in the images from which they are derived. * See boxaExtractSortedPattern() for a description of the data * format in each of the input numaa. * (2) This function does an alignment between the input * descriptions of bounding boxes for two images. The * input parameter %nperline specifies the number of boxes * to consider in each line when testing for a match, and * %nreq is the required number of lines that must be well-aligned * to get a match. * (3) Testing by alignment has 3 steps: * (a) Generating the location of word bounding boxes from the * images (prior to calling this function). * (b) Listing all possible pairs of aligned rows, based on * tolerances in horizontal and vertical positions of * the boxes. Specifically, all pairs of rows are enumerated * whose first %nperline boxes can be brought into close * alignment, based on the delx parameter for boxes in the * line and within the overall the %maxshiftx and %maxshifty * constraints. * (c) Each pair, starting with the first, is used to search * for a set of %nreq - 1 other pairs that can all be aligned * with a difference in global translation of not more * than (%delx, %dely). * </pre> */ l_int32 numaaCompareImagesByBoxes(NUMAA *naa1, NUMAA *naa2, l_int32 nperline, l_int32 nreq, l_int32 maxshiftx, l_int32 maxshifty, l_int32 delx, l_int32 dely, l_int32 *psame, l_int32 debugflag) { l_int32 n1, n2, i, j, nbox, y1, y2, xl1, xl2; l_int32 shiftx, shifty, match; l_int32 *line1, *line2; /* indicator for sufficient boxes in a line */ l_int32 *yloc1, *yloc2; /* arrays of y value for first box in a line */ l_int32 *xleft1, *xleft2; /* arrays of x value for left side of first box */ NUMA *na1, *na2, *nai1, *nai2, *nasx, *nasy; PROCNAME("numaaCompareImagesByBoxes"); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; if (!naa1) return ERROR_INT("naa1 not defined", procName, 1); if (!naa2) return ERROR_INT("naa2 not defined", procName, 1); if (nperline < 1) return ERROR_INT("nperline < 1", procName, 1); if (nreq < 1) return ERROR_INT("nreq < 1", procName, 1); n1 = numaaGetCount(naa1); n2 = numaaGetCount(naa2); if (n1 < nreq || n2 < nreq) return 0; /* Find the lines in naa1 and naa2 with sufficient boxes. * Also, find the y-values for each of the lines, and the * LH x-values of the first box in each line. */ line1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); line2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); yloc1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); yloc2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); xleft1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); xleft2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); for (i = 0; i < n1; i++) { na1 = numaaGetNuma(naa1, i, L_CLONE); numaGetIValue(na1, 0, yloc1 + i); numaGetIValue(na1, 1, xleft1 + i); nbox = (numaGetCount(na1) - 1) / 2; if (nbox >= nperline) line1[i] = 1; numaDestroy(&na1); } for (i = 0; i < n2; i++) { na2 = numaaGetNuma(naa2, i, L_CLONE); numaGetIValue(na2, 0, yloc2 + i); numaGetIValue(na2, 1, xleft2 + i); nbox = (numaGetCount(na2) - 1) / 2; if (nbox >= nperline) line2[i] = 1; numaDestroy(&na2); } /* Enumerate all possible line matches. A 'possible' line * match is one where the x and y shifts for the first box * in each line are within the maxshiftx and maxshifty * constraints, and the left and right sides of the remaining * (nperline - 1) successive boxes are within delx of each other. * The result is a set of four numas giving parameters of * each set of matching lines. */ nai1 = numaCreate(0); /* line index 1 of match */ nai2 = numaCreate(0); /* line index 2 of match */ nasx = numaCreate(0); /* shiftx for match */ nasy = numaCreate(0); /* shifty for match */ for (i = 0; i < n1; i++) { if (line1[i] == 0) continue; y1 = yloc1[i]; xl1 = xleft1[i]; na1 = numaaGetNuma(naa1, i, L_CLONE); for (j = 0; j < n2; j++) { if (line2[j] == 0) continue; y2 = yloc2[j]; if (L_ABS(y1 - y2) > maxshifty) continue; xl2 = xleft2[j]; if (L_ABS(xl1 - xl2) > maxshiftx) continue; shiftx = xl1 - xl2; /* shift to add to x2 values */ shifty = y1 - y2; /* shift to add to y2 values */ na2 = numaaGetNuma(naa2, j, L_CLONE); /* Now check if 'nperline' boxes in the two lines match */ match = testLineAlignmentX(na1, na2, shiftx, delx, nperline); if (match) { numaAddNumber(nai1, i); numaAddNumber(nai2, j); numaAddNumber(nasx, shiftx); numaAddNumber(nasy, shifty); } numaDestroy(&na2); } numaDestroy(&na1); } /* Determine if there are a sufficient number of mutually * aligned matches. Mutually aligned matches place an additional * constraint on the 'possible' matches, where the relative * shifts must not exceed the (delx, dely) distances. */ countAlignedMatches(nai1, nai2, nasx, nasy, n1, n2, delx, dely, nreq, psame, debugflag); LEPT_FREE(line1); LEPT_FREE(line2); LEPT_FREE(yloc1); LEPT_FREE(yloc2); LEPT_FREE(xleft1); LEPT_FREE(xleft2); numaDestroy(&nai1); numaDestroy(&nai2); numaDestroy(&nasx); numaDestroy(&nasy); return 0; }
l_float32 RelativeDiff(l_int32 v1, l_int32 v2) { return L_ABS(v1 - v2) / (L_MIN(v1, v2) + 1.0); }
l_float32 ComputePairNormalizedHorizontalDistance(BOX *b1, BOX *b2) { l_float32 dx = (b1->x - b2->x) + (b1->w - b2->w) / 2.0; l_float32 hdist = 2.0 * L_ABS(dx) / (b1->w + b2->w); return hdist; }
l_float32 ComputePairNormalizedToplineDistance(BOX *b1, BOX *b2) { l_float32 dy = b1->y - b2->y; l_float32 vdist = 2.0 * L_ABS(dy) / (b1->h + b2->h); return vdist; }
l_float32 ComputePairNormalizedBaselineDistance(BOX *b1, BOX *b2) { l_float32 dy = (b1->y + b1->h) - (b2->y + b2->h); l_float32 vdist = 2.0 * L_ABS(dy) / (b1->h + b2->h); return vdist; }
/*! * dewarpDebug() * * Input: dew * subdir (a subdirectory of /tmp; e.g., "dew1") * index (to help label output images; e.g., the page number) * Return: 0 if OK, 1 on error * * Notes: * (1) Prints dewarp fields and generates disparity array contour images. * The contour images are written to file: * /tmp/[subdir]/pixv_[index].png */ l_int32 dewarpDebug(L_DEWARP *dew, const char *subdir, l_int32 index) { char outdir[256], fname[64]; char *pathname; l_int32 svd, shd; PIX *pixv, *pixh; PROCNAME("dewarpDebug"); if (!dew) return ERROR_INT("dew not defined", procName, 1); if (!subdir) return ERROR_INT("subdir not defined", procName, 1); fprintf(stderr, "pageno = %d, hasref = %d, refpage = %d\n", dew->pageno, dew->hasref, dew->refpage); fprintf(stderr, "sampling = %d, redfactor = %d, minlines = %d\n", dew->sampling, dew->redfactor, dew->minlines); svd = shd = 0; if (!dew->hasref) { if (dew->sampvdispar) svd = 1; if (dew->samphdispar) shd = 1; fprintf(stderr, "sampv = %d, samph = %d\n", svd, shd); fprintf(stderr, "w = %d, h = %d\n", dew->w, dew->h); fprintf(stderr, "nx = %d, ny = %d\n", dew->nx, dew->ny); fprintf(stderr, "nlines = %d\n", dew->nlines); if (svd) { fprintf(stderr, "(min,max,abs-diff) line curvature = (%d,%d,%d)\n", dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv); } if (shd) { fprintf(stderr, "(left edge slope = %d, right edge slope = %d\n", dew->leftslope, dew->rightslope); fprintf(stderr, "(left,right,abs-diff) edge curvature = " "(%d,%d,%d)\n", dew->leftcurv, dew->rightcurv, L_ABS(dew->leftcurv - dew->rightcurv)); } } if (!svd && !shd) { fprintf(stderr, "No disparity arrays\n"); return 0; } dewarpPopulateFullRes(dew, NULL, 0, 0); lept_mkdir(subdir); snprintf(outdir, sizeof(outdir), "/tmp/%s", subdir); if (svd) { pixv = fpixRenderContours(dew->fullvdispar, 3.0, 0.15); snprintf(fname, sizeof(fname), "pixv_%d.png", index); pathname = genPathname(outdir, fname); pixWrite(pathname, pixv, IFF_PNG); pixDestroy(&pixv); FREE(pathname); } if (shd) { pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15); snprintf(fname, sizeof(fname), "pixh_%d.png", index); pathname = genPathname(outdir, fname); pixWrite(pathname, pixh, IFF_PNG); pixDestroy(&pixh); FREE(pathname); } return 0; }
/*! * dewarpaTestForValidModel() * * Input: dewa * dew * notests * Return: 0 if OK, 1 on error * * Notes: * (1) Computes validity of vertical (vvalid) model and both * vertical and horizontal (hvalid) models. * (2) If @notests == 1, this ignores the curvature constraints * and assumes that all successfully built models are valid. * (3) This is just about the models, not the rendering process, * so the value of useboth is not considered here. */ static l_int32 dewarpaTestForValidModel(L_DEWARPA *dewa, L_DEWARP *dew, l_int32 notests) { l_int32 maxcurv, diffcurv, diffedge; PROCNAME("dewarpaTestForValidModel"); if (!dewa || !dew) return ERROR_INT("dewa and dew not both defined", procName, 1); if (notests) { dew->vvalid = dew->vsuccess; dew->hvalid = dew->hsuccess; return 0; } /* No actual model was built */ if (dew->vsuccess == 0) return 0; /* Was previously found not to have a valid model */ if (dew->hasref == 1) return 0; /* vsuccess == 1; a vertical (line) model exists. * First test that the vertical curvatures are within allowed * bounds. Note that all curvatures are signed.*/ maxcurv = L_MAX(L_ABS(dew->mincurv), L_ABS(dew->maxcurv)); diffcurv = dew->maxcurv - dew->mincurv; if (maxcurv <= dewa->max_linecurv && diffcurv >= dewa->min_diff_linecurv && diffcurv <= dewa->max_diff_linecurv) { dew->vvalid = 1; } else { L_INFO("invalid vert model for page %d:\n", procName, dew->pageno); #if DEBUG_INVALID_MODELS fprintf(stderr, " max line curv = %d, max allowed = %d\n", maxcurv, dewa->max_linecurv); fprintf(stderr, " diff line curv = %d, max allowed = %d\n", diffcurv, dewa->max_diff_linecurv); #endif /* DEBUG_INVALID_MODELS */ } /* If a horizontal (edge) model exists, test for validity. */ if (dew->hsuccess) { diffedge = L_ABS(dew->leftcurv - dew->rightcurv); if (L_ABS(dew->leftslope) <= dewa->max_edgeslope && L_ABS(dew->rightslope) <= dewa->max_edgeslope && L_ABS(dew->leftcurv) <= dewa->max_edgecurv && L_ABS(dew->rightcurv) <= dewa->max_edgecurv && diffedge <= dewa->max_diff_edgecurv) { dew->hvalid = 1; } else { L_INFO("invalid horiz model for page %d:\n", procName, dew->pageno); #if DEBUG_INVALID_MODELS fprintf(stderr, " left edge slope = %d, max allowed = %d\n", dew->leftslope, dewa->max_edgeslope); fprintf(stderr, " right edge slope = %d, max allowed = %d\n", dew->rightslope, dewa->max_edgeslope); fprintf(stderr, " left edge curv = %d, max allowed = %d\n", dew->leftcurv, dewa->max_edgecurv); fprintf(stderr, " right edge curv = %d, max allowed = %d\n", dew->rightcurv, dewa->max_edgecurv); fprintf(stderr, " diff edge curv = %d, max allowed = %d\n", diffedge, dewa->max_diff_edgecurv); #endif /* DEBUG_INVALID_MODELS */ } } return 0; }
/*! * dewarpaSetValidModels() * * Input: dewa * notests * debug (1 to output information on invalid page models) * Return: 0 if OK, 1 on error * * Notes: * (1) A valid model must meet the rendering requirements, which * include whether or not a vertical disparity model exists * and conditions on curvatures for vertical and horizontal * disparity models. * (2) If @notests == 1, this ignores the curvature constraints * and assumes that all successfully built models are valid. * (3) This function does not need to be called by the application. * It is called by dewarpaInsertRefModels(), which * will destroy all invalid dewarps. Consequently, to inspect * an invalid dewarp model, it must be done before calling * dewarpaInsertRefModels(). */ l_int32 dewarpaSetValidModels(L_DEWARPA *dewa, l_int32 notests, l_int32 debug) { l_int32 i, n, maxcurv, diffcurv, diffedge; L_DEWARP *dew; PROCNAME("dewarpaSetValidModels"); if (!dewa) return ERROR_INT("dewa not defined", procName, 1); n = dewa->maxpage + 1; for (i = 0; i < n; i++) { if ((dew = dewarpaGetDewarp(dewa, i)) == NULL) continue; if (debug) { if (dew->hasref == 1) { L_INFO("page %d: has only a ref model\n", procName, i); } else if (dew->vsuccess == 0) { L_INFO("page %d: no model successfully built\n", procName, i); } else if (!notests) { maxcurv = L_MAX(L_ABS(dew->mincurv), L_ABS(dew->maxcurv)); diffcurv = dew->maxcurv - dew->mincurv; if (dewa->useboth && !dew->hsuccess) L_INFO("page %d: useboth, but no horiz disparity\n", procName, i); if (maxcurv > dewa->max_linecurv) L_INFO("page %d: max curvature %d > max_linecurv\n", procName, i, diffcurv); if (diffcurv < dewa->min_diff_linecurv) L_INFO("page %d: diff curv %d < min_diff_linecurv\n", procName, i, diffcurv); if (diffcurv > dewa->max_diff_linecurv) L_INFO("page %d: abs diff curv %d > max_diff_linecurv\n", procName, i, diffcurv); if (dew->hsuccess) { if (L_ABS(dew->leftslope) > dewa->max_edgeslope) L_INFO("page %d: abs left slope %d > max_edgeslope\n", procName, i, dew->leftslope); if (L_ABS(dew->rightslope) > dewa->max_edgeslope) L_INFO("page %d: abs right slope %d > max_edgeslope\n", procName, i, dew->rightslope); diffedge = L_ABS(dew->leftcurv - dew->rightcurv); if (L_ABS(dew->leftcurv) > dewa->max_edgecurv) L_INFO("page %d: left curvature %d > max_edgecurv\n", procName, i, dew->leftcurv); if (L_ABS(dew->rightcurv) > dewa->max_edgecurv) L_INFO("page %d: right curvature %d > max_edgecurv\n", procName, i, dew->rightcurv); if (diffedge > dewa->max_diff_edgecurv) L_INFO("page %d: abs diff left-right curv %d > " "max_diff_edgecurv\n", procName, i, diffedge); } } } dewarpaTestForValidModel(dewa, dew, notests); } return 0; }
/* * countAlignedMatches() * Input: nai1, nai2 (numas of row pairs for matches) * nasx, nasy (numas of x and y shifts for the matches) * n1, n2 (number of rows in images 1 and 2) * delx, dely (allowed difference in shifts of the match, * compared to the reference match) * nreq (number of required aligned matches) * &same (<return> 1 if %nreq row matches are found; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) This takes 4 input arrays giving parameters of all the * line matches. It looks for the maximum set of aligned * matches (matches with approximately the same overall shifts) * that do not use rows from either image more than once. */ static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx, NUMA *nasy, l_int32 n1, l_int32 n2, l_int32 delx, l_int32 dely, l_int32 nreq, l_int32 *psame, l_int32 debugflag) { l_int32 i, j, nm, shiftx, shifty, nmatch, diffx, diffy; l_int32 *ia1, *ia2, *iasx, *iasy, *index1, *index2; PROCNAME("countAlignedMatches"); if (!nai1 || !nai2 || !nasx || !nasy) return ERROR_INT("4 input numas not defined", procName, 1); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; /* Check for sufficient aligned matches, doing a double iteration * over the set of raw matches. The row index arrays * are used to verify that the same rows in either image * are not used in more than one match. Whenever there * is a match that is properly aligned, those rows are * marked in the index arrays. */ nm = numaGetCount(nai1); /* number of matches */ if (nm < nreq) return 0; ia1 = numaGetIArray(nai1); ia2 = numaGetIArray(nai2); iasx = numaGetIArray(nasx); iasy = numaGetIArray(nasy); index1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); /* keep track of rows */ index2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); for (i = 0; i < nm; i++) { if (*psame == 1) break; /* Reset row index arrays */ memset(index1, 0, 4 * n1); memset(index2, 0, 4 * n2); nmatch = 1; index1[ia1[i]] = nmatch; /* mark these rows as taken */ index2[ia2[i]] = nmatch; shiftx = iasx[i]; /* reference shift between two rows */ shifty = iasy[i]; /* ditto */ if (nreq == 1) { *psame = 1; break; } for (j = 0; j < nm; j++) { if (j == i) continue; /* Rows must both be different from any previously seen */ if (index1[ia1[j]] > 0 || index2[ia2[j]] > 0) continue; /* Check the shift for this match */ diffx = L_ABS(shiftx - iasx[j]); diffy = L_ABS(shifty - iasy[j]); if (diffx > delx || diffy > dely) continue; /* We have a match */ nmatch++; index1[ia1[j]] = nmatch; /* mark the rows */ index2[ia2[j]] = nmatch; if (nmatch >= nreq) { *psame = 1; if (debugflag) printRowIndices(index1, n1, index2, n2); break; } } } LEPT_FREE(ia1); LEPT_FREE(ia2); LEPT_FREE(iasx); LEPT_FREE(iasy); LEPT_FREE(index1); LEPT_FREE(index2); return 0; }
/*! * deskew() * * Input: pixs * redsearch (for binary search: reduction factor = 1, 2 or 4) * Return: deskewed pix, or NULL on error */ PIX * deskew(PIX *pixs, l_int32 redsearch) { l_float32 angle, conf, deg2rad; PIX *pixg; /* gray version */ PIX *pixb; /* binary version */ PIX *pixd; /* destination image */ PROCNAME("deskew"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); /* Calculate a skew angle. We may need to make a binary version of the * image for this calculation. */ if (pixGetDepth(pixs) != 1) { /* FIX ME: We should probably pick a threshold value with more care. */ /* Create a grayscale image if we need one. */ if (pixGetDepth(pixs) >= 24) { pixg = pixConvertRGBToGray(pixs, 0.0, 0.0, 0.0); } else { pixg = pixs; } pixb = pixThresholdToBinary(pixg, 127); if (pixg != pixs) { pixDestroy(&pixg); } /* Assert: We are done with any gray image. */ } else { pixb = pixs; } /* Assert: We have a valid binary image. */ if (redsearch != 1 && redsearch != 2 && redsearch != 4) return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL); deg2rad = 3.1415926535 / 180.; if (pixFindSkewSweepAndSearch(pixb, &angle, &conf, DEFAULT_SWEEP_REDUCTION, redsearch, DEFAULT_SWEEP_RANGE, DEFAULT_SWEEP_DELTA, DEFAULT_MINBS_DELTA)) { pixd = pixClone(pixs); goto finish; } if (L_ABS(angle) < MIN_DESKEW_ANGLE || conf < MIN_ALLOWED_CONFIDENCE) { pixd = pixClone(pixs); goto finish; } /* If the pixel depth of pixs is 1, we need to use a bit-depth * independent rotate instead of the more accurate area mapping rotate. */ if (pixGetDepth(pixs) == 1) { if ((pixd = pixRotateShear(pixs, 0, 0, deg2rad * angle, 0xffffff00)) == NULL) { pixd = pixClone(pixs); } } else { #if defined(COLOR_ROTATE) if ((pixd = pixRotateAMColorFast(pixs, deg2rad * angle)) == NULL) { pixd = pixClone(pixs); } #else if ((pixd = pixRotateAM(pixs, deg2rad * angle, 0xffffff00)) == NULL) { pixd = pixClone(pixs); } #endif } finish: if (pixb != pixs) { pixDestroy(&pixb); } return pixd; }
/*! * \brief pixFindBaselines() * * \param[in] pixs 1 bpp, 300 ppi * \param[out] ppta [optional] pairs of pts corresponding to * approx. ends of each text line * \param[in] pixadb for debug output; use NULL to skip * \return na of baseline y values, or NULL on error * * <pre> * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * </pre> */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, PIXA *pixadb) { l_int32 h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pix1, *pix2; PTA *pta; PROCNAME("pixFindBaselines"); if (ppta) *ppta = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Close up the text characters, removing noise */ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0); /* Estimate the resolution */ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) { pixDestroy(&pix1); return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); } h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } numaDestroy(&nasum); if (pixadb) { /* show the difference signal */ lept_mkdir("lept/baseline"); gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); pix2 = pixRead("/tmp/lept/baseline/diff.png"); pixaAddPix(pixadb, pix2, L_INSERT); } /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); numaDestroy(&nadiff); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } LEPT_FREE(array); /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } if (pixadb) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); pix2 = pixRead("/tmp/lept/baseline/loc.png"); pixaAddPix(pixadb, pix2, L_INSERT); } numaDestroy(&naval); /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0); if (pixadb) pixaAddPix(pixadb, pix2, L_COPY); boxa1 = pixConnComp(pix2, NULL, 4); pixDestroy(&pix1); pixDestroy(&pix2); if (boxaGetCount(boxa1) == 0) { numaDestroy(&naloc); boxaDestroy(&boxa1); L_INFO("no compnents after filtering\n", procName); return NULL; } boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Optionally, find the baseline segments */ pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } boxaDestroy(&boxa3); if (pixadb && pta) { /* display baselines */ l_int32 npts, x1, y1, x2, y2; pix1 = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0); } pixWrite("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG); pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); pixDestroy(&pix1); } return naloc; }
/*! * kernelDisplayInPix() * * Input: kernel * size (of grid interiors; odd; either 1 or a minimum size * of 17 is enforced) * gthick (grid thickness; either 0 or a minimum size of 2 * is enforced) * Return: pix (display of kernel), or null on error * * Notes: * (1) This gives a visual representation of a kernel. * (2) There are two modes of display: * (a) Grid lines of minimum width 2, surrounding regions * representing kernel elements of minimum size 17, * with a "plus" mark at the kernel origin, or * (b) A pix without grid lines and using 1 pixel per kernel element. * (3) For both cases, the kernel absolute value is displayed, * normalized such that the maximum absolute value is 255. * (4) Large 2D separable kernels should be used for convolution * with two 1D kernels. However, for the bilateral filter, * the computation time is independent of the size of the * 2D content kernel. */ PIX * kernelDisplayInPix(L_KERNEL *kel, l_int32 size, l_int32 gthick) { l_int32 i, j, w, h, sx, sy, cx, cy, width, x0, y0; l_int32 normval; l_float32 minval, maxval, max, val, norm; PIX *pixd, *pixt0, *pixt1; PROCNAME("kernelDisplayInPix"); if (!kel) return (PIX *)ERROR_PTR("kernel not defined", procName, NULL); /* Normalize the max value to be 255 for display */ kernelGetParameters(kel, &sy, &sx, &cy, &cx); kernelGetMinMax(kel, &minval, &maxval); max = L_MAX(maxval, -minval); if (max == 0.0) return (PIX *)ERROR_PTR("kernel elements all 0.0", procName, NULL); norm = 255. / (l_float32)max; /* Handle the 1 element/pixel case; typically with large kernels */ if (size == 1 && gthick == 0) { pixd = pixCreate(sx, sy, 8); for (i = 0; i < sy; i++) { for (j = 0; j < sx; j++) { kernelGetElement(kel, i, j, &val); normval = (l_int32)(norm * L_ABS(val)); pixSetPixel(pixd, j, i, normval); } } return pixd; } /* Enforce the constraints for the grid line version */ if (size < 17) { L_WARNING("size < 17; setting to 17\n", procName); size = 17; } if (size % 2 == 0) size++; if (gthick < 2) { L_WARNING("grid thickness < 2; setting to 2\n", procName); gthick = 2; } w = size * sx + gthick * (sx + 1); h = size * sy + gthick * (sy + 1); pixd = pixCreate(w, h, 8); /* Generate grid lines */ for (i = 0; i <= sy; i++) pixRenderLine(pixd, 0, gthick / 2 + i * (size + gthick), w - 1, gthick / 2 + i * (size + gthick), gthick, L_SET_PIXELS); for (j = 0; j <= sx; j++) pixRenderLine(pixd, gthick / 2 + j * (size + gthick), 0, gthick / 2 + j * (size + gthick), h - 1, gthick, L_SET_PIXELS); /* Generate mask for each element */ pixt0 = pixCreate(size, size, 1); pixSetAll(pixt0); /* Generate crossed lines for origin pattern */ pixt1 = pixCreate(size, size, 1); width = size / 8; pixRenderLine(pixt1, size / 2, (l_int32)(0.12 * size), size / 2, (l_int32)(0.88 * size), width, L_SET_PIXELS); pixRenderLine(pixt1, (l_int32)(0.15 * size), size / 2, (l_int32)(0.85 * size), size / 2, width, L_FLIP_PIXELS); pixRasterop(pixt1, size / 2 - width, size / 2 - width, 2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0); /* Paste the patterns in */ y0 = gthick; for (i = 0; i < sy; i++) { x0 = gthick; for (j = 0; j < sx; j++) { kernelGetElement(kel, i, j, &val); normval = (l_int32)(norm * L_ABS(val)); pixSetMaskedGeneral(pixd, pixt0, normval, x0, y0); if (i == cy && j == cx) pixPaintThroughMask(pixd, pixt1, x0, y0, 255 - normval); x0 += size + gthick; } y0 += size + gthick; } pixDestroy(&pixt0); pixDestroy(&pixt1); return pixd; }
l_int32 main(int argc, char **argv) { l_int32 ret, i, n, similar, x1, y1, val1, val2, val3, val4; l_float32 minave, minave2, maxave, fract; NUMA *na1, *na2, *na3, *na4, *na5, *na6; NUMAA *naa; PIX *pixs, *pix1, *pix2, *pix3, *pix4; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn.tif"); pix1 = pixScaleToGray6(pixs); pixDisplayWithTitle(pix1, 100, 600, NULL, rp->display); /* Find averages of min and max along about 120 horizontal lines */ fprintf(stderr, "Ignore the following 12 error messages:\n"); na1 = numaCreate(0); na3 = numaCreate(0); for (y1 = 40; y1 < 575; y1 += 5) { ret = pixMinMaxNearLine(pix1, 20, y1, 400, y1, 5, L_SCAN_BOTH, NULL, NULL, &minave, &maxave); if (!ret) { numaAddNumber(na1, (l_int32)minave); numaAddNumber(na3, (l_int32)maxave); if (rp->display) fprintf(stderr, "y = %d: minave = %d, maxave = %d\n", y1, (l_int32)minave, (l_int32)maxave); } } /* Find averages along about 120 vertical lines. We've rotated * the image by 90 degrees, so the results should be nearly * identical to the first set. Also generate a single-sided * scan (L_SCAN_NEGATIVE) for comparison with the double-sided scans. */ pix2 = pixRotateOrth(pix1, 3); pixDisplayWithTitle(pix2, 600, 600, NULL, rp->display); na2 = numaCreate(0); na4 = numaCreate(0); na5 = numaCreate(0); for (x1 = 40; x1 < 575; x1 += 5) { ret = pixMinMaxNearLine(pix2, x1, 20, x1, 400, 5, L_SCAN_BOTH, NULL, NULL, &minave, &maxave); pixMinMaxNearLine(pix2, x1, 20, x1, 400, 5, L_SCAN_NEGATIVE, NULL, NULL, &minave2, NULL); if (!ret) { numaAddNumber(na2, (l_int32)minave); numaAddNumber(na4, (l_int32)maxave); numaAddNumber(na5, (l_int32)minave2); if (rp->display) fprintf(stderr, "x = %d: minave = %d, minave2 = %d, maxave = %d\n", x1, (l_int32)minave, (l_int32)minave2, (l_int32)maxave); } } numaSimilar(na1, na2, 3.0, &similar); /* should be TRUE */ regTestCompareValues(rp, similar, 1, 0); /* 0 */ numaSimilar(na3, na4, 1.0, &similar); /* should be TRUE */ regTestCompareValues(rp, similar, 1, 0); /* 1 */ numaWrite("/tmp/lept/regout/na1.na", na1); numaWrite("/tmp/lept/regout/na2.na", na2); numaWrite("/tmp/lept/regout/na3.na", na3); numaWrite("/tmp/lept/regout/na4.na", na4); numaWrite("/tmp/lept/regout/na5.na", na5); regTestCheckFile(rp, "/tmp/lept/regout/na1.na"); /* 2 */ regTestCheckFile(rp, "/tmp/lept/regout/na2.na"); /* 3 */ regTestCheckFile(rp, "/tmp/lept/regout/na3.na"); /* 4 */ regTestCheckFile(rp, "/tmp/lept/regout/na4.na"); /* 5 */ regTestCheckFile(rp, "/tmp/lept/regout/na5.na"); /* 6 */ /* Plot the average minimums for the 3 cases */ naa = numaaCreate(3); numaaAddNuma(naa, na1, L_INSERT); /* portrait, double-sided */ numaaAddNuma(naa, na2, L_INSERT); /* landscape, double-sided */ numaaAddNuma(naa, na5, L_INSERT); /* landscape, single-sided */ gplotSimpleN(naa, GPLOT_PNG, "/tmp/lept/regout/nearline", "Average minimums along lines"); #if 0 #ifndef _WIN32 sleep(1); #else Sleep(1000); #endif /* _WIN32 */ #endif pix3 = pixRead("/tmp/lept/regout/nearline.png"); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 7 */ pixDisplayWithTitle(pix3, 100, 100, NULL, rp->display); if (rp->display) { n = numaGetCount(na3); for (i = 0; i < n; i++) { numaGetIValue(na1, i, &val1); numaGetIValue(na2, i, &val2); numaGetIValue(na3, i, &val3); numaGetIValue(na4, i, &val4); fprintf(stderr, "val1 = %d, val2 = %d, diff = %d; " "val3 = %d, val4 = %d, diff = %d\n", val1, val2, L_ABS(val1 - val2), val3, val4, L_ABS(val3 - val4)); } } numaaDestroy(&naa); numaDestroy(&na3); numaDestroy(&na4); /* Plot minima along a single line, with different distances */ pixMinMaxNearLine(pix1, 20, 200, 400, 200, 2, L_SCAN_BOTH, &na1, NULL, NULL, NULL); pixMinMaxNearLine(pix1, 20, 200, 400, 200, 5, L_SCAN_BOTH, &na2, NULL, NULL, NULL); pixMinMaxNearLine(pix1, 20, 200, 400, 200, 15, L_SCAN_BOTH, &na3, NULL, NULL, NULL); numaWrite("/tmp/lept/regout/na6.na", na1); regTestCheckFile(rp, "/tmp/lept/regout/na6.na"); /* 8 */ n = numaGetCount(na1); fract = 100.0 / n; na4 = numaTransform(na1, 0.0, fract); na5 = numaTransform(na2, 0.0, fract); na6 = numaTransform(na3, 0.0, fract); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); na1 = numaUniformSampling(na4, 100); na2 = numaUniformSampling(na5, 100); na3 = numaUniformSampling(na6, 100); naa = numaaCreate(3); numaaAddNuma(naa, na1, L_INSERT); numaaAddNuma(naa, na2, L_INSERT); numaaAddNuma(naa, na3, L_INSERT); gplotSimpleN(naa, GPLOT_PNG, "/tmp/lept/regout/nearline2", "Min along line"); pix4 = pixRead("/tmp/lept/regout/nearline2.png"); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 9 */ pixDisplayWithTitle(pix4, 800, 100, NULL, rp->display); numaaDestroy(&naa); numaDestroy(&na4); numaDestroy(&na5); numaDestroy(&na6); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pixs); return regTestCleanup(rp); }