gocr-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +21 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +49 -0
  8. data/ext/gocr/Makefile +141 -0
  9. data/ext/gocr/Makefile.in +140 -0
  10. data/ext/gocr/amiga.h +31 -0
  11. data/ext/gocr/barcode.c +2108 -0
  12. data/ext/gocr/barcode.h +11 -0
  13. data/ext/gocr/box.c +496 -0
  14. data/ext/gocr/config.h +37 -0
  15. data/ext/gocr/config.h.in +36 -0
  16. data/ext/gocr/database.c +468 -0
  17. data/ext/gocr/detect.c +1003 -0
  18. data/ext/gocr/extconf.rb +6 -0
  19. data/ext/gocr/gocr.c +436 -0
  20. data/ext/gocr/gocr.h +290 -0
  21. data/ext/gocr/jconv.c +168 -0
  22. data/ext/gocr/job.c +92 -0
  23. data/ext/gocr/lines.c +364 -0
  24. data/ext/gocr/list.c +334 -0
  25. data/ext/gocr/list.h +91 -0
  26. data/ext/gocr/ocr0.c +7312 -0
  27. data/ext/gocr/ocr0.h +63 -0
  28. data/ext/gocr/ocr0n.c +1527 -0
  29. data/ext/gocr/ocr1.c +85 -0
  30. data/ext/gocr/ocr1.h +3 -0
  31. data/ext/gocr/otsu.c +310 -0
  32. data/ext/gocr/otsu.h +23 -0
  33. data/ext/gocr/output.c +291 -0
  34. data/ext/gocr/output.h +37 -0
  35. data/ext/gocr/pcx.c +153 -0
  36. data/ext/gocr/pcx.h +9 -0
  37. data/ext/gocr/pgm2asc.c +3259 -0
  38. data/ext/gocr/pgm2asc.h +105 -0
  39. data/ext/gocr/pixel.c +538 -0
  40. data/ext/gocr/pnm.c +538 -0
  41. data/ext/gocr/pnm.h +35 -0
  42. data/ext/gocr/progress.c +87 -0
  43. data/ext/gocr/progress.h +42 -0
  44. data/ext/gocr/remove.c +715 -0
  45. data/ext/gocr/tga.c +87 -0
  46. data/ext/gocr/tga.h +6 -0
  47. data/ext/gocr/unicode.c +1318 -0
  48. data/ext/gocr/unicode.h +62 -0
  49. data/ext/gocr/unicode_defs.h +1245 -0
  50. data/ext/gocr/version.h +2 -0
  51. data/gocr-ruby.gemspec +28 -0
  52. data/image.png +0 -0
  53. data/lib/gocr.rb +6 -0
  54. data/lib/gocr/image.rb +8 -0
  55. data/lib/gocr/version.rb +3 -0
  56. metadata +156 -0
