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 }