| b797f7f by Enrico Ros at 2009-02-25 |
1 |
/*************************************************************************** |
| 0e5745d by Enrico Ros at 2009-05-31 |
2 |
* Copyright (c) 2009 Enrico Ros * |
|
3 |
* 2009 Enrico Ros <enrico.ros@email.it> * |
|
4 |
* 2009 Alberto Scarpa <skaal.sl@gmail.com> * |
| b797f7f by Enrico Ros at 2009-02-25 |
5 |
* * |
|
6 |
* Permission is hereby granted, free of charge, to any person * |
|
7 |
* obtaining a copy of this software and associated documentation * |
|
8 |
* files (the "Software"), to deal in the Software without * |
|
9 |
* restriction, including without limitation the rights to use, * |
|
10 |
* copy, modify, merge, publish, distribute, sublicense, and/or sell * |
|
11 |
* copies of the Software, and to permit persons to whom the * |
|
12 |
* Software is furnished to do so, subject to the following * |
|
13 |
* conditions: * |
|
14 |
* * |
|
15 |
* The above copyright notice and this permission notice shall be * |
|
16 |
* included in all copies or substantial portions of the Software. * |
|
17 |
* * |
|
18 |
***************************************************************************/ |
|
19 |
|
|
20 |
#include "Ocr.h" |
|
21 |
#include <QFontMetrics> |
|
22 |
#include <QPainter> |
|
23 |
#include <QBitmap> |
|
24 |
#include <QTransform> |
|
25 |
#include <QPixmap> |
|
26 |
#include <math.h> |
|
27 |
|
|
28 |
#include <QDebug> |
|
29 |
#include <QLabel> |
|
30 |
|
|
31 |
struct OcrGlyph { |
|
32 |
QChar character; |
|
33 |
QImage image; |
|
34 |
int width; |
|
35 |
int height; |
|
36 |
double ratio; |
|
37 |
// ADD more and more properties ... |
|
38 |
}; |
|
39 |
|
|
40 |
|
|
41 |
// CORE FUNCTIONS |
|
42 |
static QImage trimImage( const QImage & image ) |
|
43 |
{ |
| 9d1b99e by Enrico Ros at 2009-02-25 |
44 |
// find TOP |
|
45 |
int W = image.width(); |
|
46 |
int H = image.height(); |
|
47 |
int left = 0, top = 0, right = W - 1, bottom = H - 1; |
|
48 |
|
|
49 |
// find -left- margin |
|
50 |
bool blank = true; |
|
51 |
while ( blank && left < W ) { |
|
52 |
for ( int y = 0; y < H; y++ ) { |
|
53 |
if ( qGray( image.pixel( left, y ) ) < 32 ) { |
|
54 |
blank = false; |
|
55 |
break; |
|
56 |
} |
|
57 |
} |
|
58 |
if ( blank ) |
|
59 |
left++; |
|
60 |
} |
|
61 |
|
|
62 |
// find -top- margin |
|
63 |
blank = true; |
|
64 |
while ( blank && top < H ) { |
|
65 |
for ( int x = 0; x < W; x++ ) { |
|
66 |
if ( qGray( image.pixel( x, top ) ) < 32 ) { |
|
67 |
blank = false; |
|
68 |
break; |
|
69 |
} |
|
70 |
} |
|
71 |
if ( blank ) |
|
72 |
top++; |
|
73 |
} |
|
74 |
|
|
75 |
// find -right- margin |
|
76 |
blank = true; |
|
77 |
while ( blank && right >= 0 ) { |
|
78 |
for ( int y = 0; y < H; y++ ) { |
|
79 |
if ( qGray( image.pixel( right, y ) ) < 32 ) { |
|
80 |
blank = false; |
|
81 |
break; |
|
82 |
} |
|
83 |
} |
|
84 |
if ( blank ) |
|
85 |
right--; |
|
86 |
} |
|
87 |
|
|
88 |
// find -bottom- margin |
|
89 |
blank = true; |
|
90 |
while ( blank && bottom >= 0 ) { |
|
91 |
for ( int x = 0; x < W; x++ ) { |
|
92 |
if ( qGray( image.pixel( x, bottom ) ) < 32 ) { |
|
93 |
blank = false; |
|
94 |
break; |
|
95 |
} |
|
96 |
} |
|
97 |
if ( blank ) |
|
98 |
bottom--; |
|
99 |
} |
|
100 |
|
|
101 |
// ok: return cropped image |
|
102 |
W = right - left + 1; |
|
103 |
H = bottom - top + 1; |
|
104 |
if ( W > 0 && H > 0 ) |
|
105 |
return image.copy( left, top, W, H ); |
|
106 |
|
|
107 |
// invalid: return the same image |
|
108 |
qWarning( "trimImage: invalid image of size %dx%d", image.width(), image.height() ); |
| b797f7f by Enrico Ros at 2009-02-25 |
109 |
return image; |
|
110 |
} |
|
111 |
|
|
112 |
static OcrResult compareGlyph( const QImage & image, OcrGlyph * glyph ) |
|
113 |
{ |
|
114 |
OcrResult result; |
|
115 |
result.character = glyph->character; |
|
116 |
result.confidence = 0.0; |
|
117 |
|
|
118 |
// bail out if ratios are too different |
|
119 |
double ratio = (float)image.width() / (float)image.height(); |
|
120 |
double k2 = (ratio / glyph->ratio + glyph->ratio / ratio) / 2; |
| 9d1b99e by Enrico Ros at 2009-02-25 |
121 |
if ( k2 > 1.5 ) |
| b797f7f by Enrico Ros at 2009-02-25 |
122 |
return result; |
|
123 |
|
|
124 |
// ALGO |
|
125 |
// scale glyph image size to image size |
|
126 |
#if 0 |
|
127 |
QTransform t; |
|
128 |
t.scale( (float)glyph->image.width() / (float)image.width(), (float)glyph->image.height() / (float)image.height() ); |
|
129 |
QImage glyphImage = glyph->image.transformed( t.inverted(0), Qt::SmoothTransformation ); |
|
130 |
#endif |
|
131 |
QImage glyphImage = glyph->image.scaled( image.width(), image.height() ); |
|
132 |
|
|
133 |
int width = glyphImage.width(); |
|
134 |
int height = glyphImage.height(); |
| 0e5745d by Enrico Ros at 2009-05-31 |
135 |
//int pixels = width * height; |
| b797f7f by Enrico Ros at 2009-02-25 |
136 |
unsigned int error = 0; |
|
137 |
unsigned int analyzed = 0; |
|
138 |
for ( int y = 0; y < height; y++ ) { |
|
139 |
for ( int x = 0; x < width; x++ ) { |
|
140 |
int v1 = qGray( image.pixel( x, y ) ); |
|
141 |
int v2 = qGray( glyphImage.pixel( x, y ) ); |
|
142 |
if ( v1 >= 254 && v2 >= 254 ) |
|
143 |
continue; |
|
144 |
analyzed++; |
|
145 |
int diff = (v1 > v2 ? v1 - v2 : v2 - v1); |
|
146 |
error += (diff * diff) >> 8; |
|
147 |
} |
|
148 |
} |
|
149 |
if ( !analyzed ) |
|
150 |
return result; |
|
151 |
float KX = sqrt( (float)error / (254.0 * (float)analyzed) ); |
|
152 |
|
| 6336a38 by Enrico Ros at 2009-02-25 |
153 |
#if 0 |
|
154 |
if ( glyph->character == 'R' ) { |
| b797f7f by Enrico Ros at 2009-02-25 |
155 |
QLabel * label = new QLabel(); |
|
156 |
label->setPixmap( QPixmap::fromImage( image ) ); |
|
157 |
label->show(); |
|
158 |
|
|
159 |
label = new QLabel(); |
|
160 |
label->setPixmap( QPixmap::fromImage( glyphImage ) ); |
|
161 |
label->show(); |
| 6336a38 by Enrico Ros at 2009-02-25 |
162 |
} |
|
163 |
#endif |
| b797f7f by Enrico Ros at 2009-02-25 |
164 |
|
|
165 |
result.confidence = 1.0 - KX; |
| 9d1b99e by Enrico Ros at 2009-02-25 |
166 |
//qWarning() << result.confidence << result.character << pixels << analyzed; |
| b797f7f by Enrico Ros at 2009-02-25 |
167 |
return result; |
|
168 |
} |
|
169 |
|
|
170 |
Ocr::Ocr() |
|
171 |
{ |
|
172 |
} |
|
173 |
|
|
174 |
Ocr::~Ocr() |
|
175 |
{ |
|
176 |
clearTraning(); |
|
177 |
} |
|
178 |
|
|
179 |
void Ocr::trainFont( const QFont & font, QFontDatabase::WritingSystem writingSystem ) |
|
180 |
{ |
|
181 |
// checks |
|
182 |
if ( writingSystem != QFontDatabase::Latin ) { |
|
183 |
qWarning( "Ocr::trainFont: unsupported writing system" ); |
|
184 |
return; |
|
185 |
} |
|
186 |
|
|
187 |
// add chars |
|
188 |
QList<QChar> chars; |
|
189 |
// symbols !"#$%&'()*+,-./ |
|
190 |
//for ( int i = 33; i <= 47; i++ ) |
|
191 |
// chars.append( QChar( i ) ); |
|
192 |
// numbers |
| 9d1b99e by Enrico Ros at 2009-02-25 |
193 |
//for ( int i = 48; i <= 57; i++ ) |
|
194 |
// chars.append( QChar( i ) ); |
| b797f7f by Enrico Ros at 2009-02-25 |
195 |
// symbols :;<=>?@ |
|
196 |
//for ( int i = 58; i <= 64; i++ ) |
|
197 |
// chars.append( QChar( i ) ); |
|
198 |
// uppercase letters |
|
199 |
for ( int i = 65; i <= 90; i++ ) |
|
200 |
chars.append( QChar( i ) ); |
|
201 |
// symbols [\]^_` |
|
202 |
//for ( int i = 91; i <= 96; i++ ) |
|
203 |
// chars.append( QChar( i ) ); |
|
204 |
// lowercase letters |
| 9d1b99e by Enrico Ros at 2009-02-25 |
205 |
//for ( int i = 97; i <= 122; i++ ) |
|
206 |
// chars.append( QChar( i ) ); |
| b797f7f by Enrico Ros at 2009-02-25 |
207 |
// symbols {|}~ |
|
208 |
//for ( int i = 123; i <= 126; i++ ) |
|
209 |
// chars.append( QChar( i ) ); |
|
210 |
|
|
211 |
// generate glyphs for Latin1 and train the system |
|
212 |
QFontMetrics metrics( font ); |
|
213 |
foreach ( const QChar & character, chars ) { |
|
214 |
QRect rect = metrics.boundingRect( character ).adjusted( -5, -5, 5, 5 ); |
|
215 |
if ( !rect.isValid() ) { |
|
216 |
qWarning( "Ocr::trainFont: invalid rect for font" ); |
|
217 |
continue; |
|
218 |
} |
|
219 |
|
|
220 |
// draw glyph |
|
221 |
QImage image( rect.width(), rect.height(), QImage::Format_ARGB32 ); |
|
222 |
image.fill( 0xFFFFFFFF ); |
|
223 |
QPainter imgPainter( &image ); |
|
224 |
imgPainter.setFont( font ); |
|
225 |
imgPainter.setPen( Qt::black ); |
|
226 |
imgPainter.drawText( 0, 0, rect.width(), rect.height(), Qt::AlignCenter, character ); |
|
227 |
imgPainter.end(); |
|
228 |
|
|
229 |
// recognize glyph |
|
230 |
trainGlyph( image, character ); |
|
231 |
} |
|
232 |
} |
|
233 |
|
|
234 |
void Ocr::trainGlyph( const QImage & sourceImage, const QChar & character ) |
|
235 |
{ |
|
236 |
#if 0 |
|
237 |
QLabel * label = new QLabel(); |
|
238 |
label->setPixmap( QPixmap::fromImage( sourceImage ) ); |
|
239 |
label->setWindowTitle( character ); |
|
240 |
label->show(); |
|
241 |
#endif |
|
242 |
|
|
243 |
// checks |
|
244 |
if ( sourceImage.isNull() || sourceImage.width() < 4 || sourceImage.height() < 4 || character.isNull() ) { |
|
245 |
qWarning( "Ocr::trainGlyph: invalid input" ); |
|
246 |
return; |
|
247 |
} |
|
248 |
|
|
249 |
// prepare image for processing |
|
250 |
QImage image = trimImage( sourceImage ); |
|
251 |
|
|
252 |
// append glyph description |
|
253 |
OcrGlyph * glyph = new OcrGlyph(); |
|
254 |
glyph->character = character; |
|
255 |
glyph->image = image; |
|
256 |
glyph->width = image.width(); |
|
257 |
glyph->height = image.height(); |
|
258 |
glyph->ratio = (float)image.width() / (float)image.height(); |
|
259 |
m_glyphs.append( glyph ); |
|
260 |
} |
|
261 |
|
|
262 |
void Ocr::clearTraning() |
|
263 |
{ |
|
264 |
qDeleteAll( m_glyphs ); |
|
265 |
m_glyphs.clear(); |
|
266 |
} |
|
267 |
|
| e45848b by Enrico Ros at 2009-05-31 |
268 |
OcrResult Ocr::recognizeGlyph( const QImage & sourceImage, const QRect & __rect ) const |
| b797f7f by Enrico Ros at 2009-02-25 |
269 |
{ |
|
270 |
// checks |
|
271 |
OcrResult result; |
|
272 |
result.character = '?'; // FIXME: REMOVE THIS |
|
273 |
result.confidence = 0.0; |
|
274 |
if ( sourceImage.isNull() ) |
|
275 |
return result; |
|
276 |
|
|
277 |
// prepare image for processing |
|
278 |
QRect rect = __rect.isValid() ? __rect : QRect( 0, 0, sourceImage.width(), sourceImage.height() ); |
|
279 |
QImage image = trimImage( sourceImage.copy( rect ) ); |
|
280 |
|
|
281 |
// match all glyphs |
|
282 |
foreach ( OcrGlyph * glyph, m_glyphs ) { |
|
283 |
OcrResult comparison = compareGlyph( image, glyph ); |
|
284 |
if ( comparison.confidence > result.confidence ) { |
|
285 |
result.character = comparison.character; |
|
286 |
result.confidence = comparison.confidence; |
|
287 |
} |
|
288 |
} |
|
289 |
|
|
290 |
// done! |
|
291 |
return result; |
|
292 |
} |