@@ -0,0 +1,1003 @@
1
+ /*
2
+ This is a Optical-Character-Recognition program
3
+ Copyright (C) 2000-2010 Joerg Schulenburg
4
+
5
+ This program is free software; you can redistribute it and/or
6
+ modify it under the terms of the GNU General Public License
7
+ as published by the Free Software Foundation; either version 2
8
+ of the License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the Free Software
17
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ check README for my email address
20
+ */
21
+
22
+ #include <stdlib.h>
23
+ #include <stdio.h>
24
+ #include <string.h>
25
+ #include <ctype.h> // toupper, tolower
26
+ #include "pgm2asc.h"
27
+ #include "unicode_defs.h" /* JS.Aug2010 PICTURE def */
28
+ #include "gocr.h"
29
+
30
+ // ----- detect lines ---------------
31
+ /* suggestion: Fourier transform and set line frequency where the
32
+ amplitude has a maximum (JS: slow and not smarty enough).
33
+
34
+ option: range for line numbers 1..1000 or similar
35
+ todo: look for thickest line, and divide if thickness=2*mean_thickness
36
+ Set these elements of the box structs:
37
+
38
+ m1 <-- top of upper case letters and (bdfhkl) (may vary OCGA < HKXMNPDEF..)
39
+ m2 <-- top of letters (acegmnopqrsuvwxyz) (round litle higher than flat)
40
+ m3 <-- baseline
41
+ m4 <-- bottom of hanging letters (gqpy)
42
+
43
+ http://en.wikipedia.org/wiki/Cap_height = 0.70em(?) = 3..12..72pt
44
+ 1pt = 0.0139 inch or 0.3528 mm
45
+ 10pt-font at 72dpi = 10 pixel, 10pt at 96 dpi = 13 pixel (13 PPEm)
46
+ ascender heigh (top of OAh = round or pointed letters, O=+..3%,A=+..5%)
47
+ cap high (top of HI = flat capital letters)
48
+ median (top of x,
49
+ curved letters like "acemnor" may exceed x-height)
50
+ baseline (base of x)
51
+ descender height (bottom of p)
52
+ x-heigh = 1ex (mostly 1ex=0.5em)
53
+
54
+
55
+ performance can be improved by working with a temporary
56
+ list of boxes of the special text line
57
+
58
+ - Jun23,00 more robustness of m3 (test liebfrau1)
59
+ - Feb01,02 more robustness of m4 (test s46_084.pgm)
60
+ - Dec03,12 fix problems with footnotes
61
+ ToDo:
62
+ - generate lists of boxes per line (faster access)
63
+ - use statistics
64
+ - for each box look at it neighbours and set box-m1..m4
65
+ - m[1..4].max .min if m4.min-m3.max<1 probability lower
66
+ */
67
+ int detect_lines1(pix * p, int x0, int y0, int dx, int dy)
68
+ {
69
+ job_t *job=OCR_JOB; /* fixme global var */
70
+ int i, jj, j2, y, yy, my, mi, mc, i1, i2, i3, i4,
71
+ m1, m2, m3, m4, ma1, ma2, ma3, ma4, m3pre, m4pre;
72
+ struct box *box2, *box3; /* box3 is for verbose / debugging */
73
+ struct tlines *lines = &job->res.lines;
74
+
75
+ /* ToDo: optional read line-data from external source??? */
76
+ if (lines->num == 0) { // initialize one dummy-line for pictures etc.
77
+ lines->m4[0] = 0;
78
+ lines->m3[0] = 0;
79
+ lines->m2[0] = 0;
80
+ lines->m1[0] = 0;
81
+ lines->x0[0] = p->x; /* expand to left end during detection */
82
+ lines->x1[0] = 0; /* expand to right end */
83
+ lines->pitch[0] = job->cfg.spc; /* default word pitch */
84
+ lines->mono[0] = 0; /* default spacing, 0 = prop */
85
+ lines->num++;
86
+ }
87
+ i = lines->num;
88
+ if (dy < 4)
89
+ return 0; /* image is to low for latin chars */
90
+ my = jj = 0;
91
+ // get the mean height of all hollow chars
92
+ // (better than mean value of everything including bg-pattern or dust?)
93
+ for_each_data(&(job->res.boxlist)) {
94
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
95
+ if ( box2->c != PICTURE
96
+ && box2->num_frames>1 && box2->num_frames<4 /* 1 or 2 holes */
97
+ && box2->y0 >= y0 && box2->y1 <= y0 + dy
98
+ && box2->x0 >= x0 && box2->x1 <= x0 + dx
99
+ && box2->frame_vol[0]>0
100
+ && box2->frame_vol[1]<0
101
+ ) {
102
+ jj++;
103
+ my += box2->y1 - box2->y0 + 1;
104
+ }
105
+ } end_for_each(&(job->res.boxlist));
106
+ if (jj==0) {
107
+ // get the mean height of all chars
108
+ for_each_data(&(job->res.boxlist)) {
109
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
110
+ if ( box2->c != PICTURE
111
+ && box2->y1 - box2->y0 + 1 >= 4 /* 4x6 font */
112
+ && box2->y0 >= y0 && box2->y1 <= y0 + dy
113
+ && box2->x0 >= x0 && box2->x1 <= x0 + dx ) {
114
+ jj++;
115
+ my += box2->y1 - box2->y0 + 1;
116
+ }
117
+ } end_for_each(&(job->res.boxlist));
118
+ }
119
+ if (jj == 0)
120
+ return 0; /* no chars detected */
121
+
122
+
123
+ /* ToDo: a better way could be to mark good boxes (of typical high a-zA-Z0-9)
124
+ * first and handle only marked boxes for line scan, exclude ?!,.:;etc
125
+ * but without setect the chars itself (using good statistics)
126
+ * see adjust_text_lines()
127
+ * ToDo: check for monospaced dy ???
128
+ */
129
+ my /= jj; /* we only care about chars with high arround my */
130
+ if (job->cfg.verbose & 16)
131
+ fprintf(stderr,"\n# detect_lines1(%d %d %d %d) vvv&16 chars=%d my=%d\n# ",
132
+ x0, y0, dx, dy, jj, my);
133
+ // "my" is the average over the whole image (bad, if different fontsizes)
134
+
135
+ if (my < 4)
136
+ return 0; /* mean high is to small => error */
137
+
138
+ m4pre=m3pre=y0; /* lower bond of upper line */
139
+ // better function for scanning line around a letter ???
140
+ // or define lines around known chars "eaTmM"
141
+ for (j2 = y = y0; y < y0 + dy; y++) {
142
+ // look for max. of upper and lower bound of next line
143
+ m1 = y0 + dy;
144
+ jj = 0;
145
+ #if 1
146
+ /* this is only for test runs */
147
+ if (job->cfg.verbose & 16)
148
+ fprintf(stderr,"searching new line %d\n# ",i /* lines->num */);
149
+ #endif
150
+
151
+ box3 = NULL; /* mark the most upper box starting next line */
152
+ // find highest point of next line => store to m1-min (m1>=y)
153
+ // only objects greater 2/3*my and smaller 3*my are allowed
154
+ // a higher "!" at end of line can result in a to low m1
155
+ for_each_data(&(job->res.boxlist)) {
156
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
157
+ if (box2->line>0 || box2->c == PICTURE) continue;
158
+ if (lines->dx)
159
+ yy = lines->dy * box2->x0 / (lines->dx); /* correct crooked lines */
160
+ else yy=0;
161
+ if ( box2->y0 >= y + yy && box2->y1 < y0 + dy // lower than y
162
+ && box2->x0 >= x0 && box2->x1 < x0 + dx // within box ?
163
+ && box2->x1 - box2->x0 + 1 > 2 // 2010-10-01
164
+ && box2->c != PICTURE // no picture
165
+ && box2->num_boxes <= 1 // ignore 2 for "!?i" 3 for "&auml;"
166
+ && 3 * (box2->y1 - box2->y0) > 2 * my // not to small
167
+ && (box2->y1 - box2->y0) < 3 * my // not to big
168
+ && (box2->y1 - box2->y0) > 4) // minimum absolute size
169
+ {
170
+ if (box2->y0 < m1 + yy) {
171
+ m1 = box2->y0 - yy; /* highest upper boundary */
172
+ box3 = box2;
173
+ }
174
+ // fprintf(stderr,"\n %3d %3d %+3d %d m1= %3d",
175
+ // box2->x0, box2->y0, box2->y1 - box2->y0 + 1, box2->num_boxes, m1);
176
+ }
177
+ } end_for_each(&(job->res.boxlist));
178
+ if (!box3 || m1 >= y0+dy) break; /* no further line found */
179
+ if (job->cfg.verbose & 16)
180
+ fprintf(stderr," most upper box at new line xy= %4d %4d %+4d %+4d\n# ",
181
+ box3->x0, box3->y0, box3->x1-box3->x0, box3->y1-box3->y0);
182
+
183
+ // at the moment values depend from single chars, which can
184
+ // result in bad values (ex: 4x6 /\=)
185
+ // ToDo: 2) mean size of next line (store list of y0,y1)
186
+ // ToDo: 3) count num0[(y0-m1)*16/my], num1[(y1-m1)*16/my]
187
+ // ToDo: or down-top search horizontal nerarest neighbours
188
+ lines->x0[i] = x0 + dx - 1; /* expand during operation to left end */
189
+ lines->x1[i] = x0; /* expand to the right end of line */
190
+ m4=m2=m1; mi=m1+my; m3=m1+2*my; jj=0;
191
+ // find limits for upper bound, base line and ground line
192
+ // m2-max m3-min m4-max
193
+ for_each_data(&(job->res.boxlist)) {
194
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
195
+ if (box2->line>0 || box2->c == PICTURE) continue;
196
+ if ( box2->y0 < y0 || box2->y1 >= y0 + dy
197
+ || box2->x0 < x0 || box2->x1 >= x0 + dx ) continue; // out of image
198
+ if (lines->dx) yy = lines->dy * box2->x0 / (lines->dx);
199
+ else yy = 0;
200
+
201
+ if ( box2->y0 >= y + yy // lower than y
202
+ && box2->y1 <= y + yy + 3*my
203
+ && box2->y1 <= m4 + 2*my/3) { // 2010-10-08
204
+ if (lines->x1[i] < box2->x1)
205
+ lines->x1[i] = box2->x1; /* right end */
206
+ if (lines->x0[i] > box2->x0)
207
+ lines->x0[i] = box2->x0; /* left end */
208
+ }
209
+ /* check for ij-dots, used if chars of same high */
210
+ if ( box2->y1 - box2->y0 + 1 <= 2*my // ~ barcode or small images
211
+ && box2->y0 >= y + yy
212
+ // && box2->y0 >= y
213
+ && box2->y1 < m1 + yy + my/4
214
+ && box2->y0 < mi + yy
215
+ ) {
216
+ mi = box2->y0 - yy; /* highest upper boundary i-dot */
217
+ }
218
+ // fprintf(stderr,"\n check %3d %3d-%3d y=%d yy=%d m1=%d", box2->x0, box2->y0, box2->y1, y, yy, m1);
219
+ /* get m2-max m3-min m4-max */
220
+ if ( box2->y0 >= y + yy // lower than y
221
+ && 3 * (box2->y1 - box2->y0 + 1) > 2 * my // right size ?
222
+ && (box2->y1 - box2->y0 + 1) < 3 * my // font mix, size = 2.6*my
223
+ && (box2->y1 - box2->y0 + 1) > 3 // 4x6 lowercase=4
224
+ && box2->y0 >= m1 - my/4 // in m1 range?
225
+ && box2->y0 <= m1 + yy + 9 * my / 8 // my can be to small if mixed
226
+ // ToDo: we need a better (local?) algorithm for big headlines > 2*my
227
+ && box2->y1 <= m1 + yy + 3 * my
228
+ && box2->y1 >= m1 + yy + my / 2
229
+ // lines can differ in high, my may be to small (smaller headlines)
230
+ && box2->y0+box2->y1 <= 2*(box3->y0+my) // box3 = highest box
231
+ )
232
+ {
233
+ jj++; // count chars for debugging purpose
234
+ if (box2->y0 > m2 + yy) {
235
+ m2 = box2->y0 - yy; /* highest upper boundary */
236
+ if (job->cfg.verbose & 16)
237
+ fprintf(stderr," set m2= %3d %+3d yy= %d reason1 xy= %4d %4d\n# ",
238
+ m2, m2-m1, yy, box2->x0, box2->y0);
239
+ }
240
+ if (box2->y1 > m4 + yy && (my>6 || box2->y1 < m3+my)) {
241
+ m4 = box2->y1 - yy; /* lowest lower boundary, small font lines can touch */
242
+ if (job->cfg.verbose & 16)
243
+ fprintf(stderr," set m4= %3d %+3d yy= %d reason1 xy= %4d %4d\n# ",
244
+ m4, m4-m1, yy, box2->x0, box2->y0);
245
+ }
246
+ if ( box2->y1 < m3 + yy
247
+ && ( ( 2*box2->y1 > m2+ m4+yy && m2>m1)
248
+ || ( 4*box2->y1 > m1+3*m4+yy) ) ) // care for TeX: \(^1\)Footnote 2003
249
+ /* "'!?" could cause trouble here, therefore this lines */
250
+ /* ToDo: get_bw costs time, check pre and next */
251
+ if( get_bw(box2->x0,box2->x1,box2->y1+1 ,box2->y1+my/2,box2->p,job->cfg.cs,1) == 0
252
+ || get_bw(box2->x0,box2->x1,box2->y1+my/2,box2->y1+my/2,box2->p,job->cfg.cs,1) == 1
253
+ || num_cross(box2->x0,box2->x1,(box2->y0+box2->y1)/2,(box2->y0+box2->y1)/2,box2->p,job->cfg.cs)>2 )
254
+ {
255
+ m3 = box2->y1 - yy; /* highest lower boundary */
256
+ if (job->cfg.verbose & 16)
257
+ fprintf(stderr," set m3= %3d %+3d yy= %d reason1 xy= %4d %4d\n# ",
258
+ m3, m3-m1, yy, box2->x0, box2->y0);
259
+ // printf("\n# set1 m3 m=%3d %+2d %+2d %+2d",m1,m2-m1,m3-m1,m4-m1);
260
+ // out_x(box2);
261
+ }
262
+ if (box2->y0 + box2->y1 > 2*(m3 + yy)
263
+ && box2->y1 < m4 + yy - my/4 -1
264
+ && box2->y1 >= (m2 + m4)/2 // care for TeX: \(^1\)Footnote 2003
265
+ && m2 > m1 ) // be sure to not use ', m2 must be ok
266
+ {
267
+ m3 = box2->y1 - yy; /* highest lower boundary */
268
+ if (job->cfg.verbose & 16)
269
+ fprintf(stderr," set m3= %3d %+3d yy= %d reason2 xy= %4d %4d\n# ",
270
+ m3, m3-m1, yy, box2->x0, box2->y0);
271
+ // printf("\n# set2 m3 m=%3d %+2d %+2d %+2d",m1,m2-m1,m3-m1,m4-m1);
272
+ // out_x(box2);
273
+ }
274
+ } // good middle size char
275
+ } end_for_each(&(job->res.boxlist));
276
+
277
+ // ToDo: use holes for m1==m2 and m3==m4
278
+ if (m3==m4 && i>1) { // use predecessor 2010-10-01
279
+ if (m3-m1==lines->m3[i-1]-lines->m1[i-1])
280
+ m4= m1+lines->m4[i-1]-lines->m1[i-1];
281
+ }
282
+
283
+ if (m1==m2 && i>1) { // use predecessor 2010-10-01
284
+ if (m3-m1==lines->m3[i-1]-lines->m1[i-1])
285
+ m2= m1+lines->m2[i-1]-lines->m1[i-1];
286
+ }
287
+
288
+ #if 1
289
+ /* this is only for test runs */
290
+ if (job->cfg.verbose & 16)
291
+ fprintf(stderr," L%02d step 1 y=%4d m= %4d %+3d %+3d %+3d x= %3d %+3d"
292
+ " my=%2d chars=%3d\n# ",
293
+ i, y, m1, m2-m1, m3-m1, m4-m1, lines->x0[i],
294
+ lines->x1[i] - lines->x0[i], my, jj);
295
+ #endif
296
+
297
+ if (m3 == m1)
298
+ break;
299
+ #if 1 /* make averages about the line */
300
+ // same again better estimation
301
+ mc = (3 * m3 + m1) / 4; /* lower center ? */
302
+ ma1 = ma2 = ma3 = ma4 = i1 = i2 = i3 = i4 = jj = 0;
303
+ for_each_data(&(job->res.boxlist)) {
304
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
305
+ if (box2->line>0 || box2->c == PICTURE) continue;
306
+ if (lines->dx) yy = lines->dy * box2->x0 / (lines->dx); else yy=0;
307
+ if (box2->y0 >= y + yy && box2->y1 < y0 + dy // lower than y
308
+ && box2->x0 >= x0 && box2->x1 < x0 + dx // in box ?
309
+ && box2->c != PICTURE // no picture
310
+ && 2 * (box2->y1 - box2->y0) > my // right size ?
311
+ && (box2->y1 - box2->y0) < 4 * my) {
312
+ if ( box2->y0 - yy >= m1-my/4
313
+ && box2->y0 - yy <= m2+my/4
314
+ && box2->y1 - yy >= m3-my/4
315
+ && box2->y1 - yy <= m4+my/4 ) { /* its within allowed range! */
316
+ // jj++; // not used
317
+ if (abs(box2->y0 - yy - m1) <= abs(box2->y0 - yy - m2))
318
+ { i1++; ma1 += box2->y0 - yy; }
319
+ else { i2++; ma2 += box2->y0 - yy; }
320
+ if (abs(box2->y1 - yy - m3) < abs(box2->y1 - yy - m4))
321
+ { i3++; ma3 += box2->y1 - yy; }
322
+ else { i4++; ma4 += box2->y1 - yy; }
323
+ if (box2->x1>lines->x1[i]) lines->x1[i] = box2->x1; /* right end */
324
+ if (box2->x0<lines->x0[i]) lines->x0[i] = box2->x0; /* left end */
325
+ }
326
+ }
327
+ } end_for_each(&(job->res.boxlist));
328
+
329
+ if (i1) m1 = (ma1+i1/2) / i1; /* best rounded */
330
+ if (i2) m2 = (ma2+i2/2) / i2;
331
+ if (i3) m3 = (ma3+i3-1) / i3; /* round up */
332
+ if (i4) m4 = (ma4+i4-1) / i4;
333
+ // printf("\n# .. set3 m3 m=%3d %+2d %+2d %+2d",m1,m2-m1,m3-m1,m4-m1);
334
+
335
+ #endif
336
+
337
+ /* expand right and left end of line */
338
+ for_each_data(&(job->res.boxlist)) {
339
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
340
+ if (box2->line>0 || box2->c == PICTURE) continue;
341
+ if (lines->dx) yy = lines->dy * box2->x0 / (lines->dx);
342
+ else yy = 0;
343
+ if ( 1
344
+ // && box2->y1 >= m1-1 && box2->y0 <= m3+2 // 0811qemu2 2010-10-01
345
+ // && box2->x1 >= lines->x0[i] - 6 - job->res.avX
346
+ // && box2->x0 <= lines->x1[i] + 6 + 4*job->res.avX // in box ?
347
+ && box2->x0 >= x0 && box2->x1 < x0 + dx // in box ?
348
+ && box2->y1 - box2->y0 <= 3*(m4 - m1)/2 // ~ vert. lines
349
+ && box2->c != PICTURE // no picture
350
+ && box2->y0 >= m1-(m4-m1+4)/4 // `` in 0811qemu2 2010-10-01
351
+ && box2->y0 <= m4+2
352
+ && box2->y1 >= m1-1 // -1 for `` in 0811qemu2
353
+ && box2->y1 <= m4+(m4-m1+4)/4 ) { /* its within line */
354
+ if (box2->x1>lines->x1[i]) lines->x1[i] = box2->x1; /* right end */
355
+ if (box2->x0<lines->x0[i]) lines->x0[i] = box2->x0; /* left end */
356
+ }
357
+ } end_for_each(&(job->res.boxlist));
358
+
359
+ #if 1
360
+ /* this is only for test runs */
361
+ if (job->cfg.verbose & 16)
362
+ fprintf(stderr," L%02d step 2 y=%4d m= %4d %+3d %+3d %+3d x= %3d %+3d\n# ",
363
+ i, y, m1, m2-m1, m3-m1, m4-m1,
364
+ lines->x0[i], lines->x1[i]-lines->x0[i]);
365
+ #endif
366
+
367
+ if (m4 == m1) {
368
+ if(m3+m4>2*y) y = (m4+m3)/2; /* lower end may overlap the next line */
369
+ continue;
370
+ }
371
+ jj=0;
372
+ lines->wt[i] = 100;
373
+ if (5 * (m2 - m1 +1) < m3 - m2 || (m2 - m1) < 2) jj|=1; /* same high */
374
+ if (5 * (m4 - m3 +1) < m3 - m2 || (m4 - m3) < 1) jj|=2; /* same base */
375
+ if (jj&1) lines->wt[i] = 75*lines->wt[i]/100;
376
+ if (jj&2) lines->wt[i] = 75*lines->wt[i]/100;
377
+ if (jj>0 && job->cfg.verbose) {
378
+ fprintf(stderr," L%02d w= %3d y= %3d m= %4d %+3d %+3d %+3d\n# ",i,lines->wt[i],y,m1,m2-m1,m3-m1,m4-m1);
379
+ fprintf(stderr," L%02d y= %3d i= %3d %3d %3d %3d (counts)\n# ",i,y,i1,i2,i3,i4);
380
+ if (jj==3) fprintf(stderr," all boxes of same high! mi=%d\n# ",mi-m1);
381
+ if (jj==1) fprintf(stderr," all boxes of same upper bound!\n# ");
382
+ if (jj==2) fprintf(stderr," all boxes of same lower bound!\n# ");
383
+ }
384
+ /* ToDo: check for dots ij,. to get the missing information */
385
+ #if 1
386
+ // ToDo: do this after all lines are detected using info of other lines?
387
+ // or use all chars to solve problem cases
388
+ if (mi<m1-(m4-m1)) mi=0; // mi failed
389
+ /* jj=3: ABCDEF123456 or mnmno or gqpy or lkhfdtb => we are in trouble */
390
+ /* 2010-10-06 my/8 to my/5, see examples/ocr-b */
391
+ if (jj==3 && (m4-m1)>=my) { jj=0; m2=m1+my/5+1; m4=m3+my/5+1; } /* ABC123 */
392
+ /* using idots, may fail on "ABCDEFG&Auml;&Uuml;&Ouml;" */
393
+ if (jj==3 && mi>0 && mi<m1 && mi>m4pre) { jj=2; m1=mi; } /* use ij dots */
394
+ if (jj==1 && m2-(m3-m2)/4>m3pre ) { /* expect: acegmnopqrsuvwxyz */
395
+ if (m1-m4pre<m4-m1) /* fails for 0123ABCD+Q$ */
396
+ m1 = ( m2 + m4pre ) / 2 ;
397
+ else
398
+ m1 = ( m2 - (m3 - m2) / 4 );
399
+ }
400
+ if (jj==3)
401
+ m2 = m1 + (m3 - m1) / 4 + 1; /* expect: 0123456789ABCDEF */
402
+ if ( (m2 - m1) < 2)
403
+ m2 = m1 + 2; /* font hight < 8 pixel ? */
404
+ if (jj&2)
405
+ m4 = m3 + (m4 - m1) / 4 + 1; /* chars have same lower base */
406
+ if (jj>0 && job->cfg.verbose & 16) {
407
+ fprintf(stderr," L%02d jj %3d y= %3d m= %4d %+3d %+3d %+3d my= %4d\n# ",
408
+ i, jj, y, m1, m2-m1, m3-m1, m4-m1, my);
409
+ }
410
+ #endif
411
+
412
+
413
+ { // empty space between lines
414
+ lines->m4[i] = m4;
415
+ lines->m3[i] = m3;
416
+ lines->m2[i] = m2;
417
+ lines->m1[i] = m1;
418
+ lines->pitch[i] = job->cfg.spc; /* default word pitch */
419
+ lines->mono[i] = 0; /* default spacing, 0=prop, 1=mono */
420
+ if (job->cfg.verbose & 16)
421
+ fprintf(stderr, " L%02d final y= %3d m= %4d %+3d %+3d %+3d x= %3d %+3d w= %d\n# ",
422
+ i, y, m1, m2 - m1, m3 - m1, m4 - m1, lines->x0[i],
423
+ lines->x1[i] - lines->x0[i], lines->wt[i]);
424
+ if (i < MAXlines && m4 - m1 > 4)
425
+ i++;
426
+ if (i >= MAXlines) {
427
+ fprintf(stderr, "Warning: lines>MAXlines\n");
428
+ break;
429
+ }
430
+ }
431
+ if (m3+m4>2*y) y = (m3+m4)/2; /* lower end may overlap the next line */
432
+ if (m3>m3pre) m3pre = m3; else m3=y0; /* set for next-line scan */
433
+ if (m4>m4pre) m4pre = m4; else m4=y0; /* set for next-line scan */
434
+ }
435
+ lines->num = i;
436
+ if (job->cfg.verbose)
437
+ fprintf(stderr, " num_lines= %d", lines->num-1);
438
+ return 0;
439
+ }
440
+
441
+ // ----- layout analyzis of dx*dy region at x0,y0 -----
442
+ // ----- detect lines via recursive division (new version) ---------------
443
+ // what about text in frames???
444
+ // ToDo: change to bottom-top analyse or/and take rotation into account
445
+ int detect_lines2(pix *p,int x0,int y0,int dx,int dy,int r){
446
+ int i,x2,y2,x3,y3,x4,y4,x5,y5,y6,mx,my,x30,x31,y30,y31;
447
+ struct box *box2,*box3;
448
+ job_t *job=OCR_JOB; /* fixme */
449
+ // shrink box
450
+ if(dx<=0 || dy<=0) return 0;
451
+ if(y0+dy< p->y/128 && y0==0) return 0; /* looks like dust */
452
+ if(y0>p->y-p->y/128 && y0+dy==p->y) return 0; /* looks like dust */
453
+
454
+ if(r>1000){ return -1;} // something is wrong
455
+ if(job->cfg.verbose)fprintf(stderr,"\n# r=%2d ",r);
456
+
457
+ mx=my=i=0; // mean thickness
458
+ // remove border, shrink size
459
+ x2=x0+dx-1; // min x
460
+ y2=y0+dy-1; // min y
461
+ x3=x0; // max x
462
+ y3=y0; // max y
463
+ for_each_data(&(job->res.boxlist)) {
464
+ box3 = (struct box *)list_get_current(&(job->res.boxlist));
465
+ if(box3->y0>=y0 && box3->y1<y0+dy &&
466
+ box3->x0>=x0 && box3->x1<x0+dx)
467
+ {
468
+ if( box3->x1 > x3 ) x3=box3->x1; // max x
469
+ if( box3->x0 < x2 ) x2=box3->x0; // min x
470
+ if( box3->y1 > y3 ) y3=box3->y1; // max y
471
+ if( box3->y0 < y2 ) y2=box3->y0; // min y
472
+ if(box3->c!=PICTURE)
473
+ if( box3->y1 - box3->y0 > 4 )
474
+ {
475
+ i++;
476
+ mx+=box3->x1-box3->x0+1; // mean x
477
+ my+=box3->y1-box3->y0+1; // mean y
478
+ }
479
+ }
480
+ } end_for_each(&(job->res.boxlist));
481
+ x0=x2; dx=x3-x2+1;
482
+ y0=y2; dy=y3-y2+1;
483
+
484
+ if(i==0 || dx<=0 || dy<=0) return 0;
485
+ mx/=i;my/=i;
486
+ // better look for widest h/v-gap, ToDo: vertical lines?
487
+ if(r<8){ // max. depth
488
+
489
+ // detect widest horizontal gap
490
+ y2=y3=y4=y5=y6=0;
491
+ x2=x3=x4=x5=y5=0;// min. 3 lines
492
+ // position and thickness of gap, y6=num_gaps, nbox^2 ops
493
+ for_each_data(&(job->res.boxlist)) { // not very efficient, sorry
494
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
495
+ if( box2->c!=PICTURE ) /* ToDo: not sure, that this is a good idea */
496
+ if( box2->y0>=y0 && box2->y1<y0+dy
497
+ && box2->x0>=x0 && box2->x1<x0+dx
498
+ && box2->y1-box2->y0>my/2 ){ // no pictures & dust???
499
+
500
+ y4=y0+dy-1; // nearest vert. box
501
+ x4=x0+dx-1;
502
+ // ToDo: rotate back box2->x1,y1 to x21,y21
503
+ // look for nearest lowest (y4) and right (x4) neighbour
504
+ // of every box (box2)
505
+ for_each_data(&(job->res.boxlist)) {
506
+ box3 = (struct box *)list_get_current(&(job->res.boxlist));
507
+ if(box3!=box2)
508
+ if(box3->y0>=y0 && box3->y1<y0+dy)
509
+ if(box3->x0>=x0 && box3->x1<x0+dx)
510
+ if(box3->c!=PICTURE) /* ToDo: not sure, that this is a good idea */
511
+ if(box3->y1-box3->y0>my/2 ){
512
+ // ToDo: here we need the rotation around box2
513
+ x30=box3->x0;
514
+ x31=box3->x1;
515
+ y30=box3->y0;
516
+ y31=box3->y1;
517
+ // get min. distances to lower and to right direction
518
+ if( y31 > box2->y1 && y30 < y4 ) y4=y30-1;
519
+ if( x31 > box2->x1 && x30 < x4 ) x4=x30-1;
520
+ }
521
+ } end_for_each(&(job->res.boxlist));
522
+ // set the witdht and position of largest hor./vert. gap
523
+ // largest gap: width position
524
+ if( y4-box2->y1 > y3 ) { y3=y4-box2->y1; y2=(y4+box2->y1)/2; }
525
+ if( x4-box2->x1 > x3 ) { x3=x4-box2->x1; x2=(x4+box2->x1)/2; }
526
+ }
527
+ } end_for_each(&(job->res.boxlist));
528
+ // fprintf(stderr,"\n widest y-gap= %4d %4d",y2,y3);
529
+ // fprintf(stderr,"\n widest x-gap= %4d %4d",x2,x3);
530
+
531
+ i=0; // i=1 at x, i=2 at y
532
+ // this is the critical point
533
+ // is this a good decision or not???
534
+ if(x3>0 || y3>0){
535
+ if(x3>mx && x3>2*y3 && (dy>5*x3 || (x3>10*y3 && y3>0))) i=1; else
536
+ if(dx>5*y3 && y3>my) i=2;
537
+ }
538
+
539
+ // compare with largest box???
540
+ for_each_data(&(job->res.boxlist)) { // not very efficient, sorry
541
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
542
+ if( box2->c == PICTURE )
543
+ if( box2->y0>=y0 && box2->y1<y0+dy
544
+ && box2->x0>=x0 && box2->x1<x0+dx )
545
+ { // hline ???
546
+ // largest gap: width position
547
+ if( box2->x1-box2->x0+4 > dx && box2->y1+4<y0+dy ) { y3=1; y2=box2->y1+1; i=2; break; }
548
+ if( box2->x1-box2->x0+4 > dx && box2->y0-4>y0 ) { y3=1; y2=box2->y0-1; i=2; break; }
549
+ if( box2->y1-box2->y0+4 > dy && box2->x1+4<x0+dx ) { x3=1; x2=box2->x1+1; i=1; break; }
550
+ if( box2->y1-box2->y0+4 > dy && box2->x0-4>x0 ) { x3=1; x2=box2->x0-1; i=1; break; }
551
+ }
552
+ } end_for_each(&(job->res.boxlist));
553
+ if(job->cfg.verbose)fprintf(stderr," i=%d",i);
554
+
555
+ if(job->cfg.verbose && i) fprintf(stderr," divide at %s x=%4d y=%4d dx=%4d dy=%4d",
556
+ ((i)?( (i==1)?"x":"y" ):"?"),x2,y2,x3,y3);
557
+ // divide horizontally if v-gap is thicker than h-gap
558
+ // and length is larger 5*width
559
+ if(i==1){ detect_lines2(p,x0,y0,x2-x0+1,dy,r+1);
560
+ return detect_lines2(p,x2,y0,x0+dx-x2+1,dy,r+1); }
561
+ // divide vertically
562
+ if(i==2){ detect_lines2(p,x0,y0,dx,y2-y0+1,r+1);
563
+ return detect_lines2(p,x0,y2,dx,y0+dy-y2+1,r+1);
564
+ }
565
+ }
566
+
567
+
568
+ if(job->cfg.verbose) if(dx<5 || dy<7)fprintf(stderr," empty box");
569
+ if(dx<5 || dy<7) return 0; // do not care about dust
570
+ if(job->cfg.verbose)fprintf(stderr, " box detected at %4d %4d %4d %4d",x0,y0,dx,dy);
571
+ if(job->tmp.ppo.p){
572
+ for(i=0;i<dx;i++)put(&job->tmp.ppo,x0+i ,y0 ,255,16);
573
+ for(i=0;i<dx;i++)put(&job->tmp.ppo,x0+i ,y0+dy-1,255,16);
574
+ for(i=0;i<dy;i++)put(&job->tmp.ppo,x0 ,y0+i ,255,16);
575
+ for(i=0;i<dy;i++)put(&job->tmp.ppo,x0+dx-1,y0+i ,255,16);
576
+ // writebmp("out10.bmp",p2,job->cfg.verbose); // colored should be better
577
+ }
578
+ return detect_lines1(p,x0-0*1,y0-0*2,dx+0*2,dy+0*3);
579
+
580
+ /*
581
+ struct tlines *lines = &job->res.lines;
582
+ i=lines->num; lines->num++;
583
+ lines->m1[i]=y0; lines->m2[i]=y0+5*dy/16;
584
+ lines->m3[i]=y0+12*dy/16; lines->m4[i]=y0+dy-1;
585
+ lines->x0[i]=x0; lines->x1[i]=x0+dx-1;
586
+ if(job->cfg.verbose)fprintf(stderr," - line= %d",lines->num);
587
+ return 0;
588
+ */
589
+ }
590
+
591
+ /* ToDo: herons algorithm for square root x=(x+y/x)/2 is more efficient
592
+ * than interval subdivision (?) (germ.: Intervallschachtelung)
593
+ * without using matlib
594
+ * see http://www.math.vt.edu/people/brown/doc/sqrts.pdf
595
+ */
596
+ int my_sqrt(int x){
597
+ int y0=0,y1=x,ym;
598
+ for (;y0<y1-1;){
599
+ ym=(y0+y1)/2;
600
+ if (ym*ym<x) y0=ym; else y1=ym;
601
+ }
602
+ return y0;
603
+ }
604
+
605
+ /*
606
+ ** Detect rotation angle (one for whole image)
607
+ ** old: longest text-line and determining the angle of this line.
608
+ *
609
+ * search right nearest neighbour of each box and average vectors
610
+ * to get the text orientation,
611
+ * upside down decision is not made here (I dont know how to do it)
612
+ * ToDo: set job->res.lines.{dx,dy}
613
+ * pass 1: get mean vector to nearest char
614
+ * pass 2: get mean vector to nearest char without outriders to pass 1
615
+ * extimate direction as (dx,dy,num)[pass]
616
+ * ToDo: estimate an error, boxes only work fine for zero-rotation
617
+ * for 45 degree use vectors, not boxes to get base line
618
+ */
619
+ #define INorm 1024 /* integer unit 1.0 */
620
+ int detect_rotation_angle(job_t *job){
621
+ struct box *box2, *box3,
622
+ *box_nn; /* nearest neighbour box */
623
+ int x2, y2, x3, y3, dist, mindist, pass,
624
+ rx=0, ry=0, re=0, // final result
625
+ /* to avoid 2nd run, wie store pairs in 2 different categories */
626
+ nn[4]={0,0,0,0}, /* num_pairs used for estimation [(pass-1)%2,pass%2] */
627
+ dx[4]={0,0,0,0}, /* x-component of rotation vector per pass */
628
+ dy[4]={0,0,0,0}, /* y-component of rotation vector per pass */
629
+ er[4]={INorm/4,0,0,0}; /* mean angle deviation to pass-1 (radius^2) */
630
+ // de; /* ToDo: absolute maximum error (dx^2+dy^2) */
631
+ // ToDo: next pass: go to bigger distances and reduce max error
632
+ // error is diff between passes? or diff of bottoms and top borders (?)
633
+
634
+ rx=1024; ry=0; // default
635
+ for (pass=0;pass<4;pass++) {
636
+ for_each_data(&(job->res.boxlist)) {
637
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
638
+ if (box2->c==PICTURE) continue;
639
+ /* subfunction probability of char */
640
+ // i?
641
+ // if (box2->x1 - box2->x0 < 3) continue; /* smallest font is 4x6 */
642
+ if (box2->y1 - box2->y0 < 4) continue;
643
+ /* set maximum possible distance */
644
+ box_nn=box2; // initial box to compare with
645
+
646
+ // ToDo: clustering or majority
647
+ // the algorithm is far from being perfect, pitfalls are likely
648
+ // but its better than the old algorithm, ToDo: database-rotated-images
649
+ mindist = job->src.p.x * job->src.p.x + job->src.p.y * job->src.p.y;
650
+ /* get middle point of the box */
651
+ x2 = (box2->x0 + box2->x1)/2;
652
+ y2 = (box2->y0 + box2->y1)/2;
653
+ re=0;
654
+ /* search for nearest neighbour box_nn[pass+1] of box_nn[pass] */
655
+ for_each_data(&(job->res.boxlist)) {
656
+ box3 = (struct box *)list_get_current(&(job->res.boxlist));
657
+ /* try to select only potential neighbouring chars */
658
+ /* select out all senseless combinations */
659
+ if (box3->c==PICTURE || box3==box2) continue;
660
+ x3 = (box3->x0 + box3->x1)/2;
661
+ y3 = (box3->y0 + box3->y1)/2; /* get middle point of the box */
662
+ if (x3<x2) continue; /* simplify by going right only */
663
+ // through-away deviation of angles if > pass-1?
664
+ // scalprod max in direction, cross prod min in direction
665
+ // a,b (vectors): <a,b>^2/(|a|*|b|)^2 = 0(90deg)..0.5(45deg).. 1(0deg)
666
+ // * 1024 ??
667
+ if (pass>0) { // new variant = scalar product
668
+ // danger of int overflow, ToDo: use int fraction
669
+ re =(int) ((1.*(x3-x2)*dx[pass-1]+(y3-y2)*dy[pass-1])
670
+ *(1.*(x3-x2)*dx[pass-1]+(y3-y2)*dy[pass-1])*INorm
671
+ /(1.*((x3-x2)*(x3-x2)+(y3-y2)*(y3-y2))
672
+ *(1.*dx[pass-1]*dx[pass-1]+dy[pass-1]*dy[pass-1])));
673
+ if (INorm-re>er[pass-1]) continue; // hits mean deviation
674
+ }
675
+ /* neighbours should have same order of size (?) */
676
+ if (3*(box3->y1-box3->y0+4) < 2*(box2->y1-box2->y0+1)) continue;
677
+ if (2*(box3->y1-box3->y0+1) > 3*(box2->y1-box2->y0+4)) continue;
678
+ if (2*(box3->x1-box3->x0+1) > 5*(box2->x1-box2->x0+4)) continue;
679
+ if (5*(box3->x1-box3->x0+4) < 2*(box2->x1-box2->x0+1)) continue;
680
+ /* should be in right range, Idea: center3 outside box2? noholes */
681
+ if ((x3<box2->x1-1) && (x3>box2->x0+1)
682
+ && (y3<box2->y1-1) && (y3>box2->y0+1)) continue;
683
+ // if chars are of different size, connect careful
684
+ if ( abs(x3-x2) > 2*(box2->x1 - box2->x0 + box3->x1 - box3 ->x0 + 2)) continue;
685
+ if ( abs(y3-y2) > (box2->x1 - box2->x0 + box3->x1 - box3 ->x0 + 2)) continue;
686
+ dist = (y3-y2)*(y3-y2) + (x3-x2)*(x3-x2);
687
+ // make distances in pass-1 directions shorter or continue if not in pass-1 range?
688
+ if (dist<9) continue; /* minimum distance^2 is 3^2 */
689
+ if (dist<mindist) { mindist=dist; box_nn=box3;}
690
+ // fprintf(stderr,"x y %d %d %d %d dist %d min %d\n",
691
+ // x2,y2,x3,y3,dist,mindist);
692
+ } end_for_each(&(job->res.boxlist));
693
+
694
+ if (box_nn==box2) continue; /* has no neighbour, next box */
695
+
696
+ box3=box_nn; dist=mindist;
697
+ x3 = (box3->x0 + box3->x1)/2;
698
+ y3 = (box3->y0 + box3->y1)/2; /* get middle point of the box */
699
+ // dist = my_sqrt(1024*((x3-x2)*(x3-x2)+(y3-y2)*(y3-y2)));
700
+ // compare with first box
701
+ x2 = (box2->x0 + box2->x1)/2;
702
+ y2 = (box2->y0 + box2->y1)/2;
703
+ // if the high of neighbouring boxes differ, use min diff (y0,y1)
704
+ if (pass>0 && 16*abs(dy[pass-1]) < dx[pass-1]) // dont work for strong rot.
705
+ if (abs(box2->y1-box2->y0-box3->y1+box3->y0)>(box2->y1-box2->y0)/8) {
706
+ // ad eh ck ...
707
+ if (abs(box2->y1-box3->y1)<abs(y3-y2)) { y2=box2->y1; y3=box3->y1; }
708
+ // ag ep qu ...
709
+ if (abs(box2->y0-box3->y0)<abs(y3-y2)) { y2=box2->y0; y3=box3->y0; }
710
+ }
711
+ if (abs(x3-x2)<4) continue;
712
+ dx[pass]+=(x3-x2)*1024; /* normalized before averaging */
713
+ dy[pass]+=(y3-y2)*1024; /* 1024 is for the precision */
714
+ nn[pass]++;
715
+ if (pass>0) { // set error = mean deviation from pass -1
716
+ re = INorm-(int)((1.*(x3-x2)*dx[pass-1]+(y3-y2)*dy[pass-1])
717
+ *(1.*(x3-x2)*dx[pass-1]+(y3-y2)*dy[pass-1])*INorm
718
+ /((1.*(x3-x2)*(x3-x2)+(y3-y2)*(y3-y2))
719
+ *(1.*dx[pass-1]*dx[pass-1]+dy[pass-1]*dy[pass-1]))
720
+ );
721
+ er[pass]+=re;
722
+ }
723
+ #if 0
724
+ if(job->cfg.verbose)
725
+ fprintf(stderr,"# next nb (x,y,dx,dy,re) %6d %6d %5d %5d %5d pass %d\n",
726
+ x2, y2, x3-x2, y3-y2, re, pass+1);
727
+ #endif
728
+ } end_for_each(&(job->res.boxlist));
729
+ if (!nn[pass]) break;
730
+ if (nn[pass]) {
731
+ /* meanvalues */
732
+ rx=dx[pass]/=nn[pass];
733
+ ry=dy[pass]/=nn[pass];
734
+ if (pass>0) er[pass]/=nn[pass];
735
+ }
736
+ if(job->cfg.verbose)
737
+ fprintf(stderr,"# rotation angle (x,y,maxr,num)"
738
+ " %6d %6d %6d %4d pass %d\n",
739
+ rx, ry, er[pass], nn[pass], pass+1);
740
+ }
741
+ if (abs(ry*100)>abs(rx*50))
742
+ fprintf(stderr,"<!-- gocr will fail, strong rotation angle detected -->\n");
743
+ /* ToDo: normalize to 2^10 bit (square fits to 32 it) */
744
+ job->res.lines.dx=rx;
745
+ job->res.lines.dy=ry;
746
+ return 0;
747
+ }
748
+
749
+ /* ----- detect lines --------------- */
750
+ int detect_text_lines(pix * pp, int mo) {
751
+ int vvv=OCR_JOB->cfg.verbose; /* fixme (global var) */
752
+
753
+ if (vvv)
754
+ fprintf(stderr, "# detect.c detect_text_lines (vvv=16 for more info)\n");
755
+ if (mo & 4){
756
+ if (vvv) fprintf(stderr, "# zoning\n# ... ");
757
+ detect_lines2(pp, 0, 0, pp->x, pp->y, 0); // later replaced by better algo
758
+ } else
759
+ detect_lines1(pp, 0, 0, pp->x, pp->y); // old algo
760
+
761
+ if (vvv) fprintf(stderr,"\n");
762
+ return 0;
763
+ }
764
+
765
+
766
+ /* ----- adjust lines --------------- */
767
+ // rotation angle? OCR_JOB->res.lines.dy, .x0 removed later
768
+ // this is for cases, where m1..m4 is not very sure detected before
769
+ // chars are recognized
770
+ int adjust_text_lines(pix * pp, int mo) {
771
+ job_t *job=OCR_JOB; /* fixme */
772
+ struct box *box2;
773
+ int *m, /* summ m1..m4, num_chars for m1..m4, min m1..m4, max. m1..m4 */
774
+ l, i, dy, dx, diff=0, y0, y1;
775
+
776
+ if ((l=job->res.lines.num)<2) return 0; // ???
777
+ if (job->cfg.verbose)
778
+ fprintf(stderr, "# detect.c adjust_text_lines() ");
779
+ m=(int *)malloc(l*16*sizeof(int));
780
+ if (!m) { fprintf(stderr," malloc failed\n"); return 0;}
781
+ for (i=0;i<16*l;i++) m[i]=0; /* initialize */
782
+ dy=job->res.lines.dy; /* tan(alpha) of skewing */
783
+ dx=job->res.lines.dx; /* old: width of image */
784
+ // js: later skewing is replaced by one transformation of vectorized image
785
+
786
+ if (dx)
787
+ for_each_data(&(job->res.boxlist)) {
788
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
789
+ if (box2->line<=0) continue;
790
+ if (box2->num_ac<1) continue;
791
+ // if (box2->wac[0]<95) continue;
792
+ if (box2->wac[0]<98) continue; // 2010-10-08 95 is to bad, '' as bad r
793
+ if (box2->m2==0 || box2->y1<box2->m2) continue; // char outside line
794
+ if (box2->m3==4 || box2->y0>box2->m3) continue; // char outside line
795
+ y0=box2->y0-((box2->x1)*dy/dx); /* corrected by page skewing */
796
+ y1=box2->y1-((box2->x1)*dy/dx);
797
+ if (strchr("aemnr",(char)box2->tac[0])) { // cC vV sS oO ... is unsure!
798
+ m[box2->line*16+1]+=y0; m[box2->line*16+5]++; // num m2
799
+ m[box2->line*16+2]+=y1; m[box2->line*16+6]++; // num m3
800
+ if (m[box2->line*16+ 9]>y0) m[box2->line*16+ 9]=y0; /* min m2 */
801
+ if (m[box2->line*16+13]<y0) m[box2->line*16+13]=y0; /* max m2 */
802
+ if (m[box2->line*16+10]>y1) m[box2->line*16+10]=y1; /* min m3 */
803
+ if (m[box2->line*16+14]<y1) m[box2->line*16+14]=y1; /* max m3 */
804
+ }
805
+ if (strchr("bdhklABDEFGHIKLMNRT12346789",(char)box2->tac[0])) { // ~oO s5
806
+ m[box2->line*16+0]+=y0; m[box2->line*16+4]++; // num m1
807
+ m[box2->line*16+2]+=y1; m[box2->line*16+6]++; // num m3
808
+ if (m[box2->line*16+ 8]>y0) m[box2->line*16+ 8]=y0; /* min m1 */
809
+ if (m[box2->line*16+12]<y0) m[box2->line*16+12]=y0; /* max m1 */
810
+ if (m[box2->line*16+10]>y1) m[box2->line*16+10]=y1; /* min m3 */
811
+ if (m[box2->line*16+14]<y1) m[box2->line*16+14]=y1; /* max m3 */
812
+ }
813
+ if (strchr("gq",(char)box2->tac[0])) { // pP is unsure
814
+ m[box2->line*16+1]+=y0; m[box2->line*16+5]++; // num m2
815
+ m[box2->line*16+3]+=y1; m[box2->line*16+7]++; // num m4
816
+ if (m[box2->line*16+ 9]>y0) m[box2->line*16+ 9]=y0; /* min m2 */
817
+ if (m[box2->line*16+13]<y0) m[box2->line*16+13]=y0; /* max m2 */
818
+ if (m[box2->line*16+11]>y1) m[box2->line*16+11]=y1; /* min m4 */
819
+ if (m[box2->line*16+15]<y1) m[box2->line*16+15]=y1; /* max m4 */
820
+ }
821
+ } end_for_each(&(job->res.boxlist));
822
+
823
+ for (i=1;i<l;i++) {
824
+ diff=0; // show diff per line
825
+ if (m[i*16+4]) diff+=abs(job->res.lines.m1[i]-m[i*16+0]/m[i*16+4]);
826
+ if (m[i*16+5]) diff+=abs(job->res.lines.m2[i]-m[i*16+1]/m[i*16+5]);
827
+ if (m[i*16+6]) diff+=abs(job->res.lines.m3[i]-m[i*16+2]/m[i*16+6]);
828
+ if (m[i*16+7]) diff+=abs(job->res.lines.m4[i]-m[i*16+3]/m[i*16+7]);
829
+ /* recalculate sureness, empirically */
830
+ if (m[i*16+4]*m[i*16+5]*m[i*16+6]*m[i*16+7] > 0)
831
+ job->res.lines.wt[i]=(job->res.lines.wt[i]+100)/2;
832
+ else
833
+ job->res.lines.wt[i]=(job->res.lines.wt[i]*90)/100;
834
+ // set mean values of sure detected bounds (rounded precisely)
835
+ if ( m[i*16+4]) job->res.lines.m1[i]=(m[i*16+0]+m[i*16+4]/2)/m[i*16+4];
836
+ if ( m[i*16+5]) job->res.lines.m2[i]=(m[i*16+1]+m[i*16+5]/2)/m[i*16+5];
837
+ if ( m[i*16+6]) job->res.lines.m3[i]=(m[i*16+2]+m[i*16+6]/2)/m[i*16+6];
838
+ if ( m[i*16+7]) job->res.lines.m4[i]=(m[i*16+3]+m[i*16+7]/2)/m[i*16+7];
839
+ // care about very small fonts
840
+ if (job->res.lines.m2[i]-job->res.lines.m1[i]<=1 && m[i*16+5]==0 && m[i*16+4])
841
+ job->res.lines.m2[i]=job->res.lines.m1[i]+2;
842
+ if (job->res.lines.m2[i]-job->res.lines.m1[i]<=1 && m[i*16+4]==0 && m[i*16+5])
843
+ job->res.lines.m1[i]=job->res.lines.m2[i]-2;
844
+ if (job->res.lines.m4[i]-job->res.lines.m3[i]<=1 && m[i*16+7]==0 && m[i*16+6])
845
+ job->res.lines.m4[i]=job->res.lines.m3[i]+2;
846
+ if (job->res.lines.m4[i]-job->res.lines.m3[i]<=1 && m[i*16+6]==0 && m[i*16+7])
847
+ job->res.lines.m3[i]=job->res.lines.m4[i]-2;
848
+ if ( m[i*16+7]<1 &&
849
+ job->res.lines.m4[i]
850
+ <=job->res.lines.m3[i]+(job->res.lines.m3[i]-job->res.lines.m2[i])/4 )
851
+ job->res.lines.m4[i]=
852
+ job->res.lines.m3[i]+(job->res.lines.m3[i]-job->res.lines.m2[i])/4;
853
+ if ( m[i*16+7]<1 && m[i*16+12+2]>0 && // m4 < max.m3+..
854
+ job->res.lines.m4[i] < 2*m[i*16+12+2]-job->res.lines.m3[i]+2 )
855
+ job->res.lines.m4[i] = 2*m[i*16+12+2]-job->res.lines.m3[i]+2;
856
+ if (job->res.lines.m4[i]<=job->res.lines.m3[i])
857
+ job->res.lines.m4[i]= job->res.lines.m3[i]+1; /* 4x6 */
858
+
859
+ if (job->cfg.verbose & 17)
860
+ fprintf(stderr, "\n# line= %3d m= %4d %+3d %+3d %+3d "
861
+ " n= %2d %2d %2d %2d w= %3d diff= %d",
862
+ i, job->res.lines.m1[i],
863
+ job->res.lines.m2[i] - job->res.lines.m1[i],
864
+ job->res.lines.m3[i] - job->res.lines.m1[i],
865
+ job->res.lines.m4[i] - job->res.lines.m1[i],
866
+ m[i*16+4],m[i*16+5],m[i*16+6],m[i*16+7],
867
+ job->res.lines.wt[i], diff);
868
+ }
869
+ diff=0; // count adjusted chars
870
+ #if 1
871
+ if (dx)
872
+ for_each_data(&(job->res.boxlist)) {
873
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
874
+ if (box2->line<=0) continue;
875
+ /* check if box was on the wrong line, ToDo: search a better line */
876
+ if (2*box2->y0<2*job->res.lines.m1[box2->line]
877
+ -job->res.lines.m4[box2->line]
878
+ +job->res.lines.m1[box2->line]) box2->line=0;
879
+ if (2*box2->y1>2*job->res.lines.m4[box2->line]
880
+ +job->res.lines.m4[box2->line]
881
+ -job->res.lines.m1[box2->line]) box2->line=0;
882
+ /* do adjustments */
883
+ if (box2->num_ac>0
884
+ && box2->num_ac > 31 && box2->tac[0] < 127 /* islower(>256) may SIGSEGV */
885
+ && strchr("cCoOpPsSuUvVwWxXyYzZ",(char)box2->tac[0])) { // no_wchar
886
+ if (box2->y0-((box2->x1)*dy/dx)
887
+ < (job->res.lines.m1[box2->line]+job->res.lines.m2[box2->line])/2
888
+ && islower(box2->tac[0])
889
+ ) { setac(box2,toupper((char)box2->tac[0]),(box2->wac[0]+101)/2); diff++; }
890
+ if (box2->y0-((box2->x1)*dy/dx)
891
+ > (job->res.lines.m1[box2->line]+job->res.lines.m2[box2->line]+1)/2
892
+ && isupper(box2->tac[0])
893
+ ){ setac(box2,tolower((char)box2->tac[0]),(box2->wac[0]+101)/2); diff++; }
894
+ }
895
+ box2->m1=job->res.lines.m1[box2->line]+((box2->x1)*dy/dx);
896
+ box2->m2=job->res.lines.m2[box2->line]+((box2->x1)*dy/dx);
897
+ box2->m3=job->res.lines.m3[box2->line]+((box2->x1)*dy/dx);
898
+ box2->m4=job->res.lines.m4[box2->line]+((box2->x1)*dy/dx);
899
+ } end_for_each(&(job->res.boxlist));
900
+ #endif
901
+
902
+ free(m);
903
+ if(job->cfg.verbose) fprintf(stderr,"\n# changed_chars= %d\n",diff);
904
+ return(diff);
905
+ }
906
+
907
+ /* ---- measure mean character
908
+ * recalculate mean width and high after changes in boxlist
909
+ * ToDo: only within a Range?
910
+ */
911
+ int calc_average() {
912
+ int i = 0, x0, y0, x1, y1;
913
+ struct box *box4;
914
+ job_t *job=OCR_JOB; /* fixme */
915
+
916
+ job->res.numC = 0;
917
+ job->res.sumY = 0;
918
+ job->res.sumX = 0;
919
+ for_each_data(&(job->res.boxlist)) {
920
+ box4 = (struct box *)list_get_current(&(job->res.boxlist));
921
+ if( box4->c != PICTURE ){
922
+ x0 = box4->x0; x1 = box4->x1;
923
+ y0 = box4->y0; y1 = box4->y1;
924
+ i++;
925
+ if (job->res.avX * job->res.avY > 0) {
926
+ if (x1 - x0 + 1 > 4 * job->res.avX
927
+ && y1 - y0 + 1 > 4 * job->res.avY) continue; /* small picture */
928
+ if (4 * (y1 - y0 + 1) < job->res.avY || y1 - y0 < 2)
929
+ continue; // dots .,-_ etc.
930
+ }
931
+ if (x1 - x0 + 1 < 4
932
+ && y1 - y0 + 1 < 6 ) continue; /* dots etc */
933
+ job->res.sumX += x1 - x0 + 1;
934
+ job->res.sumY += y1 - y0 + 1;
935
+ job->res.numC++;
936
+ }
937
+ } end_for_each(&(job->res.boxlist));
938
+ if ( job->res.numC ) { /* avoid div 0 */
939
+ job->res.avY = (job->res.sumY+job->res.numC/2) / job->res.numC;
940
+ job->res.avX = (job->res.sumX+job->res.numC/2) / job->res.numC;
941
+ }
942
+ if (job->cfg.verbose){
943
+ fprintf(stderr, "# averages: mXmY= %d %d nC= %d n= %d\n",
944
+ job->res.avX, job->res.avY, job->res.numC, i);
945
+ }
946
+ return 0;
947
+ }
948
+
949
+
950
+ /* ---- analyse boxes, find pictures and mark (do this first!!!)
951
+ */
952
+ int detect_pictures(job_t *job) {
953
+ int i = 0, x0, y0, x1, y1, num_h;
954
+ struct box *box2, *box4;
955
+
956
+ if ( job->res.numC == 0 ) {
957
+ if (job->cfg.verbose) fprintf(stderr,
958
+ "# detect.c L%d Warning: numC=0\n", __LINE__);
959
+ return -1;
960
+ }
961
+ /* ToDo: set Y to uppercase mean value? */
962
+ job->res.avY = (job->res.sumY+job->res.numC/2) / job->res.numC;
963
+ job->res.avX = (job->res.sumX+job->res.numC/2) / job->res.numC;
964
+ /* ToDo: two highest volumes? crosses, on extreme volume + on border */
965
+ if (job->cfg.verbose)
966
+ fprintf(stderr, "# detect.c L%d pictures, frames, mXmY= %d %d ... ",
967
+ __LINE__, job->res.avX, job->res.avY);
968
+ for_each_data(&(job->res.boxlist)) {
969
+ box2 = (struct box *)list_get_current(&(job->res.boxlist));
970
+ if (box2->c == PICTURE) continue;
971
+ x0 = box2->x0; x1 = box2->x1;
972
+ y0 = box2->y0; y1 = box2->y1;
973
+
974
+ /* pictures could be of unusual size */
975
+ if (x1 - x0 + 1 > 4 * job->res.avX || y1 - y0 + 1 > 4 * job->res.avY) {
976
+ /* count objects on same baseline which could be chars */
977
+ /* else: big headlines could be misinterpreted as pictures */
978
+ num_h=0;
979
+ for_each_data(&(job->res.boxlist)) {
980
+ box4 = (struct box *)list_get_current(&(job->res.boxlist));
981
+ if (box4->c == PICTURE) continue;
982
+ if (box4->y1-box4->y0 > 2*(y1-y0)) continue;
983
+ if (2*(box4->y1-box4->y0) < y1-y0) continue;
984
+ if (box4->y0 > y0 + (y1-y0+1)/2
985
+ || box4->y0 < y0 - (y1-y0+1)/2
986
+ || box4->y1 > y1 + (y1-y0+1)/2
987
+ || box4->y1 < y1 - (y1-y0+1)/2) continue;
988
+ // ToDo: continue if numcross() only 1, example: |||IIIll|||
989
+ num_h++;
990
+ } end_for_each(&(job->res.boxlist));
991
+ if (num_h>4) continue;
992
+ box2->c = PICTURE;
993
+ i++;
994
+ }
995
+ /* ToDo: pictures could have low contrast=Sum((pixel(p,x,y)-160)^2) */
996
+ } end_for_each(&(job->res.boxlist));
997
+ // start second iteration
998
+ if (job->cfg.verbose) {
999
+ fprintf(stderr, " %d - boxes %d\n", i, job->res.numC-i);
1000
+ }
1001
+ calc_average();
1002
+ return 0;
1003
+ }