1
/***************************************************************************
2
 * Copyright (c) 2009 Enrico Ros                                           *
3
 *         2009 Enrico Ros <enrico.ros@email.it>                           *
4
 *         2009 Alberto Scarpa <skaal.sl@gmail.com>                        *
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
{
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() );
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;
121
    if ( k2 > 1.5 )
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();
135
    //int pixels = width * height;
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
153
#if 0
154
    if ( glyph->character == 'R' ) {
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();
162
    }
163
#endif
164
165
    result.confidence = 1.0 - KX;
166
    //qWarning() << result.confidence << result.character << pixels << analyzed;
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
193
    //for ( int i = 48; i <= 57; i++ )
194
    //    chars.append( QChar( i ) );
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
205
    //for ( int i = 97; i <= 122; i++ )
206
    //    chars.append( QChar( i ) );
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
268
OcrResult Ocr::recognizeGlyph( const QImage & sourceImage, const QRect & __rect ) const
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
}