Jörg Schulenburg
Magdeburg, June 3, 2002
It would be very helpful to know about a function which is able to decide whether the box represents a single text line or a more complex object.
Line detection is very importand for good recognition. For example it is difficult to distinguish between lowercase letter p and uppercase letter P without having a baseline (same total height). The lowercase version of p has a depht (the lower end is below the baseline) and therefore its easy to distinguish from the uppercase version if the baseline is known. The line detection is responsible for finding the baseline of every text line.
Lines of characters are detected by looking for interline spaces. These are characterized by a large number of non-black pixels in a row. Image rotation (skewing) presents a problem, therefore the program first looks only at the left half of the image. When a line is found, the left half of the right side is scanned, because lines are often short. The variation in height gives an indication of the rotation angle. Using this angle, a second run detects lines more accurately. Line detection may fail if there is dust on the image.
In version v0.2.3 this behaviour is slightly changed. To detect the rotation angle, the line through the most characters is detected.
This method needs a lot of stack space if a cluster is very large, and can cause problems with the memory.
Do you remember the algorithm for leaving a maze? Go along the right (or left) wall. This seems to be a good approach for detecting clusters without recursion. The following picture shows a trace of the maze algorithm.
first 35 steps next 36 steps ..@@@@@..@@@@<.. ..v<<<<..v<<<@.. * = starting point ..@@@@@@@@@.@^<. ..>>v@^<<<@.@@@. >^<v = go right,up,left,down ....@@@@@...@@^. ....v@@@@...@@@. @ = black pixel ....@@@@....@@^. ....v@@@....@@@. ....@@@.....@@^. ....v@@.....@@@. ....@@@.....@@^. ....v@@.....@@@. ...@@@@.....@@^. ...v<@@.....@@@. ...@@@......@@^. ...v@@......@@@. ...@@@......@@^. ...v@@......@@@. ...@@@.....@@@^. ...v@@.....@@@@. ...@@@.....@@>^. ...v@@.....@@@@. ...@@@.....@@^.. ...v@@.....@@@.. ..@@@@.....@@^.. ..v<@@.....@@@.. ..@@@@....@@@^.. ..v@@@....@@@@.. *>>>>>>>>>>>>^<< @@@@@@@@@@@@@@@@ |
The minimum and maximum coordinates can be used to create a box around the cluster. But does this algorithm work with diagonally connected pixels?
vvvv vv- white regions ......@@...... <- crossing one line ......@@...... .....@@@@..... .....@@@@..... .....@@@@..... ....@..@@@.... <- white hole / crossing two lines ....@..@@@.... <- crossing two lines ....@..@@@.... ...@....@@@... ...@....@@@... ...@....@@@... ..@@@@@@@@@@.. <- horizontal line near center ..@......@@@.. ..@......@@@.. .@........@@@. v- increasing width of pattern .@........@@@. v .@........@@@. v @@@......@@@@@ ^^^-- gap |
In the future the program should detect edges, vertices, gaps, angles and so on. This is called feature extraction (as far as I know). With such data the engine could make a cluster analysis. But this is a difficult task, if the scanned image is noisy.
..@@@@@..@@@@@.. ..==III..===II.. dx=16 dy=15 ..@@@@@@@@@.@@@. ..==III====.III. thickness 2 to 3 ....@@@@@...@@@. ....III==...III. ....@@@@....@@@. ....III=....III. ....@@@.....@@@. ....III.....III. ....@@@.....@@@. ....III.....III. ...@@@@.....@@@. ...IIII.....III. ...@@@......@@@. ...III......III. ...@@@......@@@. ...III......III. ...@@@.....@@@@. ...III.....IIII. ...@@@.....@@@@. ...III.....IIII. ...@@@.....@@@.. ...III.....III.. ..@@@@.....@@@.. ..IIII.....III.. ..@@@@....@@@@.. ..IIII....IIII.. @@@@@@@@@@@@@@@@ ================ ^^^ this causes the problem |
A better way is to find serifs (horizontal lines glued on the lower end of vertical lines) which touch together (v0.2.5).
The next picture shows blind pixels which are caused by dust on the paper. The upper right dots are not connected with the rest of the character. This can be detected via fill-algorithms. Currently the program assumes that dots near the upper end of a character are ``i''-dots or diaereses (umlaut dots).
..........................O... ..........................O... ..........................O... ..........................O... .............................. .............................. .............................. .............................. ..........@@@.......@@@@...... ..........@@@.......@@@@...... ..@@@@..@@@@@@@...@@@@@@@..... ..@@@@..@@@@@@@...@@@@@@@..... @@@@@@@@@@@@@@@@.@@@@@@@@@.... @@@@@@@@@@@@@@@@.@@@@@@@@@.... ..@@@@@@....@@@@@@.....@@@@... ..@@@@@@....@@@@@@.....@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@.......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@.......@@@@......@@@@... ..@@@@......@@@@@......@@@@@.. ..@@@@......@@@@@......@@@@@.. @@@@@@@@..@@@@@@@@@..@@@@@@@@@ @@@@@@@@..@@@@@@@@@..@@@@@@@@@ |
vv vv @@@.@@@..@@@... @@@.@@@..@@@... .@@.@@@@.@@@@..< .@@O@@@@O@@@@.. filter: .@ => O@ @. => @O .@@@..@@@..@@..< .@@@..@@@..@@.. @. => @. .@ => .@ .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. .@@@..@@@..@@@. @@@@@.@@@@.@@@@ @@@@@.@@@@.@@@@ |
....@@...@@@@@@@@@@....@@@@@@@.. ....@@...@@@@@@@@@@....@@@@@@@.. ..@@@@..@@@@@..@@@@......@@@@@.. ..@@@@..@@@@@..@@@@......@@@@@.. @@@@@@@@@@@@@.,.@@@.......@@@@.. @@@@@@@@@@@@@...@@@.......@@@@.. ..@@@@@@..@@@...@@@.......@@@@.. ..@@@@@@..@@@...@@@.......@@@@.. ...@@@@.......,.@@@@......@@@@.. ...@@@@.........@@@@......@@@@.. ...@@@@.........@@@@......@@@@.. ...@@@@.........@@@@......@@@@.. ...@@@@.......,.@@@.......@@@@.. ...@@@@.........@@@.......@@@@.. ...@@@@.........@@@.......@@@@.. ...@@@@.........@@@.......@@@@.. ...@@@........,.@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@........,.@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@..........@@@.......@@@@.. ...@@@..........@@@.......@@@@.. ...@@@........,.@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@........,.@@@@......@@@@.. ...@@@..........@@@@......@@@@.. ...@@@..........@@@@@...@@@@@@@. ...@@@..........@@@@@...@@@@@@@. ..@@@@@.......,..@@@@@@@@@.@@@@@ ..@@@@@..........@@@@@@@@@.@@@@@ @@@@@@@@@.........@@@@@@@..@@@.. @@@@@@@@@.........@@@@@@@..@@@.. ..............,....@@@.......... ...................@@@.......... ^^^ 213 weak vertical lines |
Of course the situation is more difficult with slanted characters.
The following example shows, how to deal with larger clusters. To get a fast program a first test should select the possible positions of division. That can be done by following upper and lower bows to a crease or a break. Than try to break on all detected creases, start at most important one (not implemented yet v0.2.4).
>>>>vvv<<<<< >>vv<<<< >>>vvv<<<< ......@@@@@@@..................@@.........@@@@@@@..........@@@@@@@..... ....@@@@@@@@@@@...............@@@.......@@@@@@@@@@@......@@@@@@@@@@@... ...@@@@@@@@@@@@@.............@@@@......@@@@@@@@@@@@@....@@@@@@@@@@@@@.. ..@@@@.......@@@@...........@@@@@.....@@@@.......@@@@..@@@@.......@@@@. ..@@@........@@@@..........@@@@@@@....@@@........@@@@@@@@@........@@@@. .@@@@..........@@.........@@@@@@@@...@@@@..........@@@@@@@.........@@@@ .@@@.....................@@@@.@@@@...@@@..............@@...........@@@@ .@@@....................@@@@@.@@@@...@@@...........................@@@@ @@@...@@@@@@@...........@@@@..@@@...@@@...@@@@@@...................@@@. @@@@.@@@@@@@@@@........@@@@...@@@@..@@@@.@@@@@@@@@@...............@@@@. @@@@@@@@@@@@@@@.......@@@@....@@@@..@@@@@@@@@@@@@@@...............@@@.. @@@@@@@.....@@@@@.....@@@.....@@@@..@@@@@@......@@@@@............@@@@.. @@@@.........@@@@...@@@@......@@@@..@@@@@........@@@@...........@@@.... @@@@..........@@@@.@@@@.......@@@@..@@@@..........@@@..........@@@@.... @@@@..........@@@@@@@@@.......@@@@.@@@@@..........@@@.........@@@@..... @@@@..........@@@@@@@@@@@@@@@@@@@@@@@@@@..........@@@@.......@@@@...... @@@@..........@@@@@@@@@@@@@@@@@@@@@@@@@@..........@@@@......@@@........ .@@@..........@@@@@@@@@@@@@@@@@@@@@@.@@@..........@@@@....@@@@@........ .@@@@........@@@@.............@@@@...@@@@........@@@@....@@@@.......... ..@@@@.......@@@@.............@@@@....@@@@.......@@@@...@@@@........... ..@@@@@....@@@@@..............@@@@.....@@@@....@@@@@...@@@@@@.......... ....@@@@@@@@@@@...............@@@@......@@@@@@@@@@@...@@@@@@@@@@@@@@@@@ .....@@@@@@@@@................@@@@........@@@@@@@@....@@@@@@@@@@@@@@@@@ ........@@@@...................@@..........@@@@@........@@@@@@@..@.@@@. >>>>^ ^<<>>^ ^<<<<< >>>^<<< ^^ ^ >,< show the path of the detection algorithm |
The latest version of GOCR may use different algorithms. You have to look at the sources learn more.
If the original image is gray, a critical value is calculated to extract characters from the background. This can fail, if images are on the scanned page or tha scan is bad (dark edges or borders). It is difficult to overcome this problem because graylevels are mostly restricted to the 8 bit limit (16 bit would help to overcome this problem).
Black/White images are internally converted to gray with two levels (0 and 255).
The lowest 4 bits are not used, because they are used by internal functions (this can be changed in future).
After calculation of the threshold value (otsu.c) the brightness of every pixel is recalculated to a new internal threshold value of 160 (128+32). This is a bit above the middle of the 8 bit range. The idea is to make the live easier for the other routines. Pixels which does not sure belong to the white or black ones get a value near the threshold value. Some routines can use this bit of more information to ignore outriders. Second point is, that this is necessary for using lowest for bits without destroying image informations.