/*! * \brief boxConvertToPta() * * \param[in] box * \param[in] ncorners 2 or 4 for the representation of the box * \return pta with %ncorners points, or NULL on error * * <pre> * Notes: * (1) If ncorners == 2, we select the UL and LR corners. * Otherwise we save all 4 corners in this order: UL, UR, LL, LR. * </pre> */ PTA * boxConvertToPta(BOX *box, l_int32 ncorners) { l_int32 x, y, w, h; PTA *pta; PROCNAME("boxConvertToPta"); if (!box) return (PTA *)ERROR_PTR("box not defined", procName, NULL); if (ncorners != 2 && ncorners != 4) return (PTA *)ERROR_PTR("ncorners not 2 or 4", procName, NULL); if ((pta = ptaCreate(ncorners)) == NULL) return (PTA *)ERROR_PTR("pta not made", procName, NULL); boxGetGeometry(box, &x, &y, &w, &h); ptaAddPt(pta, x, y); if (ncorners == 2) { ptaAddPt(pta, x + w - 1, y + h - 1); } else { ptaAddPt(pta, x + w - 1, y); ptaAddPt(pta, x, y + h - 1); ptaAddPt(pta, x + w - 1, y + h - 1); } return pta; }
/*! * boxaConvertToPta() * * Input: boxa * ncorners (2 or 4 for the representation of each box) * Return: pta (with @ncorners points for each box in the boxa), * or null on error * * Notes: * (1) If ncorners == 2, we select the UL and LR corners. * Otherwise we save all 4 corners in this order: UL, UR, LL, LR. */ PTA * boxaConvertToPta(BOXA *boxa, l_int32 ncorners) { l_int32 i, n, x, y, w, h; PTA *pta; PROCNAME("boxaConvertToPta"); if (!boxa) return (PTA *)ERROR_PTR("boxa not defined", procName, NULL); if (ncorners != 2 && ncorners != 4) return (PTA *)ERROR_PTR("ncorners not 2 or 4", procName, NULL); n = boxaGetCount(boxa); if ((pta = ptaCreate(n)) == NULL) return (PTA *)ERROR_PTR("pta not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); ptaAddPt(pta, x, y); if (ncorners == 2) ptaAddPt(pta, x + w - 1, y + h - 1); else { ptaAddPt(pta, x + w - 1, y); ptaAddPt(pta, x, y + h - 1); ptaAddPt(pta, x + w - 1, y + h - 1); } } return pta; }
main(int argc, char **argv) { char *filein, *fileout; l_int32 d; BOX *box1, *box2, *box3, *box4; BOXA *boxa; PIX *pixs, *pixt1, *pixt2, *pixt3; PTA *pta; static char mainName[] = "graphicstest"; if (argc != 3) exit(ERROR_INT(" Syntax: graphicstest filein fileout", mainName, 1)); filein = argv[1]; fileout = argv[2]; if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT(" Syntax: pixs not made", mainName, 1)); d = pixGetDepth(pixs); if (d <= 8) pixt1 = pixConvertTo32(pixs); else pixt1 = pixClone(pixs); /* Paint on RGB */ pixRenderLineArb(pixt1, 450, 20, 850, 320, 5, 200, 50, 125); pixRenderLineArb(pixt1, 30, 40, 440, 40, 5, 100, 200, 25); pixRenderLineBlend(pixt1, 30, 60, 440, 70, 5, 115, 200, 120, 0.3); pixRenderLineBlend(pixt1, 30, 600, 440, 670, 9, 215, 115, 30, 0.5); pixRenderLineBlend(pixt1, 130, 700, 540, 770, 9, 255, 255, 250, 0.4); pixRenderLineBlend(pixt1, 130, 800, 540, 870, 9, 0, 0, 0, 0.4); box1 = boxCreate(70, 80, 300, 245); box2 = boxCreate(470, 180, 150, 205); box3 = boxCreate(520, 220, 160, 220); box4 = boxCreate(570, 260, 160, 220); boxa = boxaCreate(3); boxaAddBox(boxa, box2, L_INSERT); boxaAddBox(boxa, box3, L_INSERT); boxaAddBox(boxa, box4, L_INSERT); pixRenderBoxArb(pixt1, box1, 3, 200, 200, 25); pixRenderBoxaBlend(pixt1, boxa, 17, 200, 200, 25, 0.4, 1); pta = ptaCreate(5); ptaAddPt(pta, 250, 300); ptaAddPt(pta, 350, 450); ptaAddPt(pta, 400, 600); ptaAddPt(pta, 212, 512); ptaAddPt(pta, 180, 375); pixRenderPolylineBlend(pixt1, pta, 17, 25, 200, 200, 0.5, 1, 1); pixWrite(fileout, pixt1, IFF_JFIF_JPEG); pixDisplay(pixt1, 200, 200); pixDestroy(&pixs); pixDestroy(&pixt1); boxDestroy(&box1); boxaDestroy(&boxa); ptaDestroy(&pta); pixDestroy(&pixs); return 0; }
/*! * pixSubsampleBoundaryPixels() * * Input: pixs (1 bpp, with only boundary pixels in fg) * skip (number to skip between samples as you traverse boundary) * Return: pta, or null on error * * Notes: * (1) If skip = 0, we take all the fg pixels. * (2) We try to traverse the boundaries in a regular way. * Some pixels may be missed, and these are then subsampled * randomly with a fraction determined by 'skip'. * (3) The most natural approach is to use a depth first (stack-based) * method to find the fg pixels. However, the pixel runs are * 4-connected and there are relatively few branches. So * instead of doing a proper depth-first search, we get nearly * the same result using two nested while loops: the outer * one continues a raster-based search for the next fg pixel, * and the inner one does a reasonable job running along * each 4-connected coutour. */ PTA * pixSubsampleBoundaryPixels(PIX *pixs, l_int32 skip) { l_int32 x, y, xn, yn, xs, ys, xa, ya, count; PIX *pixt; PTA *pta; PROCNAME("pixSubsampleBoundaryPixels"); if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PTA *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (skip < 0) return (PTA *)ERROR_PTR("skip < 0", procName, NULL); if (skip == 0) return ptaGetPixelsFromPix(pixs, NULL); pta = ptaCreate(0); pixt = pixCopy(NULL, pixs); xs = ys = 0; while (nextOnPixelInRaster(pixt, xs, ys, &xn, &yn)) { /* new series */ xs = xn; ys = yn; /* Add first point in this series */ ptaAddPt(pta, xs, ys); /* Trace out boundary, erasing all and saving every (skip + 1)th */ x = xs; y = ys; pixSetPixel(pixt, x, y, 0); count = 0; while (adjacentOnPixelInRaster(pixt, x, y, &xa, &ya)) { x = xa; y = ya; pixSetPixel(pixt, x, y, 0); if (count == skip) { ptaAddPt(pta, x, y); count = 0; } else { count++; } } } pixDestroy(&pixt); return pta; }
/*! * ptaRemoveDupsByAset() * * Input: ptas (assumed to be integer values) * Return: ptad (with duplicates removed), or null on error * * Notes: * (1) This is slower than ptaRemoveDupsByHash(), mostly because * of the nlogn sort to build up the rbtree. Do not use for * large numbers of points (say, > 1M). */ PTA * ptaRemoveDupsByAset(PTA *ptas) { l_int32 i, n, x, y; PTA *ptad; l_uint64 hash; L_ASET *set; RB_TYPE key; PROCNAME("ptaRemoveDupsByAset"); if (!ptas) return (PTA *)ERROR_PTR("ptas not defined", procName, NULL); set = l_asetCreate(L_UINT_TYPE); n = ptaGetCount(ptas); ptad = ptaCreate(n); for (i = 0; i < n; i++) { ptaGetIPt(ptas, i, &x, &y); l_hashPtToUint64(x, y, &hash); key.utype = hash; if (!l_asetFind(set, key)) { ptaAddPt(ptad, x, y); l_asetInsert(set, key); } } l_asetDestroy(&set); return ptad; }
/*! * ptaSortByIndex() * * Input: ptas * naindex (na that maps from the new pta to the input pta) * Return: ptad (sorted), or null on error */ PTA * ptaSortByIndex(PTA *ptas, NUMA *naindex) { l_int32 i, index, n; l_float32 x, y; PTA *ptad; PROCNAME("ptaSortByIndex"); if (!ptas) return (PTA *)ERROR_PTR("ptas not defined", procName, NULL); if (!naindex) return (PTA *)ERROR_PTR("naindex not defined", procName, NULL); /* Build up sorted pta using sort index */ n = numaGetCount(naindex); if ((ptad = ptaCreate(n)) == NULL) return (PTA *)ERROR_PTR("ptad not made", procName, NULL); for (i = 0; i < n; i++) { numaGetIValue(naindex, i, &index); ptaGetPt(ptas, index, &x, &y); ptaAddPt(ptad, x, y); } return ptad; }
/*! * ptaRotate() * * Input: ptas (for initial points) * (xc, yc) (location of center of rotation) * angle (rotation in radians; clockwise is positive) * (&ptad) (<return> new locations) * Return: 0 if OK; 1 on error * * Notes; * (1) See createMatrix2dScale() for details of transform. */ PTA * ptaRotate(PTA *ptas, l_float32 xc, l_float32 yc, l_float32 angle) { l_int32 i, npts; l_float32 x, y, xp, yp, sina, cosa; PTA *ptad; PROCNAME("ptaRotate"); if (!ptas) return (PTA *)ERROR_PTR("ptas not defined", procName, NULL); npts = ptaGetCount(ptas); if ((ptad = ptaCreate(npts)) == NULL) return (PTA *)ERROR_PTR("ptad not made", procName, NULL); sina = sin(angle); cosa = cos(angle); for (i = 0; i < npts; i++) { ptaGetPt(ptas, i, &x, &y); xp = xc + (x - xc) * cosa - (y - yc) * sina; yp = yc + (x - xc) * sina + (y - yc) * cosa; ptaAddPt(ptad, xp, yp); } return ptad; }
/*! * ptaGetMeanVerticals() * * Input: pixs (1 bpp, single c.c.) * x,y (location of UL corner of pixs with respect to page image * Return: pta (mean y-values in component for each x-value, * both translated by (x,y) */ PTA * pixGetMeanVerticals(PIX *pixs, l_int32 x, l_int32 y) { l_int32 w, h, i, j, wpl, sum, count; l_uint32 *line, *data; PTA *pta; PROCNAME("pixGetMeanVerticals"); if (!pixs || pixGetDepth(pixs) != 1) return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); pta = ptaCreate(w); data = pixGetData(pixs); wpl = pixGetWpl(pixs); for (j = 0; j < w; j++) { line = data; sum = count = 0; for (i = 0; i < h; i++) { if (GET_DATA_BIT(line, j) == 1) { sum += i; count += 1; } line += wpl; } if (count == 0) continue; ptaAddPt(pta, x + j, y + (sum / count)); } return pta; }
/*! * ptaAffineTransform() * * Input: ptas (for initial points) * mat (3x3 transform matrix; canonical form) * Return: ptad (transformed points), or null on error */ PTA * ptaAffineTransform(PTA *ptas, l_float32 *mat) { l_int32 i, npts; l_float32 vecs[3], vecd[3]; PTA *ptad; PROCNAME("ptaAffineTransform"); if (!ptas) return (PTA *)ERROR_PTR("ptas not defined", procName, NULL); if (!mat) return (PTA *)ERROR_PTR("transform not defined", procName, NULL); vecs[2] = 1; npts = ptaGetCount(ptas); if ((ptad = ptaCreate(npts)) == NULL) return (PTA *)ERROR_PTR("ptad not made", procName, NULL); for (i = 0; i < npts; i++) { ptaGetPt(ptas, i, &vecs[0], &vecs[1]); l_productMatVec(mat, vecs, vecd, 3); ptaAddPt(ptad, vecd[0], vecd[1]); } return ptad; }
static l_float32 * Generate3PtTransformVector() { l_int32 i; l_float32 *vc; PTA *ptas, *ptad; ptas = ptaCreate(3); ptad = ptaCreate(3); for (i = 0; i < 3; i++) { ptaAddPt(ptas, xs[i], ys[i]); ptaAddPt(ptad, xd[i], yd[i]); } getAffineXformCoeffs(ptad, ptas, &vc); ptaDestroy(&ptas); ptaDestroy(&ptad); return vc; }
static PTA * BuildPointSet(l_int32 w, l_int32 h, l_int32 add_dups) { l_int32 i, j; PTA *pta; pta = ptaCreate(w * h); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) ptaAddPt(pta, j, i); if (add_dups) { /* extra (0.2 * w * h) points */ for (j = 0.4 * w; j < 0.6 * w; j++) ptaAddPt(pta, j, i); } } return pta; }
/*! * boxaExtractAsPta() * * Input: boxa * &ptal (<optional return> array of left locations vs. index) * &ptat (<optional return> array of top locations vs. index) * &ptar (<optional return> array of right locations vs. index) * &ptab (<optional return> array of bottom locations vs. index) * keepinvalid (1 to keep invalid boxes; 0 to remove them) * Return: 0 if OK, 1 on error */ l_int32 boxaExtractAsPta(BOXA *boxa, PTA **pptal, PTA **pptat, PTA **pptar, PTA **pptab, l_int32 keepinvalid) { l_int32 i, n, left, top, right, bot, w, h; PROCNAME("boxaExtractAsPta"); if (!pptal && !pptar && !pptat && !pptab) return ERROR_INT("no output requested", procName, 1); if (pptal) *pptal = NULL; if (pptat) *pptat = NULL; if (pptar) *pptar = NULL; if (pptab) *pptab = NULL; if (!boxa) return ERROR_INT("boxa not defined", procName, 1); if (!keepinvalid && boxaGetValidCount(boxa) == 0) return ERROR_INT("no valid boxes", procName, 1); n = boxaGetCount(boxa); if (pptal) *pptal = ptaCreate(n); if (pptat) *pptat = ptaCreate(n); if (pptar) *pptar = ptaCreate(n); if (pptab) *pptab = ptaCreate(n); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa, i, &left, &top, &w, &h); if (!keepinvalid && (w <= 0 || h <= 0)) continue; right = left + w - 1; bot = top + h - 1; if (pptal) ptaAddPt(*pptal, i, left); if (pptat) ptaAddPt(*pptat, i, top); if (pptar) ptaAddPt(*pptar, i, right); if (pptab) ptaAddPt(*pptab, i, bot); } return 0; }
static l_float32 * Generate4PtTransformVector(l_int32 type) { l_int32 i; l_float32 *vc; PTA *ptas, *ptad; ptas = ptaCreate(4); ptad = ptaCreate(4); for (i = 0; i < 4; i++) { ptaAddPt(ptas, xs[i], ys[i]); ptaAddPt(ptad, xd[i], yd[i]); } if (type == PROJECTIVE) getProjectiveXformCoeffs(ptad, ptas, &vc); else /* BILINEAR */ getBilinearXformCoeffs(ptad, ptas, &vc); ptaDestroy(&ptas); ptaDestroy(&ptad); return vc; }
static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad) { *pptas = ptaCreate(4); ptaAddPt(*pptas, x1[i], y1[i]); ptaAddPt(*pptas, x2[i], y2[i]); ptaAddPt(*pptas, x3[i], y3[i]); ptaAddPt(*pptas, x4[i], y4[i]); *pptad = ptaCreate(4); ptaAddPt(*pptad, xp1[i], yp1[i]); ptaAddPt(*pptad, xp2[i], yp2[i]); ptaAddPt(*pptad, xp3[i], yp3[i]); ptaAddPt(*pptad, xp4[i], yp4[i]); return; }
/*! * ptaIntersectionByHash() * * Input: pta1, pta2 * Return: ptad (intersection of the point sets), or null on error * * Notes: * (1) This is faster than ptaIntersectionByAset(), because the * bucket lookup is O(n). It should be used if the pts are * integers (e.g., representing pixel positions). */ PTA * ptaIntersectionByHash(PTA *pta1, PTA *pta2) { l_int32 n1, n2, nsmall, i, x, y, index1, index2; l_uint32 nsize2; l_uint64 key; L_DNAHASH *dahash1, *dahash2; PTA *pta_small, *pta_big, *ptad; PROCNAME("ptaIntersectionByHash"); if (!pta1) return (PTA *)ERROR_PTR("pta1 not defined", procName, NULL); if (!pta2) return (PTA *)ERROR_PTR("pta2 not defined", procName, NULL); /* Put the elements of the biggest pta into a dnahash */ n1 = ptaGetCount(pta1); n2 = ptaGetCount(pta2); pta_small = (n1 < n2) ? pta1 : pta2; /* do not destroy pta_small */ pta_big = (n1 < n2) ? pta2 : pta1; /* do not destroy pta_big */ dahash1 = l_dnaHashCreateFromPta(pta_big); /* Build up the intersection of points. Add to ptad * if the point is in pta_big (using dahash1) but hasn't * yet been seen in the traversal of pta_small (using dahash2). */ ptad = ptaCreate(0); nsmall = ptaGetCount(pta_small); findNextLargerPrime(nsmall / 20, &nsize2); /* buckets in hash table */ dahash2 = l_dnaHashCreate(nsize2, 0); for (i = 0; i < nsmall; i++) { ptaGetIPt(pta_small, i, &x, &y); ptaFindPtByHash(pta_big, dahash1, x, y, &index1); if (index1 >= 0) { /* found */ ptaFindPtByHash(pta_small, dahash2, x, y, &index2); if (index2 == -1) { /* not found */ ptaAddPt(ptad, x, y); l_hashPtToUint64Fast(nsize2, x, y, &key); l_dnaHashAdd(dahash2, key, (l_float64)i); } } } l_dnaHashDestroy(&dahash1); l_dnaHashDestroy(&dahash2); return ptad; }
// Computes the coefficients of a randomized projective transformation. // The image transform requires backward transformation coefficient, and the // box transform the forward coefficients. // Returns the incolor arg to pixProjective. int ProjectiveCoeffs(int width, int height, TRand* randomizer, float** im_coeffs, float** box_coeffs) { // Setup "from" points. Pta* src_pts = ptaCreate(4); ptaAddPt(src_pts, 0.0f, 0.0f); ptaAddPt(src_pts, width, 0.0f); ptaAddPt(src_pts, width, height); ptaAddPt(src_pts, 0.0f, height); // Extract factors from pseudo-random sequence. float factors[FN_NUM_FACTORS]; float shear = 0.0f; // Shear is signed. for (int i = 0; i < FN_NUM_FACTORS; ++i) { // Everything is squared to make wild values rarer. if (i == FN_SHEAR) { // Shear is signed. shear = randomizer->SignedRand(0.5 / 3.0); shear = shear >= 0.0 ? shear * shear : -shear * shear; // Keep the sheared points within the original rectangle. if (shear < -factors[FN_X0]) shear = -factors[FN_X0]; if (shear > factors[FN_X1]) shear = factors[FN_X1]; factors[i] = shear; } else if (i != FN_INCOLOR) { factors[i] = fabs(randomizer->SignedRand(1.0)); if (i <= FN_Y3) factors[i] *= 5.0 / 8.0; else factors[i] *= 0.5; factors[i] *= factors[i]; } } // Setup "to" points. Pta* dest_pts = ptaCreate(4); ptaAddPt(dest_pts, factors[FN_X0] * width, factors[FN_Y0] * height); ptaAddPt(dest_pts, (1.0f - factors[FN_X1]) * width, factors[FN_Y1] * height); ptaAddPt(dest_pts, (1.0f - factors[FN_X1] + shear) * width, (1 - factors[FN_Y2]) * height); ptaAddPt(dest_pts, (factors[FN_X0] + shear) * width, (1 - factors[FN_Y3]) * height); getProjectiveXformCoeffs(dest_pts, src_pts, im_coeffs); getProjectiveXformCoeffs(src_pts, dest_pts, box_coeffs); ptaDestroy(&src_pts); ptaDestroy(&dest_pts); return factors[FN_INCOLOR] > 0.5f ? L_BRING_IN_WHITE : L_BRING_IN_BLACK; }
/** Returns the polygon outline of the current block. The returned Pta must * be ptaDestroy-ed after use. */ Pta* PageIterator::BlockPolygon() const { if (it_->block() == NULL || it_->block()->block == NULL) return NULL; // Already at the end! if (it_->block()->block->poly_block() == NULL) return NULL; // No layout analysis used - no polygon. ICOORDELT_IT it(it_->block()->block->poly_block()->points()); Pta* pta = ptaCreate(it.length()); int num_pts = 0; for (it.mark_cycle_pt(); !it.cycled_list(); it.forward(), ++num_pts) { ICOORD* pt = it.data(); // Convert to top-down coords within the input image. float x = static_cast<float>(pt->x()) / scale_ + rect_left_; float y = rect_top_ + rect_height_ - static_cast<float>(pt->y()) / scale_; ptaAddPt(pta, x, y); } return pta; }
static void MakePtas(l_int32 i, l_int32 npts, /* 3 or 4 */ PTA **pptas, PTA **pptad) { *pptas = ptaCreate(npts); ptaAddPt(*pptas, x1[i], y1[i]); ptaAddPt(*pptas, x2[i], y2[i]); ptaAddPt(*pptas, x3[i], y3[i]); if (npts == 4) ptaAddPt(*pptas, x4[i], y4[i]); *pptad = ptaCreate(npts); ptaAddPt(*pptad, xp1[i], yp1[i]); ptaAddPt(*pptad, xp2[i], yp2[i]); ptaAddPt(*pptad, xp3[i], yp3[i]); if (npts == 4) ptaAddPt(*pptad, xp4[i], yp4[i]); return; }
static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad) { PTA *ptas, *ptad; ptas = ptaCreate(4); ptaAddPt(ptas, xs1[i], ys1[i]); ptaAddPt(ptas, xs2[i], ys2[i]); ptaAddPt(ptas, xs3[i], ys3[i]); ptaAddPt(ptas, xs4[i], ys4[i]); ptad = ptaCreate(4); ptaAddPt(ptad, xd1[i], yd1[i]); ptaAddPt(ptad, xd2[i], yd2[i]); ptaAddPt(ptad, xd3[i], yd3[i]); ptaAddPt(ptad, xd4[i], yd4[i]); *pptas = ptas; *pptad = ptad; return; }
/*! * ptaRemoveDupsByHash() * * Input: ptas (assumed to be integer values) * &ptad (<return> unique set of pts; duplicates removed) * &dahash (<optional return> dnahash used for lookup) * Return: 0 if OK, 1 on error * * Notes: * (1) Generates a pta with unique values. * (2) The dnahash is built up with ptad to assure uniqueness. * It can be used to find if a point is in the set: * ptaFindPtByHash(ptad, dahash, x, y, &index) * (3) The hash of the (x,y) location is simple and fast. It scales * up with the number of buckets to insure a fairly random * bucket selection for adjacent points. * (4) A Dna is used rather than a Numa because we need accurate * representation of 32-bit integers that are indices into ptas. * Integer --> float --> integer conversion makes errors for * integers larger than 10M. * (5) This is faster than ptaRemoveDupsByAset(), because the * bucket lookup is O(n), although there is a double-loop * lookup within the dna in each bucket. */ l_int32 ptaRemoveDupsByHash(PTA *ptas, PTA **pptad, L_DNAHASH **pdahash) { l_int32 i, n, index, items, x, y; l_uint32 nsize; l_uint64 key; l_float64 val; PTA *ptad; L_DNAHASH *dahash; PROCNAME("ptaRemoveDupsByHash"); if (pdahash) *pdahash = NULL; if (!pptad) return ERROR_INT("&ptad not defined", procName, 1); *pptad = NULL; if (!ptas) return ERROR_INT("ptas not defined", procName, 1); n = ptaGetCount(ptas); findNextLargerPrime(n / 20, &nsize); /* buckets in hash table */ dahash = l_dnaHashCreate(nsize, 8); ptad = ptaCreate(n); *pptad = ptad; for (i = 0, items = 0; i < n; i++) { ptaGetIPt(ptas, i, &x, &y); ptaFindPtByHash(ptad, dahash, x, y, &index); if (index < 0) { /* not found */ l_hashPtToUint64Fast(nsize, x, y, &key); l_dnaHashAdd(dahash, key, (l_float64)items); ptaAddPt(ptad, x, y); items++; } } if (pdahash) *pdahash = dahash; else l_dnaHashDestroy(&dahash); return 0; }
/*! * ptaIntersectionByAset() * * Input: pta1, pta2 * Return: ptad (intersection of the point sets), or null on error * * Notes: * (1) See sarrayIntersectionByAset() for the approach. * (2) The key is a 64-bit hash from the (x,y) pair. * (3) This is slower than ptaIntersectionByHash(), mostly because * of the nlogn sort to build up the rbtree. Do not use for * large numbers of points (say, > 1M). */ PTA * ptaIntersectionByAset(PTA *pta1, PTA *pta2) { l_int32 n1, n2, i, n, x, y; l_uint64 hash; L_ASET *set1, *set2; RB_TYPE key; PTA *pta_small, *pta_big, *ptad; PROCNAME("ptaIntersectionByAset"); if (!pta1) return (PTA *)ERROR_PTR("pta1 not defined", procName, NULL); if (!pta2) return (PTA *)ERROR_PTR("pta2 not defined", procName, NULL); /* Put the elements of the biggest array into a set */ n1 = ptaGetCount(pta1); n2 = ptaGetCount(pta2); pta_small = (n1 < n2) ? pta1 : pta2; /* do not destroy pta_small */ pta_big = (n1 < n2) ? pta2 : pta1; /* do not destroy pta_big */ set1 = l_asetCreateFromPta(pta_big); /* Build up the intersection of points */ ptad = ptaCreate(0); n = ptaGetCount(pta_small); set2 = l_asetCreate(L_UINT_TYPE); for (i = 0; i < n; i++) { ptaGetIPt(pta_small, i, &x, &y); l_hashPtToUint64(x, y, &hash); key.utype = hash; if (l_asetFind(set1, key) && !l_asetFind(set2, key)) { ptaAddPt(ptad, x, y); l_asetInsert(set2, key); } } l_asetDestroy(&set1); l_asetDestroy(&set2); return ptad; }
void QSPLINE::plot(Pix *pix) const { if (pix == NULL) { return; } int32_t segment; // Index of segment int16_t step; // Index of poly piece double increment; // x increment double x; // x coord double height = static_cast<double>(pixGetHeight(pix)); Pta* points = ptaCreate(QSPLINE_PRECISION * segments); const int kLineWidth = 5; for (segment = 0; segment < segments; segment++) { increment = static_cast<double>((xcoords[segment + 1] - xcoords[segment])) / QSPLINE_PRECISION; x = xcoords[segment]; for (step = 0; step <= QSPLINE_PRECISION; step++) { double y = height - quadratics[segment].y(x); ptaAddPt(points, x, y); x += increment; } } switch (pixGetDepth(pix)) { case 1: pixRenderPolyline(pix, points, kLineWidth, L_SET_PIXELS, 1); break; case 32: pixRenderPolylineArb(pix, points, kLineWidth, 255, 0, 0, 1); break; default: pixRenderPolyline(pix, points, kLineWidth, L_CLEAR_PIXELS, 1); break; } ptaDestroy(&points); }
/*! * ptaScale() * * Input: ptas (for initial points) * scalex (horizontal scale factor) * scaley (vertical scale factor) * Return: 0 if OK; 1 on error * * Notes; * (1) See createMatrix2dScale() for details of transform. */ PTA * ptaScale(PTA *ptas, l_float32 scalex, l_float32 scaley) { l_int32 i, npts; l_float32 x, y; PTA *ptad; PROCNAME("ptaScale"); if (!ptas) return (PTA *)ERROR_PTR("ptas not defined", procName, NULL); npts = ptaGetCount(ptas); if ((ptad = ptaCreate(npts)) == NULL) return (PTA *)ERROR_PTR("ptad not made", procName, NULL); for (i = 0; i < npts; i++) { ptaGetPt(ptas, i, &x, &y); ptaAddPt(ptad, scalex * x, scaley * y); } return ptad; }
/*! * \brief pixGetLocalSkewAngles() * * \param[in] pixs 1 bpp * \param[in] nslices the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; 0 for default * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and not * larger than redsweep; use 0 for default value * \param[in] sweeprange half the full range, assumed about 0; in degrees; * use 0.0 for default value * \param[in] sweepdelta angle increment of sweep; in degrees; * use 0.0 for default value * \param[in] minbsdelta min binary search increment angle; in degrees; * use 0.0 for default value * \param[out] pa [optional] slope of skew as fctn of y * \param[out] pb [optional] intercept at y=0 of skew as fctn of y * \param[in] debug 1 for generating plot of skew angle vs. y; 0 otherwise * \return naskew, or NULL on error * * <pre> * Notes: * (1) The local skew is measured in a set of overlapping strips. * We then do a least square linear fit parameters to get * the slope and intercept parameters a and b in * skew-angle = a * y + b (degrees) * for the local skew as a function of raster line y. * This is then used to make naskew, which can be interpreted * as the computed skew angle (in degrees) at the left edge * of each raster line. * (2) naskew can then be used to find the baselines of text, because * each text line has a baseline that should intersect * the left edge of the image with the angle given by this * array, evaluated at the raster line of intersection. * </pre> */ NUMA * pixGetLocalSkewAngles(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_float32 *pa, l_float32 *pb, l_int32 debug) { l_int32 w, h, hs, i, ystart, yend, ovlap, npts; l_float32 angle, conf, ycenter, a, b; BOX *box; GPLOT *gplot; NUMA *naskew, *nax, *nay; PIX *pix; PTA *pta; PROCNAME("pixGetLocalSkewAngles"); if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (nslices < 2 || nslices > 20) nslices = DEFAULT_SLICES; if (redsweep < 1 || redsweep > 8) redsweep = DEFAULT_SWEEP_REDUCTION; if (redsearch < 1 || redsearch > redsweep) redsearch = DEFAULT_BS_REDUCTION; if (sweeprange == 0.0) sweeprange = DEFAULT_SWEEP_RANGE; if (sweepdelta == 0.0) sweepdelta = DEFAULT_SWEEP_DELTA; if (minbsdelta == 0.0) minbsdelta = DEFAULT_MINBS_DELTA; pixGetDimensions(pixs, &w, &h, NULL); hs = h / nslices; ovlap = (l_int32)(OVERLAP_FRACTION * hs); pta = ptaCreate(nslices); for (i = 0; i < nslices; i++) { ystart = L_MAX(0, hs * i - ovlap); yend = L_MIN(h - 1, hs * (i + 1) + ovlap); ycenter = (ystart + yend) / 2; box = boxCreate(0, ystart, w, yend - ystart + 1); pix = pixClipRectangle(pixs, box, NULL); pixFindSkewSweepAndSearch(pix, &angle, &conf, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta); if (conf > MIN_ALLOWED_CONFIDENCE) ptaAddPt(pta, ycenter, angle); pixDestroy(&pix); boxDestroy(&box); } /* Do linear least squares fit */ if ((npts = ptaGetCount(pta)) < 2) { ptaDestroy(&pta); return (NUMA *)ERROR_PTR("can't fit skew", procName, NULL); } ptaGetLinearLSF(pta, &a, &b, NULL); if (pa) *pa = a; if (pb) *pb = b; /* Make skew angle array as function of raster line */ naskew = numaCreate(h); for (i = 0; i < h; i++) { angle = a * i + b; numaAddNumber(naskew, angle); } if (debug) { lept_mkdir("lept/baseline"); ptaGetArrays(pta, &nax, &nay); gplot = gplotCreate("/tmp/lept/baseline/skew", GPLOT_PNG, "skew as fctn of y", "y (in raster lines from top)", "angle (in degrees)"); gplotAddPlot(gplot, NULL, naskew, GPLOT_POINTS, "linear lsf"); gplotAddPlot(gplot, nax, nay, GPLOT_POINTS, "actual data pts"); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&nax); numaDestroy(&nay); } ptaDestroy(&pta); return naskew; }
/*! * \brief pixGetLocalSkewTransform() * * \param[in] pixs * \param[in] nslices the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; use 0 for default * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value * \param[in] sweeprange half the full range, assumed about 0; in degrees; * use 0.0 for default value * \param[in] sweepdelta angle increment of sweep; in degrees; * use 0.0 for default value * \param[in] minbsdelta min binary search increment angle; in degrees; * use 0.0 for default value * \param[out] pptas 4 points in the source * \param[out] pptad the corresponding 4 pts in the dest * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This generates two pairs of points in the src, each pair * corresponding to a pair of points that would lie along * the same raster line in a transformed (dewarped) image. * (2) The sets of 4 src and 4 dest points returned by this function * can then be used, in a projective or bilinear transform, * to remove keystoning in the src. * </pre> */ l_int32 pixGetLocalSkewTransform(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, PTA **pptas, PTA **pptad) { l_int32 w, h, i; l_float32 deg2rad, angr, angd, dely; NUMA *naskew; PTA *ptas, *ptad; PROCNAME("pixGetLocalSkewTransform"); if (!pptas || !pptad) return ERROR_INT("&ptas and &ptad not defined", procName, 1); *pptas = *pptad = NULL; if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); if (nslices < 2 || nslices > 20) nslices = DEFAULT_SLICES; if (redsweep < 1 || redsweep > 8) redsweep = DEFAULT_SWEEP_REDUCTION; if (redsearch < 1 || redsearch > redsweep) redsearch = DEFAULT_BS_REDUCTION; if (sweeprange == 0.0) sweeprange = DEFAULT_SWEEP_RANGE; if (sweepdelta == 0.0) sweepdelta = DEFAULT_SWEEP_DELTA; if (minbsdelta == 0.0) minbsdelta = DEFAULT_MINBS_DELTA; naskew = pixGetLocalSkewAngles(pixs, nslices, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta, NULL, NULL, 0); if (!naskew) return ERROR_INT("naskew not made", procName, 1); deg2rad = 3.14159265 / 180.; w = pixGetWidth(pixs); h = pixGetHeight(pixs); ptas = ptaCreate(4); ptad = ptaCreate(4); *pptas = ptas; *pptad = ptad; /* Find i for skew line that intersects LHS at i and RHS at h / 20 */ for (i = 0; i < h; i++) { numaGetFValue(naskew, i, &angd); angr = angd * deg2rad; dely = w * tan(angr); if (i - dely > 0.05 * h) break; } ptaAddPt(ptas, 0, i); ptaAddPt(ptas, w - 1, i - dely); ptaAddPt(ptad, 0, i); ptaAddPt(ptad, w - 1, i); /* Find i for skew line that intersects LHS at i and RHS at 19h / 20 */ for (i = h - 1; i > 0; i--) { numaGetFValue(naskew, i, &angd); angr = angd * deg2rad; dely = w * tan(angr); if (i - dely < 0.95 * h) break; } ptaAddPt(ptas, 0, i); ptaAddPt(ptas, w - 1, i - dely); ptaAddPt(ptad, 0, i); ptaAddPt(ptad, w - 1, i); numaDestroy(&naskew); return 0; }
/*! * \brief pixFindBaselines() * * \param[in] pixs 1 bpp, 300 ppi * \param[out] ppta [optional] pairs of pts corresponding to * approx. ends of each text line * \param[in] pixadb for debug output; use NULL to skip * \return na of baseline y values, or NULL on error * * <pre> * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * </pre> */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, PIXA *pixadb) { l_int32 h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pix1, *pix2; PTA *pta; PROCNAME("pixFindBaselines"); if (ppta) *ppta = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Close up the text characters, removing noise */ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0); /* Estimate the resolution */ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) { pixDestroy(&pix1); return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); } h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } numaDestroy(&nasum); if (pixadb) { /* show the difference signal */ lept_mkdir("lept/baseline"); gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); pix2 = pixRead("/tmp/lept/baseline/diff.png"); pixaAddPix(pixadb, pix2, L_INSERT); } /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); numaDestroy(&nadiff); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } LEPT_FREE(array); /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } if (pixadb) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); pix2 = pixRead("/tmp/lept/baseline/loc.png"); pixaAddPix(pixadb, pix2, L_INSERT); } numaDestroy(&naval); /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0); if (pixadb) pixaAddPix(pixadb, pix2, L_COPY); boxa1 = pixConnComp(pix2, NULL, 4); pixDestroy(&pix1); pixDestroy(&pix2); if (boxaGetCount(boxa1) == 0) { numaDestroy(&naloc); boxaDestroy(&boxa1); L_INFO("no compnents after filtering\n", procName); return NULL; } boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Optionally, find the baseline segments */ pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } boxaDestroy(&boxa3); if (pixadb && pta) { /* display baselines */ l_int32 npts, x1, y1, x2, y2; pix1 = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0); } pixWrite("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG); pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); pixDestroy(&pix1); } return naloc; }
/*! * pixGenerateSelWithRuns() * * Input: pix (1 bpp, typically small, to be used as a pattern) * nhlines (number of hor lines along which elements are found) * nvlines (number of vert lines along which elements are found) * distance (min distance from boundary pixel; use 0 for default) * minlength (min runlength to set hit or miss; use 0 for default) * toppix (number of extra pixels of bg added above) * botpix (number of extra pixels of bg added below) * leftpix (number of extra pixels of bg added to left) * rightpix (number of extra pixels of bg added to right) * &pixe (<optional return> input pix expanded by extra pixels) * Return: sel (hit-miss for input pattern), or null on error * * Notes: * (1) The horizontal and vertical lines along which elements are * selected are roughly equally spaced. The actual locations of * the hits and misses are the centers of respective run-lengths. * (2) No elements are selected that are less than 'distance' pixels away * from a boundary pixel of the same color. This makes the * match much more robust to edge noise. Valid inputs of * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or * greater than 4, we reset it to the default value. * (3) The 4 numbers for adding rectangles of pixels outside the fg * can be use if the pattern is expected to be surrounded by bg * (white) pixels. On the other hand, if the pattern may be near * other fg (black) components on some sides, use 0 for those sides. * (4) The pixels added to a side allow you to have miss elements there. * There is a constraint between distance, minlength, and * the added pixels for this to work. We illustrate using the * default values. If you add 5 pixels to the top, and use a * distance of 1, then you end up with a vertical run of at least * 4 bg pixels along the top edge of the image. If you use a * minimum runlength of 3, each vertical line will always find * a miss near the center of its run. However, if you use a * minimum runlength of 5, you will not get a miss on every vertical * line. As another example, if you have 7 added pixels and a * distance of 2, you can use a runlength up to 5 to guarantee * that the miss element is recorded. We give a warning if the * contraint does not guarantee a miss element outside the * image proper. * (5) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. */ SEL * pixGenerateSelWithRuns(PIX *pixs, l_int32 nhlines, l_int32 nvlines, l_int32 distance, l_int32 minlength, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, xval, yval, i, j, nh, nm; l_float32 delh, delw; NUMA *nah, *nam; PIX *pixt1, *pixt2, *pixfg, *pixbg; PTA *ptah, *ptam; SEL *seld, *sel; PROCNAME("pixGenerateSelWithRuns"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (nhlines < 1 && nvlines < 1) return (SEL *)ERROR_PTR("nvlines and nhlines both < 1", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; if (minlength <= 0) minlength = DEFAULT_MIN_RUNLENGTH; if (distance > MAX_DISTANCE_TO_BOUNDARY) { L_WARNING("distance too large; setting to max value", procName); distance = MAX_DISTANCE_TO_BOUNDARY; } /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (toppix || botpix || leftpix || rightpix) { x = y = 0; if (toppix) { h += toppix; y = toppix; if (toppix < distance + minlength) L_WARNING("no miss elements in added top pixels", procName); } if (botpix) { h += botpix; if (botpix < distance + minlength) L_WARNING("no miss elements in added bot pixels", procName); } if (leftpix) { w += leftpix; x = leftpix; if (leftpix < distance + minlength) L_WARNING("no miss elements in added left pixels", procName); } if (rightpix) { w += rightpix; if (rightpix < distance + minlength) L_WARNING("no miss elements in added right pixels", procName); } pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else pixt2 = pixClone(pixt1); if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are at least 'distance' pixels * away from the boundary pixels in their set */ seld = selCreateBrick(2 * distance + 1, 2 * distance + 1, distance, distance, SEL_HIT); pixfg = pixErode(NULL, pixt2, seld); pixbg = pixDilate(NULL, pixt2, seld); pixInvert(pixbg, pixbg); selDestroy(&seld); pixDestroy(&pixt2); /* Accumulate hit and miss points */ ptah = ptaCreate(0); ptam = ptaCreate(0); if (nhlines >= 1) { delh = (l_float32)h / (l_float32)(nhlines + 1); for (i = 0, y = 0; i < nhlines; i++) { y += (l_int32)(delh + 0.5); nah = pixGetRunCentersOnLine(pixfg, -1, y, minlength); nam = pixGetRunCentersOnLine(pixbg, -1, y, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &xval); ptaAddPt(ptah, xval, y); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &xval); ptaAddPt(ptam, xval, y); } numaDestroy(&nah); numaDestroy(&nam); } } if (nvlines >= 1) { delw = (l_float32)w / (l_float32)(nvlines + 1); for (i = 0, x = 0; i < nvlines; i++) { x += (l_int32)(delw + 0.5); nah = pixGetRunCentersOnLine(pixfg, x, -1, minlength); nam = pixGetRunCentersOnLine(pixbg, x, -1, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &yval); ptaAddPt(ptah, x, yval); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &yval); ptaAddPt(ptam, x, yval); } numaDestroy(&nah); numaDestroy(&nam); } } /* Make the Sel with those points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); nh = ptaGetCount(ptah); for (i = 0; i < nh; i++) { ptaGetIPt(ptah, i, &x, &y); selSetElement(sel, y, x, SEL_HIT); } nm = ptaGetCount(ptam); for (i = 0; i < nm; i++) { ptaGetIPt(ptam, i, &x, &y); selSetElement(sel, y, x, SEL_MISS); } pixDestroy(&pixfg); pixDestroy(&pixbg); ptaDestroy(&ptah); ptaDestroy(&ptam); return sel; }
/*! * pixSearchBinaryMaze() * * Input: pixs (1 bpp, maze) * xi, yi (beginning point; use same initial point * that was used to generate the maze) * xf, yf (end point, or close to it) * &ppixd (<optional return> maze with path illustrated, or * if no path possible, the part of the maze * that was searched) * Return: pta (shortest path), or null if either no path * exists or on error * * Notes: * (1) Because of the overhead in calling pixGetPixel() and * pixSetPixel(), we have used raster line pointers and the * GET_DATA* and SET_DATA* macros for many of the pix accesses. * (2) Commentary: * The goal is to find the shortest path between beginning and * end points, without going through walls, and there are many * ways to solve this problem. * We use a queue to implement a breadth-first search. Two auxiliary * "image" data structures can be used: one to mark the visited * pixels and one to give the direction to the parent for each * visited pixels. The first structure is used to avoid putting * pixels on the queue more than once, and the second is used * for retracing back to the origin, like the breadcrumbs in * Hansel and Gretel. Each pixel taken off the queue is destroyed * after it is used to locate the allowed neighbors. In fact, * only one distance image is required, if you initialize it * to some value that signifies "not yet visited." (We use * a binary image for marking visited pixels because it is clearer.) * This method for a simple search of a binary maze is implemented in * searchBinaryMaze(). * An alternative method would store the (manhattan) distance * from the start point with each pixel on the queue. The children * of each pixel get a distance one larger than the parent. These * values can be stored in an auxiliary distance map image * that is constructed simultaneously with the search. Once the * end point is reached, the distance map is used to backtrack * along a minimum path. There may be several equal length * minimum paths, any one of which can be chosen this way. */ PTA * pixSearchBinaryMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 i, j, x, y, w, h, d, found; l_uint32 val, rpixel, gpixel, bpixel; void **lines1, **linem1, **linep8, **lined32; MAZEEL *el, *elp; PIX *pixd; /* the shortest path written on the maze image */ PIX *pixm; /* for bookkeeping, to indicate pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_QUEUE *lq; PTA *pta; PROCNAME("pixSearchBinaryMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 1) return (PTA *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (xi <= 0 || xi >= w) return (PTA *)ERROR_PTR("xi not valid", procName, NULL); if (yi <= 0 || yi >= h) return (PTA *)ERROR_PTR("yi not valid", procName, NULL); pixGetPixel(pixs, xi, yi, &val); if (val != 0) return (PTA *)ERROR_PTR("(xi,yi) not bg pixel", procName, NULL); pixd = NULL; pta = NULL; /* Find a bg pixel near input point (xf, yf) */ localSearchForBackground(pixs, &xf, &yf, 5); #if DEBUG_MAZE fprintf(stderr, "(xi, yi) = (%d, %d), (xf, yf) = (%d, %d)\n", xi, yi, xf, yf); #endif /* DEBUG_MAZE */ pixm = pixCreate(w, h, 1); /* initialized to OFF */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines1 = pixGetLinePtrs(pixs, NULL); linem1 = pixGetLinePtrs(pixm, NULL); linep8 = pixGetLinePtrs(pixp, NULL); lq = lqueueCreate(0); /* Prime the queue with the first pixel; it is OFF */ el = mazeelCreate(xi, yi, 0); /* don't need direction here */ pixSetPixel(pixm, xi, yi, 1); /* mark visited */ lqueueAdd(lq, el); /* Fill up the pix storing directions to parents, * stopping when we hit the point (xf, yf) */ found = FALSE; while (lqueueGetCount(lq) > 0) { elp = (MAZEEL *)lqueueRemove(lq); x = elp->x; y = elp->y; if (x == xf && y == yf) { found = TRUE; FREE(elp); break; } if (x > 0) { /* check to west */ val = GET_DATA_BIT(linem1[y], x - 1); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y], x - 1); /* mark visited */ val = GET_DATA_BIT(lines1[y], x - 1); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent E */ el = mazeelCreate(x - 1, y, 0); lqueueAdd(lq, el); } } } if (y > 0) { /* check north */ val = GET_DATA_BIT(linem1[y - 1], x); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y - 1], x); /* mark visited */ val = GET_DATA_BIT(lines1[y - 1], x); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent S */ el = mazeelCreate(x, y - 1, 0); lqueueAdd(lq, el); } } } if (x < w - 1) { /* check east */ val = GET_DATA_BIT(linem1[y], x + 1); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y], x + 1); /* mark visited */ val = GET_DATA_BIT(lines1[y], x + 1); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent W */ el = mazeelCreate(x + 1, y, 0); lqueueAdd(lq, el); } } } if (y < h - 1) { /* check south */ val = GET_DATA_BIT(linem1[y + 1], x); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y + 1], x); /* mark visited */ val = GET_DATA_BIT(lines1[y + 1], x); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent N */ el = mazeelCreate(x, y + 1, 0); lqueueAdd(lq, el); } } } FREE(elp); } lqueueDestroy(&lq, TRUE); pixDestroy(&pixm); FREE(linem1); if (ppixd) { pixd = pixUnpackBinary(pixs, 32, 1); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ if (!found) { L_INFO(" No path found", procName); if (pixd) { /* paint all visited locations */ lined32 = pixGetLinePtrs(pixd, NULL); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val = GET_DATA_BYTE(linep8[i], j); if (val != 0 && pixd) SET_DATA_FOUR_BYTES(lined32[i], j, gpixel); } } FREE(lined32); } } else { /* write path onto pixd */ L_INFO(" Path found", procName); pta = ptaCreate(0); x = xf; y = yf; while (1) { ptaAddPt(pta, x, y); if (x == xi && y == yi) break; if (pixd) pixSetPixel(pixd, x, y, gpixel); pixGetPixel(pixp, x, y, &val); if (val == DIR_NORTH) y--; else if (val == DIR_SOUTH) y++; else if (val == DIR_EAST) x++; else if (val == DIR_WEST) x--; } } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); FREE(lines1); FREE(linep8); return pta; }
/*! * pixSearchGrayMaze() * * Input: pixs (1 bpp, maze) * xi, yi (beginning point; use same initial point * that was used to generate the maze) * xf, yf (end point, or close to it) * &ppixd (<optional return> maze with path illustrated, or * if no path possible, the part of the maze * that was searched) * Return: pta (shortest path), or null if either no path * exists or on error * * Commentary: * Consider first a slight generalization of the binary maze * search problem. Suppose that you can go through walls, * but the cost is higher (say, an increment of 3 to go into * a wall pixel rather than 1)? You're still trying to find * the shortest path. One way to do this is with an ordered * queue, and a simple way to visualize an ordered queue is as * a set of stacks, each stack being marked with the distance * of each pixel in the stack from the start. We place the * start pixel in stack 0, pop it, and process its 4 children. * Each pixel is given a distance that is incremented from that * of its parent (0 in this case), depending on if it is a wall * pixel or not. That value may be recorded on a distance map, * according to the algorithm below. For children of the first * pixel, those not on a wall go in stack 1, and wall * children go in stack 3. Stack 0 being emptied, the process * then continues with pixels being popped from stack 1. * Here is the algorithm for each child pixel. The pixel's * distance value, were it to be placed on a stack, is compared * with the value for it that is on the distance map. There * are three possible cases: * (1) If the pixel has not yet been registered, it is pushed * on its stack and the distance is written to the map. * (2) If it has previously been registered with a higher distance, * the distance on the map is relaxed to that of the * current pixel, which is then placed on its stack. * (3) If it has previously been registered with an equal * or lower value, the pixel is discarded. * The pixels are popped and processed successively from * stack 1, and when stack 1 is empty, popping starts on stack 2. * This continues until the destination pixel is popped off * a stack. The minimum path is then derived from the distance map, * going back from the end point as before. This is just Dijkstra's * algorithm for a directed graph; here, the underlying graph * (consisting of the pixels and four edges connecting each pixel * to its 4-neighbor) is a special case of a directed graph, where * each edge is bi-directional. The implementation of this generalized * maze search is left as an exercise to the reader. * * Let's generalize a bit further. Suppose the "maze" is just * a grayscale image -- think of it as an elevation map. The cost * of moving on this surface depends on the height, or the gradient, * or whatever you want. All that is required is that the cost * is specified and non-negative on each link between adjacent * pixels. Now the problem becomes: find the least cost path * moving on this surface between two specified end points. * For example, if the cost across an edge between two pixels * depends on the "gradient", you can use: * cost = 1 + L_ABS(deltaV) * where deltaV is the difference in value between two adjacent * pixels. If the costs are all integers, we can still use an array * of stacks to avoid ordering the queue (e.g., by using a heap sort.) * This is a neat problem, because you don't even have to build a * maze -- you can can use it on any grayscale image! * * Rather than using an array of stacks, a more practical * approach is to implement with a priority queue, which is * a queue that is sorted so that the elements with the largest * (or smallest) key values always come off first. The * priority queue is efficiently implemented as a heap, and * this is how we do it. Suppose you run the algorithm * using a priority queue, doing the bookkeeping with an * auxiliary image data structure that saves the distance of * each pixel put on the queue as before, according to the method * described above. We implement it as a 2-way choice by * initializing the distance array to a large value and putting * a pixel on the queue if its distance is less than the value * found on the array. When you finally pop the end pixel from * the queue, you're done, and you can trace the path backward, * either always going downhill or using an auxiliary image to * give you the direction to go at each step. This is implemented * here in searchGrayMaze(). * * Do we really have to use a sorted queue? Can we solve this * generalized maze with an unsorted queue of pixels? (Or even * an unsorted stack, doing a depth-first search (DFS)?) * Consider a different algorithm for this generalized maze, where * we travel again breadth first, but this time use a single, * unsorted queue. An auxiliary image is used as before to * store the distances and to determine if pixels get pushed * on the stack or dropped. As before, we must allow pixels * to be revisited, with relaxation of the distance if a shorter * path arrives later. As a result, we will in general have * multiple instances of the same pixel on the stack with different * distances. However, because the queue is not ordered, some of * these pixels will be popped when another instance with a lower * distance is still on the stack. Here, we're just popping them * in the order they go on, rather than setting up a priority * based on minimum distance. Thus, unlike the priority queue, * when a pixel is popped we have to check the distance map to * see if a pixel with a lower distance has been put on the queue, * and, if so, we discard the pixel we just popped. So the * "while" loop looks like this: * - pop a pixel from the queue * - check its distance against the distance stored in the * distance map; if larger, discard * - otherwise, for each of its neighbors: * - compute its distance from the start pixel * - compare this distance with that on the distance map: * - if the distance map value higher, relax the distance * and push the pixel on the queue * - if the distance map value is lower, discard the pixel * * How does this loop terminate? Before, with an ordered queue, * it terminates when you pop the end pixel. But with an unordered * queue (or stack), the first time you hit the end pixel, the * distance is not guaranteed to be correct, because the pixels * along the shortest path may not have yet been visited and relaxed. * Because the shortest path can theoretically go anywhere, * we must keep going. How do we know when to stop? Dijkstra * uses an ordered queue to systematically remove nodes from * further consideration. (Each time a pixel is popped, we're * done with it; it's "finalized" in the Dijkstra sense because * we know the shortest path to it.) However, with an unordered * queue, the brute force answer is: stop when the queue * (or stack) is empty, because then every pixel in the image * has been assigned its minimum "distance" from the start pixel. * * This is similar to the situation when you use a stack for the * simpler uniform-step problem: with breadth-first search (BFS) * the pixels on the queue are automatically ordered, so you are * done when you locate the end pixel as a neighbor of a popped pixel; * whereas depth-first search (DFS), using a stack, requires, * in general, a search of every accessible pixel. Further, if * a pixel is revisited with a smaller distance, that distance is * recorded and the pixel is put on the stack again. * * But surely, you ask, can't we stop sooner? What if the * start and end pixels are very close to each other? * OK, suppose they are, and you have very high walls and a * long snaking level path that is actually the minimum cost. * That long path can wind back and forth across the entire * maze many times before ending up at the end point, which * could be just over a wall from the start. With the unordered * queue, you very quickly get a high distance for the end * pixel, which will be relaxed to the minimum distance only * after all the pixels of the path have been visited and placed * on the queue, multiple times for many of them. So that's the * price for not ordering the queue! */ PTA * pixSearchGrayMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 x, y, w, h, d; l_uint32 val, valr, vals, rpixel, gpixel, bpixel; void **lines8, **liner32, **linep8; l_int32 cost, dist, distparent, sival, sivals; MAZEEL *el, *elp; PIX *pixd; /* optionally plot the path on this RGB version of pixs */ PIX *pixr; /* for bookkeeping, to indicate the minimum distance */ /* to pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_HEAP *lh; PTA *pta; PROCNAME("pixSearchGrayMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8) return (PTA *)ERROR_PTR("pixs not 8 bpp", procName, NULL); if (xi <= 0 || xi >= w) return (PTA *)ERROR_PTR("xi not valid", procName, NULL); if (yi <= 0 || yi >= h) return (PTA *)ERROR_PTR("yi not valid", procName, NULL); pixd = NULL; pta = NULL; pixr = pixCreate(w, h, 32); pixSetAll(pixr); /* initialize to max value */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines8 = pixGetLinePtrs(pixs, NULL); linep8 = pixGetLinePtrs(pixp, NULL); liner32 = pixGetLinePtrs(pixr, NULL); lh = lheapCreate(0, L_SORT_INCREASING); /* always remove closest pixels */ /* Prime the heap with the first pixel */ pixGetPixel(pixs, xi, yi, &val); el = mazeelCreate(xi, yi, 0); /* don't need direction here */ el->distance = 0; pixGetPixel(pixs, xi, yi, &val); el->val = val; pixSetPixel(pixr, xi, yi, 0); /* distance is 0 */ lheapAdd(lh, el); /* Breadth-first search with priority queue (implemented by a heap), labeling direction to parents in pixp and minimum distance to visited pixels in pixr. Stop when we pull the destination point (xf, yf) off the queue. */ while (lheapGetCount(lh) > 0) { elp = (MAZEEL *)lheapRemove(lh); if (!elp) return (PTA *)ERROR_PTR("heap broken!!", procName, NULL); x = elp->x; y = elp->y; if (x == xf && y == yf) { /* exit condition */ FREE(elp); break; } distparent = (l_int32)elp->distance; val = elp->val; sival = val; if (x > 0) { /* check to west */ vals = GET_DATA_BYTE(lines8[y], x - 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x - 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x - 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent to E */ el = mazeelCreate(x - 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y > 0) { /* check north */ vals = GET_DATA_BYTE(lines8[y - 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y - 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y - 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent to S */ el = mazeelCreate(x, y - 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (x < w - 1) { /* check east */ vals = GET_DATA_BYTE(lines8[y], x + 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x + 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x + 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent to W */ el = mazeelCreate(x + 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y < h - 1) { /* check south */ vals = GET_DATA_BYTE(lines8[y + 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y + 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y + 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent to N */ el = mazeelCreate(x, y + 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } FREE(elp); } lheapDestroy(&lh, TRUE); if (ppixd) { pixd = pixConvert8To32(pixs); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ x = xf; y = yf; pta = ptaCreate(0); while (1) { /* write path onto pixd */ ptaAddPt(pta, x, y); if (x == xi && y == yi) break; if (pixd) pixSetPixel(pixd, x, y, gpixel); pixGetPixel(pixp, x, y, &val); if (val == DIR_NORTH) y--; else if (val == DIR_SOUTH) y++; else if (val == DIR_EAST) x++; else if (val == DIR_WEST) x--; pixGetPixel(pixr, x, y, &val); #if DEBUG_PATH fprintf(stderr, "(x,y) = (%d, %d); dist = %d\n", x, y, val); #endif /* DEBUG_PATH */ } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); pixDestroy(&pixr); FREE(lines8); FREE(linep8); FREE(liner32); return pta; }
/*! * dewarpBuildModel() * * Input: dew * debugflag (1 for debugging output) * Return: 0 if OK, 1 on error * * Notes: * (1) This is the basic function that builds the vertical * disparity array, which allows determination of the * src pixel in the input image corresponding to each * dest pixel in the dewarped image. * (2) The method is as follows: * * Estimate the centers of all the long textlines and * fit a LS quadratic to each one. This smooths the curves. * * Sample each curve at a regular interval, find the y-value * of the flat point on each curve, and subtract the sampled * curve value from this value. This is the vertical * disparity. * * Fit a LS quadratic to each set of vertically aligned * disparity samples. This smooths the disparity values * in the vertical direction. Then resample at the same * regular interval, We now have a regular grid of smoothed * vertical disparity valuels. * * Interpolate this grid to get a full resolution disparity * map. This can be applied directly to the src image * pixels to dewarp the image in the vertical direction, * making all textlines horizontal. */ l_int32 dewarpBuildModel(L_DEWARP *dew, l_int32 debugflag) { char *tempname; l_int32 i, j, nlines, nx, ny, sampling; l_float32 c0, c1, c2, x, y, flaty, val; l_float32 *faflats; NUMA *nax, *nafit, *nacurve, *nacurves, *naflat, *naflats, *naflatsi; PIX *pixs, *pixt1, *pixt2; PTA *pta, *ptad; PTAA *ptaa1, *ptaa2, *ptaa3, *ptaa4, *ptaa5, *ptaa6, *ptaa7; FPIX *fpix1, *fpix2, *fpix3; PROCNAME("dewarpBuildModel"); if (!dew) return ERROR_INT("dew not defined", procName, 1); pixs = dew->pixs; if (debugflag) { pixDisplayWithTitle(pixs, 0, 0, "pixs", 1); pixWriteTempfile("/tmp", "pixs.png", pixs, IFF_PNG, NULL); } /* Make initial estimate of centers of textlines */ ptaa1 = pixGetTextlineCenters(pixs, DEBUG_TEXTLINE_CENTERS); if (debugflag) { pixt1 = pixConvertTo32(pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa1); pixWriteTempfile("/tmp", "lines1.png", pixt2, IFF_PNG, NULL); pixDestroy(&pixt1); pixDestroy(&pixt2); } /* Remove all lines that are not near the length * of the longest line. */ ptaa2 = ptaaRemoveShortLines(pixs, ptaa1, 0.8, DEBUG_SHORT_LINES); if (debugflag) { pixt1 = pixConvertTo32(pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa2); pixWriteTempfile("/tmp", "lines2.png", pixt2, IFF_PNG, NULL); pixDestroy(&pixt1); pixDestroy(&pixt2); } nlines = ptaaGetCount(ptaa2); if (nlines < dew->minlines) return ERROR_INT("insufficient lines to build model", procName, 1); /* Do quadratic fit to smooth each line. A single quadratic * over the entire width of the line appears to be sufficient. * Quartics tend to overfit to noise. Each line is thus * represented by three coefficients: c2 * x^2 + c1 * x + c0. * Using the coefficients, sample each fitted curve uniformly * across the full width of the image. */ sampling = dew->sampling; nx = dew->nx; ny = dew->ny; ptaa3 = ptaaCreate(nlines); nacurve = numaCreate(nlines); /* stores curvature coeff c2 */ for (i = 0; i < nlines; i++) { /* for each line */ pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL); numaAddNumber(nacurve, c2); ptad = ptaCreate(nx); for (j = 0; j < nx; j++) { /* uniformly sampled in x */ x = j * sampling; applyQuadraticFit(c2, c1, c0, x, &y); ptaAddPt(ptad, x, y); } ptaaAddPta(ptaa3, ptad, L_INSERT); ptaDestroy(&pta); } if (debugflag) { ptaa4 = ptaaCreate(nlines); for (i = 0; i < nlines; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetQuadraticLSF(pta, NULL, NULL, NULL, &nafit); ptad = ptaCreateFromNuma(nax, nafit); ptaaAddPta(ptaa4, ptad, L_INSERT); ptaDestroy(&pta); numaDestroy(&nax); numaDestroy(&nafit); } pixt1 = pixConvertTo32(pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa4); pixWriteTempfile("/tmp", "lines3.png", pixt2, IFF_PNG, NULL); pixDestroy(&pixt1); pixDestroy(&pixt2); ptaaDestroy(&ptaa4); } /* Find and save the flat points in each curve. */ naflat = numaCreate(nlines); for (i = 0; i < nlines; i++) { pta = ptaaGetPta(ptaa3, i, L_CLONE); numaGetFValue(nacurve, i, &c2); if (c2 <= 0) /* flat point at bottom; max value of y in curve */ ptaGetRange(pta, NULL, NULL, NULL, &flaty); else /* flat point at top; min value of y in curve */ ptaGetRange(pta, NULL, NULL, &flaty, NULL); numaAddNumber(naflat, flaty); ptaDestroy(&pta); } /* Sort the lines in ptaa3 by their position */ naflatsi = numaGetSortIndex(naflat, L_SORT_INCREASING); naflats = numaSortByIndex(naflat, naflatsi); nacurves = numaSortByIndex(nacurve, naflatsi); dew->naflats = naflats; dew->nacurves = nacurves; ptaa4 = ptaaSortByIndex(ptaa3, naflatsi); numaDestroy(&naflat); numaDestroy(&nacurve); numaDestroy(&naflatsi); if (debugflag) { tempname = genTempFilename("/tmp", "naflats.na", 0); numaWrite(tempname, naflats); FREE(tempname); } /* Convert the sampled points in ptaa3 to a sampled disparity with * with respect to the flat point in the curve. */ ptaa5 = ptaaCreate(nlines); for (i = 0; i < nlines; i++) { pta = ptaaGetPta(ptaa4, i, L_CLONE); numaGetFValue(naflats, i, &flaty); ptad = ptaCreate(nx); for (j = 0; j < nx; j++) { ptaGetPt(pta, j, &x, &y); ptaAddPt(ptad, x, flaty - y); } ptaaAddPta(ptaa5, ptad, L_INSERT); ptaDestroy(&pta); } if (debugflag) { tempname = genTempFilename("/tmp", "ptaa5.ptaa", 0); ptaaWrite(tempname, ptaa5, 0); FREE(tempname); } /* Generate a ptaa taking vertical 'columns' from ptaa5. * We want to fit the vertical disparity on the column to the * vertical position of the line, which we call 'y' here and * obtain from naflats. */ ptaa6 = ptaaCreate(nx); faflats = numaGetFArray(naflats, L_NOCOPY); for (j = 0; j < nx; j++) { pta = ptaCreate(nlines); for (i = 0; i < nlines; i++) { y = faflats[i]; ptaaGetPt(ptaa5, i, j, NULL, &val); /* disparity value */ ptaAddPt(pta, y, val); } ptaaAddPta(ptaa6, pta, L_INSERT); } if (debugflag) { tempname = genTempFilename("/tmp", "ptaa6.ptaa", 0); ptaaWrite(tempname, ptaa6, 0); FREE(tempname); } /* Do quadratic fit vertically on a subset of pixel columns * for the vertical displacement, which identifies the * src pixel(s) for each dest pixel. Sample the displacement * on a regular grid in the vertical direction. */ ptaa7 = ptaaCreate(nx); /* uniformly sampled across full height of image */ for (j = 0; j < nx; j++) { /* for each column */ pta = ptaaGetPta(ptaa6, j, L_CLONE); ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL); ptad = ptaCreate(ny); for (i = 0; i < ny; i++) { /* uniformly sampled in y */ y = i * sampling; applyQuadraticFit(c2, c1, c0, y, &val); ptaAddPt(ptad, y, val); } ptaaAddPta(ptaa7, ptad, L_INSERT); ptaDestroy(&pta); } if (debugflag) { tempname = genTempFilename("/tmp", "ptaa7.ptaa", 0); ptaaWrite(tempname, ptaa7, 0); FREE(tempname); } /* Save the result in a fpix at the specified subsampling */ fpix1 = fpixCreate(nx, ny); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { ptaaGetPt(ptaa7, j, i, NULL, &val); fpixSetPixel(fpix1, j, i, val); } } dew->sampvdispar = fpix1; /* Generate a full res fpix for vertical dewarping. We require that * the size of this fpix is at least as big as the input image. */ fpix2 = fpixScaleByInteger(fpix1, sampling); dew->fullvdispar = fpix2; if (debugflag) { pixt1 = fpixRenderContours(fpix2, -2., 2.0, 0.2); pixWriteTempfile("/tmp", "vert-contours.png", pixt1, IFF_PNG, NULL); pixDisplay(pixt1, 1000, 0); pixDestroy(&pixt1); } /* Generate full res and sampled fpix for horizontal dewarping. This * works to the extent that the line curvature is due to bending * out of the plane normal to the camera, and not wide-angle * "fishbowl" distortion. Also generate the sampled horizontal * disparity array. */ if (dew->applyhoriz) { fpix3 = fpixBuildHorizontalDisparity(fpix2, 0, &dew->extraw); dew->fullhdispar = fpix3; dew->samphdispar = fpixSampledDisparity(fpix3, dew->sampling); if (debugflag) { pixt1 = fpixRenderContours(fpix3, -2., 2.0, 0.2); pixWriteTempfile("/tmp", "horiz-contours.png", pixt1, IFF_PNG, NULL); pixDisplay(pixt1, 1000, 0); pixDestroy(&pixt1); } } dew->success = 1; ptaaDestroy(&ptaa1); ptaaDestroy(&ptaa2); ptaaDestroy(&ptaa3); ptaaDestroy(&ptaa4); ptaaDestroy(&ptaa5); ptaaDestroy(&ptaa6); ptaaDestroy(&ptaa7); return 0; }