/*! * pixTranslate() * * Input: pixd (<optional> destination: this can be null, * equal to pixs, or different from pixs) * pixs * hshift (horizontal shift; hshift > 0 is to right) * vshift (vertical shift; vshift > 0 is down) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: pixd, or null on error. * * Notes: * (1) The general pattern is: * pixd = pixTranslate(pixd, pixs, ...); * For clarity, when you know the case, use one of these: * pixd = pixTranslate(NULL, pixs, ...); // new * pixTranslate(pixs, pixs, ...); // in-place * pixTranslate(pixd, pixs, ...); // to existing pixd * (2) If an existing pixd is not the same size as pixs, the * image data will be reallocated. */ PIX * pixTranslate(PIX *pixd, PIX *pixs, l_int32 hshift, l_int32 vshift, l_int32 incolor) { PROCNAME("pixTranslate"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); /* Prepare pixd for in-place operation */ if ((pixd = pixCopy(pixd, pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixRasteropIP(pixd, hshift, vshift, incolor); return pixd; }
/*! * pixRotateOrth() * * Input: pixs (all depths) * quads (0-3; number of 90 degree cw rotations) * Return: pixd, or null on error */ PIX * pixRotateOrth(PIX *pixs, l_int32 quads) { PROCNAME("pixRotateOrth"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (quads < 0 || quads > 4) return (PIX *)ERROR_PTR("quads not in {0,1,2,3,4}", procName, NULL); if (quads == 0 || quads == 4) return pixCopy(NULL, pixs); else if (quads == 1) return pixRotate90(pixs, 1); else if (quads == 2) return pixRotate180(NULL, pixs); else /* quads == 3 */ return pixRotate90(pixs, -1); }
main(int argc, char **argv) { l_int32 i, j; PIX *pixs, *pixt, *pixd; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("test8.jpg"); pixt = pixCopy(NULL, pixs); /* Copy, in-place and one COLUMN at a time, from the right side to the left side. */ for (j = 0; j < 200; j++) pixRasterop(pixs, 20 + j, 20, 1, 250, PIX_SRC, pixs, 250 + j, 20); pixDisplayWithTitle(pixs, 50, 50, "in-place copy", rp->display); /* Copy, in-place and one ROW at a time, from the right side to the left side. */ for (i = 0; i < 250; i++) pixRasterop(pixt, 20, 20 + i, 200, 1, PIX_SRC, pixt, 250, 20 + i); /* Test */ regTestComparePix(rp, pixs, pixt); /* 0 */ pixDestroy(&pixs); pixDestroy(&pixt); /* Show the mirrored border, which uses the general pixRasterop() on an image in-place. */ pixs = pixRead("test8.jpg"); pixt = pixRemoveBorder(pixs, 40); pixd = pixAddMirroredBorder(pixt, 40, 40, 40, 40); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */ pixDisplayWithTitle(pixd, 650, 50, "mirrored border", rp->display); pixDestroy(&pixs); pixDestroy(&pixt); pixDestroy(&pixd); return regTestCleanup(rp); }
static void CopyPtras(L_PTRA *papixs, L_PTRA *paboxs, L_PTRA **ppapixd, L_PTRA **ppaboxd) { l_int32 i, imax; BOX *box; PIX *pix; ptraGetMaxIndex(papixs, &imax); *ppapixd = ptraCreate(imax + 1); *ppaboxd = ptraCreate(imax + 1); for (i = 0; i <= imax; i++) { pix = pixCopy(NULL, (PIX *)ptraGetPtrToItem(papixs, i)); box = boxCopy((BOX *)ptraGetPtrToItem(paboxs, i)); ptraAdd(*ppapixd, pix); ptraAdd(*ppaboxd, box); } return; }
/*! * \brief pixSetStrokeWidth() * * \param[in] pixs 1 bpp pix * \param[in] width set stroke width to this value, in [1 ... 100]. * \param[in] thinfirst 1 to thin all pix to a skeleton first; 0 to skip * \param[in] connectivity 4 or 8, to be used if %thinfirst == 1 * \return pixd with stroke width set to %width, or NULL on error * * <pre> * Notes: * (1) See notes in pixaSetStrokeWidth(). * (2) A white border of sufficient width to avoid boundary * artifacts in the thickening step is added before thinning. * (3) %connectivity == 8 usually gives a slightly smoother result. * </pre> */ PIX * pixSetStrokeWidth(PIX *pixs, l_int32 width, l_int32 thinfirst, l_int32 connectivity) { char buf[16]; l_int32 border; PIX *pix1, *pix2, *pixd; PROCNAME("pixSetStrokeWidth"); if (!pixs || (pixGetDepth(pixs) != 1)) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (width < 1 || width > 100) return (PIX *)ERROR_PTR("width not in [1 ... 100]", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (!thinfirst && width == 1) /* nothing to do */ return pixCopy(NULL, pixs); /* Add a white border */ border = width / 2; pix1 = pixAddBorder(pixs, border, 0); /* Thin to a skeleton */ if (thinfirst) pix2 = pixThinConnected(pix1, L_THIN_FG, connectivity, 0); else pix2 = pixClone(pix1); pixDestroy(&pix1); /* Dilate */ snprintf(buf, sizeof(buf), "D%d.%d", width, width); pixd = pixMorphSequence(pix2, buf, 0); pixCopyText(pixd, pixs); pixDestroy(&pix2); return pixd; }
/*! * pixThresholdToValue() * * Input: pixd (<optional>; if not null, must be equal to pixs) * pixs (8, 16, 32 bpp) * threshval * setval * Return: pixd always * * Notes: * - operation can be in-place (pixs == pixd) or to a new pixd * - if setval > threshval, sets pixels with a value >= threshval to setval * - if setval < threshval, sets pixels with a value <= threshval to setval * - if setval == threshval, no-op */ PIX * pixThresholdToValue(PIX *pixd, PIX *pixs, l_int32 threshval, l_int32 setval) { l_int32 w, h, d, wpld; l_uint32 *datad; PROCNAME("pixThresholdToValue"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); d = pixGetDepth(pixs); if (d != 8 && d != 16 && d != 32) return (PIX *)ERROR_PTR("pixs not 8, 16 or 32 bpp", procName, pixd); if (pixd && (pixs != pixd)) return (PIX *)ERROR_PTR("pixd exists and is not pixs", procName, pixd); if (threshval < 0 || setval < 0) return (PIX *)ERROR_PTR("threshval & setval not < 0", procName, pixd); if (d == 8 && setval > 255) return (PIX *)ERROR_PTR("setval > 255 for 8 bpp", procName, pixd); if (d == 16 && setval > 0xffff) return (PIX *)ERROR_PTR("setval > 0xffff for 16 bpp", procName, pixd); if (!pixd) pixd = pixCopy(NULL, pixs); if (setval == threshval) { L_WARNING("setval == threshval; no operation", procName); return pixd; } datad = pixGetData(pixd); pixGetDimensions(pixd, &w, &h, NULL); wpld = pixGetWpl(pixd); thresholdToValueLow(datad, w, h, d, wpld, threshval, setval); return pixd; }
/*! * pixDilateGray3() * * Input: pixs (8 bpp, not cmapped) * hsize (1 or 3) * vsize (1 or 3) * Return: pixd, or null on error * * Notes: * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits) * (2) If hsize = vsize = 1, just returns a copy. */ PIX * pixDilateGray3(PIX *pixs, l_int32 hsize, l_int32 vsize) { PIX *pixt, *pixb, *pixbd, *pixd; PROCNAME("pixDilateGray3"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pix has colormap", procName, NULL); if ((hsize != 1 && hsize != 3) || (vsize != 1 && vsize != 3)) return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL); if (hsize == 1 && vsize == 1) return pixCopy(NULL, pixs); pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0); if (vsize == 1) pixbd = pixDilateGray3h(pixb); else if (hsize == 1) pixbd = pixDilateGray3v(pixb); else { /* vize == hsize == 3 */ pixt = pixDilateGray3h(pixb); pixbd = pixDilateGray3v(pixt); pixDestroy(&pixt); } pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8); pixDestroy(&pixb); pixDestroy(&pixbd); return pixd; }
/* * Clean dark background action handling */ void MainWindow::on_actionCleanDarkBackground_triggered() { PIX *pixt; CDBDialog cdb_dialog(this); cdb_dialog.setValues(blackval, whiteval, thresh); connect(&cdb_dialog, SIGNAL(cdbParamsChanged(int, int, int)), this, SLOT(slotCleanDarkBackground(int, int , int))); if (cdb_dialog.exec() == QDialog::Accepted) { blackval = cdb_dialog.blackVal->value(); whiteval = cdb_dialog.whiteVal->value(); thresh = cdb_dialog.treshold->value(); pixt = cleanDarkBackground(blackval, whiteval, thresh); pixs = pixCopy(NULL, pixt); pixDestroy(&pixt); setPixToScene(); modified = true; updateTitle(); this->statusBar()->showMessage(tr("Finished..."), 2000); } else { setPixToScene(); } }
/*! * pixRankFilterRGB() * * Input: pixs (32 bpp) * wf, hf (width and height of filter; each is >= 1) * rank (in [0.0 ... 1.0]) * Return: pixd (of rank values), or null on error * * Notes: * (1) This defines, for each pixel in pixs, a neighborhood of * pixels given by a rectangle "centered" on the pixel. * This set of wf*hf pixels has a distribution of values. * For each component, if the values are sorted in increasing * order, we choose the component such that rank*(wf*hf-1) * pixels have a lower or equal value and * (1-rank)*(wf*hf-1) pixels have an equal or greater value. * (2) Apply gray rank filtering to each component independently. * (3) See notes in pixRankFilterGray() for further details. */ PIX * pixRankFilterRGB(PIX *pixs, l_int32 wf, l_int32 hf, l_float32 rank) { PIX *pixr, *pixg, *pixb, *pixrf, *pixgf, *pixbf, *pixd; PROCNAME("pixRankFilterRGB"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (wf < 1 || hf < 1) return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL); if (rank < 0.0 || rank > 1.0) return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL); if (wf == 1 && hf == 1) /* no-op */ return pixCopy(NULL, pixs); pixr = pixGetRGBComponent(pixs, COLOR_RED); pixg = pixGetRGBComponent(pixs, COLOR_GREEN); pixb = pixGetRGBComponent(pixs, COLOR_BLUE); pixrf = pixRankFilterGray(pixr, wf, hf, rank); pixgf = pixRankFilterGray(pixg, wf, hf, rank); pixbf = pixRankFilterGray(pixb, wf, hf, rank); pixd = pixCreateRGBImage(pixrf, pixgf, pixbf); pixDestroy(&pixr); pixDestroy(&pixg); pixDestroy(&pixb); pixDestroy(&pixrf); pixDestroy(&pixgf); pixDestroy(&pixbf); return pixd; }
/*! * pixRotate180() * * Input: pixd (<optional>; can be null, equal to pixs, * or different from pixs) * pixs (all depths) * Return: pixd, or null on error * * Notes: * (1) This does a 180 rotation of the image about the center, * which is equivalent to a left-right flip about a vertical * line through the image center, followed by a top-bottom * flip about a horizontal line through the image center. * (2) There are 3 cases for input: * (a) pixd == null (creates a new pixd) * (b) pixd == pixs (in-place operation) * (c) pixd != pixs (existing pixd) * (3) For clarity, use these three patterns, respectively: * (a) pixd = pixRotate180(NULL, pixs); * (b) pixRotate180(pixs, pixs); * (c) pixRotate180(pixd, pixs); */ PIX * pixRotate180(PIX *pixd, PIX *pixs) { l_int32 d; PROCNAME("pixRotate180"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); d = pixGetDepth(pixs); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) return (PIX *)ERROR_PTR("pixs not in {1,2,4,8,16,32} bpp", procName, NULL); /* Prepare pixd for in-place operation */ if ((pixd = pixCopy(pixd, pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixFlipLR(pixd, pixd); pixFlipTB(pixd, pixd); return pixd; }
// Creates and returns a Pix distorted by various means according to the bool // flags. If boxes is not nullptr, the boxes are resized/positioned according to // any spatial distortion and also by the integer reduction factor box_scale // so they will match what the network will output. // Returns nullptr on error. The returned Pix must be pixDestroyed. Pix* PrepareDistortedPix(const Pix* pix, bool perspective, bool invert, bool white_noise, bool smooth_noise, bool blur, int box_reduction, TRand* randomizer, GenericVector<TBOX>* boxes) { Pix* distorted = pixCopy(nullptr, const_cast<Pix*>(pix)); // Things to do to synthetic training data. if (invert && randomizer->SignedRand(1.0) < 0) pixInvert(distorted, distorted); if ((white_noise || smooth_noise) && randomizer->SignedRand(1.0) > 0.0) { // TODO(rays) Cook noise in a more thread-safe manner than rand(). // Attempt to make the sequences reproducible. srand(randomizer->IntRand()); Pix* pixn = pixAddGaussianNoise(distorted, 8.0); pixDestroy(&distorted); if (smooth_noise) { distorted = pixBlockconv(pixn, 1, 1); pixDestroy(&pixn); } else { distorted = pixn; } } if (blur && randomizer->SignedRand(1.0) > 0.0) { Pix* blurred = pixBlockconv(distorted, 1, 1); pixDestroy(&distorted); distorted = blurred; } if (perspective) GeneratePerspectiveDistortion(0, 0, randomizer, &distorted, boxes); if (boxes != nullptr) { for (int b = 0; b < boxes->size(); ++b) { (*boxes)[b].scale(1.0f / box_reduction); if ((*boxes)[b].width() <= 0) (*boxes)[b].set_right((*boxes)[b].left() + 1); } } return distorted; }
main(int argc, char **argv) { l_int32 error; l_uint32 *data; PIX *pix1, *pix2, *pix3, *pix1c, *pix2c, *pix1t, *pix2t, *pixd; PIXA *pixa; static char mainName[] = "pixmem_reg"; error = 0; pixa = pixaCreate(0); /* Copy with internal resizing: onto a cmapped image */ pix1 = pixRead("weasel4.16c.png"); pix2 = pixRead("feyn-fract.tif"); pix3 = pixRead("lucasta.150.jpg"); fprintf(stderr, "before copy 2 --> 3\n"); pixCopy(pix3, pix2); Compare(pix2, pix3, &error); pixSaveTiled(pix3, pixa, 4, 1, 30, 32); fprintf(stderr, "before copy 3 --> 1\n"); pixCopy(pix1, pix3); Compare(pix2, pix1, &error); pixSaveTiled(pix1, pixa, 4, 0, 30, 32); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Copy with internal resizing: from a cmapped image */ pix1 = pixRead("weasel4.16c.png"); pix2 = pixRead("feyn-fract.tif"); pix3 = pixRead("lucasta.150.jpg"); fprintf(stderr, "before copy 1 --> 2\n"); pixCopy(pix2, pix1); Compare(pix2, pix1, &error); pixSaveTiled(pix2, pixa, 1, 1, 30, 32); fprintf(stderr, "before copy 2 --> 3\n"); pixCopy(pix3, pix2); Compare(pix3, pix2, &error); pixSaveTiled(pix3, pixa, 1, 0, 30, 32); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Transfer of data pixs --> pixd, when pixs is not cloned. * pixs is destroyed. */ pix1 = pixRead("weasel4.16c.png"); pix2 = pixRead("feyn-fract.tif"); pix3 = pixRead("lucasta.150.jpg"); pix1c = pixCopy(NULL, pix1); fprintf(stderr, "before transfer 1 --> 2\n"); pixTransferAllData(pix2, &pix1, 0, 0); Compare(pix2, pix1c, &error); pixSaveTiled(pix2, pixa, 1, 1, 30, 32); fprintf(stderr, "before transfer 2 --> 3\n"); pixTransferAllData(pix3, &pix2, 0, 0); Compare(pix3, pix1c, &error); pixSaveTiled(pix3, pixa, 1, 0, 30, 32); pixDestroy(&pix1c); pixDestroy(&pix3); /* Another transfer of data pixs --> pixd, when pixs is not cloned. * pixs is destroyed. */ pix1 = pixRead("weasel4.16c.png"); pix2 = pixRead("feyn-fract.tif"); pix3 = pixRead("lucasta.150.jpg"); pix1c = pixCopy(NULL, pix1); pix2c = pixCopy(NULL, pix2); fprintf(stderr, "before copy transfer 1 --> 2\n"); pixTransferAllData(pix2, &pix1c, 0, 0); Compare(pix2, pix1, &error); pixSaveTiled(pix2, pixa, 1, 0, 30, 32); fprintf(stderr, "before copy transfer 2 --> 3\n"); pixTransferAllData(pix3, &pix2, 0, 0); Compare(pix3, pix1, &error); pixSaveTiled(pix3, pixa, 1, 0, 30, 32); pixDestroy(&pix1); pixDestroy(&pix2c); pixDestroy(&pix3); /* Transfer of data pixs --> pixd, when pixs is cloned. * pixs has its refcount reduced by 1. */ pix1 = pixRead("weasel4.16c.png"); pix2 = pixRead("feyn-fract.tif"); pix3 = pixRead("lucasta.150.jpg"); pix1c = pixClone(pix1); pix2c = pixClone(pix2); fprintf(stderr, "before clone transfer 1 --> 2\n"); pixTransferAllData(pix2, &pix1c, 0, 0); Compare(pix2, pix1, &error); pixSaveTiled(pix2, pixa, 1, 0, 30, 32); fprintf(stderr, "before clone transfer 2 --> 3\n"); pixTransferAllData(pix3, &pix2c, 0, 0); Compare(pix3, pix1, &error); pixSaveTiled(pix3, pixa, 1, 0, 30, 32); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Extraction of data when pixs is not cloned, putting * the data into a new template of pixs. */ pix2 = pixRead("feyn-fract.tif"); fprintf(stderr, "no clone: before extraction and reinsertion of 2\n"); pix2c = pixCopy(NULL, pix2); /* for later reference */ data = pixExtractData(pix2); pix2t = pixCreateTemplateNoInit(pix2); pixFreeData(pix2t); pixSetData(pix2t, data); Compare(pix2c, pix2t, &error); pixSaveTiled(pix2t, pixa, 4, 1, 30, 32); pixDestroy(&pix2); pixDestroy(&pix2c); pixDestroy(&pix2t); /* Extraction of data when pixs is cloned, putting * a copy of the data into a new template of pixs. */ pix1 = pixRead("weasel4.16c.png"); fprintf(stderr, "clone: before extraction and reinsertion of 1\n"); pix1c = pixClone(pix1); /* bump refcount of pix1 to 2 */ data = pixExtractData(pix1); /* should make a copy of data */ pix1t = pixCreateTemplateNoInit(pix1); pixFreeData(pix1t); pixSetData(pix1t, data); Compare(pix1c, pix1t, &error); pixSaveTiled(pix1t, pixa, 1, 0, 30, 32); pixDestroy(&pix1); pixDestroy(&pix1c); pixDestroy(&pix1t); pixd = pixaDisplay(pixa, 0, 0); pixDisplay(pixd, 100, 100); pixWrite("/tmp/junkpixmem.png", pixd, IFF_PNG); pixaDestroy(&pixa); pixDestroy(&pixd); if (error) fprintf(stderr, "Fail: an error occurred\n"); else fprintf(stderr, "Success: no errors\n"); return 0; }
l_int32 main(int argc, char **argv) { l_int32 irval, igval, ibval; l_float32 rval, gval, bval, fract, fgfract; L_BMF *bmf; BOX *box; BOXA *boxa; FPIX *fpix; PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7; PIX *pix8, *pix9, *pix10, *pix11, *pix12, *pix13, *pix14, *pix15; PIXA *pixa; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixa = pixaCreate(0); pixs = pixRead("breviar38.150.jpg"); /* pixs = pixRead("breviar32.150.jpg"); */ pixaAddPix(pixa, pixs, L_CLONE); regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 0 */ pixDisplayWithTitle(pixs, 0, 0, "Input image", rp->display); /* Extract the blue component, which is small in all the text * regions, including in the highlight color region */ pix1 = pixGetRGBComponent(pixs, COLOR_BLUE); pixaAddPix(pixa, pix1, L_CLONE); regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */ pixDisplayWithTitle(pix1, 200, 0, "Blue component", rp->display); /* Do a background normalization, with the background set to * approximately 200 */ pix2 = pixBackgroundNormSimple(pix1, NULL, NULL); pixaAddPix(pixa, pix2, L_COPY); regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 2 */ pixDisplayWithTitle(pix2, 400, 0, "BG normalized to 200", rp->display); /* Do a linear transform on the gray pixels, with 50 going to * black and 160 going to white. 50 is sufficiently low to * make both the red and black print quite dark. Quantize * to a few equally spaced gray levels. This is the image * to which highlight color will be applied. */ pixGammaTRC(pix2, pix2, 1.0, 50, 160); pix3 = pixThresholdOn8bpp(pix2, 7, 1); pixaAddPix(pixa, pix3, L_CLONE); regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 3 */ pixDisplayWithTitle(pix3, 600, 0, "Basic quantized with white bg", rp->display); /* Identify the regions of red text. First, make a mask * consisting of all pixels such that (R-B)/B is larger * than 2.0. This will have all the red, plus a lot of * the dark pixels. */ fpix = pixComponentFunction(pixs, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0); pix4 = fpixThresholdToPix(fpix, 2.0); pixInvert(pix4, pix4); /* red plus some dark text */ pixaAddPix(pixa, pix4, L_CLONE); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */ pixDisplayWithTitle(pix4, 800, 0, "Red plus dark pixels", rp->display); /* Make a mask consisting of all the red and background pixels */ pix5 = pixGetRGBComponent(pixs, COLOR_RED); pix6 = pixThresholdToBinary(pix5, 128); pixInvert(pix6, pix6); /* red plus background (white) */ /* Intersect the two masks to get a mask consisting of pixels * that are almost certainly red. This is the seed. */ pix7 = pixAnd(NULL, pix4, pix6); /* red only (seed) */ pixaAddPix(pixa, pix7, L_COPY); regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 5 */ pixDisplayWithTitle(pix7, 0, 600, "Seed for red color", rp->display); /* Make the clipping mask by thresholding the image with * the background cleaned to white. */ pix8 = pixThresholdToBinary(pix2, 230); /* mask */ pixaAddPix(pixa, pix8, L_CLONE); regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 6 */ pixDisplayWithTitle(pix8, 200, 600, "Clipping mask for red components", rp->display); /* Fill into the mask from the seed */ pixSeedfillBinary(pix7, pix7, pix8, 8); /* filled: red plus touching */ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 7 */ pixDisplayWithTitle(pix7, 400, 600, "Red component mask filled", rp->display); /* Remove long horizontal and vertical lines from the filled result */ pix9 = pixMorphSequence(pix7, "o40.1", 0); pixSubtract(pix7, pix7, pix9); /* remove long horizontal lines */ pixDestroy(&pix9); pix9 = pixMorphSequence(pix7, "o1.40", 0); pixSubtract(pix7, pix7, pix9); /* remove long vertical lines */ /* Close the regions to be colored */ pix10 = pixMorphSequence(pix7, "c5.1", 0); pixaAddPix(pixa, pix10, L_CLONE); regTestWritePixAndCheck(rp, pix10, IFF_PNG); /* 8 */ pixDisplayWithTitle(pix10, 600, 600, "Components defining regions allowing coloring", rp->display); /* Sanity check on amount to be colored. Only accept images * with less than 10% of all the pixels with highlight color */ pixForegroundFraction(pix10, &fgfract); if (fgfract >= 0.10) { L_INFO("too much highlighting: fract = %6.3f; removing it\n", rp->testname, fgfract); pixClearAll(pix10); pixSetPixel(pix10, 0, 0, 1); } /* Get the bounding boxes of the regions to be colored */ boxa = pixConnCompBB(pix10, 8); /* Get a color to paint that is representative of the * actual highlight color in the image. Scale each * color component up from the average by an amount necessary * to saturate the red. Then divide the green and * blue components by 2.0. */ pixGetAverageMaskedRGB(pixs, pix7, 0, 0, 1, L_MEAN_ABSVAL, &rval, &gval, &bval); fract = 255.0 / rval; irval = lept_roundftoi(fract * rval); igval = lept_roundftoi(fract * gval / 2.0); ibval = lept_roundftoi(fract * bval / 2.0); fprintf(stderr, "(r,g,b) = (%d,%d,%d)\n", irval, igval, ibval); /* Color the quantized gray version in the selected regions */ pix11 = pixColorGrayRegions(pix3, boxa, L_PAINT_DARK, 220, irval, igval, ibval); pixaAddPix(pixa, pix11, L_CLONE); regTestWritePixAndCheck(rp, pix11, IFF_PNG); /* 9 */ pixDisplayWithTitle(pix11, 800, 600, "Final colored result", rp->display); pixaAddPix(pixa, pixs, L_CLONE); /* Test colorization on gray and cmapped gray */ pix12 = pixColorGrayRegions(pix2, boxa, L_PAINT_DARK, 220, 0, 255, 0); pixaAddPix(pixa, pix12, L_CLONE); regTestWritePixAndCheck(rp, pix12, IFF_PNG); /* 10 */ pixDisplayWithTitle(pix12, 900, 600, "Colorizing boxa gray", rp->display); box = boxCreate(200, 200, 250, 350); pix13 = pixCopy(NULL, pix2); pixColorGray(pix13, box, L_PAINT_DARK, 220, 0, 0, 255); pixaAddPix(pixa, pix13, L_CLONE); regTestWritePixAndCheck(rp, pix13, IFF_PNG); /* 11 */ pixDisplayWithTitle(pix13, 1000, 600, "Colorizing box gray", rp->display); pix14 = pixThresholdTo4bpp(pix2, 6, 1); pix15 = pixColorGrayRegions(pix14, boxa, L_PAINT_DARK, 220, 0, 0, 255); pixaAddPix(pixa, pix15, L_CLONE); regTestWritePixAndCheck(rp, pix15, IFF_PNG); /* 12 */ pixDisplayWithTitle(pix15, 1100, 600, "Colorizing boxa cmap", rp->display); pixColorGrayCmap(pix14, box, L_PAINT_DARK, 0, 255, 255); pixaAddPix(pixa, pix14, L_CLONE); regTestWritePixAndCheck(rp, pix14, IFF_PNG); /* 13 */ pixDisplayWithTitle(pix14, 1200, 600, "Colorizing box cmap", rp->display); boxDestroy(&box); /* Generate a pdf of the intermediate results */ lept_mkdir("lept"); L_INFO("Writing to /tmp/lept/colorize.pdf\n", rp->testname); pixaConvertToPdf(pixa, 90, 1.0, 0, 0, "Colorizing highlighted text", "/tmp/lept/colorize.pdf"); pixaDestroy(&pixa); fpixDestroy(&fpix); boxDestroy(&box); boxaDestroy(&boxa); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); pixDestroy(&pix7); pixDestroy(&pix8); pixDestroy(&pix9); pixDestroy(&pix10); pixDestroy(&pix11); pixDestroy(&pix12); pixDestroy(&pix13); pixDestroy(&pix14); pixDestroy(&pix15); /* Test the color detector */ pixa = pixaCreate(7); bmf = bmfCreate("./fonts", 4); pix1 = TestForRedColor(rp, "brev06.75.jpg", 1, bmf); /* 14 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev10.75.jpg", 0, bmf); /* 15 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev14.75.jpg", 1, bmf); /* 16 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev20.75.jpg", 1, bmf); /* 17 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev36.75.jpg", 0, bmf); /* 18 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev53.75.jpg", 1, bmf); /* 19 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev56.75.jpg", 1, bmf); /* 20 */ pixaAddPix(pixa, pix1, L_INSERT); /* Generate a pdf of the color detector results */ L_INFO("Writing to /tmp/lept/colordetect.pdf\n", rp->testname); pixaConvertToPdf(pixa, 45, 1.0, 0, 0, "Color detection", "/tmp/lept/colordetect.pdf"); pixaDestroy(&pixa); bmfDestroy(&bmf); return regTestCleanup(rp); }
/*! * pixGrayMorphSequence() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * dispy (if dispsep != 0, this gives the y-value of the * UL corner for display; otherwise it is ignored) * Return: pixd, or null on error * * Notes: * (1) This works on 8 bpp grayscale images. * (2) This runs a pipeline of operations; no branching is allowed. * (3) This only uses brick SELs. * (4) A new image is always produced; the input image is not changed. * (5) This contains an interpreter, allowing sequences to be * generated and run. * (6) The format of the sequence string is defined below. * (7) In addition to morphological operations, the composite * morph/subtract tophat can be performed. * (8) Sel sizes (width, height) must each be odd numbers. * (9) Intermediate results can optionally be displayed * (10) The sequence string is formatted as follows: * - An arbitrary number of operations, each separated * by a '+' character. White space is ignored. * - Each operation begins with a case-independent character * specifying the operation: * d or D (dilation) * e or E (erosion) * o or O (opening) * c or C (closing) * t or T (tophat) * - The args to the morphological operations are bricks of hits, * and are formatted as a.b, where a and b are horizontal and * vertical dimensions, rsp. (each must be an odd number) * - The args to the tophat are w or W (for white tophat) * or b or B (for black tophat), followed by a.b as for * the dilation, erosion, opening and closing. * Example valid sequences are: * "c5.3 + o7.5" * "c9.9 + tw9.9" */ PIX * pixGrayMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep, l_int32 dispy) { char *rawop, *op; l_int32 nops, i, valid, w, h, x; PIX *pixt1, *pixt2; SARRAY *sa; PROCNAME("pixGrayMorphSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); /* Verify that the operation sequence is valid */ valid = TRUE; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': case 'e': case 'E': case 'o': case 'O': case 'c': case 'C': if (sscanf(&op[1], "%d.%d", &w, &h) != 2) { fprintf(stderr, "*** op: %s invalid\n", op); valid = FALSE; break; } if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) { fprintf(stderr, "*** op: %s; w = %d, h = %d; must both be odd\n", op, w, h); valid = FALSE; break; } /* fprintf(stderr, "op = %s; w = %d, h = %d\n", op, w, h); */ break; case 't': case 'T': if (op[1] != 'w' && op[1] != 'W' && op[1] != 'b' && op[1] != 'B') { fprintf(stderr, "*** op = %s; arg %c must be 'w' or 'b'\n", op, op[1]); valid = FALSE; break; } sscanf(&op[2], "%d.%d", &w, &h); if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) { fprintf(stderr, "*** op: %s; w = %d, h = %d; must both be odd\n", op, w, h); valid = FALSE; break; } /* fprintf(stderr, "op = %s", op); */ break; default: fprintf(stderr, "*** nonexistent op = %s\n", op); valid = FALSE; } FREE(op); } if (!valid) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence invalid", procName, NULL); } /* Parse and operate */ pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixDilateGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixOpenGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixCloseGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 't': case 'T': sscanf(&op[2], "%d.%d", &w, &h); if (op[1] == 'w' || op[1] == 'W') pixt2 = pixTophat(pixt1, w, h, L_TOPHAT_WHITE); else /* 'b' or 'B' */ pixt2 = pixTophat(pixt1, w, h, L_TOPHAT_BLACK); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } sarrayDestroy(&sa); return pixt1; }
int main(int argc, char **argv) { char *filein, *fileout; l_int32 i, w, h, liney, linex, same; l_float32 angle, deg2rad; PIX *pixt1, *pixt2, *pixs, *pixd; static char mainName[] = "sheartest"; if (argc != 4) return ERROR_INT(" Syntax: sheartest filein angle fileout", mainName, 1); /* Compare in-place H shear with H shear to a new pix */ pixt1 = pixRead("marge.jpg"); pixGetDimensions(pixt1, &w, &h, NULL); pixt2 = pixHShear(NULL, pixt1, (l_int32)(0.3 * h), 0.17, L_BRING_IN_WHITE); pixHShearIP(pixt1, (l_int32)(0.3 * h), 0.17, L_BRING_IN_WHITE); pixEqual(pixt1, pixt2, &same); if (same) fprintf(stderr, "Correct for H shear\n"); else fprintf(stderr, "Error for H shear\n"); pixDestroy(&pixt1); pixDestroy(&pixt2); /* Compare in-place V shear with V shear to a new pix */ pixt1 = pixRead("marge.jpg"); pixGetDimensions(pixt1, &w, &h, NULL); pixt2 = pixVShear(NULL, pixt1, (l_int32)(0.3 * w), 0.17, L_BRING_IN_WHITE); pixVShearIP(pixt1, (l_int32)(0.3 * w), 0.17, L_BRING_IN_WHITE); pixEqual(pixt1, pixt2, &same); if (same) fprintf(stderr, "Correct for V shear\n"); else fprintf(stderr, "Error for V shear\n"); pixDestroy(&pixt1); pixDestroy(&pixt2); filein = argv[1]; angle = atof(argv[2]); fileout = argv[3]; deg2rad = 3.1415926535 / 180.; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pix not made", mainName, 1); pixGetDimensions(pixs, &w, &h, NULL); #if 0 /* Select an operation from this list ... * ------------------------------------------ pixd = pixHShear(NULL, pixs, liney, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixVShear(NULL, pixs, linex, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixHShearCorner(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixVShearCorner(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixHShearCenter(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixVShearCenter(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE); pixHShearIP(pixs, liney, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixs; pixVShearIP(pixs, linex, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixs; pixRasteropHip(pixs, 0, h/3, -50, L_BRING_IN_WHITE); pixd = pixs; pixRasteropVip(pixs, 0, w/3, -50, L_BRING_IN_WHITE); pixd = pixs; * ------------------------------------------ * ... and use it in the following: */ pixd = pixHShear(NULL, pixs, liney, deg2rad * angle, L_BRING_IN_WHITE); pixWrite(fileout, pixd, IFF_PNG); pixDisplay(pixd, 50, 50); pixDestroy(&pixd); #endif #if 0 /* Do a horizontal shear about a line */ for (i = 0; i < NTIMES; i++) { liney = i * h / (NTIMES - 1); if (liney >= h) liney = h - 1; pixd = pixHShear(NULL, pixs, liney, deg2rad * angle, L_BRING_IN_WHITE); pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i); pixDestroy(&pixd); } #endif #if 0 /* Do a vertical shear about a line */ for (i = 0; i < NTIMES; i++) { linex = i * w / (NTIMES - 1); if (linex >= w) linex = w - 1; pixd = pixVShear(NULL, pixs, linex, deg2rad * angle, L_BRING_IN_WHITE); pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i); pixDestroy(&pixd); } #endif #if 0 /* Do a horizontal in-place shear about a line */ pixSetPadBits(pixs, 0); for (i = 0; i < NTIMES; i++) { pixd = pixCopy(NULL, pixs); liney = i * h / (NTIMES - 1); if (liney >= h) liney = h - 1; pixHShearIP(pixd, liney, deg2rad * angle, L_BRING_IN_WHITE); pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i); pixDestroy(&pixd); } #endif #if 0 /* Do a vertical in-place shear about a line */ for (i = 0; i < NTIMES; i++) { pixd = pixCopy(NULL, pixs); linex = i * w / (NTIMES - 1); if (linex >= w) linex = w - 1; pixVShearIP(pixd, linex, deg2rad * angle, L_BRING_IN_WHITE); pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i); pixDestroy(&pixd); } #endif pixDestroy(&pixs); return 0; }
/*! * pixThinGeneral() * * Input: pixs (1 bpp) * type (L_THIN_FG, L_THIN_BG) * sela (of Sels for parallel composite HMTs) * maxiters (max number of iters allowed; use 0 to iterate * until completion) * Return: pixd, or null on error * * Notes: * (1) See notes in pixThin(). That function chooses among * the best of the Sels for thinning. * (2) This is a general function that takes a Sela of HMTs * that are used in parallel for thinning from each * of four directions. One iteration consists of four * such parallel thins. */ PIX * pixThinGeneral(PIX *pixs, l_int32 type, SELA *sela, l_int32 maxiters) { l_int32 i, j, r, nsels, same; PIXA *pixahmt; PIX **pixhmt; /* array owned by pixahmt; do not destroy! */ PIX *pixd, *pixt; SEL *sel, *selr; PROCNAME("pixThinGeneral"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (type != L_THIN_FG && type != L_THIN_BG) return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); if (!sela) return (PIX *)ERROR_PTR("sela not defined", procName, NULL); if (maxiters == 0) maxiters = 10000; /* Set up array of temp pix to hold hmts */ nsels = selaGetCount(sela); pixahmt = pixaCreate(nsels); for (i = 0; i < nsels; i++) { pixt = pixCreateTemplate(pixs); pixaAddPix(pixahmt, pixt, L_INSERT); } pixhmt = pixaGetPixArray(pixahmt); if (!pixhmt) return (PIX *)ERROR_PTR("pixhmt array not made", procName, NULL); #if DEBUG_SELS pixt = selaDisplayInPix(sela, 35, 3, 15, 4); pixDisplayWithTitle(pixt, 100, 100, "allsels", 1); pixDestroy(&pixt); #endif /* DEBUG_SELS */ /* Set up initial image for fg thinning */ if (type == L_THIN_FG) pixd = pixCopy(NULL, pixs); else /* bg thinning */ pixd = pixInvert(NULL, pixs); /* Thin the fg, with up to maxiters iterations */ for (i = 0; i < maxiters; i++) { pixt = pixCopy(NULL, pixd); /* test for completion */ for (r = 0; r < 4; r++) { /* over 90 degree rotations of Sels */ for (j = 0; j < nsels; j++) { /* over individual sels in sela */ sel = selaGetSel(sela, j); /* not a copy */ selr = selRotateOrth(sel, r); pixHMT(pixhmt[j], pixd, selr); selDestroy(&selr); if (j > 0) pixOr(pixhmt[0], pixhmt[0], pixhmt[j]); /* accum result */ } pixSubtract(pixd, pixd, pixhmt[0]); /* remove result */ } pixEqual(pixd, pixt, &same); pixDestroy(&pixt); if (same) { L_INFO("%d iterations to completion\n", procName, i); break; } } if (type == L_THIN_BG) pixInvert(pixd, pixd); pixaDestroy(&pixahmt); return pixd; }
/*! * pixMorphCompSequenceDwa() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * Return: pixd, or null on error * * Notes: * (1) This does dwa morphology on binary images, using brick Sels. * (2) This runs a pipeline of operations; no branching is allowed. * (3) It implements all brick Sels that have dimensions up to 63 * on each side, using a composite (linear + comb) when useful. * (4) A new image is always produced; the input image is not changed. * (5) This contains an interpreter, allowing sequences to be * generated and run. * (6) See pixMorphSequence() for further information about usage. */ PIX * pixMorphCompSequenceDwa(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op; l_int32 nops, i, j, nred, fact, w, h, x, y, border; l_int32 level[4]; PIX *pixt1, *pixt2; SARRAY *sa; PROCNAME("pixMorphCompSequenceDwa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixDilateCompBrickDwa(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeCompBrickDwa(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenCompBrickDwa(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseCompBrickDwa(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1], level[2], level[3]); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); } sarrayDestroy(&sa); return pixt1; }
/*! * \brief pixConnCompPixa() * * \param[in] pixs 1 bpp * \param[out] ppixa pixa of each c.c. * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) This finds bounding boxes of 4- or 8-connected components * in a binary image, and saves images of each c.c * in a pixa array. * (2) It sets up 2 temporary pix, and for each c.c. that is * located in raster order, it erases the c.c. from one pix, * then uses the b.b. to extract the c.c. from the two pix using * an XOR, and finally erases the c.c. from the second pix. * (3) A clone of the returned boxa (where all boxes in the array * are clones) is inserted into the pixa. * (4) If the input is valid, this always returns a boxa and a pixa. * If pixs is empty, the boxa and pixa will be empty. * </pre> */ BOXA * pixConnCompPixa(PIX *pixs, PIXA **ppixa, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompPixa"); if (!ppixa) return (BOXA *)ERROR_PTR("&pixa not defined", procName, NULL); *ppixa = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = NULL; pix1 = pix2 = pix3 = pix4 = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ pix1 = pixCopy(NULL, pixs); pix2 = pixCopy(NULL, pixs); if (!pix1 || !pix2) { L_ERROR("pix1 or pix2 not made\n", procName); goto cleanup; } h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; pixa = pixaCreate(0); boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); pixaDestroy(&pixa); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); /* Save the c.c. and remove from pix2 as well */ pix3 = pixClipRectangle(pix1, box, NULL); pix4 = pixClipRectangle(pix2, box, NULL); pixXor(pix3, pix3, pix4); pixRasterop(pix2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST, pix3, 0, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix4); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pix1, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pix1, IFF_PNG); #endif /* DEBUG */ /* Remove old boxa of pixa and replace with a clone copy */ boxaDestroy(&pixa->boxa); pixa->boxa = boxaCopy(boxa, L_CLONE); *ppixa = pixa; /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pix1); pixDestroy(&pix2); return boxa; }
// Top-level method to perform splitting based on current settings. // Returns true if a split was actually performed. // split_for_pageseg should be true if the splitting is being done prior to // page segmentation. This mode uses the flag // pageseg_devanagari_split_strategy to determine the splitting strategy. bool ShiroRekhaSplitter::Split(bool split_for_pageseg) { SplitStrategy split_strategy = split_for_pageseg ? pageseg_split_strategy_ : ocr_split_strategy_; if (split_strategy == NO_SPLIT) { return false; // Nothing to do. } ASSERT_HOST(split_strategy == MINIMAL_SPLIT || split_strategy == MAXIMAL_SPLIT); ASSERT_HOST(orig_pix_); if (devanagari_split_debuglevel > 0) { tprintf("Splitting shiro-rekha ...\n"); tprintf("Split strategy = %s\n", split_strategy == MINIMAL_SPLIT ? "Minimal" : "Maximal"); tprintf("Initial pageseg available = %s\n", segmentation_block_list_ ? "yes" : "no"); } // Create a copy of original image to store the splitting output. pixDestroy(&splitted_image_); splitted_image_ = pixCopy(NULL, orig_pix_); // Initialize debug image if required. if (devanagari_split_debugimage) { pixDestroy(&debug_image_); debug_image_ = pixConvertTo32(orig_pix_); } // Determine all connected components in the input image. A close operation // may be required prior to this, depending on the current settings. Pix* pix_for_ccs = pixClone(orig_pix_); if (perform_close_ && global_xheight_ != kUnspecifiedXheight && !segmentation_block_list_) { if (devanagari_split_debuglevel > 0) { tprintf("Performing a global close operation..\n"); } // A global measure is available for xheight, but no local information // exists. pixDestroy(&pix_for_ccs); pix_for_ccs = pixCopy(NULL, orig_pix_); PerformClose(pix_for_ccs, global_xheight_); } Pixa* ccs; Boxa* tmp_boxa = pixConnComp(pix_for_ccs, &ccs, 8); boxaDestroy(&tmp_boxa); pixDestroy(&pix_for_ccs); // Iterate over all connected components. Get their bounding boxes and clip // out the image regions corresponding to these boxes from the original image. // Conditionally run splitting on each of them. Boxa* regions_to_clear = boxaCreate(0); for (int i = 0; i < pixaGetCount(ccs); ++i) { Box* box = ccs->boxa->box[i]; Pix* word_pix = pixClipRectangle(orig_pix_, box, NULL); ASSERT_HOST(word_pix); int xheight = GetXheightForCC(box); if (xheight == kUnspecifiedXheight && segmentation_block_list_ && devanagari_split_debugimage) { pixRenderBoxArb(debug_image_, box, 1, 255, 0, 0); } // If some xheight measure is available, attempt to pre-eliminate small // blobs from the shiro-rekha process. This is primarily to save the CCs // corresponding to punctuation marks/small dots etc which are part of // larger graphemes. if (xheight == kUnspecifiedXheight || (box->w > xheight / 3 && box->h > xheight / 2)) { SplitWordShiroRekha(split_strategy, word_pix, xheight, box->x, box->y, regions_to_clear); } else if (devanagari_split_debuglevel > 0) { tprintf("CC dropped from splitting: %d,%d (%d, %d)\n", box->x, box->y, box->w, box->h); } pixDestroy(&word_pix); } // Actually clear the boxes now. for (int i = 0; i < boxaGetCount(regions_to_clear); ++i) { Box* box = boxaGetBox(regions_to_clear, i, L_CLONE); pixClearInRect(splitted_image_, box); boxDestroy(&box); } boxaDestroy(®ions_to_clear); pixaDestroy(&ccs); if (devanagari_split_debugimage) { DumpDebugImage(split_for_pageseg ? "pageseg_split_debug.png" : "ocr_split_debug.png"); } return true; }
/*! * \brief pixWriteMemBmp() * * \param[out] pfdata data of bmp formatted image * \param[out] pfsize size of returned data * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) 2 bpp bmp files are not valid in the spec, and are * written as 8 bpp. * (2) pix with depth <= 8 bpp are written with a colormap. * 16 bpp gray and 32 bpp rgb pix are written without a colormap. * (3) The transparency component in an rgb pix is ignored. * All 32 bpp pix have the bmp alpha component set to 255 (opaque). * (4) The bmp colormap entries, RGBA_QUAD, are the same as * the ones used for colormaps in leptonica. This allows * a simple memcpy for bmp output. * </pre> */ l_int32 pixWriteMemBmp(l_uint8 **pfdata, size_t *pfsize, PIX *pixs) { l_uint8 pel[4]; l_uint8 *cta = NULL; /* address of the bmp color table array */ l_uint8 *fdata, *data, *fmdata; l_int32 cmaplen; /* number of bytes in the bmp colormap */ l_int32 ncolors, val, stepsize; l_int32 w, h, d, fdepth, xres, yres; l_int32 pixWpl, pixBpl, extrabytes, fBpl, fWpl, i, j, k; l_int32 heapcm; /* extra copy of cta on the heap ? 1 : 0 */ l_uint32 offbytes, fimagebytes; l_uint32 *line, *pword; size_t fsize; BMP_FH *bmpfh; BMP_IH *bmpih; PIX *pix; PIXCMAP *cmap; RGBA_QUAD *pquad; PROCNAME("pixWriteMemBmp"); if (pfdata) *pfdata = NULL; if (pfsize) *pfsize = 0; if (!pfdata) return ERROR_INT("&fdata not defined", procName, 1 ); if (!pfsize) return ERROR_INT("&fsize not defined", procName, 1 ); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d == 2) { L_WARNING("2 bpp files can't be read; converting to 8 bpp\n", procName); pix = pixConvert2To8(pixs, 0, 85, 170, 255, 1); d = 8; } else { pix = pixCopy(NULL, pixs); } fdepth = (d == 32) ? 24 : d; /* Resolution is given in pixels/meter */ xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); pixWpl = pixGetWpl(pix); pixBpl = 4 * pixWpl; fWpl = (w * fdepth + 31) / 32; fBpl = 4 * fWpl; fimagebytes = h * fBpl; if (fimagebytes > 4LL * L_MAX_ALLOWED_PIXELS) { pixDestroy(&pix); return ERROR_INT("image data is too large", procName, 1); } /* If not rgb or 16 bpp, the bmp data is required to have a colormap */ heapcm = 0; if (d == 32 || d == 16) { /* 24 bpp rgb or 16 bpp: no colormap */ ncolors = 0; cmaplen = 0; } else if ((cmap = pixGetColormap(pix))) { /* existing colormap */ ncolors = pixcmapGetCount(cmap); cmaplen = ncolors * sizeof(RGBA_QUAD); cta = (l_uint8 *)cmap->array; } else { /* no existing colormap; d <= 8; make a binary or gray one */ if (d == 1) { cmaplen = sizeof(bwmap); ncolors = 2; cta = (l_uint8 *)bwmap; } else { /* d = 2,4,8; use a grayscale output colormap */ ncolors = 1 << fdepth; cmaplen = ncolors * sizeof(RGBA_QUAD); heapcm = 1; cta = (l_uint8 *)LEPT_CALLOC(cmaplen, 1); stepsize = 255 / (ncolors - 1); for (i = 0, val = 0, pquad = (RGBA_QUAD *)cta; i < ncolors; i++, val += stepsize, pquad++) { pquad->blue = pquad->green = pquad->red = val; pquad->alpha = 255; /* opaque */ } } } #if DEBUG {l_uint8 *pcmptr; pcmptr = (l_uint8 *)pixGetColormap(pix)->array; fprintf(stderr, "Pix colormap[0] = %c%c%c%d\n", pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]); fprintf(stderr, "Pix colormap[1] = %c%c%c%d\n", pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]); } #endif /* DEBUG */ offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen; fsize = offbytes + fimagebytes; fdata = (l_uint8 *)LEPT_CALLOC(fsize, 1); *pfdata = fdata; *pfsize = fsize; /* Convert to little-endian and write the file header data */ bmpfh = (BMP_FH *)fdata; bmpfh->bfType = convertOnBigEnd16(BMP_ID); bmpfh->bfSize = convertOnBigEnd16(fsize & 0x0000ffff); bmpfh->bfFill1 = convertOnBigEnd16((fsize >> 16) & 0x0000ffff); bmpfh->bfOffBits = convertOnBigEnd16(offbytes & 0x0000ffff); bmpfh->bfFill2 = convertOnBigEnd16((offbytes >> 16) & 0x0000ffff); /* Convert to little-endian and write the info header data */ bmpih = (BMP_IH *)(fdata + BMP_FHBYTES); bmpih->biSize = convertOnBigEnd32(BMP_IHBYTES); bmpih->biWidth = convertOnBigEnd32(w); bmpih->biHeight = convertOnBigEnd32(h); bmpih->biPlanes = convertOnBigEnd16(1); bmpih->biBitCount = convertOnBigEnd16(fdepth); bmpih->biSizeImage = convertOnBigEnd32(fimagebytes); bmpih->biXPelsPerMeter = convertOnBigEnd32(xres); bmpih->biYPelsPerMeter = convertOnBigEnd32(yres); bmpih->biClrUsed = convertOnBigEnd32(ncolors); bmpih->biClrImportant = convertOnBigEnd32(ncolors); /* Copy the colormap data and free the cta if necessary */ if (ncolors > 0) { memcpy(fdata + BMP_FHBYTES + BMP_IHBYTES, cta, cmaplen); if (heapcm) LEPT_FREE(cta); } /* When you write a binary image with a colormap * that sets BLACK to 0, you must invert the data */ if (fdepth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) { pixInvert(pix, pix); } /* An endian byte swap is also required */ pixEndianByteSwap(pix); /* Transfer the image data. Image origin for bmp is at lower right. */ fmdata = fdata + offbytes; if (fdepth != 24) { /* typ 1 or 8 bpp */ data = (l_uint8 *)pixGetData(pix) + pixBpl * (h - 1); for (i = 0; i < h; i++) { memcpy(fmdata, data, fBpl); data -= pixBpl; fmdata += fBpl; } } else { /* 32 bpp pix; 24 bpp file * See the comments in pixReadStreamBmp() to * understand the logic behind the pixel ordering below. * Note that we have again done an endian swap on * little endian machines before arriving here, so that * the bytes are ordered on both platforms as: Red Green Blue -- |-----------|------------|-----------|-----------| */ extrabytes = fBpl - 3 * w; line = pixGetData(pix) + pixWpl * (h - 1); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pword = line + j; pel[2] = *((l_uint8 *)pword + COLOR_RED); pel[1] = *((l_uint8 *)pword + COLOR_GREEN); pel[0] = *((l_uint8 *)pword + COLOR_BLUE); memcpy(fmdata, &pel, 3); fmdata += 3; } if (extrabytes) { for (k = 0; k < extrabytes; k++) { memcpy(fmdata, &pel, 1); fmdata++; } } line -= pixWpl; } } pixDestroy(&pix); return 0; }
/* * pixWriteSegmentedPageToPS() * * Input: pixs (all depths; colormap ok) * pixm (<optional> 1 bpp segmentation mask over image region) * textscale (scale of text output relative to pixs) * imagescale (scale of image output relative to pixs) * threshold (threshold for binarization; typ. 190) * pageno (page number in set; use 1 for new output file) * fileout (output ps file) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates the PS string for a mixed text/image page, * and adds it to an existing file if @pageno > 1. * The PS output is determined by fitting the result to * a letter-size (8.5 x 11 inch) page. * (2) The two images (pixs and pixm) are at the same resolution * (typically 300 ppi). They are used to generate two compressed * images, pixb and pixc, that are put directly into the output * PS file. * (3) pixb is the text component. In the PostScript world, we think of * it as a mask through which we paint black. It is produced by * scaling pixs by @textscale, and thresholding to 1 bpp. * (4) pixc is the image component, which is that part of pixs under * the mask pixm. It is scaled from pixs by @imagescale. * (5) Typical values are textscale = 2.0 and imagescale = 0.5. * (6) If pixm == NULL, the page has only text. If it is all black, * the page is all image and has no text. * (7) This can be used to write a multi-page PS file, by using * sequential page numbers with the same output file. It can * also be used to write separate PS files for each page, * by using different output files with @pageno = 0 or 1. */ l_int32 pixWriteSegmentedPageToPS(PIX *pixs, PIX *pixm, l_float32 textscale, l_float32 imagescale, l_int32 threshold, l_int32 pageno, const char *fileout) { l_int32 alltext, notext, d, ret; l_uint32 val; l_float32 scaleratio; PIX *pixmi, *pixmis, *pixt, *pixg, *pixsc, *pixb, *pixc; PROCNAME("pixWriteSegmentedPageToPS"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!fileout) return ERROR_INT("fileout not defined", procName, 1); if (imagescale <= 0.0 || textscale <= 0.0) return ERROR_INT("relative scales must be > 0.0", procName, 1); /* Analyze the page. Determine the ratio by which the * binary text mask is scaled relative to the image part. * If there is no image region (alltext == TRUE), the * text mask will be rendered directly to fit the page, * and scaleratio = 1.0. */ alltext = TRUE; notext = FALSE; scaleratio = 1.0; if (pixm) { pixZero(pixm, &alltext); /* pixm empty: all text */ if (alltext) pixm = NULL; /* treat it as not existing here */ else { pixmi = pixInvert(NULL, pixm); pixZero(pixmi, ¬ext); /* pixm full; no text */ pixDestroy(&pixmi); scaleratio = textscale / imagescale; } } if (pixGetDepth(pixs) == 1) { /* render tiff g4 */ pixb = pixClone(pixs); pixc = NULL; } else { pixt = pixConvertTo8Or32(pixs, 0, 0); /* this can be a clone of pixs */ /* Get the binary text mask. Note that pixg cannot be a * clone of pixs, because it may be altered by pixSetMasked(). */ pixb = NULL; if (notext == FALSE) { d = pixGetDepth(pixt); if (d == 8) pixg = pixCopy(NULL, pixt); else /* d == 32 */ pixg = pixConvertRGBToLuminance(pixt); if (pixm) /* clear out the image parts */ pixSetMasked(pixg, pixm, 255); if (textscale == 1.0) pixsc = pixClone(pixg); else if (textscale >= 0.7) pixsc = pixScaleGrayLI(pixg, textscale, textscale); else pixsc = pixScaleAreaMap(pixg, textscale, textscale); pixb = pixThresholdToBinary(pixsc, threshold); pixDestroy(&pixg); pixDestroy(&pixsc); } /* Get the scaled image region */ pixc = NULL; if (pixm) { if (imagescale == 1.0) pixsc = pixClone(pixt); /* can possibly be a clone of pixs */ else pixsc = pixScale(pixt, imagescale, imagescale); /* If pixm is not full, clear the pixels in pixsc * corresponding to bg in pixm, where there can be text * that is written through the mask pixb. Note that * we could skip this and use pixsc directly in * pixWriteMixedToPS(); however, clearing these * non-image regions to a white background will reduce * the size of pixc (relative to pixsc), and hence * reduce the size of the PS file that is generated. * Use a copy so that we don't accidentally alter pixs. */ if (notext == FALSE) { pixmis = pixScale(pixm, imagescale, imagescale); pixmi = pixInvert(NULL, pixmis); val = (d == 8) ? 0xff : 0xffffff00; pixc = pixCopy(NULL, pixsc); pixSetMasked(pixc, pixmi, val); /* clear non-image part */ pixDestroy(&pixmis); pixDestroy(&pixmi); } else pixc = pixClone(pixsc); pixDestroy(&pixsc); } pixDestroy(&pixt); } ret = pixWriteMixedToPS(pixb, pixc, scaleratio, pageno, fileout); pixDestroy(&pixb); pixDestroy(&pixc); return ret; }
/*! * pixColorMorph() * * Input: pixs * type (L_MORPH_DILATE, L_MORPH_ERODE, L_MORPH_OPEN, * or L_MORPH_CLOSE) * hsize (of Sel; must be odd; origin implicitly in center) * vsize (ditto) * Return: pixd * * Notes: * (1) This does the morph operation on each component separately, * and recombines the result. * (2) Sel is a brick with all elements being hits. * (3) If hsize = vsize = 1, just returns a copy. */ PIX * pixColorMorph(PIX *pixs, l_int32 type, l_int32 hsize, l_int32 vsize) { PIX *pixr, *pixg, *pixb, *pixrm, *pixgm, *pixbm, *pixd; PROCNAME("pixColorMorph"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (type != L_MORPH_DILATE && type != L_MORPH_ERODE && type != L_MORPH_OPEN && type != L_MORPH_CLOSE) return (PIX *)ERROR_PTR("invalid morph type", procName, NULL); if (hsize < 1 || vsize < 1) return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL); if ((hsize & 1) == 0 ) { L_WARNING("horiz sel size must be odd; increasing by 1", procName); hsize++; } if ((vsize & 1) == 0 ) { L_WARNING("vert sel size must be odd; increasing by 1", procName); vsize++; } if (hsize == 1 && vsize == 1) return pixCopy(NULL, pixs); pixr = pixGetRGBComponent(pixs, COLOR_RED); pixg = pixGetRGBComponent(pixs, COLOR_GREEN); pixb = pixGetRGBComponent(pixs, COLOR_BLUE); if (type == L_MORPH_DILATE) { pixrm = pixDilateGray(pixr, hsize, vsize); pixgm = pixDilateGray(pixg, hsize, vsize); pixbm = pixDilateGray(pixb, hsize, vsize); } else if (type == L_MORPH_ERODE) { pixrm = pixErodeGray(pixr, hsize, vsize); pixgm = pixErodeGray(pixg, hsize, vsize); pixbm = pixErodeGray(pixb, hsize, vsize); } else if (type == L_MORPH_OPEN) { pixrm = pixOpenGray(pixr, hsize, vsize); pixgm = pixOpenGray(pixg, hsize, vsize); pixbm = pixOpenGray(pixb, hsize, vsize); } else { /* type == L_MORPH_CLOSE */ pixrm = pixCloseGray(pixr, hsize, vsize); pixgm = pixCloseGray(pixg, hsize, vsize); pixbm = pixCloseGray(pixb, hsize, vsize); } pixd = pixCreateRGBImage(pixrm, pixgm, pixbm); pixDestroy(&pixr); pixDestroy(&pixrm); pixDestroy(&pixg); pixDestroy(&pixgm); pixDestroy(&pixb); pixDestroy(&pixbm); return pixd; }
l_int32 main(int argc, char **argv) { l_int32 i, n; l_float32 a, b, c, d, e; NUMA *nax, *nafit; PIX *pixs, *pixn, *pixg, *pixb, *pixt1, *pixt2; PIXA *pixa; PTA *pta, *ptad; PTAA *ptaa1, *ptaa2; pixs = pixRead("cat-35.jpg"); /* pixs = pixRead("zanotti-78.jpg"); */ /* Normalize for varying background and binarize */ pixn = pixBackgroundNormSimple(pixs, NULL, NULL); pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2); pixb = pixThresholdToBinary(pixg, 130); pixDestroy(&pixn); pixDestroy(&pixg); /* Get the textline centers */ pixa = pixaCreate(6); ptaa1 = dewarpGetTextlineCenters(pixb, 0); pixt1 = pixCreateTemplate(pixs); pixSetAll(pixt1); pixt2 = pixDisplayPtaa(pixt1, ptaa1); pixWrite("/tmp/textline1.png", pixt2, IFF_PNG); pixDisplayWithTitle(pixt2, 0, 100, "textline centers 1", 1); pixaAddPix(pixa, pixt2, L_INSERT); pixDestroy(&pixt1); /* Remove short lines */ fprintf(stderr, "Num all lines = %d\n", ptaaGetCount(ptaa1)); ptaa2 = dewarpRemoveShortLines(pixb, ptaa1, 0.8, 0); pixt1 = pixCreateTemplate(pixs); pixSetAll(pixt1); pixt2 = pixDisplayPtaa(pixt1, ptaa2); pixWrite("/tmp/textline2.png", pixt2, IFF_PNG); pixDisplayWithTitle(pixt2, 300, 100, "textline centers 2", 1); pixaAddPix(pixa, pixt2, L_INSERT); pixDestroy(&pixt1); n = ptaaGetCount(ptaa2); fprintf(stderr, "Num long lines = %d\n", n); ptaaDestroy(&ptaa1); pixDestroy(&pixb); /* Long lines over input image */ pixt1 = pixCopy(NULL, pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa2); pixWrite("/tmp/textline3.png", pixt2, IFF_PNG); pixDisplayWithTitle(pixt2, 600, 100, "textline centers 3", 1); pixaAddPix(pixa, pixt2, L_INSERT); pixDestroy(&pixt1); /* Quadratic fit to curve */ pixt1 = pixCopy(NULL, pixs); for (i = 0; i < n; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetQuadraticLSF(pta, &a, &b, &c, &nafit); fprintf(stderr, "Quadratic: a = %10.6f, b = %7.3f, c = %7.3f\n", a, b, c); ptad = ptaCreateFromNuma(nax, nafit); pixDisplayPta(pixt1, pixt1, ptad); ptaDestroy(&pta); ptaDestroy(&ptad); numaDestroy(&nax); numaDestroy(&nafit); } pixWrite("/tmp/textline4.png", pixt1, IFF_PNG); pixDisplayWithTitle(pixt1, 900, 100, "textline centers 4", 1); pixaAddPix(pixa, pixt1, L_INSERT); /* Cubic fit to curve */ pixt1 = pixCopy(NULL, pixs); for (i = 0; i < n; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetCubicLSF(pta, &a, &b, &c, &d, &nafit); fprintf(stderr, "Cubic: a = %10.6f, b = %10.6f, c = %7.3f, d = %7.3f\n", a, b, c, d); ptad = ptaCreateFromNuma(nax, nafit); pixDisplayPta(pixt1, pixt1, ptad); ptaDestroy(&pta); ptaDestroy(&ptad); numaDestroy(&nax); numaDestroy(&nafit); } pixWrite("/tmp/textline5.png", pixt1, IFF_PNG); pixDisplayWithTitle(pixt1, 1200, 100, "textline centers 5", 1); pixaAddPix(pixa, pixt1, L_INSERT); /* Quartic fit to curve */ pixt1 = pixCopy(NULL, pixs); for (i = 0; i < n; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetQuarticLSF(pta, &a, &b, &c, &d, &e, &nafit); fprintf(stderr, "Quartic: a = %7.3f, b = %7.3f, c = %9.5f, d = %7.3f, e = %7.3f\n", a, b, c, d, e); ptad = ptaCreateFromNuma(nax, nafit); pixDisplayPta(pixt1, pixt1, ptad); ptaDestroy(&pta); ptaDestroy(&ptad); numaDestroy(&nax); numaDestroy(&nafit); } pixWrite("/tmp/textline6.png", pixt1, IFF_PNG); pixDisplayWithTitle(pixt1, 1500, 100, "textline centers 6", 1); pixaAddPix(pixa, pixt1, L_INSERT); pixaConvertToPdf(pixa, 300, 0.5, L_JPEG_ENCODE, 75, "LS fittings to textlines", "/tmp/dewarp_fittings.pdf"); pixaDestroy(&pixa); pixDestroy(&pixs); ptaaDestroy(&ptaa2); return 0; }
int main(int argc, char **argv) { l_int32 i, j; l_int32 w, h, bw, bh, wpls, rval, gval, bval, same; l_uint32 pixel; l_uint32 *lines, *datas; l_float32 sum1, sum2, ave1, ave2, ave3, ave4, diff1, diff2; l_float32 var1, var2, var3; BOX *box1, *box2; NUMA *na, *na1, *na2, *na3, *na4; PIX *pix, *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pixg, *pixd; PIXA *pixa; static char mainName[] = "numa2_reg"; if (argc != 1) return ERROR_INT(" Syntax: numa2_reg", mainName, 1); lept_mkdir("lept/numa2"); /* -------------------------------------------------------------------* * Numa-windowed stats * * -------------------------------------------------------------------*/ #if DO_ALL na = numaRead("lyra.5.na"); numaWindowedStats(na, 5, &na1, &na2, &na3, &na4); gplotSimple1(na, GPLOT_PNG, "/tmp/lept/numa2/lyra6", "Original"); gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/numa2/lyra7", "Mean"); gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/numa2/lyra8", "Mean Square"); gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/numa2/lyra9", "Variance"); gplotSimple1(na4, GPLOT_PNG, "/tmp/lept/numa2/lyra10", "RMS Difference"); pixa = pixaCreate(5); pix1 = pixRead("/tmp/lept/numa2/lyra6.png"); pix2 = pixRead("/tmp/lept/numa2/lyra7.png"); pix3 = pixRead("/tmp/lept/numa2/lyra8.png"); pix4 = pixRead("/tmp/lept/numa2/lyra9.png"); pix5 = pixRead("/tmp/lept/numa2/lyra10.png"); pixaAddPix(pixa, pix1, L_INSERT); pixaAddPix(pixa, pix2, L_INSERT); pixaAddPix(pixa, pix3, L_INSERT); pixaAddPix(pixa, pix4, L_INSERT); pixaAddPix(pixa, pix5, L_INSERT); pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2); pixDisplay(pixd, 100, 0); pixWrite("/tmp/lept/numa2/window.png", pixd, IFF_PNG); pixDestroy(&pixd); pixaDestroy(&pixa); numaDestroy(&na); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); numaDestroy(&na4); #endif /* -------------------------------------------------------------------* * Extraction on a line * * -------------------------------------------------------------------*/ #if DO_ALL /* First, make a pretty image */ w = h = 200; pixs = pixCreate(w, h, 32); wpls = pixGetWpl(pixs); datas = pixGetData(pixs); for (i = 0; i < 200; i++) { lines = datas + i * wpls; for (j = 0; j < 200; j++) { rval = (l_int32)((255. * j) / w + (255. * i) / h); gval = (l_int32)((255. * 2 * j) / w + (255. * 2 * i) / h) % 255; bval = (l_int32)((255. * 4 * j) / w + (255. * 4 * i) / h) % 255; composeRGBPixel(rval, gval, bval, &pixel); lines[j] = pixel; } } pixg = pixConvertTo8(pixs, 0); /* and a grayscale version */ pixWrite("/tmp/lept/numa_pixg.png", pixg, IFF_PNG); pixDisplay(pixg, 450, 100); na1 = pixExtractOnLine(pixg, 20, 20, 180, 20, 1); na2 = pixExtractOnLine(pixg, 40, 30, 40, 170, 1); na3 = pixExtractOnLine(pixg, 20, 170, 180, 30, 1); na4 = pixExtractOnLine(pixg, 20, 190, 180, 10, 1); gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/numa2/ext1", "Horizontal"); gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/numa2/ext2", "Vertical"); gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/numa2/ext3", "Slightly more horizontal than vertical"); gplotSimple1(na4, GPLOT_PNG, "/tmp/lept/numa2/ext4", "Slightly more vertical than horizontal"); pixa = pixaCreate(4); pix1 = pixRead("/tmp/lept/numa2/ext1.png"); pix2 = pixRead("/tmp/lept/numa2/ext2.png"); pix3 = pixRead("/tmp/lept/numa2/ext3.png"); pix4 = pixRead("/tmp/lept/numa2/ext4.png"); pixaAddPix(pixa, pix1, L_INSERT); pixaAddPix(pixa, pix2, L_INSERT); pixaAddPix(pixa, pix3, L_INSERT); pixaAddPix(pixa, pix4, L_INSERT); pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2); pixDisplay(pixd, 100, 450); pixWrite("/tmp/lept/numa2/extract.png", pixd, IFF_PNG); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixg); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); numaDestroy(&na4); #endif /* -------------------------------------------------------------------* * Row and column pixel sums * * -------------------------------------------------------------------*/ #if DO_ALL /* Sum by columns in two halves (left and right) */ pixs = pixRead("test8.jpg"); pixGetDimensions(pixs, &w, &h, NULL); box1 = boxCreate(0, 0, w / 2, h); box2 = boxCreate(w / 2, 0, w - 2 / 2, h); na1 = pixAverageByColumn(pixs, box1, L_BLACK_IS_MAX); na2 = pixAverageByColumn(pixs, box2, L_BLACK_IS_MAX); numaJoin(na1, na2, 0, -1); na3 = pixAverageByColumn(pixs, NULL, L_BLACK_IS_MAX); numaSimilar(na1, na3, 0.0, &same); if (same) fprintf(stderr, "Same for columns\n"); else fprintf(stderr, "Error for columns\n"); pix = pixConvertTo32(pixs); pixRenderPlotFromNumaGen(&pix, na3, L_HORIZONTAL_LINE, 3, h / 2, 80, 1, 0xff000000); pixRenderPlotFromNuma(&pix, na3, L_PLOT_AT_BOT, 3, 80, 0xff000000); boxDestroy(&box1); boxDestroy(&box2); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); /* Sum by rows in two halves (top and bottom) */ box1 = boxCreate(0, 0, w, h / 2); box2 = boxCreate(0, h / 2, w, h - h / 2); na1 = pixAverageByRow(pixs, box1, L_WHITE_IS_MAX); na2 = pixAverageByRow(pixs, box2, L_WHITE_IS_MAX); numaJoin(na1, na2, 0, -1); na3 = pixAverageByRow(pixs, NULL, L_WHITE_IS_MAX); numaSimilar(na1, na3, 0.0, &same); if (same) fprintf(stderr, "Same for rows\n"); else fprintf(stderr, "Error for rows\n"); pixRenderPlotFromNumaGen(&pix, na3, L_VERTICAL_LINE, 3, w / 2, 80, 1, 0x00ff0000); pixRenderPlotFromNuma(&pix, na3, L_PLOT_AT_RIGHT, 3, 80, 0x00ff0000); pixDisplay(pix, 500, 200); boxDestroy(&box1); boxDestroy(&box2); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); pixDestroy(&pix); /* Average left by rows; right by columns; compare totals */ box1 = boxCreate(0, 0, w / 2, h); box2 = boxCreate(w / 2, 0, w - 2 / 2, h); na1 = pixAverageByRow(pixs, box1, L_WHITE_IS_MAX); na2 = pixAverageByColumn(pixs, box2, L_WHITE_IS_MAX); numaGetSum(na1, &sum1); /* sum of averages of left box */ numaGetSum(na2, &sum2); /* sum of averages of right box */ ave1 = sum1 / h; ave2 = 2.0 * sum2 / w; ave3 = 0.5 * (ave1 + ave2); /* average over both halves */ fprintf(stderr, "ave1 = %8.4f\n", sum1 / h); fprintf(stderr, "ave2 = %8.4f\n", 2.0 * sum2 / w); pixAverageInRect(pixs, NULL, &ave4); /* entire image */ diff1 = ave4 - ave3; diff2 = w * h * ave4 - (0.5 * w * sum1 + h * sum2); if (diff1 < 0.001) fprintf(stderr, "Average diffs are correct\n"); else fprintf(stderr, "Average diffs are wrong: diff1 = %7.5f\n", diff1); if (diff2 < 20) /* float-to-integer roundoff */ fprintf(stderr, "Pixel sums are correct\n"); else fprintf(stderr, "Pixel sums are in error: diff = %7.0f\n", diff2); /* Variance left and right halves. Variance doesn't average * in a simple way, unlike pixel sums. */ pixVarianceInRect(pixs, box1, &var1); /* entire image */ pixVarianceInRect(pixs, box2, &var2); /* entire image */ pixVarianceInRect(pixs, NULL, &var3); /* entire image */ fprintf(stderr, "0.5 * (var1 + var2) = %7.3f, var3 = %7.3f\n", 0.5 * (var1 + var2), var3); boxDestroy(&box1); boxDestroy(&box2); numaDestroy(&na1); numaDestroy(&na2); #endif /* -------------------------------------------------------------------* * Row and column variances * * -------------------------------------------------------------------*/ #if DO_ALL /* Display variance by rows and columns */ box1 = boxCreate(415, 0, 130, 425); boxGetGeometry(box1, NULL, NULL, &bw, &bh); na1 = pixVarianceByRow(pixs, box1); na2 = pixVarianceByColumn(pixs, box1); pix = pixConvertTo32(pixs); pix1 = pixCopy(NULL, pix); pixRenderPlotFromNumaGen(&pix, na1, L_VERTICAL_LINE, 3, 415, 100, 1, 0xff000000); pixRenderPlotFromNumaGen(&pix, na2, L_HORIZONTAL_LINE, 3, bh / 2, 100, 1, 0x00ff0000); pixRenderPlotFromNuma(&pix1, na1, L_PLOT_AT_LEFT, 3, 60, 0x00ff0000); pixRenderPlotFromNuma(&pix1, na1, L_PLOT_AT_MID_VERT, 3, 60, 0x0000ff00); pixRenderPlotFromNuma(&pix1, na1, L_PLOT_AT_RIGHT, 3, 60, 0xff000000); pixRenderPlotFromNuma(&pix1, na2, L_PLOT_AT_TOP, 3, 60, 0x0000ff00); pixRenderPlotFromNuma(&pix1, na2, L_PLOT_AT_MID_HORIZ, 3, 60, 0xff000000); pixRenderPlotFromNuma(&pix1, na2, L_PLOT_AT_BOT, 3, 60, 0x00ff0000); pixDisplay(pix, 500, 900); pixDisplay(pix1, 500, 1000); boxDestroy(&box1); numaDestroy(&na1); numaDestroy(&na2); pixDestroy(&pix); pixDestroy(&pix1); pixDestroy(&pixs); /* Again on a different image */ pix1 = pixRead("boxedpage.jpg"); pix2 = pixConvertTo8(pix1, 0); pixGetDimensions(pix2, &w, &h, NULL); na1 = pixVarianceByRow(pix2, NULL); pix3 = pixConvertTo32(pix1); pixRenderPlotFromNumaGen(&pix3, na1, L_VERTICAL_LINE, 3, 0, 70, 1, 0xff000000); na2 = pixVarianceByColumn(pix2, NULL); pixRenderPlotFromNumaGen(&pix3, na2, L_HORIZONTAL_LINE, 3, bh - 1, 70, 1, 0x00ff0000); pixDisplay(pix3, 1000, 0); numaDestroy(&na1); numaDestroy(&na2); pixDestroy(&pix3); /* Again, with an erosion */ pix3 = pixErodeGray(pix2, 3, 21); pixDisplay(pix3, 1400, 0); na1 = pixVarianceByRow(pix3, NULL); pix4 = pixConvertTo32(pix1); pixRenderPlotFromNumaGen(&pix4, na1, L_VERTICAL_LINE, 3, 30, 70, 1, 0xff000000); na2 = pixVarianceByColumn(pix3, NULL); pixRenderPlotFromNumaGen(&pix4, na2, L_HORIZONTAL_LINE, 3, bh - 1, 70, 1, 0x00ff0000); pixDisplay(pix4, 1000, 550); numaDestroy(&na1); numaDestroy(&na2); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); #endif /* -------------------------------------------------------------------* * Windowed variance along a line * * -------------------------------------------------------------------*/ #if DO_ALL pix1 = pixRead("boxedpage.jpg"); pix2 = pixConvertTo8(pix1, 0); pixGetDimensions(pix2, &w, &h, NULL); pix3 = pixCopy(NULL, pix1); /* Plot along horizontal line */ pixWindowedVarianceOnLine(pix2, L_HORIZONTAL_LINE, h / 2 - 30, 0, w, 5, &na1); pixRenderPlotFromNumaGen(&pix1, na1, L_HORIZONTAL_LINE, 3, h / 2 - 30, 80, 1, 0xff000000); pixRenderPlotFromNuma(&pix3, na1, L_PLOT_AT_TOP, 3, 60, 0x00ff0000); pixRenderPlotFromNuma(&pix3, na1, L_PLOT_AT_BOT, 3, 60, 0x0000ff00); /* Plot along vertical line */ pixWindowedVarianceOnLine(pix2, L_VERTICAL_LINE, 0.78 * w, 0, h, 5, &na2); pixRenderPlotFromNumaGen(&pix1, na2, L_VERTICAL_LINE, 3, 0.78 * w, 60, 1, 0x00ff0000); pixRenderPlotFromNuma(&pix3, na2, L_PLOT_AT_LEFT, 3, 60, 0xff000000); pixRenderPlotFromNuma(&pix3, na2, L_PLOT_AT_RIGHT, 3, 60, 0x00ff0000); pixDisplay(pix1, 1000, 1000); pixDisplay(pix3, 1500, 1000); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); numaDestroy(&na1); numaDestroy(&na2); #endif return 0; }
main(int argc, char **argv) { char bufname[256]; l_int32 i, j, w, h, d, x, y, wpls; l_uint32 *datas, *lines; l_float32 *vc; l_float32 *mat1, *mat2, *mat3, *mat1i, *mat2i, *mat3i, *matdinv; l_float32 matd[9], matdi[9]; BOXA *boxa, *boxa2; PIX *pix, *pixs, *pixb, *pixg, *pixc, *pixcs; PIX *pixd, *pixt1, *pixt2, *pixt3; PIXA *pixa; PTA *ptas, *ptad; static char mainName[] = "affine_reg"; if (argc != 1) exit(ERROR_INT(" Syntax: affine_reg", mainName, 1)); if ((pixs = pixRead("feyn.tif")) == NULL) exit(ERROR_INT("pixs not made", mainName, 1)); #if ALL /* Test invertability of sequential. */ pixa = pixaCreate(0); for (i = 0; i < 3; i++) { pixb = pixAddBorder(pixs, ADDED_BORDER_PIXELS, 0); MakePtas(i, &ptas, &ptad); pixt1 = pixAffineSequential(pixb, ptad, ptas, 0, 0); pixSaveTiled(pixt1, pixa, 3, 1, 20, 8); pixt2 = pixAffineSequential(pixt1, ptas, ptad, 0, 0); pixSaveTiled(pixt2, pixa, 3, 0, 20, 0); pixd = pixRemoveBorder(pixt2, ADDED_BORDER_PIXELS); pixXor(pixd, pixd, pixs); pixSaveTiled(pixd, pixa, 3, 0, 20, 0); sprintf(bufname, "/tmp/junkseq%d.png", i); pixWrite(bufname, pixd, IFF_PNG); pixDestroy(&pixb); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixd); ptaDestroy(&ptas); ptaDestroy(&ptad); } pixt1 = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine1.png", pixt1, IFF_PNG); pixDisplay(pixt1, 100, 100); pixDestroy(&pixt1); pixaDestroy(&pixa); #endif #if ALL /* Test invertability of sampling */ pixa = pixaCreate(0); for (i = 0; i < 3; i++) { pixb = pixAddBorder(pixs, ADDED_BORDER_PIXELS, 0); MakePtas(i, &ptas, &ptad); pixt1 = pixAffineSampledPta(pixb, ptad, ptas, L_BRING_IN_WHITE); pixSaveTiled(pixt1, pixa, 3, 1, 20, 8); pixt2 = pixAffineSampledPta(pixt1, ptas, ptad, L_BRING_IN_WHITE); pixSaveTiled(pixt2, pixa, 3, 0, 20, 0); pixd = pixRemoveBorder(pixt2, ADDED_BORDER_PIXELS); pixXor(pixd, pixd, pixs); pixSaveTiled(pixd, pixa, 3, 0, 20, 0); if (i == 0) pixWrite("/tmp/junksamp.png", pixt1, IFF_PNG); pixDestroy(&pixb); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixd); ptaDestroy(&ptas); ptaDestroy(&ptad); } pixt1 = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine2.png", pixt1, IFF_PNG); pixDisplay(pixt1, 100, 300); pixDestroy(&pixt1); pixaDestroy(&pixa); #endif #if ALL /* Test invertability of interpolation on grayscale */ pixa = pixaCreate(0); pixg = pixScaleToGray3(pixs); for (i = 0; i < 3; i++) { pixb = pixAddBorder(pixg, ADDED_BORDER_PIXELS / 3, 255); MakePtas(i, &ptas, &ptad); pixt1 = pixAffinePta(pixb, ptad, ptas, L_BRING_IN_WHITE); pixSaveTiled(pixt1, pixa, 1, 1, 20, 8); pixt2 = pixAffinePta(pixt1, ptas, ptad, L_BRING_IN_WHITE); pixSaveTiled(pixt2, pixa, 1, 0, 20, 0); pixd = pixRemoveBorder(pixt2, ADDED_BORDER_PIXELS / 3); pixXor(pixd, pixd, pixg); pixSaveTiled(pixd, pixa, 1, 0, 20, 0); if (i == 0) pixWrite("/tmp/junkinterp.png", pixt1, IFF_PNG); pixDestroy(&pixb); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixd); ptaDestroy(&ptas); ptaDestroy(&ptad); } pixt1 = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine3.png", pixt1, IFF_PNG); pixDisplay(pixt1, 100, 500); pixDestroy(&pixt1); pixaDestroy(&pixa); pixDestroy(&pixg); #endif #if ALL /* Test invertability of interpolation on color */ pixa = pixaCreate(0); pixc = pixRead("test24.jpg"); pixcs = pixScale(pixc, 0.3, 0.3); for (i = 0; i < 3; i++) { pixb = pixAddBorder(pixcs, ADDED_BORDER_PIXELS / 4, 0xffffff00); MakePtas(i, &ptas, &ptad); pixt1 = pixAffinePta(pixb, ptad, ptas, L_BRING_IN_WHITE); pixSaveTiled(pixt1, pixa, 1, 1, 20, 32); pixt2 = pixAffinePta(pixt1, ptas, ptad, L_BRING_IN_WHITE); pixSaveTiled(pixt2, pixa, 1, 0, 20, 0); pixd = pixRemoveBorder(pixt2, ADDED_BORDER_PIXELS / 4); pixXor(pixd, pixd, pixcs); pixSaveTiled(pixd, pixa, 1, 0, 20, 0); pixDestroy(&pixb); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixd); ptaDestroy(&ptas); ptaDestroy(&ptad); } pixt1 = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine4.png", pixt1, IFF_PNG); pixDisplay(pixt1, 100, 500); pixDestroy(&pixt1); pixaDestroy(&pixa); pixDestroy(&pixc); pixDestroy(&pixcs); #endif #if ALL /* Comparison between sequential and sampling */ MakePtas(3, &ptas, &ptad); pixa = pixaCreate(0); /* Use sequential transforms */ pixt1 = pixAffineSequential(pixs, ptas, ptad, ADDED_BORDER_PIXELS, ADDED_BORDER_PIXELS); pixSaveTiled(pixt1, pixa, 2, 0, 20, 8); /* Use sampled transform */ pixt2 = pixAffineSampledPta(pixs, ptas, ptad, L_BRING_IN_WHITE); pixSaveTiled(pixt2, pixa, 2, 0, 20, 8); /* Compare the results */ pixXor(pixt2, pixt2, pixt1); pixSaveTiled(pixt2, pixa, 2, 0, 20, 8); pixd = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine5.png", pixd, IFF_PNG); pixDisplay(pixd, 100, 700); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixd); pixaDestroy(&pixa); ptaDestroy(&ptas); ptaDestroy(&ptad); #endif #if ALL /* Get timings and test with large distortion */ MakePtas(4, &ptas, &ptad); pixa = pixaCreate(0); pixg = pixScaleToGray3(pixs); startTimer(); pixt1 = pixAffineSequential(pixg, ptas, ptad, 0, 0); fprintf(stderr, " Time for pixAffineSequentialPta(): %6.2f sec\n", stopTimer()); pixSaveTiled(pixt1, pixa, 1, 1, 20, 8); startTimer(); pixt2 = pixAffineSampledPta(pixg, ptas, ptad, L_BRING_IN_WHITE); fprintf(stderr, " Time for pixAffineSampledPta(): %6.2f sec\n", stopTimer()); pixSaveTiled(pixt2, pixa, 1, 0, 20, 8); startTimer(); pixt3 = pixAffinePta(pixg, ptas, ptad, L_BRING_IN_WHITE); fprintf(stderr, " Time for pixAffinePta(): %6.2f sec\n", stopTimer()); pixSaveTiled(pixt3, pixa, 1, 0, 20, 8); pixXor(pixt1, pixt1, pixt2); pixSaveTiled(pixt1, pixa, 1, 1, 20, 8); pixXor(pixt2, pixt2, pixt3); pixSaveTiled(pixt2, pixa, 1, 0, 20, 8); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixd = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine6.png", pixd, IFF_PNG); pixDisplay(pixd, 100, 900); pixDestroy(&pixd); pixDestroy(&pixg); pixaDestroy(&pixa); ptaDestroy(&ptas); ptaDestroy(&ptad); #endif pixDestroy(&pixs); #if 1 /* Set up pix and boxa */ pixa = pixaCreate(0); pix = pixRead("lucasta.1.300.tif"); pixTranslate(pix, pix, 70, 0, L_BRING_IN_WHITE); pixt1 = pixCloseBrick(NULL, pix, 14, 5); pixOpenBrick(pixt1, pixt1, 1, 2); boxa = pixConnComp(pixt1, NULL, 8); pixs = pixConvertTo32(pix); pixGetDimensions(pixs, &w, &h, NULL); pixc = pixCopy(NULL, pixs); RenderHashedBoxa(pixc, boxa, 113); pixSaveTiled(pixc, pixa, 2, 1, 30, 32); pixDestroy(&pix); pixDestroy(&pixc); pixDestroy(&pixt1); /* Set up an affine transform in matd, and apply it to boxa */ mat1 = createMatrix2dTranslate(SHIFTX, SHIFTY); mat2 = createMatrix2dScale(SCALEX, SCALEY); mat3 = createMatrix2dRotate(w / 2, h / 2, ROTATION); l_productMat3(mat3, mat2, mat1, matd, 3); boxa2 = boxaAffineTransform(boxa, matd); /* Set up the inverse transform in matdi */ mat1i = createMatrix2dTranslate(-SHIFTX, -SHIFTY); mat2i = createMatrix2dScale(1.0/ SCALEX, 1.0 / SCALEY); mat3i = createMatrix2dRotate(w / 2, h / 2, -ROTATION); l_productMat3(mat1i, mat2i, mat3i, matdi, 3); /* Invert the original affine transform in matdinv */ affineInvertXform(matd, &matdinv); fprintf(stderr, "Affine transform, applied to boxa\n"); for (i = 0; i < 9; i++) { if (i && (i % 3 == 0)) fprintf(stderr, "\n"); fprintf(stderr, " %7.3f ", matd[i]); } fprintf(stderr, "\nInverse transform, made by composing inverse parts"); for (i = 0; i < 9; i++) { if (i % 3 == 0) fprintf(stderr, "\n"); fprintf(stderr, " %7.3f ", matdi[i]); } fprintf(stderr, "\nInverse transform, made by inverting the affine xform"); for (i = 0; i < 6; i++) { if (i % 3 == 0) fprintf(stderr, "\n"); fprintf(stderr, " %7.3f ", matdinv[i]); } fprintf(stderr, "\n"); /* Apply the inverted affine transform pixs */ pixd = pixAffine(pixs, matdinv, L_BRING_IN_WHITE); RenderHashedBoxa(pixd, boxa2, 513); pixSaveTiled(pixd, pixa, 2, 0, 30, 32); pixDestroy(&pixd); pixd = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkaffine7.png", pixd, IFF_PNG); pixDisplay(pixd, 100, 900); pixDestroy(&pixd); pixDestroy(&pixs); pixaDestroy(&pixa); boxaDestroy(&boxa); boxaDestroy(&boxa2); FREE(mat1); FREE(mat2); FREE(mat3); FREE(mat1i); FREE(mat2i); FREE(mat3i); #endif return 0; }
/*! * \brief pixConnCompBB() * * \param[in] pixs 1 bpp * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) Finds bounding boxes of 4- or 8-connected components * in a binary image. * (2) This works on a copy of the input pix. The c.c. are located * in raster order and erased one at a time. In the process, * the b.b. is computed and saved. * </pre> */ BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pixt; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompBB"); if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = NULL; pixt = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ if ((pixt = pixCopy(NULL, pixs)) == NULL) return (BOXA *)ERROR_PTR("pixt not made", procName, NULL); h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pixt, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pixt1, IFF_PNG); #endif /* DEBUG */ /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pixt); return boxa; }
/*! * pixSelectBySize() * * Input: pixs (1 bpp) * width, height (threshold dimensions) * connectivity (4 or 8) * type (L_SELECT_WIDTH, L_SELECT_HEIGHT, * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH) * relation (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 otherwise) * Return: filtered pixd, or null on error * * Notes: * (1) The args specify constraints on the size of the * components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) If the selection type is L_SELECT_WIDTH, the input * height is ignored, and v.v. * (4) To keep small components, use relation = L_SELECT_IF_LT or * L_SELECT_IF_LTE. * To keep large components, use relation = L_SELECT_IF_GT or * L_SELECT_IF_GTE. */ PIX * pixSelectBySize(PIX *pixs, l_int32 width, l_int32 height, l_int32 connectivity, l_int32 type, l_int32 relation, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectBySize"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT && type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid relation", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Identify and select the components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectBySize(pixas, width, height, type, relation, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } }
// Returns a list of regions (boxes) which should be cleared in the original // image so as to perform shiro-rekha splitting. Pix is assumed to carry one // (or less) word only. Xheight measure could be the global estimate, the row // estimate, or unspecified. If unspecified, over splitting may occur, since a // conservative estimate of stroke width along with an associated multiplier // is used in its place. It is advisable to have a specified xheight when // splitting for classification/training. // A vertical projection histogram of all the on-pixels in the input pix is // computed. The maxima of this histogram is regarded as an approximate location // of the shiro-rekha. By descending on the maxima's peak on both sides, // stroke width of shiro-rekha is estimated. // A horizontal projection histogram is computed for a sub-image of the input // image, which extends from just below the shiro-rekha down to a certain // leeway. The leeway depends on the input xheight, if provided, else a // conservative multiplier on approximate stroke width is used (which may lead // to over-splitting). void ShiroRekhaSplitter::SplitWordShiroRekha(SplitStrategy split_strategy, Pix* pix, int xheight, int word_left, int word_top, Boxa* regions_to_clear) { if (split_strategy == NO_SPLIT) { return; } int width = pixGetWidth(pix); int height = pixGetHeight(pix); // Statistically determine the yextents of the shiro-rekha. int shirorekha_top, shirorekha_bottom, shirorekha_ylevel; GetShiroRekhaYExtents(pix, &shirorekha_top, &shirorekha_bottom, &shirorekha_ylevel); // Since the shiro rekha is also a stroke, its width is equal to the stroke // width. int stroke_width = shirorekha_bottom - shirorekha_top + 1; // Some safeguards to protect CCs we do not want to be split. // These are particularly useful when the word wasn't eliminated earlier // because xheight information was unavailable. if (shirorekha_ylevel > height / 2) { // Shirorekha shouldn't be in the bottom half of the word. if (devanagari_split_debuglevel > 0) { tprintf("Skipping splitting CC at (%d, %d): shirorekha in lower half..\n", word_left, word_top); } return; } if (stroke_width > height / 3) { // Even the boldest of fonts shouldn't do this. if (devanagari_split_debuglevel > 0) { tprintf("Skipping splitting CC at (%d, %d): stroke width too huge..\n", word_left, word_top); } return; } // Clear the ascender and descender regions of the word. // Obtain a vertical projection histogram for the resulting image. Box* box_to_clear = boxCreate(0, shirorekha_top - stroke_width / 3, width, 5 * stroke_width / 3); Pix* word_in_xheight = pixCopy(NULL, pix); pixClearInRect(word_in_xheight, box_to_clear); // Also clear any pixels which are below shirorekha_bottom + some leeway. // The leeway is set to xheight if the information is available, else it is a // multiplier applied to the stroke width. int leeway_to_keep = stroke_width * 3; if (xheight != kUnspecifiedXheight) { // This is because the xheight-region typically includes the shiro-rekha // inside it, i.e., the top of the xheight range corresponds to the top of // shiro-rekha. leeway_to_keep = xheight - stroke_width; } box_to_clear->y = shirorekha_bottom + leeway_to_keep; box_to_clear->h = height - box_to_clear->y; pixClearInRect(word_in_xheight, box_to_clear); boxDestroy(&box_to_clear); PixelHistogram vert_hist; vert_hist.ConstructVerticalCountHist(word_in_xheight); pixDestroy(&word_in_xheight); // If the number of black pixel in any column of the image is less than a // fraction of the stroke width, treat it as noise / a stray mark. Perform // these changes inside the vert_hist data itself, as that is used later on as // a bit vector for the final split decision at every column. for (int i = 0; i < width; ++i) { if (vert_hist.hist()[i] <= stroke_width / 4) vert_hist.hist()[i] = 0; else vert_hist.hist()[i] = 1; } // In order to split the line at any point, we make sure that the width of the // gap is atleast half the stroke width. int i = 0; int cur_component_width = 0; while (i < width) { if (!vert_hist.hist()[i]) { int j = 0; while (i + j < width && !vert_hist.hist()[i+j]) ++j; if (j >= stroke_width / 2 && cur_component_width >= stroke_width / 2) { // Perform a shiro-rekha split. The intervening region lies from i to // i+j-1. // A minimal single-pixel split makes the estimation of intra- and // inter-word spacing easier during page layout analysis, // whereas a maximal split may be needed for OCR, depending on // how the engine was trained. bool minimal_split = (split_strategy == MINIMAL_SPLIT); int split_width = minimal_split ? 1 : j; int split_left = minimal_split ? i + (j / 2) - (split_width / 2) : i; if (!minimal_split || (i != 0 && i + j != width)) { Box* box_to_clear = boxCreate(word_left + split_left, word_top + shirorekha_top - stroke_width / 3, split_width, 5 * stroke_width / 3); if (box_to_clear) { boxaAddBox(regions_to_clear, box_to_clear, L_CLONE); // Mark this in the debug image if needed. if (devanagari_split_debugimage) { pixRenderBoxArb(debug_image_, box_to_clear, 1, 128, 255, 128); } boxDestroy(&box_to_clear); cur_component_width = 0; } } } i += j; } else { ++i; ++cur_component_width; } } }
/*! * pixWriteMemWebP() * * Input: &encdata (<return> webp encoded data of pixs) * &encsize (<return> size of webp encoded data) * pixs (any depth, cmapped OK) * quality (0 - 100; default ~80) * lossless (use 1 for lossless; 0 for lossy) * Return: 0 if OK, 1 on error * * Notes: * (1) Lossless and lossy encoding are entirely different in webp. * @quality applies to lossy, and is ignored for lossless. * (2) The input image is converted to RGB if necessary. If spp == 3, * we set the alpha channel to fully opaque (255), and * WebPEncodeRGBA() then removes the alpha chunk when encoding, * setting the internal header field has_alpha to 0. */ l_int32 pixWriteMemWebP(l_uint8 **pencdata, size_t *pencsize, PIX *pixs, l_int32 quality, l_int32 lossless) { l_int32 w, h, d, wpl, stride; l_uint32 *data; PIX *pix1, *pix2; PROCNAME("pixWriteMemWebP"); if (!pencdata) return ERROR_INT("&encdata not defined", procName, 1); *pencdata = NULL; if (!pencsize) return ERROR_INT("&encsize not defined", procName, 1); *pencsize = 0; if (!pixs) return ERROR_INT("&pixs not defined", procName, 1); if (lossless == 0 && (quality < 0 || quality > 100)) return ERROR_INT("quality not in [0 ... 100]", procName, 1); if ((pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR)) == NULL) return ERROR_INT("failure to remove color map", procName, 1); /* Convert to rgb if not 32 bpp; pix2 must not be a clone of pixs. */ if (pixGetDepth(pix1) != 32) pix2 = pixConvertTo32(pix1); else pix2 = pixCopy(NULL, pix1); pixDestroy(&pix1); pixGetDimensions(pix2, &w, &h, &d); if (w <= 0 || h <= 0 || d != 32) { pixDestroy(&pix2); return ERROR_INT("pix2 not 32 bpp or of 0 size", procName, 1); } /* If spp == 3, need to set alpha layer to opaque (all 1s). */ if (pixGetSpp(pix2) == 3) pixSetComponentArbitrary(pix2, L_ALPHA_CHANNEL, 255); /* Webp encoder assumes big-endian byte order for RGBA components */ pixEndianByteSwap(pix2); wpl = pixGetWpl(pix2); data = pixGetData(pix2); stride = wpl * 4; if (lossless) { *pencsize = WebPEncodeLosslessRGBA((uint8_t *)data, w, h, stride, pencdata); } else { *pencsize = WebPEncodeRGBA((uint8_t *)data, w, h, stride, quality, pencdata); } pixDestroy(&pix2); if (*pencsize == 0) { free(pencdata); *pencdata = NULL; return ERROR_INT("webp encoding failed", procName, 1); } return 0; }
main(int argc, char **argv) { char *filein; l_int32 i, orient; l_float32 upconf1, upconf2, leftconf1, leftconf2, conf1, conf2; PIX *pixs, *pixt1, *pixt2; static char mainName[] = "flipdetect_reg"; if (argc != 2) exit(ERROR_INT(" Syntax: flipdetect_reg filein", mainName, 1)); filein = argv[1]; if ((pixt1 = pixRead(filein)) == NULL) exit(ERROR_INT("pixt1 not made", mainName, 1)); pixs = pixConvertTo1(pixt1, 130); pixDestroy(&pixt1); fprintf(stderr, "\nTest orientation detection\n"); startTimer(); pixOrientDetect(pixs, &upconf1, &leftconf1, 0, 0); fprintf(stderr, "Time for rop orient test: %7.3f sec\n", stopTimer()); makeOrientDecision(upconf1, leftconf1, 0, 0, &orient, 1); startTimer(); pixOrientDetectDwa(pixs, &upconf2, &leftconf2, 0, 0); fprintf(stderr, "Time for dwa orient test: %7.3f sec\n", stopTimer()); if (upconf1 == upconf2 && leftconf1 == leftconf2) { printStarredMessage("Orient results identical"); fprintf(stderr, "upconf = %7.3f, leftconf = %7.3f\n", upconf1, leftconf1); } else { printStarredMessage("Orient results differ"); fprintf(stderr, "upconf1 = %7.3f, upconf2 = %7.3f\n", upconf1, upconf2); fprintf(stderr, "leftconf1 = %7.3f, leftconf2 = %7.3f\n", leftconf1, leftconf2); } pixt1 = pixCopy(NULL, pixs); fprintf(stderr, "\nTest orient detection for 4 orientations\n"); for (i = 0; i < 4; i++) { pixOrientDetectDwa(pixt1, &upconf2, &leftconf2, 0, 0); makeOrientDecision(upconf2, leftconf2, 0, 0, &orient, 1); if (i == 3) break; pixt2 = pixRotate90(pixt1, 1); pixDestroy(&pixt1); pixt1 = pixt2; } pixDestroy(&pixt1); fprintf(stderr, "\nTest mirror reverse detection\n"); startTimer(); pixMirrorDetect(pixs, &conf1, 0, 1); fprintf(stderr, "Time for rop mirror flip test: %7.3f sec\n", stopTimer()); startTimer(); pixMirrorDetectDwa(pixs, &conf2, 0, 0); fprintf(stderr, "Time for dwa mirror flip test: %7.3f sec\n", stopTimer()); if (conf1 == conf2) { printStarredMessage("Mirror results identical"); fprintf(stderr, "conf = %7.3f\n", conf1); } else { printStarredMessage("Mirror results differ"); fprintf(stderr, "conf1 = %7.3f, conf2 = %7.3f\n", conf1, conf2); } fprintf(stderr, "\nSafer version of up-down tests\n"); pixUpDownDetectGeneral(pixs, &conf1, 0, 10, 1); pixUpDownDetectGeneralDwa(pixs, &conf2, 0, 10, 1); if (conf1 == conf2) fprintf(stderr, "Confidence results are identical\n"); else fprintf(stderr, "Confidence results differ\n"); pixDestroy(&pixs); exit(0); }