Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/dewarp2.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /*====================================================================* | |
| 2 - Copyright (C) 2001 Leptonica. All rights reserved. | |
| 3 - | |
| 4 - Redistribution and use in source and binary forms, with or without | |
| 5 - modification, are permitted provided that the following conditions | |
| 6 - are met: | |
| 7 - 1. Redistributions of source code must retain the above copyright | |
| 8 - notice, this list of conditions and the following disclaimer. | |
| 9 - 2. Redistributions in binary form must reproduce the above | |
| 10 - copyright notice, this list of conditions and the following | |
| 11 - disclaimer in the documentation and/or other materials | |
| 12 - provided with the distribution. | |
| 13 - | |
| 14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY | |
| 18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 *====================================================================*/ | |
| 26 | |
| 27 /*! | |
| 28 * \file dewarp2.c | |
| 29 * <pre> | |
| 30 * | |
| 31 * Build the page disparity model | |
| 32 * | |
| 33 * Build basic page disparity model | |
| 34 * l_int32 dewarpBuildPageModel() | |
| 35 * l_int32 dewarpFindVertDisparity() | |
| 36 * l_int32 dewarpFindHorizDisparity() | |
| 37 * PTAA *dewarpGetTextlineCenters() | |
| 38 * static PTA *dewarpGetMeanVerticals() | |
| 39 * PTAA *dewarpRemoveShortLines() | |
| 40 * static l_int32 dewarpGetLineEndPoints() | |
| 41 * static l_int32 dewarpFilterLineEndPoints() | |
| 42 * static PTA *dewarpRemoveBadEndPoints() | |
| 43 * static l_int32 dewarpIsLineCoverageValid() | |
| 44 * static l_int32 dewarpLinearLSF() | |
| 45 * static l_int32 dewarpQuadraticLSF() | |
| 46 * | |
| 47 * Build disparity model for slope near binding | |
| 48 * l_int32 dewarpFindHorizSlopeDisparity() | |
| 49 * | |
| 50 * Build the line disparity model | |
| 51 * l_int32 dewarpBuildLineModel() | |
| 52 * | |
| 53 * Query model status | |
| 54 * l_int32 dewarpaModelStatus() | |
| 55 * | |
| 56 * Rendering helpers | |
| 57 * static l_int32 pixRenderMidYs() | |
| 58 * static l_int32 pixRenderHorizEndPoints | |
| 59 * </pre> | |
| 60 */ | |
| 61 | |
| 62 #ifdef HAVE_CONFIG_H | |
| 63 #include <config_auto.h> | |
| 64 #endif /* HAVE_CONFIG_H */ | |
| 65 | |
| 66 #include <math.h> | |
| 67 #include "allheaders.h" | |
| 68 | |
| 69 static PTA *dewarpGetMeanVerticals(PIX *pixs, l_int32 x, l_int32 y); | |
| 70 static l_int32 dewarpGetLineEndPoints(l_int32 h, PTAA *ptaa, PTA **pptal, | |
| 71 PTA **pptar); | |
| 72 static l_int32 dewarpFilterLineEndPoints(L_DEWARP *dew, PTA *ptal1, PTA *ptar1, | |
| 73 PTA **pptal2, PTA **pptar2); | |
| 74 static PTA *dewarpRemoveBadEndPoints(l_int32 w, PTA *ptas); | |
| 75 static l_int32 dewarpIsLineCoverageValid(PTAA *ptaa2, l_int32 h, | |
| 76 l_int32 *pntop, l_int32 *pnbot, | |
| 77 l_int32 *pytop, l_int32 *pybot); | |
| 78 static l_int32 dewarpLinearLSF(PTA *ptad, l_float32 *pa, l_float32 *pb, | |
| 79 l_float32 *pmederr); | |
| 80 static l_int32 dewarpQuadraticLSF(PTA *ptad, l_float32 *pa, l_float32 *pb, | |
| 81 l_float32 *pc, l_float32 *pmederr); | |
| 82 static l_int32 pixRenderMidYs(PIX *pixs, NUMA *namidys, l_int32 linew); | |
| 83 static l_int32 pixRenderHorizEndPoints(PIX *pixs, PTA *ptal, PTA *ptar, | |
| 84 l_uint32 color); | |
| 85 | |
| 86 | |
| 87 #ifndef NO_CONSOLE_IO | |
| 88 #define DEBUG_TEXTLINE_CENTERS 0 /* set this to 1 for debugging */ | |
| 89 #define DEBUG_SHORT_LINES 0 /* ditto */ | |
| 90 #else | |
| 91 #define DEBUG_TEXTLINE_CENTERS 0 /* always must be 0 */ | |
| 92 #define DEBUG_SHORT_LINES 0 /* ditto */ | |
| 93 #endif /* !NO_CONSOLE_IO */ | |
| 94 | |
| 95 /* Special parameter values for reducing horizontal disparity */ | |
| 96 static const l_float32 MinRatioLinesToHeight = 0.45f; | |
| 97 static const l_int32 MinLinesForHoriz1 = 10; /* initially */ | |
| 98 static const l_int32 MinLinesForHoriz2 = 3; /* after, in each half */ | |
| 99 static const l_float32 AllowedWidthFract = 0.05f; /* no bigger */ | |
| 100 | |
| 101 | |
| 102 /*----------------------------------------------------------------------* | |
| 103 * Build basic page disparity model * | |
| 104 *----------------------------------------------------------------------*/ | |
| 105 /*! | |
| 106 * \brief dewarpBuildPageModel() | |
| 107 * | |
| 108 * \param[in] dew | |
| 109 * \param[in] debugfile use NULL to skip writing this | |
| 110 * \return 0 if OK, 1 if unable to build the model or on error | |
| 111 * | |
| 112 * <pre> | |
| 113 * Notes: | |
| 114 * (1) This is the basic function that builds the horizontal and | |
| 115 * vertical disparity arrays, which allow determination of the | |
| 116 * src pixel in the input image corresponding to each | |
| 117 * dest pixel in the dewarped image. | |
| 118 * (2) Sets vsuccess = 1 if the vertical disparity array builds. | |
| 119 * Always attempts to build the horizontal disparity array, | |
| 120 * even if it will not be requested (useboth == 0). | |
| 121 * Sets hsuccess = 1 if horizontal disparity builds. | |
| 122 * (3) The method is as follows: | |
| 123 * (a) Estimate the points along the centers of all the | |
| 124 * long textlines. If there are too few lines, no | |
| 125 * disparity models are built. | |
| 126 * (b) From the vertical deviation of the lines, estimate | |
| 127 * the vertical disparity. | |
| 128 * (c) From the ends of the lines, estimate the horizontal | |
| 129 * disparity, assuming that the text is made of lines | |
| 130 * that are close to left and right justified. | |
| 131 * (d) One can also compute an additional contribution to the | |
| 132 * horizontal disparity, inferred from slopes of the top | |
| 133 * and bottom lines. We do not do this. | |
| 134 * (4) In more detail for the vertical disparity: | |
| 135 * (a) Fit a LS quadratic to center locations along each line. | |
| 136 * This smooths the curves. | |
| 137 * (b) Sample each curve at a regular interval, find the y-value | |
| 138 * of the mid-point on each curve, and subtract the sampled | |
| 139 * curve value from this value. This is the vertical | |
| 140 * disparity at sampled points along each curve. | |
| 141 * (c) Fit a LS quadratic to each set of vertically aligned | |
| 142 * disparity samples. This smooths the disparity values | |
| 143 * in the vertical direction. Then resample at the same | |
| 144 * regular interval. We now have a regular grid of smoothed | |
| 145 * vertical disparity valuels. | |
| 146 * (5) Once the sampled vertical disparity array is found, it can be | |
| 147 * interpolated to get a full resolution vertical disparity map. | |
| 148 * This can be applied directly to the src image pixels | |
| 149 * to dewarp the image in the vertical direction, making | |
| 150 * all textlines horizontal. Likewise, the horizontal | |
| 151 * disparity array is used to left- and right-align the | |
| 152 * longest textlines. | |
| 153 * </pre> | |
| 154 */ | |
| 155 l_ok | |
| 156 dewarpBuildPageModel(L_DEWARP *dew, | |
| 157 const char *debugfile) | |
| 158 { | |
| 159 l_int32 linecount, ntop, nbot, ytop, ybot, ret; | |
| 160 PIX *pixs, *pix1, *pix2, *pix3; | |
| 161 PTA *pta; | |
| 162 PTAA *ptaa1, *ptaa2; | |
| 163 | |
| 164 if (!dew) | |
| 165 return ERROR_INT("dew not defined", __func__, 1); | |
| 166 | |
| 167 dew->debug = (debugfile) ? 1 : 0; | |
| 168 dew->vsuccess = dew->hsuccess = 0; | |
| 169 pixs = dew->pixs; | |
| 170 if (debugfile) { | |
| 171 lept_rmdir("lept/dewmod"); /* erase previous images */ | |
| 172 lept_mkdir("lept/dewmod"); | |
| 173 pixDisplayWithTitle(pixs, 0, 0, "pixs", 1); | |
| 174 pixWriteDebug("/tmp/lept/dewmod/0010.png", pixs, IFF_PNG); | |
| 175 } | |
| 176 | |
| 177 /* Make initial estimate of centers of textlines */ | |
| 178 ptaa1 = dewarpGetTextlineCenters(pixs, debugfile || DEBUG_TEXTLINE_CENTERS); | |
| 179 if (!ptaa1) { | |
| 180 L_WARNING("textline centers not found; model not built\n", __func__); | |
| 181 return 1; | |
| 182 } | |
| 183 if (debugfile) { | |
| 184 pix1 = pixConvertTo32(pixs); | |
| 185 pta = generatePtaFilledCircle(1); | |
| 186 pix2 = pixGenerateFromPta(pta, 5, 5); | |
| 187 pix3 = pixDisplayPtaaPattern(NULL, pix1, ptaa1, pix2, 2, 2); | |
| 188 pixWriteDebug("/tmp/lept/dewmod/0020.png", pix3, IFF_PNG); | |
| 189 pixDestroy(&pix1); | |
| 190 pixDestroy(&pix2); | |
| 191 pixDestroy(&pix3); | |
| 192 ptaDestroy(&pta); | |
| 193 } | |
| 194 | |
| 195 /* Remove all lines that are not at least 0.8 times the length | |
| 196 * of the longest line. */ | |
| 197 ptaa2 = dewarpRemoveShortLines(pixs, ptaa1, 0.8f, | |
| 198 debugfile || DEBUG_SHORT_LINES); | |
| 199 if (debugfile) { | |
| 200 pix1 = pixConvertTo32(pixs); | |
| 201 pta = generatePtaFilledCircle(1); | |
| 202 pix2 = pixGenerateFromPta(pta, 5, 5); | |
| 203 pix3 = pixDisplayPtaaPattern(NULL, pix1, ptaa2, pix2, 2, 2); | |
| 204 pixWriteDebug("/tmp/lept/dewmod/0030.png", pix3, IFF_PNG); | |
| 205 pixDestroy(&pix1); | |
| 206 pixDestroy(&pix2); | |
| 207 pixDestroy(&pix3); | |
| 208 ptaDestroy(&pta); | |
| 209 } | |
| 210 ptaaDestroy(&ptaa1); | |
| 211 | |
| 212 /* Verify that there are sufficient "long" lines */ | |
| 213 linecount = ptaaGetCount(ptaa2); | |
| 214 if (linecount < dew->minlines) { | |
| 215 ptaaDestroy(&ptaa2); | |
| 216 L_WARNING("linecount %d < min req'd number of lines (%d) for model\n", | |
| 217 __func__, linecount, dew->minlines); | |
| 218 return 1; | |
| 219 } | |
| 220 | |
| 221 /* Verify that the lines have a reasonable coverage of the | |
| 222 * vertical extent of the page. */ | |
| 223 if (dewarpIsLineCoverageValid(ptaa2, pixGetHeight(pixs), | |
| 224 &ntop, &nbot, &ytop, &ybot) == FALSE) { | |
| 225 ptaaDestroy(&ptaa2); | |
| 226 L_WARNING("invalid line coverage: ntop = %d, nbot = %d;" | |
| 227 " spanning [%d ... %d] in height %d\n", __func__, | |
| 228 ntop, nbot, ytop, ybot, pixGetHeight(pixs)); | |
| 229 return 1; | |
| 230 } | |
| 231 | |
| 232 /* Get the sampled vertical disparity from the textline centers. | |
| 233 * The disparity array will push pixels vertically so that each | |
| 234 * textline is flat and centered at the y-position of the mid-point. */ | |
| 235 if (dewarpFindVertDisparity(dew, ptaa2, 0) != 0) { | |
| 236 L_WARNING("vertical disparity not built\n", __func__); | |
| 237 ptaaDestroy(&ptaa2); | |
| 238 return 1; | |
| 239 } | |
| 240 | |
| 241 /* Get the sampled horizontal disparity from the left and right | |
| 242 * edges of the text. The disparity array will expand the image | |
| 243 * linearly outward to align the text edges vertically. | |
| 244 * Do this even if useboth == 0; we still calculate it even | |
| 245 * if we don't plan to use it. */ | |
| 246 if ((ret = dewarpFindHorizDisparity(dew, ptaa2)) == 0) | |
| 247 L_INFO("hsuccess = 1\n", __func__); | |
| 248 | |
| 249 /* Debug output */ | |
| 250 if (debugfile) { | |
| 251 dewarpPopulateFullRes(dew, NULL, 0, 0); | |
| 252 pix1 = fpixRenderContours(dew->fullvdispar, 3.0f, 0.15f); | |
| 253 pixWriteDebug("/tmp/lept/dewmod/0060.png", pix1, IFF_PNG); | |
| 254 pixDisplay(pix1, 1000, 0); | |
| 255 pixDestroy(&pix1); | |
| 256 if (ret == 0) { | |
| 257 pix1 = fpixRenderContours(dew->fullhdispar, 3.0f, 0.15f); | |
| 258 pixWriteDebug("/tmp/lept/dewmod/0070.png", pix1, IFF_PNG); | |
| 259 pixDisplay(pix1, 1000, 0); | |
| 260 pixDestroy(&pix1); | |
| 261 } | |
| 262 convertFilesToPdf("/tmp/lept/dewmod", NULL, 135, 1.0, 0, 0, | |
| 263 "Dewarp Build Model", debugfile); | |
| 264 lept_stderr("pdf file: %s\n", debugfile); | |
| 265 } | |
| 266 | |
| 267 ptaaDestroy(&ptaa2); | |
| 268 return 0; | |
| 269 } | |
| 270 | |
| 271 | |
| 272 /*! | |
| 273 * \brief dewarpFindVertDisparity() | |
| 274 * | |
| 275 * \param[in] dew | |
| 276 * \param[in] ptaa unsmoothed lines, not vertically ordered | |
| 277 * \param[in] rotflag 0 if using dew->pixs; 1 if rotated by 90 degrees cw | |
| 278 * \return 0 if OK, 1 on error | |
| 279 * | |
| 280 * <pre> | |
| 281 * Notes: | |
| 282 * (1) This starts with points along the centers of textlines. | |
| 283 * It does quadratic fitting (and smoothing), first along the | |
| 284 * lines and then in the vertical direction, to generate | |
| 285 * the sampled vertical disparity map. This can then be | |
| 286 * interpolated to full resolution and used to remove | |
| 287 * the vertical line warping. | |
| 288 * (2) Use %rotflag == 1 if you are dewarping vertical lines, as | |
| 289 * is done in dewarpBuildLineModel(). The usual case is for | |
| 290 * %rotflag == 0. | |
| 291 * (3) Note that this builds a vertical disparity model (VDM), but | |
| 292 * does not check it against constraints for validity. | |
| 293 * Constraint checking is done after building the models, | |
| 294 * and before inserting reference models. | |
| 295 * (4) This sets the vsuccess flag to 1 on success. | |
| 296 * (5) Pix debug output goes to /tmp/dewvert/ for collection into | |
| 297 * a pdf. Non-pix debug output goes to /tmp. | |
| 298 * </pre> | |
| 299 */ | |
| 300 l_ok | |
| 301 dewarpFindVertDisparity(L_DEWARP *dew, | |
| 302 PTAA *ptaa, | |
| 303 l_int32 rotflag) | |
| 304 { | |
| 305 l_int32 i, j, nlines, npts, nx, ny, sampling; | |
| 306 l_float32 c0, c1, c2, x, y, midy, val, medval, meddev, minval, maxval; | |
| 307 l_float32 *famidys; | |
| 308 NUMA *nax, *nafit, *nacurve0, *nacurve1, *nacurves; | |
| 309 NUMA *namidy, *namidys, *namidysi; | |
| 310 PIX *pix1, *pix2, *pixcirc, *pixdb; | |
| 311 PTA *pta, *ptad, *ptacirc; | |
| 312 PTAA *ptaa0, *ptaa1, *ptaa2, *ptaa3, *ptaa4, *ptaa5, *ptaat; | |
| 313 FPIX *fpix; | |
| 314 | |
| 315 if (!dew) | |
| 316 return ERROR_INT("dew not defined", __func__, 1); | |
| 317 dew->vsuccess = 0; | |
| 318 if (!ptaa) | |
| 319 return ERROR_INT("ptaa not defined", __func__, 1); | |
| 320 | |
| 321 if (dew->debug) L_INFO("finding vertical disparity\n", __func__); | |
| 322 | |
| 323 /* Do quadratic fit to smooth each line. A single quadratic | |
| 324 * over the entire width of the line appears to be sufficient. | |
| 325 * Quartics tend to overfit to noise. Each line is thus | |
| 326 * represented by three coefficients: y(x) = c2 * x^2 + c1 * x + c0. | |
| 327 * Using the coefficients, sample each fitted curve uniformly | |
| 328 * across the full width of the image. The result is in ptaa0. */ | |
| 329 sampling = dew->sampling; | |
| 330 nx = (rotflag) ? dew->ny : dew->nx; | |
| 331 ny = (rotflag) ? dew->nx : dew->ny; | |
| 332 nlines = ptaaGetCount(ptaa); | |
| 333 dew->nlines = nlines; | |
| 334 ptaa0 = ptaaCreate(nlines); | |
| 335 nacurve0 = numaCreate(nlines); /* stores curvature coeff c2 */ | |
| 336 pixdb = (rotflag) ? pixRotateOrth(dew->pixs, 1) : pixClone(dew->pixs); | |
| 337 for (i = 0; i < nlines; i++) { /* for each line */ | |
| 338 pta = ptaaGetPta(ptaa, i, L_CLONE); | |
| 339 ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL); | |
| 340 numaAddNumber(nacurve0, c2); | |
| 341 ptad = ptaCreate(nx); | |
| 342 for (j = 0; j < nx; j++) { /* uniformly sampled in x */ | |
| 343 x = j * sampling; | |
| 344 applyQuadraticFit(c2, c1, c0, x, &y); | |
| 345 ptaAddPt(ptad, x, y); | |
| 346 } | |
| 347 ptaaAddPta(ptaa0, ptad, L_INSERT); | |
| 348 ptaDestroy(&pta); | |
| 349 } | |
| 350 if (dew->debug) { | |
| 351 lept_mkdir("lept/dewarp"); | |
| 352 lept_mkdir("lept/dewdebug"); | |
| 353 lept_mkdir("lept/dewmod"); | |
| 354 ptaat = ptaaCreate(nlines); | |
| 355 for (i = 0; i < nlines; i++) { | |
| 356 pta = ptaaGetPta(ptaa, i, L_CLONE); | |
| 357 ptaGetArrays(pta, &nax, NULL); | |
| 358 ptaGetQuadraticLSF(pta, NULL, NULL, NULL, &nafit); | |
| 359 ptad = ptaCreateFromNuma(nax, nafit); | |
| 360 ptaaAddPta(ptaat, ptad, L_INSERT); | |
| 361 ptaDestroy(&pta); | |
| 362 numaDestroy(&nax); | |
| 363 numaDestroy(&nafit); | |
| 364 } | |
| 365 pix1 = pixConvertTo32(pixdb); | |
| 366 pta = generatePtaFilledCircle(1); | |
| 367 pixcirc = pixGenerateFromPta(pta, 5, 5); | |
| 368 pix2 = pixDisplayPtaaPattern(NULL, pix1, ptaat, pixcirc, 2, 2); | |
| 369 pixWriteDebug("/tmp/lept/dewmod/0041.png", pix2, IFF_PNG); | |
| 370 pixDestroy(&pix1); | |
| 371 pixDestroy(&pix2); | |
| 372 ptaDestroy(&pta); | |
| 373 pixDestroy(&pixcirc); | |
| 374 ptaaDestroy(&ptaat); | |
| 375 } | |
| 376 | |
| 377 /* Remove lines with outlier curvatures. | |
| 378 * Note that this is just looking for internal consistency in | |
| 379 * the line curvatures. It is not rejecting lines based on | |
| 380 * the magnitude of the curvature. That is done when constraints | |
| 381 * are applied for valid models. */ | |
| 382 numaGetMedianDevFromMedian(nacurve0, &medval, &meddev); | |
| 383 L_INFO("\nPage %d\n", __func__, dew->pageno); | |
| 384 L_INFO("Pass 1: Curvature: medval = %f, meddev = %f\n", | |
| 385 __func__, medval, meddev); | |
| 386 ptaa1 = ptaaCreate(nlines); | |
| 387 nacurve1 = numaCreate(nlines); | |
| 388 for (i = 0; i < nlines; i++) { /* for each line */ | |
| 389 numaGetFValue(nacurve0, i, &val); | |
| 390 if (L_ABS(val - medval) > 7.0 * meddev) /* TODO: reduce to ~ 3.0 */ | |
| 391 continue; | |
| 392 pta = ptaaGetPta(ptaa0, i, L_CLONE); | |
| 393 ptaaAddPta(ptaa1, pta, L_INSERT); | |
| 394 numaAddNumber(nacurve1, val); | |
| 395 } | |
| 396 nlines = ptaaGetCount(ptaa1); | |
| 397 numaDestroy(&nacurve0); | |
| 398 | |
| 399 /* Save the min and max curvature (in micro-units) */ | |
| 400 numaGetMin(nacurve1, &minval, NULL); | |
| 401 numaGetMax(nacurve1, &maxval, NULL); | |
| 402 dew->mincurv = lept_roundftoi(1000000. * minval); | |
| 403 dew->maxcurv = lept_roundftoi(1000000. * maxval); | |
| 404 L_INFO("Pass 2: Min/max curvature = (%d, %d)\n", __func__, | |
| 405 dew->mincurv, dew->maxcurv); | |
| 406 | |
| 407 /* Find and save the y values at the mid-points in each curve. | |
| 408 * If the slope is zero anywhere, it will typically be here. */ | |
| 409 namidy = numaCreate(nlines); | |
| 410 for (i = 0; i < nlines; i++) { | |
| 411 pta = ptaaGetPta(ptaa1, i, L_CLONE); | |
| 412 npts = ptaGetCount(pta); | |
| 413 ptaGetPt(pta, npts / 2, NULL, &midy); | |
| 414 numaAddNumber(namidy, midy); | |
| 415 ptaDestroy(&pta); | |
| 416 } | |
| 417 | |
| 418 /* Sort the lines in ptaa1c by their vertical position, going down */ | |
| 419 namidysi = numaGetSortIndex(namidy, L_SORT_INCREASING); | |
| 420 namidys = numaSortByIndex(namidy, namidysi); | |
| 421 nacurves = numaSortByIndex(nacurve1, namidysi); | |
| 422 numaDestroy(&dew->namidys); /* in case previously made */ | |
| 423 numaDestroy(&dew->nacurves); | |
| 424 dew->namidys = namidys; | |
| 425 dew->nacurves = nacurves; | |
| 426 ptaa2 = ptaaSortByIndex(ptaa1, namidysi); | |
| 427 numaDestroy(&namidy); | |
| 428 numaDestroy(&nacurve1); | |
| 429 numaDestroy(&namidysi); | |
| 430 if (dew->debug) { | |
| 431 numaWriteDebug("/tmp/lept/dewdebug/midys.na", namidys); | |
| 432 numaWriteDebug("/tmp/lept/dewdebug/curves.na", nacurves); | |
| 433 pix1 = pixConvertTo32(pixdb); | |
| 434 ptacirc = generatePtaFilledCircle(5); | |
| 435 pixcirc = pixGenerateFromPta(ptacirc, 11, 11); | |
| 436 srand(3); | |
| 437 pixDisplayPtaaPattern(pix1, pix1, ptaa2, pixcirc, 5, 5); | |
| 438 srand(3); /* use the same colors for text and reference lines */ | |
| 439 pixRenderMidYs(pix1, namidys, 2); | |
| 440 pix2 = (rotflag) ? pixRotateOrth(pix1, 3) : pixClone(pix1); | |
| 441 pixWriteDebug("/tmp/lept/dewmod/0042.png", pix2, IFF_PNG); | |
| 442 pixDisplay(pix2, 0, 0); | |
| 443 ptaDestroy(&ptacirc); | |
| 444 pixDestroy(&pixcirc); | |
| 445 pixDestroy(&pix1); | |
| 446 pixDestroy(&pix2); | |
| 447 } | |
| 448 pixDestroy(&pixdb); | |
| 449 | |
| 450 /* Convert the sampled points in ptaa2 to a sampled disparity with | |
| 451 * with respect to the y value at the mid point in the curve. | |
| 452 * The disparity is the distance the point needs to move; | |
| 453 * plus is downward. */ | |
| 454 ptaa3 = ptaaCreate(nlines); | |
| 455 for (i = 0; i < nlines; i++) { | |
| 456 pta = ptaaGetPta(ptaa2, i, L_CLONE); | |
| 457 numaGetFValue(namidys, i, &midy); | |
| 458 ptad = ptaCreate(nx); | |
| 459 for (j = 0; j < nx; j++) { | |
| 460 ptaGetPt(pta, j, &x, &y); | |
| 461 ptaAddPt(ptad, x, midy - y); | |
| 462 } | |
| 463 ptaaAddPta(ptaa3, ptad, L_INSERT); | |
| 464 ptaDestroy(&pta); | |
| 465 } | |
| 466 if (dew->debug) { | |
| 467 ptaaWriteDebug("/tmp/lept/dewdebug/ptaa3.ptaa", ptaa3, 0); | |
| 468 } | |
| 469 | |
| 470 /* Generate ptaa4 by taking vertical 'columns' from ptaa3. | |
| 471 * We want to fit the vertical disparity on the column to the | |
| 472 * vertical position of the line, which we call 'y' here and | |
| 473 * obtain from namidys. So each pta in ptaa4 is the set of | |
| 474 * vertical disparities down a column of points. The columns | |
| 475 * in ptaa4 are equally spaced in x. */ | |
| 476 ptaa4 = ptaaCreate(nx); | |
| 477 famidys = numaGetFArray(namidys, L_NOCOPY); | |
| 478 for (j = 0; j < nx; j++) { | |
| 479 pta = ptaCreate(nlines); | |
| 480 for (i = 0; i < nlines; i++) { | |
| 481 y = famidys[i]; | |
| 482 ptaaGetPt(ptaa3, i, j, NULL, &val); /* disparity value */ | |
| 483 ptaAddPt(pta, y, val); | |
| 484 } | |
| 485 ptaaAddPta(ptaa4, pta, L_INSERT); | |
| 486 } | |
| 487 if (dew->debug) { | |
| 488 ptaaWriteDebug("/tmp/lept/dewdebug/ptaa4.ptaa", ptaa4, 0); | |
| 489 } | |
| 490 | |
| 491 /* Do quadratic fit vertically on each of the pixel columns | |
| 492 * in ptaa4, for the vertical displacement (which identifies the | |
| 493 * src pixel(s) for each dest pixel) as a function of y (the | |
| 494 * y value of the mid-points for each line). Then generate | |
| 495 * ptaa5 by sampling the fitted vertical displacement on a | |
| 496 * regular grid in the vertical direction. Each pta in ptaa5 | |
| 497 * gives the vertical displacement for regularly sampled y values | |
| 498 * at a fixed x. */ | |
| 499 ptaa5 = ptaaCreate(nx); /* uniformly sampled across full height of image */ | |
| 500 for (j = 0; j < nx; j++) { /* for each column */ | |
| 501 pta = ptaaGetPta(ptaa4, j, L_CLONE); | |
| 502 ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL); | |
| 503 ptad = ptaCreate(ny); | |
| 504 for (i = 0; i < ny; i++) { /* uniformly sampled in y */ | |
| 505 y = i * sampling; | |
| 506 applyQuadraticFit(c2, c1, c0, y, &val); | |
| 507 ptaAddPt(ptad, y, val); | |
| 508 } | |
| 509 ptaaAddPta(ptaa5, ptad, L_INSERT); | |
| 510 ptaDestroy(&pta); | |
| 511 } | |
| 512 if (dew->debug) { | |
| 513 ptaaWriteDebug("/tmp/lept/dewdebug/ptaa5.ptaa", ptaa5, 0); | |
| 514 convertFilesToPdf("/tmp/lept/dewmod", "004", 135, 1.0, 0, 0, | |
| 515 "Dewarp Vert Disparity", | |
| 516 "/tmp/lept/dewarp/vert_disparity.pdf"); | |
| 517 lept_stderr("pdf file: /tmp/lept/dewarp/vert_disparity.pdf\n"); | |
| 518 } | |
| 519 | |
| 520 /* Save the result in a fpix at the specified subsampling */ | |
| 521 fpix = fpixCreate(nx, ny); | |
| 522 for (i = 0; i < ny; i++) { | |
| 523 for (j = 0; j < nx; j++) { | |
| 524 ptaaGetPt(ptaa5, j, i, NULL, &val); | |
| 525 fpixSetPixel(fpix, j, i, val); | |
| 526 } | |
| 527 } | |
| 528 dew->sampvdispar = fpix; | |
| 529 dew->vsuccess = 1; | |
| 530 | |
| 531 ptaaDestroy(&ptaa0); | |
| 532 ptaaDestroy(&ptaa1); | |
| 533 ptaaDestroy(&ptaa2); | |
| 534 ptaaDestroy(&ptaa3); | |
| 535 ptaaDestroy(&ptaa4); | |
| 536 ptaaDestroy(&ptaa5); | |
| 537 return 0; | |
| 538 } | |
| 539 | |
| 540 | |
| 541 /*! | |
| 542 * \brief dewarpFindHorizDisparity() | |
| 543 * | |
| 544 * \param[in] dew | |
| 545 * \param[in] ptaa unsmoothed lines, not vertically ordered | |
| 546 * \return 0 if OK, 1 if horizontal disparity array is not built, or on error | |
| 547 * | |
| 548 * <pre> | |
| 549 * Notes: | |
| 550 * (1) This builds a horizontal disparity model (HDM), but | |
| 551 * does not check it against constraints for validity. | |
| 552 * Constraint checking is done at rendering time. | |
| 553 * (2) Horizontal disparity is not required for a successful model; | |
| 554 * only the vertical disparity is required. This will not be | |
| 555 * called if the function to build the vertical disparity fails. | |
| 556 * (3) This sets the hsuccess flag to 1 on success. | |
| 557 * (4) Internally in ptal1, ptar1, ptal2, ptar2: x and y are reversed, | |
| 558 * so the 'y' value is horizontal distance across the image width. | |
| 559 * (5) Debug output goes to /tmp/lept/dewmod/ for collection into a pdf. | |
| 560 * </pre> | |
| 561 */ | |
| 562 l_ok | |
| 563 dewarpFindHorizDisparity(L_DEWARP *dew, | |
| 564 PTAA *ptaa) | |
| 565 { | |
| 566 l_int32 i, j, h, nx, ny, sampling, ret, linear_edgefit; | |
| 567 l_float32 c0, c1, cl0, cl1, cl2, cr0, cr1, cr2; | |
| 568 l_float32 x, y, refl, refr; | |
| 569 l_float32 val, mederr; | |
| 570 NUMA *nald, *nard; | |
| 571 PIX *pix1; | |
| 572 PTA *ptal1, *ptar1; /* left/right end points of lines; initial */ | |
| 573 PTA *ptal2, *ptar2; /* left/right end points; after filtering */ | |
| 574 PTA *ptal3, *ptar3; /* left and right block, fitted, uniform spacing */ | |
| 575 PTA *pta, *ptat, *pta1, *pta2; | |
| 576 PTAA *ptaah; | |
| 577 FPIX *fpix; | |
| 578 | |
| 579 if (!dew) | |
| 580 return ERROR_INT("dew not defined", __func__, 1); | |
| 581 dew->hsuccess = 0; | |
| 582 if (!ptaa) | |
| 583 return ERROR_INT("ptaa not defined", __func__, 1); | |
| 584 | |
| 585 if (dew->debug) L_INFO("finding horizontal disparity\n", __func__); | |
| 586 | |
| 587 /* Get the endpoints of the lines, and sort from top to bottom */ | |
| 588 h = pixGetHeight(dew->pixs); | |
| 589 ret = dewarpGetLineEndPoints(h, ptaa, &ptal1, &ptar1); | |
| 590 if (ret) { | |
| 591 L_INFO("Horiz disparity not built\n", __func__); | |
| 592 return 1; | |
| 593 } | |
| 594 if (dew->debug) { | |
| 595 lept_mkdir("lept/dewdebug"); | |
| 596 lept_mkdir("lept/dewarp"); | |
| 597 ptaWriteDebug("/tmp/lept/dewdebug/endpts_left1.pta", ptal1, 1); | |
| 598 ptaWriteDebug("/tmp/lept/dewdebug/endpts_right1.pta", ptar1, 1); | |
| 599 } | |
| 600 | |
| 601 /* Filter the points by x-location to prevent 2-column images | |
| 602 * from getting confused about left and right endpoints. We | |
| 603 * require valid left points to not be farther than | |
| 604 * 0.20 * (remaining distance to the right edge of the image) | |
| 605 * to the right of the leftmost endpoint, and similarly for | |
| 606 * the right endpoints. (Note: x and y are reversed in the pta.) | |
| 607 * Also require end points to be near the medians in the | |
| 608 * upper and lower halves. */ | |
| 609 ret = dewarpFilterLineEndPoints(dew, ptal1, ptar1, &ptal2, &ptar2); | |
| 610 ptaDestroy(&ptal1); | |
| 611 ptaDestroy(&ptar1); | |
| 612 if (ret) { | |
| 613 L_INFO("Not enough filtered end points\n", __func__); | |
| 614 return 1; | |
| 615 } | |
| 616 | |
| 617 /* Do either a linear or a quadratic fit to the left and right | |
| 618 * endpoints of the longest lines. It is not necessary to use | |
| 619 * the noisy LSF fit function, because we've removed outlier | |
| 620 * end points by selecting the long lines. | |
| 621 * For the linear fit, each line is represented by 2 coefficients: | |
| 622 * x(y) = c1 * y + c0. | |
| 623 * For the quadratic fit, each line is represented by 3 coefficients: | |
| 624 * x(y) = c2 * y^2 + c1 * y + c0. | |
| 625 * Then using the coefficients, sample each fitted curve uniformly | |
| 626 * along the full height of the image. */ | |
| 627 sampling = dew->sampling; | |
| 628 nx = dew->nx; | |
| 629 ny = dew->ny; | |
| 630 linear_edgefit = (dew->dewa->max_edgecurv == 0) ? TRUE : FALSE; | |
| 631 | |
| 632 if (linear_edgefit) { | |
| 633 /* Fit the left side, using linear LSF on the set of long lines. */ | |
| 634 dewarpLinearLSF(ptal2, &cl1, &cl0, &mederr); | |
| 635 dew->leftslope = lept_roundftoi(1000. * cl1); /* milli-units */ | |
| 636 dew->leftcurv = 0; /* micro-units */ | |
| 637 L_INFO("Left linear LSF median error = %5.2f\n", __func__, mederr); | |
| 638 L_INFO("Left edge slope = %d\n", __func__, dew->leftslope); | |
| 639 ptal3 = ptaCreate(ny); | |
| 640 for (i = 0; i < ny; i++) { /* uniformly sample in y */ | |
| 641 y = i * sampling; | |
| 642 applyLinearFit(cl1, cl0, y, &x); | |
| 643 ptaAddPt(ptal3, x, y); | |
| 644 } | |
| 645 | |
| 646 /* Do a linear LSF on the right side. */ | |
| 647 dewarpLinearLSF(ptar2, &cr1, &cr0, &mederr); | |
| 648 dew->rightslope = lept_roundftoi(1000.0 * cr1); /* milli-units */ | |
| 649 dew->rightcurv = 0; /* micro-units */ | |
| 650 L_INFO("Right linear LSF median error = %5.2f\n", __func__, mederr); | |
| 651 L_INFO("Right edge slope = %d\n", __func__, dew->rightslope); | |
| 652 ptar3 = ptaCreate(ny); | |
| 653 for (i = 0; i < ny; i++) { /* uniformly sample in y */ | |
| 654 y = i * sampling; | |
| 655 applyLinearFit(cr1, cr0, y, &x); | |
| 656 ptaAddPt(ptar3, x, y); | |
| 657 } | |
| 658 } else { /* quadratic edge fit */ | |
| 659 /* Fit the left side, using quadratic LSF on the long lines. */ | |
| 660 dewarpQuadraticLSF(ptal2, &cl2, &cl1, &cl0, &mederr); | |
| 661 dew->leftslope = lept_roundftoi(1000. * cl1); /* milli-units */ | |
| 662 dew->leftcurv = lept_roundftoi(1000000. * cl2); /* micro-units */ | |
| 663 L_INFO("Left quad LSF median error = %5.2f\n", __func__, mederr); | |
| 664 L_INFO("Left edge slope = %d\n", __func__, dew->leftslope); | |
| 665 L_INFO("Left edge curvature = %d\n", __func__, dew->leftcurv); | |
| 666 ptal3 = ptaCreate(ny); | |
| 667 for (i = 0; i < ny; i++) { /* uniformly sample in y */ | |
| 668 y = i * sampling; | |
| 669 applyQuadraticFit(cl2, cl1, cl0, y, &x); | |
| 670 ptaAddPt(ptal3, x, y); | |
| 671 } | |
| 672 | |
| 673 /* Do a quadratic LSF on the right side. */ | |
| 674 dewarpQuadraticLSF(ptar2, &cr2, &cr1, &cr0, &mederr); | |
| 675 dew->rightslope = lept_roundftoi(1000.0 * cr1); /* milli-units */ | |
| 676 dew->rightcurv = lept_roundftoi(1000000. * cr2); /* micro-units */ | |
| 677 L_INFO("Right quad LSF median error = %5.2f\n", __func__, mederr); | |
| 678 L_INFO("Right edge slope = %d\n", __func__, dew->rightslope); | |
| 679 L_INFO("Right edge curvature = %d\n", __func__, dew->rightcurv); | |
| 680 ptar3 = ptaCreate(ny); | |
| 681 for (i = 0; i < ny; i++) { /* uniformly sample in y */ | |
| 682 y = i * sampling; | |
| 683 applyQuadraticFit(cr2, cr1, cr0, y, &x); | |
| 684 ptaAddPt(ptar3, x, y); | |
| 685 } | |
| 686 } | |
| 687 | |
| 688 if (dew->debug) { | |
| 689 PTA *ptalft, *ptarft; | |
| 690 h = pixGetHeight(dew->pixs); | |
| 691 pta1 = ptaCreate(h); | |
| 692 pta2 = ptaCreate(h); | |
| 693 if (linear_edgefit) { | |
| 694 for (i = 0; i < h; i++) { | |
| 695 applyLinearFit(cl1, cl0, i, &x); | |
| 696 ptaAddPt(pta1, x, i); | |
| 697 applyLinearFit(cr1, cr0, i, &x); | |
| 698 ptaAddPt(pta2, x, i); | |
| 699 } | |
| 700 } else { /* quadratic edge fit */ | |
| 701 for (i = 0; i < h; i++) { | |
| 702 applyQuadraticFit(cl2, cl1, cl0, i, &x); | |
| 703 ptaAddPt(pta1, x, i); | |
| 704 applyQuadraticFit(cr2, cr1, cr0, i, &x); | |
| 705 ptaAddPt(pta2, x, i); | |
| 706 } | |
| 707 } | |
| 708 pix1 = pixDisplayPta(NULL, dew->pixs, pta1); | |
| 709 pixDisplayPta(pix1, pix1, pta2); | |
| 710 pixRenderHorizEndPoints(pix1, ptal2, ptar2, 0xff000000); | |
| 711 pixDisplay(pix1, 600, 800); | |
| 712 pixWriteDebug("/tmp/lept/dewmod/0051.png", pix1, IFF_PNG); | |
| 713 pixDestroy(&pix1); | |
| 714 | |
| 715 pix1 = pixDisplayPta(NULL, dew->pixs, pta1); | |
| 716 pixDisplayPta(pix1, pix1, pta2); | |
| 717 ptalft = ptaTranspose(ptal3); | |
| 718 ptarft = ptaTranspose(ptar3); | |
| 719 pixRenderHorizEndPoints(pix1, ptalft, ptarft, 0x0000ff00); | |
| 720 pixDisplay(pix1, 800, 800); | |
| 721 pixWriteDebug("/tmp/lept/dewmod/0052.png", pix1, IFF_PNG); | |
| 722 convertFilesToPdf("/tmp/lept/dewmod", "005", 135, 1.0, 0, 0, | |
| 723 "Dewarp Horiz Disparity", | |
| 724 "/tmp/lept/dewarp/horiz_disparity.pdf"); | |
| 725 lept_stderr("pdf file: /tmp/lept/dewarp/horiz_disparity.pdf\n"); | |
| 726 pixDestroy(&pix1); | |
| 727 ptaDestroy(&pta1); | |
| 728 ptaDestroy(&pta2); | |
| 729 ptaDestroy(&ptalft); | |
| 730 ptaDestroy(&ptarft); | |
| 731 } | |
| 732 | |
| 733 /* Find the x value at the midpoints (in y) of the two vertical lines, | |
| 734 * ptal3 and ptar3. These are the reference values for each of the | |
| 735 * lines. Then use the difference between the these midpoint | |
| 736 * values and the actual x coordinates of the lines to represent | |
| 737 * the horizontal disparity (nald, nard) on the vertical lines | |
| 738 * for the sampled y values. */ | |
| 739 ptaGetPt(ptal3, ny / 2, &refl, NULL); | |
| 740 ptaGetPt(ptar3, ny / 2, &refr, NULL); | |
| 741 nald = numaCreate(ny); | |
| 742 nard = numaCreate(ny); | |
| 743 for (i = 0; i < ny; i++) { | |
| 744 ptaGetPt(ptal3, i, &x, NULL); | |
| 745 numaAddNumber(nald, refl - x); | |
| 746 ptaGetPt(ptar3, i, &x, NULL); | |
| 747 numaAddNumber(nard, refr - x); | |
| 748 } | |
| 749 | |
| 750 /* Now for each pair of sampled values of the two lines (at the | |
| 751 * same value of y), do a linear interpolation to generate | |
| 752 * the horizontal disparity on all sampled points between them. */ | |
| 753 ptaah = ptaaCreate(ny); | |
| 754 for (i = 0; i < ny; i++) { | |
| 755 pta = ptaCreate(2); | |
| 756 numaGetFValue(nald, i, &val); | |
| 757 ptaAddPt(pta, refl, val); | |
| 758 numaGetFValue(nard, i, &val); | |
| 759 ptaAddPt(pta, refr, val); | |
| 760 ptaGetLinearLSF(pta, &c1, &c0, NULL); /* horiz disparity along line */ | |
| 761 ptat = ptaCreate(nx); | |
| 762 for (j = 0; j < nx; j++) { | |
| 763 x = j * sampling; | |
| 764 applyLinearFit(c1, c0, x, &val); | |
| 765 ptaAddPt(ptat, x, val); | |
| 766 } | |
| 767 ptaaAddPta(ptaah, ptat, L_INSERT); | |
| 768 ptaDestroy(&pta); | |
| 769 } | |
| 770 numaDestroy(&nald); | |
| 771 numaDestroy(&nard); | |
| 772 | |
| 773 /* Save the result in a fpix at the specified subsampling */ | |
| 774 fpix = fpixCreate(nx, ny); | |
| 775 for (i = 0; i < ny; i++) { | |
| 776 for (j = 0; j < nx; j++) { | |
| 777 ptaaGetPt(ptaah, i, j, NULL, &val); | |
| 778 fpixSetPixel(fpix, j, i, val); | |
| 779 } | |
| 780 } | |
| 781 dew->samphdispar = fpix; | |
| 782 dew->hsuccess = 1; | |
| 783 ptaDestroy(&ptal2); | |
| 784 ptaDestroy(&ptar2); | |
| 785 ptaDestroy(&ptal3); | |
| 786 ptaDestroy(&ptar3); | |
| 787 ptaaDestroy(&ptaah); | |
| 788 return 0; | |
| 789 } | |
| 790 | |
| 791 | |
| 792 /*! | |
| 793 * \brief dewarpGetTextlineCenters() | |
| 794 * | |
| 795 * \param[in] pixs 1 bpp | |
| 796 * \param[in] debugflag 1 for debug output | |
| 797 * \return ptaa of center values of textlines | |
| 798 * | |
| 799 * <pre> | |
| 800 * Notes: | |
| 801 * (1) This in general does not have a point for each value | |
| 802 * of x, because there will be gaps between words. | |
| 803 * It doesn't matter because we will fit a quadratic to the | |
| 804 * points that we do have. | |
| 805 * </pre> | |
| 806 */ | |
| 807 PTAA * | |
| 808 dewarpGetTextlineCenters(PIX *pixs, | |
| 809 l_int32 debugflag) | |
| 810 { | |
| 811 char buf[64]; | |
| 812 l_int32 i, w, h, bx, by, nsegs, csize1, csize2; | |
| 813 BOXA *boxa; | |
| 814 PIX *pix1, *pix2; | |
| 815 PIXA *pixa1, *pixa2; | |
| 816 PTA *pta; | |
| 817 PTAA *ptaa; | |
| 818 | |
| 819 if (!pixs || pixGetDepth(pixs) != 1) | |
| 820 return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 821 pixGetDimensions(pixs, &w, &h, NULL); | |
| 822 | |
| 823 if (debugflag) L_INFO("finding text line centers\n", __func__); | |
| 824 | |
| 825 /* Filter to solidify the text lines within the x-height region, | |
| 826 * and to remove most of the ascenders and descenders. | |
| 827 * We start with a small vertical opening to remove noise beyond | |
| 828 * the line that can cause an error in the line end points. | |
| 829 * The small closing (csize1) is used to bridge the gaps between | |
| 830 * letters. The large closing (csize2) bridges the gaps between | |
| 831 * words; using 1/30 of the page width usually suffices. */ | |
| 832 csize1 = L_MAX(15, w / 80); | |
| 833 csize2 = L_MAX(40, w / 30); | |
| 834 snprintf(buf, sizeof(buf), "o1.3 + c%d.1 + o%d.1 + c%d.1", | |
| 835 csize1, csize1, csize2); | |
| 836 pix1 = pixMorphSequence(pixs, buf, 0); | |
| 837 | |
| 838 /* Remove the components (e.g., embedded images) that have | |
| 839 * long vertical runs (>= 50 pixels). You can't use bounding | |
| 840 * boxes because connected component b.b. of lines can be quite | |
| 841 * tall due to slope and curvature. */ | |
| 842 pix2 = pixMorphSequence(pix1, "e1.50", 0); /* seed */ | |
| 843 pixSeedfillBinary(pix2, pix2, pix1, 8); /* tall components */ | |
| 844 pixXor(pix2, pix2, pix1); /* remove tall */ | |
| 845 | |
| 846 if (debugflag) { | |
| 847 lept_mkdir("lept/dewmod"); | |
| 848 pixWriteDebug("/tmp/lept/dewmod/0011.tif", pix1, IFF_TIFF_G4); | |
| 849 pixDisplayWithTitle(pix1, 0, 600, "pix1", 1); | |
| 850 pixWriteDebug("/tmp/lept/dewmod/0012.tif", pix2, IFF_TIFF_G4); | |
| 851 pixDisplayWithTitle(pix2, 0, 800, "pix2", 1); | |
| 852 } | |
| 853 pixDestroy(&pix1); | |
| 854 | |
| 855 /* Get the 8-connected components ... */ | |
| 856 boxa = pixConnComp(pix2, &pixa1, 8); | |
| 857 pixDestroy(&pix2); | |
| 858 boxaDestroy(&boxa); | |
| 859 if (pixaGetCount(pixa1) == 0) { | |
| 860 pixaDestroy(&pixa1); | |
| 861 return NULL; | |
| 862 } | |
| 863 | |
| 864 /* ... and remove the short width and very short height c.c */ | |
| 865 pixa2 = pixaSelectBySize(pixa1, 100, 4, L_SELECT_IF_BOTH, | |
| 866 L_SELECT_IF_GT, NULL); | |
| 867 if ((nsegs = pixaGetCount(pixa2)) == 0) { | |
| 868 pixaDestroy(&pixa1); | |
| 869 pixaDestroy(&pixa2); | |
| 870 return NULL; | |
| 871 } | |
| 872 if (debugflag) { | |
| 873 pix2 = pixaDisplay(pixa2, w, h); | |
| 874 pixWriteDebug("/tmp/lept/dewmod/0013.tif", pix2, IFF_TIFF_G4); | |
| 875 pixDisplayWithTitle(pix2, 0, 1000, "pix2", 1); | |
| 876 pixDestroy(&pix2); | |
| 877 } | |
| 878 | |
| 879 /* For each c.c., get the weighted center of each vertical column. | |
| 880 * The result is a set of points going approximately through | |
| 881 * the center of the x-height part of the text line. */ | |
| 882 ptaa = ptaaCreate(nsegs); | |
| 883 for (i = 0; i < nsegs; i++) { | |
| 884 pixaGetBoxGeometry(pixa2, i, &bx, &by, NULL, NULL); | |
| 885 pix2 = pixaGetPix(pixa2, i, L_CLONE); | |
| 886 pta = dewarpGetMeanVerticals(pix2, bx, by); | |
| 887 ptaaAddPta(ptaa, pta, L_INSERT); | |
| 888 pixDestroy(&pix2); | |
| 889 } | |
| 890 if (debugflag) { | |
| 891 pix1 = pixCreateTemplate(pixs); | |
| 892 pix2 = pixDisplayPtaa(pix1, ptaa); | |
| 893 pixWriteDebug("/tmp/lept/dewmod/0014.tif", pix2, IFF_PNG); | |
| 894 pixDisplayWithTitle(pix2, 0, 1200, "pix3", 1); | |
| 895 pixDestroy(&pix1); | |
| 896 pixDestroy(&pix2); | |
| 897 } | |
| 898 | |
| 899 pixaDestroy(&pixa1); | |
| 900 pixaDestroy(&pixa2); | |
| 901 return ptaa; | |
| 902 } | |
| 903 | |
| 904 | |
| 905 /*! | |
| 906 * \brief dewarpGetMeanVerticals() | |
| 907 * | |
| 908 * \param[in] pixs 1 bpp, single c.c. | |
| 909 * \param[in] x,y location of UL corner of pixs, relative to page image | |
| 910 * \return pta (mean y-values in component for each x-value, | |
| 911 * both translated by (x,y | |
| 912 */ | |
| 913 static PTA * | |
| 914 dewarpGetMeanVerticals(PIX *pixs, | |
| 915 l_int32 x, | |
| 916 l_int32 y) | |
| 917 { | |
| 918 l_int32 w, h, i, j, wpl, sum, count; | |
| 919 l_uint32 *line, *data; | |
| 920 PTA *pta; | |
| 921 | |
| 922 if (!pixs || pixGetDepth(pixs) != 1) | |
| 923 return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 924 | |
| 925 pixGetDimensions(pixs, &w, &h, NULL); | |
| 926 pta = ptaCreate(w); | |
| 927 data = pixGetData(pixs); | |
| 928 wpl = pixGetWpl(pixs); | |
| 929 for (j = 0; j < w; j++) { | |
| 930 line = data; | |
| 931 sum = count = 0; | |
| 932 for (i = 0; i < h; i++) { | |
| 933 if (GET_DATA_BIT(line, j) == 1) { | |
| 934 sum += i; | |
| 935 count += 1; | |
| 936 } | |
| 937 line += wpl; | |
| 938 } | |
| 939 if (count == 0) continue; | |
| 940 ptaAddPt(pta, x + j, y + (sum / count)); | |
| 941 } | |
| 942 | |
| 943 return pta; | |
| 944 } | |
| 945 | |
| 946 | |
| 947 /*! | |
| 948 * \brief dewarpRemoveShortLines() | |
| 949 * | |
| 950 * \param[in] pixs 1 bpp | |
| 951 * \param[in] ptaas input lines | |
| 952 * \param[in] fract minimum fraction of longest line to keep | |
| 953 * \param[in] debugflag | |
| 954 * \return ptaad containing only lines of sufficient length, | |
| 955 * or NULL on error | |
| 956 */ | |
| 957 PTAA * | |
| 958 dewarpRemoveShortLines(PIX *pixs, | |
| 959 PTAA *ptaas, | |
| 960 l_float32 fract, | |
| 961 l_int32 debugflag) | |
| 962 { | |
| 963 l_int32 w, n, i, index, maxlen, len; | |
| 964 l_float32 minx, maxx; | |
| 965 NUMA *na, *naindex; | |
| 966 PIX *pix1, *pix2; | |
| 967 PTA *pta; | |
| 968 PTAA *ptaad; | |
| 969 | |
| 970 if (!pixs || pixGetDepth(pixs) != 1) | |
| 971 return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 972 if (!ptaas) | |
| 973 return (PTAA *)ERROR_PTR("ptaas undefined", __func__, NULL); | |
| 974 | |
| 975 pixGetDimensions(pixs, &w, NULL, NULL); | |
| 976 n = ptaaGetCount(ptaas); | |
| 977 ptaad = ptaaCreate(n); | |
| 978 na = numaCreate(n); | |
| 979 for (i = 0; i < n; i++) { | |
| 980 pta = ptaaGetPta(ptaas, i, L_CLONE); | |
| 981 ptaGetRange(pta, &minx, &maxx, NULL, NULL); | |
| 982 numaAddNumber(na, maxx - minx + 1); | |
| 983 ptaDestroy(&pta); | |
| 984 } | |
| 985 | |
| 986 /* Sort by length and find all that are long enough */ | |
| 987 naindex = numaGetSortIndex(na, L_SORT_DECREASING); | |
| 988 numaGetIValue(naindex, 0, &index); | |
| 989 numaGetIValue(na, index, &maxlen); | |
| 990 if (maxlen < 0.5 * w) | |
| 991 L_WARNING("lines are relatively short\n", __func__); | |
| 992 pta = ptaaGetPta(ptaas, index, L_CLONE); | |
| 993 ptaaAddPta(ptaad, pta, L_INSERT); | |
| 994 for (i = 1; i < n; i++) { | |
| 995 numaGetIValue(naindex, i, &index); | |
| 996 numaGetIValue(na, index, &len); | |
| 997 if (len < fract * maxlen) break; | |
| 998 pta = ptaaGetPta(ptaas, index, L_CLONE); | |
| 999 ptaaAddPta(ptaad, pta, L_INSERT); | |
| 1000 } | |
| 1001 | |
| 1002 if (debugflag) { | |
| 1003 pix1 = pixCopy(NULL, pixs); | |
| 1004 pix2 = pixDisplayPtaa(pix1, ptaad); | |
| 1005 pixDisplayWithTitle(pix2, 0, 200, "pix4", 1); | |
| 1006 pixDestroy(&pix1); | |
| 1007 pixDestroy(&pix2); | |
| 1008 } | |
| 1009 | |
| 1010 numaDestroy(&na); | |
| 1011 numaDestroy(&naindex); | |
| 1012 return ptaad; | |
| 1013 } | |
| 1014 | |
| 1015 | |
| 1016 /*! | |
| 1017 * \brief dewarpGetLineEndPoints() | |
| 1018 * | |
| 1019 * \param[in] h height of pixs | |
| 1020 * \param[in] ptaa lines | |
| 1021 * \param[out] pptal left end points of each line | |
| 1022 * \param[out] pptar right end points of each line | |
| 1023 * \return 0 if OK, 1 on error. | |
| 1024 * | |
| 1025 * <pre> | |
| 1026 * Notes: | |
| 1027 * (1) We require that the set of end points extends over 45% of the | |
| 1028 * height of the input image, to insure good coverage and | |
| 1029 * avoid extrapolating the curvature too far beyond the | |
| 1030 * actual textlines. Large extrapolations are particularly | |
| 1031 * dangerous if used as a reference model. We also require | |
| 1032 * at least 10 lines of text. | |
| 1033 * (2) We sort the lines from top to bottom (sort by x in the ptas). | |
| 1034 * (3) For fitting the endpoints, x = f(y), we transpose x and y. | |
| 1035 * Thus all these ptas have x and y swapped! | |
| 1036 * </pre> | |
| 1037 */ | |
| 1038 static l_int32 | |
| 1039 dewarpGetLineEndPoints(l_int32 h, | |
| 1040 PTAA *ptaa, | |
| 1041 PTA **pptal, | |
| 1042 PTA **pptar) | |
| 1043 { | |
| 1044 l_int32 i, n, npt, x, y; | |
| 1045 l_float32 miny, maxy, ratio; | |
| 1046 PTA *pta, *ptal1, *ptar1; | |
| 1047 | |
| 1048 if (!pptal || !pptar) | |
| 1049 return ERROR_INT("&ptal and &ptar not both defined", __func__, 1); | |
| 1050 *pptal = *pptar = NULL; | |
| 1051 if (!ptaa) | |
| 1052 return ERROR_INT("ptaa undefined", __func__, 1); | |
| 1053 | |
| 1054 /* Are there at least 10 lines? */ | |
| 1055 n = ptaaGetCount(ptaa); | |
| 1056 if (n < MinLinesForHoriz1) { | |
| 1057 L_INFO("only %d lines; too few\n", __func__, n); | |
| 1058 return 1; | |
| 1059 } | |
| 1060 | |
| 1061 /* Extract the line end points, and transpose x and y values */ | |
| 1062 ptal1 = ptaCreate(n); | |
| 1063 ptar1 = ptaCreate(n); | |
| 1064 for (i = 0; i < n; i++) { | |
| 1065 pta = ptaaGetPta(ptaa, i, L_CLONE); | |
| 1066 ptaGetIPt(pta, 0, &x, &y); | |
| 1067 ptaAddPt(ptal1, y, x); /* transpose */ | |
| 1068 npt = ptaGetCount(pta); | |
| 1069 ptaGetIPt(pta, npt - 1, &x, &y); | |
| 1070 ptaAddPt(ptar1, y, x); /* transpose */ | |
| 1071 ptaDestroy(&pta); | |
| 1072 } | |
| 1073 | |
| 1074 /* Use the min and max of the y value on the left side. */ | |
| 1075 ptaGetRange(ptal1, &miny, &maxy, NULL, NULL); | |
| 1076 ratio = (maxy - miny) / (l_float32)h; | |
| 1077 if (ratio < MinRatioLinesToHeight) { | |
| 1078 L_INFO("ratio lines to height, %f, too small\n", __func__, ratio); | |
| 1079 ptaDestroy(&ptal1); | |
| 1080 ptaDestroy(&ptar1); | |
| 1081 return 1; | |
| 1082 } | |
| 1083 | |
| 1084 /* Sort from top to bottom */ | |
| 1085 *pptal = ptaSort(ptal1, L_SORT_BY_X, L_SORT_INCREASING, NULL); | |
| 1086 *pptar = ptaSort(ptar1, L_SORT_BY_X, L_SORT_INCREASING, NULL); | |
| 1087 ptaDestroy(&ptal1); | |
| 1088 ptaDestroy(&ptar1); | |
| 1089 return 0; | |
| 1090 } | |
| 1091 | |
| 1092 | |
| 1093 /*! | |
| 1094 * \brief dewarpFilterLineEndPoints() | |
| 1095 * | |
| 1096 * \param[in] dew | |
| 1097 * \param[in] ptal input left end points of each line | |
| 1098 * \param[in] ptar input right end points of each line | |
| 1099 * \param[out] pptalf filtered left end points | |
| 1100 * \param[out] pptarf filtered right end points | |
| 1101 * \return 0 if OK, 1 on error. | |
| 1102 * | |
| 1103 * <pre> | |
| 1104 * Notes: | |
| 1105 * (1) Avoid confusion with multiple columns by requiring that line | |
| 1106 * end points be close enough to leftmost and rightmost end points. | |
| 1107 * Must have at least 8 points on left and right after this step. | |
| 1108 * (2) Apply second filtering step, find the median positions in | |
| 1109 * top and bottom halves, and removing end points that are | |
| 1110 * displaced too much from these in the x direction. | |
| 1111 * Must have at least 6 points on left and right after this step. | |
| 1112 * (3) Reminder: x and y in the pta are transposed; think x = f(y). | |
| 1113 * </pre> | |
| 1114 */ | |
| 1115 static l_int32 | |
| 1116 dewarpFilterLineEndPoints(L_DEWARP *dew, | |
| 1117 PTA *ptal, | |
| 1118 PTA *ptar, | |
| 1119 PTA **pptalf, | |
| 1120 PTA **pptarf) | |
| 1121 { | |
| 1122 l_int32 w, i, n; | |
| 1123 l_float32 ymin, ymax, xvall, xvalr, yvall, yvalr; | |
| 1124 PTA *ptal1, *ptar1, *ptal2, *ptar2; | |
| 1125 | |
| 1126 if (!ptal || !ptar) | |
| 1127 return ERROR_INT("ptal or ptar not defined", __func__, 1); | |
| 1128 *pptalf = *pptarf = NULL; | |
| 1129 | |
| 1130 /* First filter for lines near left and right margins */ | |
| 1131 w = pixGetWidth(dew->pixs); | |
| 1132 ptaGetMinMax(ptal, NULL, &ymin, NULL, NULL); | |
| 1133 ptaGetMinMax(ptar, NULL, NULL, NULL, &ymax); | |
| 1134 n = ptaGetCount(ptal); /* ptar is the same size; at least 10 */ | |
| 1135 ptal1 = ptaCreate(n); | |
| 1136 ptar1 = ptaCreate(n); | |
| 1137 for (i = 0; i < n; i++) { | |
| 1138 ptaGetPt(ptal, i, &xvall, &yvall); | |
| 1139 ptaGetPt(ptar, i, &xvalr, &yvalr); | |
| 1140 if (yvall < ymin + 0.20 * (w - ymin) && | |
| 1141 yvalr > 0.80 * ymax) { | |
| 1142 ptaAddPt(ptal1, xvall, yvall); | |
| 1143 ptaAddPt(ptar1, xvalr, yvalr); | |
| 1144 } | |
| 1145 } | |
| 1146 if (dew->debug) { | |
| 1147 ptaWriteDebug("/tmp/lept/dewdebug/endpts_left2.pta", ptal1, 1); | |
| 1148 ptaWriteDebug("/tmp/lept/dewdebug/endpts_right2.pta", ptar1, 1); | |
| 1149 } | |
| 1150 | |
| 1151 n = L_MIN(ptaGetCount(ptal1), ptaGetCount(ptar1)); | |
| 1152 if (n < MinLinesForHoriz1 - 2) { | |
| 1153 ptaDestroy(&ptal1); | |
| 1154 ptaDestroy(&ptar1); | |
| 1155 L_INFO("First filter: only %d endpoints; needed 8\n", __func__, n); | |
| 1156 return 1; | |
| 1157 } | |
| 1158 | |
| 1159 /* Remove outlier points */ | |
| 1160 ptal2 = dewarpRemoveBadEndPoints(w, ptal1); | |
| 1161 ptar2 = dewarpRemoveBadEndPoints(w, ptar1); | |
| 1162 ptaDestroy(&ptal1); | |
| 1163 ptaDestroy(&ptar1); | |
| 1164 if (!ptal2 || !ptar2) { | |
| 1165 ptaDestroy(&ptal2); | |
| 1166 ptaDestroy(&ptar2); | |
| 1167 L_INFO("Second filter: too few endpoints left after outliers removed\n", | |
| 1168 __func__); | |
| 1169 return 1; | |
| 1170 } | |
| 1171 if (dew->debug) { | |
| 1172 ptaWriteDebug("/tmp/lept/dewdebug/endpts_left3.pta", ptal2, 1); | |
| 1173 ptaWriteDebug("/tmp/lept/dewdebug/endpts_right3.pta", ptar2, 1); | |
| 1174 } | |
| 1175 | |
| 1176 *pptalf = ptal2; | |
| 1177 *pptarf = ptar2; | |
| 1178 return 0; | |
| 1179 } | |
| 1180 | |
| 1181 | |
| 1182 /*! | |
| 1183 * \brief dewarpRemoveBadEndPoints() | |
| 1184 * | |
| 1185 * \param[in] w width of input image | |
| 1186 * \param[in] ptas left or right line end points | |
| 1187 * \return ptad filtered left or right end points, or NULL on error. | |
| 1188 * | |
| 1189 * <pre> | |
| 1190 * Notes: | |
| 1191 * (1) The input set is sorted by line position (x value). | |
| 1192 * Break into two (upper and lower); for each find the median | |
| 1193 * horizontal (y value), and remove all points farther than | |
| 1194 * a fraction of the image width from this. Make sure each | |
| 1195 * part still has at least 3 points, and join the two sections | |
| 1196 * before returning. | |
| 1197 * (2) Reminder: x and y in the pta are transposed; think x = f(y). | |
| 1198 * </pre> | |
| 1199 */ | |
| 1200 static PTA * | |
| 1201 dewarpRemoveBadEndPoints(l_int32 w, | |
| 1202 PTA *ptas) | |
| 1203 { | |
| 1204 l_int32 i, n, nu, nd; | |
| 1205 l_float32 rval, xval, yval, delta; | |
| 1206 PTA *ptau1, *ptau2, *ptad1, *ptad2; | |
| 1207 | |
| 1208 if (!ptas) | |
| 1209 return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); | |
| 1210 | |
| 1211 delta = AllowedWidthFract * w; | |
| 1212 n = ptaGetCount(ptas); /* will be at least 8 */ | |
| 1213 | |
| 1214 /* Check the upper half */ | |
| 1215 ptau1 = ptaSelectRange(ptas, 0, n / 2); | |
| 1216 ptaGetRankValue(ptau1, 0.5, NULL, L_SORT_BY_Y, &rval); | |
| 1217 nu = ptaGetCount(ptau1); | |
| 1218 ptau2 = ptaCreate(nu); | |
| 1219 for (i = 0; i < nu; i++) { | |
| 1220 ptaGetPt(ptau1, i, &xval, &yval); /* transposed */ | |
| 1221 if (L_ABS(rval - yval) <= delta) | |
| 1222 ptaAddPt(ptau2, xval, yval); | |
| 1223 } | |
| 1224 ptaDestroy(&ptau1); | |
| 1225 if (ptaGetCount(ptau2) < MinLinesForHoriz2) { | |
| 1226 ptaDestroy(&ptau2); | |
| 1227 L_INFO("Second filter: upper set is too small after outliers removed\n", | |
| 1228 __func__); | |
| 1229 return NULL; | |
| 1230 } | |
| 1231 | |
| 1232 /* Check the lower half */ | |
| 1233 ptad1 = ptaSelectRange(ptas, n / 2 + 1, -1); | |
| 1234 ptaGetRankValue(ptad1, 0.5, NULL, L_SORT_BY_Y, &rval); | |
| 1235 nd = ptaGetCount(ptad1); | |
| 1236 ptad2 = ptaCreate(nd); | |
| 1237 for (i = 0; i < nd; i++) { | |
| 1238 ptaGetPt(ptad1, i, &xval, &yval); /* transposed */ | |
| 1239 if (L_ABS(rval - yval) <= delta) | |
| 1240 ptaAddPt(ptad2, xval, yval); | |
| 1241 } | |
| 1242 ptaDestroy(&ptad1); | |
| 1243 if (ptaGetCount(ptad2) < MinLinesForHoriz2) { | |
| 1244 ptaDestroy(&ptau2); | |
| 1245 ptaDestroy(&ptad2); | |
| 1246 L_INFO("Second filter: lower set is too small after outliers removed\n", | |
| 1247 __func__); | |
| 1248 return NULL; | |
| 1249 } | |
| 1250 | |
| 1251 ptaJoin(ptau2, ptad2, 0, -1); | |
| 1252 ptaDestroy(&ptad2); | |
| 1253 return ptau2; | |
| 1254 } | |
| 1255 | |
| 1256 | |
| 1257 /*! | |
| 1258 * \brief dewarpIsLineCoverageValid() | |
| 1259 * | |
| 1260 * \param[in] ptaa of validated lines | |
| 1261 * \param[in] h height of pix | |
| 1262 * \param[out] pntop number of lines in top half | |
| 1263 * \param[out] pnbot number of lines in bottom half | |
| 1264 * \param[out] pytop location of top line | |
| 1265 * \param[out] pybot location of bottom line | |
| 1266 * \return 1 if coverage is valid, 0 if not or on error. | |
| 1267 * | |
| 1268 * <pre> | |
| 1269 * Notes: | |
| 1270 * (1) The criterion for valid coverage is: | |
| 1271 * (a) there must be at least 4 lines in each half (top and bottom) | |
| 1272 * of the image. | |
| 1273 * (b) the coverage must be at least 50% of the image height | |
| 1274 * </pre> | |
| 1275 */ | |
| 1276 static l_int32 | |
| 1277 dewarpIsLineCoverageValid(PTAA *ptaa, | |
| 1278 l_int32 h, | |
| 1279 l_int32 *pntop, | |
| 1280 l_int32 *pnbot, | |
| 1281 l_int32 *pytop, | |
| 1282 l_int32 *pybot) | |
| 1283 { | |
| 1284 l_int32 i, n, iy, both_halves, ntop, nbot, ytop, ybot, nmin; | |
| 1285 l_float32 y, fraction; | |
| 1286 NUMA *na; | |
| 1287 | |
| 1288 if (!ptaa) | |
| 1289 return ERROR_INT("ptaa not defined", __func__, 0); | |
| 1290 if ((n = ptaaGetCount(ptaa)) == 0) | |
| 1291 return ERROR_INT("ptaa empty", __func__, 0); | |
| 1292 if (h <= 0) | |
| 1293 return ERROR_INT("invalid h", __func__, 0); | |
| 1294 if (!pntop || !pnbot) | |
| 1295 return ERROR_INT("&ntop and &nbot not defined", __func__, 0); | |
| 1296 if (!pytop || !pybot) | |
| 1297 return ERROR_INT("&ytop and &ybot not defined", __func__, 0); | |
| 1298 | |
| 1299 na = numaCreate(n); | |
| 1300 for (i = 0; i < n; i++) { | |
| 1301 ptaaGetPt(ptaa, i, 0, NULL, &y); | |
| 1302 numaAddNumber(na, y); | |
| 1303 } | |
| 1304 numaSort(na, na, L_SORT_INCREASING); | |
| 1305 for (i = 0, ntop = 0; i < n; i++) { | |
| 1306 numaGetIValue(na, i, &iy); | |
| 1307 if (i == 0) ytop = iy; | |
| 1308 if (i == n - 1) ybot = iy; | |
| 1309 if (iy < 0.5 * h) | |
| 1310 ntop++; | |
| 1311 } | |
| 1312 numaDestroy(&na); | |
| 1313 nbot = n - ntop; | |
| 1314 *pntop = ntop; | |
| 1315 *pnbot = nbot; | |
| 1316 *pytop = ytop; | |
| 1317 *pybot = ybot; | |
| 1318 nmin = 4; /* minimum number of lines required in each half */ | |
| 1319 both_halves = (ntop >= nmin) && (nbot >= nmin); | |
| 1320 fraction = (l_float32)(ybot - ytop) / (l_float32)h; | |
| 1321 if (both_halves && fraction > 0.50) | |
| 1322 return 1; | |
| 1323 return 0; | |
| 1324 } | |
| 1325 | |
| 1326 | |
| 1327 /*! | |
| 1328 * \brief dewarpLinearLSF() | |
| 1329 * | |
| 1330 * \param[in] ptad left or right end points of longest lines | |
| 1331 * \param[out] pa coeff a of LSF: y = ax + b | |
| 1332 * \param[out] pb coeff b of LSF: y = ax + b | |
| 1333 * \param[out] pmederr [optional] median error | |
| 1334 * \return 0 if OK, 1 on error. | |
| 1335 * | |
| 1336 * <pre> | |
| 1337 * Notes: | |
| 1338 * (1) This is used for finding the left or right sides of the text | |
| 1339 * block, computed as a best-fit line. Only the longest lines | |
| 1340 * are input, so there are no outlier line ends. | |
| 1341 * (2) The ptas for the end points all have x and y swapped. | |
| 1342 * </pre> | |
| 1343 */ | |
| 1344 static l_int32 | |
| 1345 dewarpLinearLSF(PTA *ptad, | |
| 1346 l_float32 *pa, | |
| 1347 l_float32 *pb, | |
| 1348 l_float32 *pmederr) | |
| 1349 { | |
| 1350 l_int32 i, n; | |
| 1351 l_float32 x, y, xp, c0, c1; | |
| 1352 NUMA *naerr; | |
| 1353 | |
| 1354 if (pmederr) *pmederr = 0.0; | |
| 1355 if (!pa || !pb) | |
| 1356 return ERROR_INT("not all ptrs are defined", __func__, 1); | |
| 1357 *pa = *pb = 0.0; | |
| 1358 if (!ptad) | |
| 1359 return ERROR_INT("ptad not defined", __func__, 1); | |
| 1360 | |
| 1361 /* Fit to the longest lines */ | |
| 1362 ptaGetLinearLSF(ptad, &c1, &c0, NULL); | |
| 1363 *pa = c1; | |
| 1364 *pb = c0; | |
| 1365 | |
| 1366 /* Optionally, find the median error */ | |
| 1367 if (pmederr) { | |
| 1368 n = ptaGetCount(ptad); | |
| 1369 naerr = numaCreate(n); | |
| 1370 for (i = 0; i < n; i++) { | |
| 1371 ptaGetPt(ptad, i, &y, &xp); | |
| 1372 applyLinearFit(c1, c0, y, &x); | |
| 1373 numaAddNumber(naerr, L_ABS(x - xp)); | |
| 1374 } | |
| 1375 numaGetMedian(naerr, pmederr); | |
| 1376 numaDestroy(&naerr); | |
| 1377 } | |
| 1378 return 0; | |
| 1379 } | |
| 1380 | |
| 1381 | |
| 1382 /*! | |
| 1383 * \brief dewarpQuadraticLSF() | |
| 1384 * | |
| 1385 * \param[in] ptad left or right end points of longest lines | |
| 1386 * \param[out] pa coeff a of LSF: y = ax^2 + bx + c | |
| 1387 * \param[out] pb coeff b of LSF: y = ax^2 + bx + c | |
| 1388 * \param[out] pc coeff c of LSF: y = ax^2 + bx + c | |
| 1389 * \param[out] pmederr [optional] median error | |
| 1390 * \return 0 if OK, 1 on error. | |
| 1391 * | |
| 1392 * <pre> | |
| 1393 * Notes: | |
| 1394 * (1) This is used for finding the left or right sides of the text | |
| 1395 * block, computed as a best-fit quadratic curve. Only the | |
| 1396 * longest lines are input, so there are no outlier line ends. | |
| 1397 * (2) The ptas for the end points all have x and y swapped. | |
| 1398 * </pre> | |
| 1399 */ | |
| 1400 static l_int32 | |
| 1401 dewarpQuadraticLSF(PTA *ptad, | |
| 1402 l_float32 *pa, | |
| 1403 l_float32 *pb, | |
| 1404 l_float32 *pc, | |
| 1405 l_float32 *pmederr) | |
| 1406 { | |
| 1407 l_int32 i, n; | |
| 1408 l_float32 x, y, xp, c0, c1, c2; | |
| 1409 NUMA *naerr; | |
| 1410 | |
| 1411 if (pmederr) *pmederr = 0.0; | |
| 1412 if (!pa || !pb || !pc) | |
| 1413 return ERROR_INT("not all ptrs are defined", __func__, 1); | |
| 1414 *pa = *pb = *pc = 0.0; | |
| 1415 if (!ptad) | |
| 1416 return ERROR_INT("ptad not defined", __func__, 1); | |
| 1417 | |
| 1418 /* Fit to the longest lines */ | |
| 1419 ptaGetQuadraticLSF(ptad, &c2, &c1, &c0, NULL); | |
| 1420 *pa = c2; | |
| 1421 *pb = c1; | |
| 1422 *pc = c0; | |
| 1423 | |
| 1424 /* Optionally, find the median error */ | |
| 1425 if (pmederr) { | |
| 1426 n = ptaGetCount(ptad); | |
| 1427 naerr = numaCreate(n); | |
| 1428 for (i = 0; i < n; i++) { | |
| 1429 ptaGetPt(ptad, i, &y, &xp); | |
| 1430 applyQuadraticFit(c2, c1, c0, y, &x); | |
| 1431 numaAddNumber(naerr, L_ABS(x - xp)); | |
| 1432 } | |
| 1433 numaGetMedian(naerr, pmederr); | |
| 1434 numaDestroy(&naerr); | |
| 1435 } | |
| 1436 return 0; | |
| 1437 } | |
| 1438 | |
| 1439 | |
| 1440 /*----------------------------------------------------------------------* | |
| 1441 * Build disparity model for slope near binding * | |
| 1442 *----------------------------------------------------------------------*/ | |
| 1443 /*! | |
| 1444 * \brief dewarpFindHorizSlopeDisparity() | |
| 1445 * | |
| 1446 * \param[in] dew | |
| 1447 * \param[in] pixb 1 bpp, with vert and horiz disparity removed | |
| 1448 * \param[in] fractthresh threshold fractional difference in density | |
| 1449 * \param[in] parity 0 if even page, 1 if odd page | |
| 1450 * \return 0 if OK, 1 on error | |
| 1451 * | |
| 1452 * <pre> | |
| 1453 * Notes: | |
| 1454 * (1) %fractthresh is a threshold on the fractional difference in stroke | |
| 1455 * density between between left and right sides. Process this | |
| 1456 * disparity only if the absolute value of the fractional | |
| 1457 * difference equals or exceeds this threshold. | |
| 1458 * (2) %parity indicates where the binding is: on the left for | |
| 1459 * %parity == 0 and on the right for %parity == 1. | |
| 1460 * (3) This takes a 1 bpp %pixb where both vertical and horizontal | |
| 1461 * disparity have been applied, so the text lines are straight and, | |
| 1462 * more importantly, the line end points are vertically aligned. | |
| 1463 * It estimates the foreshortening of the characters on the | |
| 1464 * binding side, and if significant, computes a one-dimensional | |
| 1465 * horizontal disparity function to compensate. | |
| 1466 * (4) The first attempt was to use the average width of the | |
| 1467 * connected components (c.c.) in vertical slices. This does not work | |
| 1468 * reliably, because the horizontal compression of the text is | |
| 1469 * often accompanied by horizontal joining of c.c. | |
| 1470 * (5) We use the density of vertical strokes, measured by first using | |
| 1471 * a vertical opening, which improves the signal. The result | |
| 1472 * is relatively insensitive to the size of the opening; we use | |
| 1473 * a 10-pixel opening. The relative density is measured by | |
| 1474 * finding the number of c.c. in a full height sliding window | |
| 1475 * of width 50 pixels, and compute every 25 pixels. Similar results | |
| 1476 * are obtained counting c.c. that either intersect the window | |
| 1477 * or are fully contained within it. | |
| 1478 * (6) Debug output goes to /tmp/lept/dewmod/ for collection into a pdf. | |
| 1479 * </pre> | |
| 1480 */ | |
| 1481 l_ok | |
| 1482 dewarpFindHorizSlopeDisparity(L_DEWARP *dew, | |
| 1483 PIX *pixb, | |
| 1484 l_float32 fractthresh, | |
| 1485 l_int32 parity) | |
| 1486 { | |
| 1487 l_int32 i, j, x, n1, n2, nb, ne, count, w, h, ival, prev; | |
| 1488 l_int32 istart, iend, first, last, x0, x1, nx, ny; | |
| 1489 l_float32 fract, delta, sum, aveval, fval, del, denom; | |
| 1490 l_float32 ca, cb, cc, cd, ce, y; | |
| 1491 BOX *box; | |
| 1492 BOXA *boxa1, *boxa2; | |
| 1493 GPLOT *gplot; | |
| 1494 NUMA *na1, *na2, *na3, *na4, *nasum; | |
| 1495 PIX *pix1; | |
| 1496 PTA *pta1; | |
| 1497 FPIX *fpix; | |
| 1498 | |
| 1499 if (!dew) | |
| 1500 return ERROR_INT("dew not defined", __func__, 1); | |
| 1501 if (!dew->vvalid || !dew->hvalid) | |
| 1502 return ERROR_INT("invalid vert or horiz disparity model", __func__, 1); | |
| 1503 if (!pixb || pixGetDepth(pixb) != 1) | |
| 1504 return ERROR_INT("pixb not defined or not 1 bpp", __func__, 1); | |
| 1505 | |
| 1506 if (dew->debug) L_INFO("finding slope horizontal disparity\n", __func__); | |
| 1507 | |
| 1508 /* Find the bounding boxes of the vertical strokes; remove noise */ | |
| 1509 pix1 = pixMorphSequence(pixb, "o1.10", 0); | |
| 1510 pixDisplay(pix1, 100, 100); | |
| 1511 boxa1 = pixConnCompBB(pix1, 4); | |
| 1512 boxa2 = boxaSelectBySize(boxa1, 0, 5, L_SELECT_HEIGHT, L_SELECT_IF_GT, | |
| 1513 NULL); | |
| 1514 nb = boxaGetCount(boxa2); | |
| 1515 lept_stderr("number of components: %d\n", nb); | |
| 1516 boxaDestroy(&boxa1); | |
| 1517 | |
| 1518 /* Estimate the horizontal density of vertical strokes */ | |
| 1519 na1 = numaCreate(0); | |
| 1520 numaSetParameters(na1, 0, 25); | |
| 1521 pixGetDimensions(pixb, &w, &h, NULL); | |
| 1522 for (x = 0; x + 50 < w; x += 25) { | |
| 1523 box = boxCreate(x, 0, 50, h); | |
| 1524 boxaContainedInBoxCount(boxa2, box, &count); | |
| 1525 numaAddNumber(na1, count); | |
| 1526 boxDestroy(&box); | |
| 1527 } | |
| 1528 if (dew->debug) { | |
| 1529 lept_mkdir("lept/dew"); | |
| 1530 gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/dew/0091", NULL); | |
| 1531 lept_mv("/tmp/lept/dew/0091.png", "lept/dewmod", NULL, NULL); | |
| 1532 pixWriteDebug("/tmp/lept/dewmod/0090.png", pix1, IFF_PNG); | |
| 1533 } | |
| 1534 pixDestroy(&pix1); | |
| 1535 boxaDestroy(&boxa2); | |
| 1536 | |
| 1537 /* Find the left and right end local maxima; if the difference | |
| 1538 * is small, quit. */ | |
| 1539 n1 = numaGetCount(na1); | |
| 1540 prev = 0; | |
| 1541 istart = 0; | |
| 1542 first = 0; | |
| 1543 for (i = 0; i < n1; i++) { | |
| 1544 numaGetIValue(na1, i, &ival); | |
| 1545 if (ival >= prev) { | |
| 1546 prev = ival; | |
| 1547 continue; | |
| 1548 } else { | |
| 1549 first = prev; | |
| 1550 istart = i - 1; | |
| 1551 break; | |
| 1552 } | |
| 1553 } | |
| 1554 prev = 0; | |
| 1555 last = 0; | |
| 1556 iend = n1 - 1; | |
| 1557 for (i = n1 - 1; i >= 0; i--) { | |
| 1558 numaGetIValue(na1, i, &ival); | |
| 1559 if (ival >= prev) { | |
| 1560 prev = ival; | |
| 1561 continue; | |
| 1562 } else { | |
| 1563 last = prev; | |
| 1564 iend = i + 1; | |
| 1565 break; | |
| 1566 } | |
| 1567 } | |
| 1568 na2 = numaClipToInterval(na1, istart, iend); | |
| 1569 numaDestroy(&na1); | |
| 1570 n2 = numaGetCount(na2); | |
| 1571 delta = (parity == 0) ? last - first : first - last; | |
| 1572 denom = L_MAX(1.0, (l_float32)(L_MIN(first, last))); | |
| 1573 fract = (l_float32)delta / denom; | |
| 1574 if (dew->debug) { | |
| 1575 L_INFO("Slope-disparity: first = %d, last = %d, fract = %7.3f\n", | |
| 1576 __func__, first, last, fract); | |
| 1577 gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/dew/0092", NULL); | |
| 1578 lept_mv("/tmp/lept/dew/0092.png", "lept/dewmod", NULL, NULL); | |
| 1579 } | |
| 1580 if (fract < fractthresh) { | |
| 1581 L_INFO("Small slope-disparity: first = %d, last = %d, fract = %7.3f\n", | |
| 1582 __func__, first, last, fract); | |
| 1583 numaDestroy(&na2); | |
| 1584 return 0; | |
| 1585 } | |
| 1586 | |
| 1587 /* Find the density far from the binding, and normalize to 1. */ | |
| 1588 ne = n2 - n2 % 2; | |
| 1589 if (parity == 0) | |
| 1590 numaGetSumOnInterval(na2, 0, ne / 2 - 1, &sum); | |
| 1591 else /* parity == 1 */ | |
| 1592 numaGetSumOnInterval(na2, ne / 2, ne - 1, &sum); | |
| 1593 denom = L_MAX(1.0, (l_float32)(ne / 2)); | |
| 1594 aveval = sum / denom; | |
| 1595 na3 = numaMakeConstant(aveval, n2); | |
| 1596 numaArithOp(na2, na2, na3, L_ARITH_DIVIDE); | |
| 1597 numaDestroy(&na3); | |
| 1598 if (dew->debug) { | |
| 1599 L_INFO("Average background density: %5.1f\n", __func__, aveval); | |
| 1600 gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/dew/0093", NULL); | |
| 1601 lept_mv("/tmp/lept/dew/0093.png", "lept/dewmod", NULL, NULL); | |
| 1602 } | |
| 1603 | |
| 1604 /* Fit the normalized density curve to a quartic */ | |
| 1605 pta1 = numaConvertToPta1(na2); | |
| 1606 ptaWriteStream(stderr, pta1, 0); | |
| 1607 /* ptaGetQuadraticLSF(pta1, NULL, NULL, NULL, &na3); */ | |
| 1608 ptaGetQuarticLSF(pta1, &ca, &cb, &cc, &cd, &ce, &na3); | |
| 1609 ptaGetArrays(pta1, &na4, NULL); | |
| 1610 if (dew->debug) { | |
| 1611 gplot = gplotSimpleXY1(na4, na3, GPLOT_LINES, GPLOT_PNG, | |
| 1612 "/tmp/lept/dew/0094", NULL); | |
| 1613 gplotDestroy(&gplot); | |
| 1614 lept_mv("/tmp/lept/dew/0094.png", "lept/dewmod", NULL, NULL); | |
| 1615 } | |
| 1616 ptaDestroy(&pta1); | |
| 1617 | |
| 1618 /* Integrate from the high point down to 1 (or v.v) to get the | |
| 1619 * disparity needed to make the density constant. */ | |
| 1620 nasum = numaMakeConstant(0, w); /* area under the curve above 1.0 */ | |
| 1621 if (parity == 0) { | |
| 1622 for (i = n2 - 1; i >= 0; i--) { | |
| 1623 numaGetFValue(na3, i, &fval); | |
| 1624 if (fval < 1.0) break; | |
| 1625 } | |
| 1626 numaGetIValue(na4, i + 1, &x0); | |
| 1627 numaGetIValue(na4, n2 - 1, &x1); | |
| 1628 numaSetParameters(nasum, x0, 1); | |
| 1629 sum = 0.0; | |
| 1630 for (x = x0; x < x1; x++) { | |
| 1631 applyQuarticFit(ca, cb, cc, cd, ce, (l_float32)x, &y); | |
| 1632 sum += (y - 1.0); | |
| 1633 numaReplaceNumber(nasum, x, sum); | |
| 1634 } | |
| 1635 for (x = x1; x < w; x++) | |
| 1636 numaReplaceNumber(nasum, x, sum); | |
| 1637 } else { /* parity == 1 */ | |
| 1638 for (i = 0; i < n2; i++) { | |
| 1639 numaGetFValue(na3, i, &fval); | |
| 1640 if (fval < 1.0) break; | |
| 1641 } | |
| 1642 numaGetIValue(na4, 0, &x0); | |
| 1643 numaGetIValue(na4, i - 1, &x1); | |
| 1644 numaSetParameters(nasum, x0, 1); | |
| 1645 sum = 0.0; | |
| 1646 for (x = x1; x >= x0; x--) { | |
| 1647 applyQuarticFit(ca, cb, cc, cd, ce, (l_float32)x, &y); | |
| 1648 sum += (y - 1.0); | |
| 1649 numaReplaceNumber(nasum, x, sum); | |
| 1650 } | |
| 1651 for (x = x0; x >= 0; x--) | |
| 1652 numaReplaceNumber(nasum, x, sum); | |
| 1653 } | |
| 1654 | |
| 1655 /* Save the result in a fpix at the specified subsampling */ | |
| 1656 nx = dew->nx; | |
| 1657 ny = dew->ny; | |
| 1658 fpix = fpixCreate(nx, ny); | |
| 1659 del = (l_float32)w / (l_float32)nx; | |
| 1660 for (i = 0; i < ny; i++) { | |
| 1661 for (j = 0; j < nx; j++) { | |
| 1662 x = del * j; | |
| 1663 numaGetFValue(nasum, x, &fval); | |
| 1664 fpixSetPixel(fpix, j, i, fval); | |
| 1665 } | |
| 1666 } | |
| 1667 dew->sampydispar = fpix; | |
| 1668 dew->ysuccess = 1; | |
| 1669 | |
| 1670 numaDestroy(&na2); | |
| 1671 numaDestroy(&na3); | |
| 1672 numaDestroy(&na4); | |
| 1673 numaDestroy(&nasum); | |
| 1674 return 0; | |
| 1675 } | |
| 1676 | |
| 1677 | |
| 1678 /*----------------------------------------------------------------------* | |
| 1679 * Build line disparity model * | |
| 1680 *----------------------------------------------------------------------*/ | |
| 1681 /*! | |
| 1682 * \brief dewarpBuildLineModel() | |
| 1683 * | |
| 1684 * \param[in] dew | |
| 1685 * \param[in] opensize size of opening to remove perpendicular lines | |
| 1686 * \param[in] debugfile use NULL to skip writing this | |
| 1687 * \return 0 if OK, 1 if unable to build the model or on error | |
| 1688 * | |
| 1689 * <pre> | |
| 1690 * Notes: | |
| 1691 * (1) This builds the horizontal and vertical disparity arrays | |
| 1692 * for an input of ruled lines, typically for calibration. | |
| 1693 * In book scanning, you could lay the ruled paper over a page. | |
| 1694 * Then for that page and several below it, you can use the | |
| 1695 * disparity correction of the line model to dewarp the pages. | |
| 1696 * (2) The dew has been initialized with the image of ruled lines. | |
| 1697 * These lines must be continuous, but we do a small amount | |
| 1698 * of pre-processing here to insure that. | |
| 1699 * (3) %opensize is typically about 8. It must be larger than | |
| 1700 * the thickness of the lines to be extracted. This is the | |
| 1701 * default value, which is applied if %opensize < 3. | |
| 1702 * (4) Sets vsuccess = 1 and hsuccess = 1 if the vertical and/or | |
| 1703 * horizontal disparity arrays build. | |
| 1704 * (5) Similar to dewarpBuildPageModel(), except here the vertical | |
| 1705 * and horizontal disparity arrays are both built from ruled lines. | |
| 1706 * See notes there. | |
| 1707 * </pre> | |
| 1708 */ | |
| 1709 l_ok | |
| 1710 dewarpBuildLineModel(L_DEWARP *dew, | |
| 1711 l_int32 opensize, | |
| 1712 const char *debugfile) | |
| 1713 { | |
| 1714 char buf[64]; | |
| 1715 l_int32 i, j, bx, by, ret, nlines; | |
| 1716 BOXA *boxa; | |
| 1717 PIX *pixs, *pixh, *pixv, *pix, *pix1, *pix2; | |
| 1718 PIXA *pixa1, *pixa2; | |
| 1719 PTA *pta; | |
| 1720 PTAA *ptaa1, *ptaa2; | |
| 1721 | |
| 1722 if (!dew) | |
| 1723 return ERROR_INT("dew not defined", __func__, 1); | |
| 1724 if (opensize < 3) { | |
| 1725 L_WARNING("opensize should be >= 3; setting to 8\n", __func__); | |
| 1726 opensize = 8; /* default */ | |
| 1727 } | |
| 1728 | |
| 1729 dew->debug = (debugfile) ? 1 : 0; | |
| 1730 dew->vsuccess = dew->hsuccess = 0; | |
| 1731 pixs = dew->pixs; | |
| 1732 if (debugfile) { | |
| 1733 lept_rmdir("lept/dewline"); /* erase previous images */ | |
| 1734 lept_mkdir("lept/dewline"); | |
| 1735 lept_rmdir("lept/dewmod"); /* erase previous images */ | |
| 1736 lept_mkdir("lept/dewmod"); | |
| 1737 lept_mkdir("lept/dewarp"); | |
| 1738 pixDisplayWithTitle(pixs, 0, 0, "pixs", 1); | |
| 1739 pixWriteDebug("/tmp/lept/dewline/001.png", pixs, IFF_PNG); | |
| 1740 } | |
| 1741 | |
| 1742 /* Extract and solidify the horizontal and vertical lines. We use | |
| 1743 * the horizontal lines to derive the vertical disparity, and v.v. | |
| 1744 * Both disparities are computed using the vertical disparity | |
| 1745 * algorithm; the horizontal disparity is found from the | |
| 1746 * vertical lines by rotating them clockwise by 90 degrees. | |
| 1747 * On the first pass, we compute the horizontal disparity, from | |
| 1748 * the vertical lines, by rotating them by 90 degrees (so they | |
| 1749 * are horizontal) and computing the vertical disparity on them; | |
| 1750 * we rotate the resulting fpix array for the horizontal disparity | |
| 1751 * back by -90 degrees. On the second pass, we compute the vertical | |
| 1752 * disparity from the horizontal lines in the usual fashion. */ | |
| 1753 snprintf(buf, sizeof(buf), "d1.3 + c%d.1 + o%d.1", opensize - 2, opensize); | |
| 1754 pixh = pixMorphSequence(pixs, buf, 0); /* horiz */ | |
| 1755 snprintf(buf, sizeof(buf), "d3.1 + c1.%d + o1.%d", opensize - 2, opensize); | |
| 1756 pix1 = pixMorphSequence(pixs, buf, 0); /* vert */ | |
| 1757 pixv = pixRotateOrth(pix1, 1); /* vert rotated to horizontal */ | |
| 1758 pixa1 = pixaCreate(2); | |
| 1759 pixaAddPix(pixa1, pixv, L_INSERT); /* get horizontal disparity first */ | |
| 1760 pixaAddPix(pixa1, pixh, L_INSERT); | |
| 1761 pixDestroy(&pix1); | |
| 1762 | |
| 1763 /*--------------------------------------------------------------*/ | |
| 1764 /* Process twice: first for horiz disparity, then for vert */ | |
| 1765 /*--------------------------------------------------------------*/ | |
| 1766 for (i = 0; i < 2; i++) { | |
| 1767 pix = pixaGetPix(pixa1, i, L_CLONE); | |
| 1768 pixDisplay(pix, 0, 900); | |
| 1769 boxa = pixConnComp(pix, &pixa2, 8); | |
| 1770 nlines = boxaGetCount(boxa); | |
| 1771 boxaDestroy(&boxa); | |
| 1772 if (nlines < dew->minlines) { | |
| 1773 L_WARNING("only found %d lines\n", __func__, nlines); | |
| 1774 pixDestroy(&pix); | |
| 1775 pixaDestroy(&pixa1); | |
| 1776 continue; | |
| 1777 } | |
| 1778 | |
| 1779 /* Identify the pixels along the skeleton of each line */ | |
| 1780 ptaa1 = ptaaCreate(nlines); | |
| 1781 for (j = 0; j < nlines; j++) { | |
| 1782 pixaGetBoxGeometry(pixa2, j, &bx, &by, NULL, NULL); | |
| 1783 pix1 = pixaGetPix(pixa2, j, L_CLONE); | |
| 1784 pta = dewarpGetMeanVerticals(pix1, bx, by); | |
| 1785 ptaaAddPta(ptaa1, pta, L_INSERT); | |
| 1786 pixDestroy(&pix1); | |
| 1787 } | |
| 1788 pixaDestroy(&pixa2); | |
| 1789 if (debugfile) { | |
| 1790 pix1 = pixConvertTo32(pix); | |
| 1791 pix2 = pixDisplayPtaa(pix1, ptaa1); | |
| 1792 snprintf(buf, sizeof(buf), "/tmp/lept/dewline/%03d.png", 2 + 2 * i); | |
| 1793 pixWriteDebug(buf, pix2, IFF_PNG); | |
| 1794 pixDestroy(&pix1); | |
| 1795 pixDestroy(&pix2); | |
| 1796 } | |
| 1797 | |
| 1798 /* Remove all lines that are not at least 0.75 times the length | |
| 1799 * of the longest line. */ | |
| 1800 ptaa2 = dewarpRemoveShortLines(pix, ptaa1, 0.75, DEBUG_SHORT_LINES); | |
| 1801 if (debugfile) { | |
| 1802 pix1 = pixConvertTo32(pix); | |
| 1803 pix2 = pixDisplayPtaa(pix1, ptaa2); | |
| 1804 snprintf(buf, sizeof(buf), "/tmp/lept/dewline/%03d.png", 3 + 2 * i); | |
| 1805 pixWriteDebug(buf, pix2, IFF_PNG); | |
| 1806 pixDestroy(&pix1); | |
| 1807 pixDestroy(&pix2); | |
| 1808 } | |
| 1809 ptaaDestroy(&ptaa1); | |
| 1810 nlines = ptaaGetCount(ptaa2); | |
| 1811 if (nlines < dew->minlines) { | |
| 1812 pixDestroy(&pix); | |
| 1813 ptaaDestroy(&ptaa2); | |
| 1814 L_WARNING("%d lines: too few to build model\n", __func__, nlines); | |
| 1815 continue; | |
| 1816 } | |
| 1817 | |
| 1818 /* Get the sampled 'vertical' disparity from the textline | |
| 1819 * centers. The disparity array will push pixels vertically | |
| 1820 * so that each line is flat and centered at the y-position | |
| 1821 * of the mid-point. */ | |
| 1822 ret = dewarpFindVertDisparity(dew, ptaa2, 1 - i); | |
| 1823 | |
| 1824 /* If i == 0, move the result to the horizontal disparity, | |
| 1825 * rotating it back by -90 degrees. */ | |
| 1826 if (i == 0) { /* horizontal disparity, really */ | |
| 1827 if (ret) { | |
| 1828 L_WARNING("horizontal disparity not built\n", __func__); | |
| 1829 } else { | |
| 1830 L_INFO("hsuccess = 1\n", __func__); | |
| 1831 dew->samphdispar = fpixRotateOrth(dew->sampvdispar, 3); | |
| 1832 fpixDestroy(&dew->sampvdispar); | |
| 1833 if (debugfile) | |
| 1834 lept_mv("/tmp/lept/dewarp/vert_disparity.pdf", | |
| 1835 "lept/dewarp", "horiz_disparity.pdf", NULL); | |
| 1836 } | |
| 1837 dew->hsuccess = dew->vsuccess; | |
| 1838 dew->vsuccess = 0; | |
| 1839 } else { /* i == 1 */ | |
| 1840 if (ret) | |
| 1841 L_WARNING("vertical disparity not built\n", __func__); | |
| 1842 else | |
| 1843 L_INFO("vsuccess = 1\n", __func__); | |
| 1844 } | |
| 1845 ptaaDestroy(&ptaa2); | |
| 1846 pixDestroy(&pix); | |
| 1847 } | |
| 1848 pixaDestroy(&pixa1); | |
| 1849 | |
| 1850 /* Debug output */ | |
| 1851 if (debugfile) { | |
| 1852 if (dew->vsuccess == 1) { | |
| 1853 dewarpPopulateFullRes(dew, NULL, 0, 0); | |
| 1854 pix1 = fpixRenderContours(dew->fullvdispar, 3.0f, 0.15f); | |
| 1855 pixWriteDebug("/tmp/lept/dewline/006.png", pix1, IFF_PNG); | |
| 1856 pixDisplay(pix1, 1000, 0); | |
| 1857 pixDestroy(&pix1); | |
| 1858 } | |
| 1859 if (dew->hsuccess == 1) { | |
| 1860 pix1 = fpixRenderContours(dew->fullhdispar, 3.0f, 0.15f); | |
| 1861 pixWriteDebug("/tmp/lept/dewline/007.png", pix1, IFF_PNG); | |
| 1862 pixDisplay(pix1, 1000, 0); | |
| 1863 pixDestroy(&pix1); | |
| 1864 } | |
| 1865 convertFilesToPdf("/tmp/lept/dewline", NULL, 135, 1.0, 0, 0, | |
| 1866 "Dewarp Build Line Model", debugfile); | |
| 1867 lept_stderr("pdf file: %s\n", debugfile); | |
| 1868 } | |
| 1869 | |
| 1870 return 0; | |
| 1871 } | |
| 1872 | |
| 1873 | |
| 1874 /*----------------------------------------------------------------------* | |
| 1875 * Query model status * | |
| 1876 *----------------------------------------------------------------------*/ | |
| 1877 /*! | |
| 1878 * \brief dewarpaModelStatus() | |
| 1879 * | |
| 1880 * \param[in] dewa | |
| 1881 * \param[in] pageno | |
| 1882 * \param[out] pvsuccess [optional] 1 on success | |
| 1883 * \param[out] phsuccess [optional] 1 on success | |
| 1884 * \return 0 if OK, 1 on error | |
| 1885 * | |
| 1886 * <pre> | |
| 1887 * Notes: | |
| 1888 * (1) This tests if a model has been built, not if it is valid. | |
| 1889 * </pre> | |
| 1890 */ | |
| 1891 l_ok | |
| 1892 dewarpaModelStatus(L_DEWARPA *dewa, | |
| 1893 l_int32 pageno, | |
| 1894 l_int32 *pvsuccess, | |
| 1895 l_int32 *phsuccess) | |
| 1896 { | |
| 1897 L_DEWARP *dew; | |
| 1898 | |
| 1899 if (pvsuccess) *pvsuccess = 0; | |
| 1900 if (phsuccess) *phsuccess = 0; | |
| 1901 if (!dewa) | |
| 1902 return ERROR_INT("dewa not defined", __func__, 1); | |
| 1903 | |
| 1904 if ((dew = dewarpaGetDewarp(dewa, pageno)) == NULL) | |
| 1905 return ERROR_INT("dew not retrieved", __func__, 1); | |
| 1906 if (pvsuccess) *pvsuccess = dew->vsuccess; | |
| 1907 if (phsuccess) *phsuccess = dew->hsuccess; | |
| 1908 return 0; | |
| 1909 } | |
| 1910 | |
| 1911 | |
| 1912 /*----------------------------------------------------------------------* | |
| 1913 * Rendering helpers * | |
| 1914 *----------------------------------------------------------------------*/ | |
| 1915 /*! | |
| 1916 * \brief pixRenderMidYs() | |
| 1917 * | |
| 1918 * \param[in] pixs 32 bpp | |
| 1919 * \param[in] namidys y location of reference lines for vertical disparity | |
| 1920 * \param[in] linew width of rendered line; typ 2 | |
| 1921 * \return 0 if OK, 1 on error | |
| 1922 */ | |
| 1923 static l_int32 | |
| 1924 pixRenderMidYs(PIX *pixs, | |
| 1925 NUMA *namidys, | |
| 1926 l_int32 linew) | |
| 1927 { | |
| 1928 l_int32 i, n, w, yval, rval, gval, bval; | |
| 1929 PIXCMAP *cmap; | |
| 1930 | |
| 1931 if (!pixs) | |
| 1932 return ERROR_INT("pixs not defined", __func__, 1); | |
| 1933 if (!namidys) | |
| 1934 return ERROR_INT("namidys not defined", __func__, 1); | |
| 1935 | |
| 1936 w = pixGetWidth(pixs); | |
| 1937 n = numaGetCount(namidys); | |
| 1938 cmap = pixcmapCreateRandom(8, 0, 0); | |
| 1939 for (i = 0; i < n; i++) { | |
| 1940 pixcmapGetColor(cmap, i % 256, &rval, &gval, &bval); | |
| 1941 numaGetIValue(namidys, i, &yval); | |
| 1942 pixRenderLineArb(pixs, 0, yval, w, yval, linew, rval, gval, bval); | |
| 1943 } | |
| 1944 pixcmapDestroy(&cmap); | |
| 1945 return 0; | |
| 1946 } | |
| 1947 | |
| 1948 | |
| 1949 /*! | |
| 1950 * \brief pixRenderHorizEndPoints() | |
| 1951 * | |
| 1952 * \param[in] pixs 32 bpp | |
| 1953 * \param[in] ptal left side line end points | |
| 1954 * \param[in] ptar right side line end points | |
| 1955 * \param[in] color 0xrrggbb00 | |
| 1956 * \return 0 if OK, 1 on error | |
| 1957 */ | |
| 1958 static l_int32 | |
| 1959 pixRenderHorizEndPoints(PIX *pixs, | |
| 1960 PTA *ptal, | |
| 1961 PTA *ptar, | |
| 1962 l_uint32 color) | |
| 1963 { | |
| 1964 PIX *pixcirc; | |
| 1965 PTA *ptalt, *ptart, *ptacirc; | |
| 1966 | |
| 1967 if (!pixs) | |
| 1968 return ERROR_INT("pixs not defined", __func__, 1); | |
| 1969 if (!ptal || !ptar) | |
| 1970 return ERROR_INT("ptal and ptar not both defined", __func__, 1); | |
| 1971 | |
| 1972 ptacirc = generatePtaFilledCircle(5); | |
| 1973 pixcirc = pixGenerateFromPta(ptacirc, 11, 11); | |
| 1974 ptalt = ptaTranspose(ptal); | |
| 1975 ptart = ptaTranspose(ptar); | |
| 1976 | |
| 1977 pixDisplayPtaPattern(pixs, pixs, ptalt, pixcirc, 5, 5, color); | |
| 1978 pixDisplayPtaPattern(pixs, pixs, ptart, pixcirc, 5, 5, color); | |
| 1979 ptaDestroy(&ptacirc); | |
| 1980 ptaDestroy(&ptalt); | |
| 1981 ptaDestroy(&ptart); | |
| 1982 pixDestroy(&pixcirc); | |
| 1983 return 0; | |
| 1984 } |
