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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +49 -0
- data/ext/gocr/Makefile +141 -0
- data/ext/gocr/Makefile.in +140 -0
- data/ext/gocr/amiga.h +31 -0
- data/ext/gocr/barcode.c +2108 -0
- data/ext/gocr/barcode.h +11 -0
- data/ext/gocr/box.c +496 -0
- data/ext/gocr/config.h +37 -0
- data/ext/gocr/config.h.in +36 -0
- data/ext/gocr/database.c +468 -0
- data/ext/gocr/detect.c +1003 -0
- data/ext/gocr/extconf.rb +6 -0
- data/ext/gocr/gocr.c +436 -0
- data/ext/gocr/gocr.h +290 -0
- data/ext/gocr/jconv.c +168 -0
- data/ext/gocr/job.c +92 -0
- data/ext/gocr/lines.c +364 -0
- data/ext/gocr/list.c +334 -0
- data/ext/gocr/list.h +91 -0
- data/ext/gocr/ocr0.c +7312 -0
- data/ext/gocr/ocr0.h +63 -0
- data/ext/gocr/ocr0n.c +1527 -0
- data/ext/gocr/ocr1.c +85 -0
- data/ext/gocr/ocr1.h +3 -0
- data/ext/gocr/otsu.c +310 -0
- data/ext/gocr/otsu.h +23 -0
- data/ext/gocr/output.c +291 -0
- data/ext/gocr/output.h +37 -0
- data/ext/gocr/pcx.c +153 -0
- data/ext/gocr/pcx.h +9 -0
- data/ext/gocr/pgm2asc.c +3259 -0
- data/ext/gocr/pgm2asc.h +105 -0
- data/ext/gocr/pixel.c +538 -0
- data/ext/gocr/pnm.c +538 -0
- data/ext/gocr/pnm.h +35 -0
- data/ext/gocr/progress.c +87 -0
- data/ext/gocr/progress.h +42 -0
- data/ext/gocr/remove.c +715 -0
- data/ext/gocr/tga.c +87 -0
- data/ext/gocr/tga.h +6 -0
- data/ext/gocr/unicode.c +1318 -0
- data/ext/gocr/unicode.h +62 -0
- data/ext/gocr/unicode_defs.h +1245 -0
- data/ext/gocr/version.h +2 -0
- data/gocr-ruby.gemspec +28 -0
- data/image.png +0 -0
- data/lib/gocr.rb +6 -0
- data/lib/gocr/image.rb +8 -0
- data/lib/gocr/version.rb +3 -0
- metadata +156 -0
data/ext/gocr/detect.c
ADDED
@@ -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 "ä"
|
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ÄÜÖ" */
|
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
|
+
}
|