/*! * \brief pixDeskewLocal() * * \param[in] pixs 1 bpp * \param[in] nslices the number of horizontal overlapping slices; * must be larger than 1 and not exceed 20; * use 0 for default * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value * \param[in] sweeprange half the full range, assumed about 0; in degrees; * use 0.0 for default value * \param[in] sweepdelta angle increment of sweep; in degrees; * use 0.0 for default value * \param[in] minbsdelta min binary search increment angle; in degrees; * use 0.0 for default value * \return pixd, or NULL on error * * <pre> * Notes: * (1) This function allows deskew of a page whose skew changes * approximately linearly with vertical position. It uses * a projective transform that in effect does a differential * shear about the LHS of the page, and makes all text lines * horizontal. * (2) The origin of the keystoning can be either a cheap document * feeder that rotates the page as it is passed through, or a * camera image taken from either the left or right side * of the vertical. * (3) The image transformation is a projective warping, * not a rotation. Apart from this function, the text lines * must be properly aligned vertically with respect to each * other. This can be done by pre-processing the page; e.g., * by rotating or horizontally shearing it. * Typically, this can be achieved by vertically aligning * the page edge. * </pre> */ PIX * pixDeskewLocal(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta) { l_int32 ret; PIX *pixd; PTA *ptas, *ptad; PROCNAME("pixDeskewLocal"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Skew array gives skew angle (deg) as fctn of raster line * where it intersects the LHS of the image */ ret = pixGetLocalSkewTransform(pixs, nslices, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta, &ptas, &ptad); if (ret != 0) return (PIX *)ERROR_PTR("transform pts not found", procName, NULL); /* Use a projective transform */ pixd = pixProjectiveSampledPta(pixs, ptad, ptas, L_BRING_IN_WHITE); ptaDestroy(&ptas); ptaDestroy(&ptad); return pixd; }
int main(int argc, char* argv[]) { PIX *pixs, *pixb, *pixt; int minb; if (argc < 2) { USAGE: fprintf(stderr, "Usage: %s </path/to/text-image>\n" "\t\t[binarize-threshold] [minw:minh:maxw:maxh]\n", strrchr(argv[0], '/') + 1); return EINVAL; } if (2 < argc) { errno = 0; minb = strtol(argv[2], NULL, 10); if (errno < 0) { fprintf(stderr, "strtol: %s\n", strerror(errno)); goto USAGE; } } else minb = 180; if (!(pixs = pixRead(argv[1]))) ; if (1 && (pixt = pixBackgroundNormMorph(pixs, NULL, 4, 5, 248))) { pixDestroy(&pixs); pixs = pixt; } else if (0 && (pixt = pixBackgroundNorm(pixs, NULL, NULL, 10, 15, 60, 40, 248, 2, 1))) { pixDestroy(&pixs); pixs = pixt; } if (1 && (pixt = pixFindSkewAndDeskew(pixs, 1, NULL, NULL))) { pixDestroy(&pixs); pixs = pixt; } if (0 && pixDisplay(pixs, 0, 0)) ; if (1) { PTA *ptas, *ptad; if (!(pixb = pixConvertTo1(pixs, minb))) ; // pixt = pixDeskewLocal(pixs, 10, 0, 0, 0.0, 0.0, 0.0)) if (!pixGetLocalSkewTransform(pixb, 10, 0, 0, 0.0, 0.0, 0.0, &ptas, &ptad)) { if ((pixt = pixProjectiveSampledPta(pixs, ptad, ptas, L_BRING_IN_WHITE))) { pixDestroy(&pixs); pixs = pixt; } ptaDestroy(&ptas); ptaDestroy(&ptad); } pixDestroy(&pixb); } if (0 && (pixt = pixGammaTRC(NULL, pixs, 1.0, 30, 180))) { pixDestroy(&pixs); pixs = pixt; } if (!(pixb = pixConvertTo1(pixs, minb))) ; if (0) { pixDestroy(&pixs); pixs = pixCopy(pixs, pixb); } // XXX: if (1) { BOX* box; int i, n, j, m; PIX *pixi, *pixl; BOXA *boxi, *boxl; int x, y, w, h, wid; int X = INT_MAX, Y = INT_MAX, W = 0, H; // XXX: do smaller(or no) pixOpenBrick if (pixGetRegionsBinary(pixb, &pixi, &pixl, NULL, 0)) ; boxl = pixConnComp(pixl, NULL, 4); n = boxaGetCount(boxl); for (i = 0; i < n; ++i) { BOXA* boxa; box = boxaGetBox(boxl, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); if (w < 30 || h < 30 || w < h || h < (w / 40)) { boxDestroy(&box); continue; boxaRemoveBox(boxl, i); } if (x < X) X = x; if (y < Y) Y = y; if (W < w) W = w; pixt = pixClipRectangle(pixb, box, NULL); boxDestroy(&box); // XXX: for English if (0) pixt = pixDilateBrick(pixt, pixt, h >> 1, h >> 1); else pixt = pixDilateBrick(pixt, pixt, 16 < h ? h >> 4 : 1, h << 1); if (0 && pixDisplay(pixt, 0, 0)) ; boxa = pixConnComp(pixt, NULL, 8); pixDestroy(&pixt); wid = (h * 3) >> 2; //boxaShift(boxa, x, y); m = boxaGetCount(boxa); for (j = 0; j < m; ++j) { int x0, y0, w0; box = boxaGetBox(boxa, j, L_CLONE); boxGetGeometry(box, &x0, &y0, &w0, NULL); // merge adjacent 2 or 3 small boxes if (1 && w0 < wid && (j + 1) < m) { BOX* boxn; int xn, wn; boxn = boxaGetBox(boxa, j + 1, L_CLONE); boxGetGeometry(boxn, &xn, NULL, &wn, NULL); if ((w0 = xn + wn - x0) < h) { boxaSparseClearBox(boxa, ++j); if (w0 < wid && (j + 1) < m) { boxDestroy(&boxn); boxn = boxaGetBox(boxa, j + 1, L_CLONE); boxGetGeometry(boxn, &xn, NULL, &wn, NULL); if ((wn = xn + wn - x0) < h) { boxaSparseClearBox(boxa, ++j); w0 = wn; } } boxSetGeometry(box, -1, -1, w0, -1); } boxDestroy(&boxn); } boxSetGeometry(box, x + x0, y + y0, -1, -1); boxDestroy(&box); } boxaSparseCompact(boxa); if (1 && (pixt = pixDrawBoxa(pixs, boxa, 1, 0xff000000))) { pixDestroy(&pixs); pixs = pixt; } boxaDestroy(&boxa); } H = y + h;