pax_global_header00006660000000000000000000000064124154110350014506gustar00rootroot0000000000000052 comment=592f3783252ebf4097d3b329d89b411c2197c088 ocrad-0.24/000077500000000000000000000000001241541103500125235ustar00rootroot00000000000000ocrad-0.24/AUTHORS000066400000000000000000000003211241541103500135670ustar00rootroot00000000000000GNU Ocrad was written by Antonio Diaz Diaz. Thanks to Klaas Freitag for his ideas about the ORF file feature. Thanks also to the many people who have contributed useful ideas, sample images, testing, etc... ocrad-0.24/COPYING000066400000000000000000000431511241541103500135620ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ocrad-0.24/ChangeLog000066400000000000000000000174211241541103500143020ustar00rootroot000000000000002014-10-03 Antonio Diaz Diaz * Version 0.24 released. * Improvements in character recognition. * Allow more than one '-e, --filter' option. * Added new filters 'same_height', 'upper_num' and 'upper_num_only'. * New file histogram.h. * ocrcheck.cc: Renamed to ocradcheck.cc. * ocrad.texi: Fixed description of OCRAD_result_blocks. * License changed to GPL version 2 or later. 2014-03-10 Antonio Diaz Diaz * Version 0.23 released. * Improvements in character recognition. * Filters of type '*_only' now remove leading whitespace. * ocradlib.h: Changed 'uint8_t' to 'unsigned char'. * Added some missing inclusions of 'cstdlib'. * ocrad.texinfo: Renamed to ocrad.texi. 2013-07-09 Antonio Diaz Diaz * Version 0.22 released. * Scaling and smoothing are now made before thresholding. * Improvements in character recognition. * ocradlib.h: Added new function OCRAD_set_utf8_format. * Small improvements have been made in manual and man page. * Changed quote characters in messages as advised by GNU Standards. * configure: Options now accept a separate argument. * configure: 'datadir' renamed to 'datarootdir'. * Makefile.in: Added new target 'install-bin'. 2011-01-10 Antonio Diaz Diaz * Version 0.21 released. * Fixed some internal errors triggered by noisy input. * ocrad.texinfo: Added chapter 'OCR Results File'. * main.cc: Set stdin/stdout in binary mode on MSVC and OS2. 2010-07-16 Antonio Diaz Diaz * Version 0.20 released. * ocradlib.h: Added new function OCRAD_scale. * ocradlib.h: Added new function OCRAD_result_chars_total. * ocradlib.h: Added new function OCRAD_result_chars_block. * ocradlib.h: Added new function OCRAD_result_chars_line. 2010-01-27 Antonio Diaz Diaz * Version 0.19 released. * Added library interface (ocradlib.h). * Option '-p, --crop' replaced with similar but different option '-u, --cut', which can accept coordinates taken from the ORF file. * Recognition of files with a single character and without white space at the edges has been fixed. * testsuite/check.sh: Added new tests for the library interface and for single character images. * New files ocradlib.h, ocradlib.cc, ocrcheck.cc. * Makefile.in: Added '--name' option to help2man invocation. 2009-05-08 Antonio Diaz Diaz * Version 0.18 released. * Added a layout analyser able to process arbitrary pages. * Added new option '-q, --quiet'. * The '--layout' option no more accepts an argument. * The '--crop' option now accepts negative coordinates. * New recognized letter; 'a' with ring above. * Fixed recognition on files with a single big character. * Fixed bug that didn't write maxval when saving pgm or ppm. * Fixed some includes that prevented compilation with GCC 4.3.0. * 'make install-info' should now work on Debian and OS X. * Makefile.in: Man page is now installed by default. * New file testsuite/check.sh. * Arg_parser updated to 1.2. * Verbosity control of messages has been modified. 2007-06-29 Antonio Diaz Diaz * Version 0.17 released. * License updated to GPL version 3 or later. * '--scale' no more suppresses ORF output. * Improved removal of thick frames. * Changed 'Textline' to accept more than one big initial. * Class 'Block' renamed to 'Blob'. * 'configure' and 'Makefile.in' have been modified to be more GNU-standards compliant. 2006-10-20 Antonio Diaz Diaz * Version 0.16 released. * Added new option '-e, --filter'. * Better algorithm for vertical space detection (blank lines). * Some fixes made to 'configure' script. * Added two new debug levels. * Improvements in character recognition. 2006-04-03 Antonio Diaz Diaz * Version 0.15 released. * Added new argument parser that replaces 'getopt_long'. * Fixed a bug that prevented compilation with GCC 4.1. 2006-02-15 Antonio Diaz Diaz * Version 0.14 released. * Ocrad is now able to read ppm files. * Added new class 'Page_image' (256-level greymap). * Added automatic and adaptive binarization by Otsu's method. * Added new option '-p, --crop'. * Added two new chapters 'Image Format Conversion' and 'Algorithm' to the texinfo file. * Target 'check' added to Makefile. * Changed 'ocrad.png' icon to color, one line. 2005-10-10 Antonio Diaz Diaz * Version 0.13 released. * Ocrad is now able to read pgm files. * Added new rational number class. * Use rationals instead of integers in space detection algorithm. * Better algorithm for space detection in tables. * 'vector' replaced by 'vector' in bitmap (faster). * Variable-size arrays replaced by vectors in block.cc. * Fixed sizeof(size_t) != sizeof(int) on some 64 bit systems. * Improved number recognition (mainly in textline_r2.cc). * Overflow detection when loading or scaling file. * Fixed a miscompilation with GCC 3.3.1. * Class 'Vrhomboid' merged into files 'track.h' and 'track.cc'. 2005-06-07 Antonio Diaz Diaz * Version 0.12 released. * Change in internal representation; Blockmap has been eliminated. * Text inside tables of solid lines is now recognized. * Improvements in character recognition. * Fixed possible integer overflow when loading pbm file. 2005-02-12 Antonio Diaz Diaz * Version 0.11 released. * Added new option '-s, --scale'. * Improvements in character recognition. * Fixed bug in '--transform' (introduced in 0.10). 2004-12-09 Antonio Diaz Diaz * Version 0.10 released. * Added new suboption '-D7X'. * Change in internal representation; only 1 Blockmap per Textpage. * Use of absolute coordinates in ORF file. * Improved space detection algorithm. * Improvements in character recognition and separation. 2004-10-23 Antonio Diaz Diaz * Version 0.9 released. * Added new option '-t, --transform'. * 'DESTDIR' now works as expected. * New class 'Textpage' is top of internal representation. 2004-05-23 Antonio Diaz Diaz * Version 0.8 released. * Better algorithm for line detection. * New feature '-x -' (export ORF file to stdout). * Small improvements in picture elimination. 2004-02-09 Antonio Diaz Diaz * Version 0.7 released. * Internal change to UCS instead of ISO 8859-1. * Default charset is now ISO 8859-15 (latin9). * Ocrad now recognizes Turkish characters (ISO 8859-9). * Added new output format (UTF-8). * Added new options '-c, --charset' and '-F, --format'. * Added man page. 2003-12-18 Antonio Diaz Diaz * Version 0.6 released. * 'configure' is now compatible with 'sh'. * Better algorithm for lowercase-uppercase decision. * Small changes to line detector. * Fixed bug (output of char 0 when separating some merged chars). 2003-10-18 Antonio Diaz Diaz * Version 0.5 released. * Corrected bug when creating ORF file from stdin. * Added the ability to read multiple files from stdin. * Use 'vector' instead of 'list' due to problem with GCC 3.3.1. * Faster 'processing' of pictures. 2003-09-03 Antonio Diaz Diaz * Version 0.4 released. * More standard configure and Makefile. * Added info file. * Small changes to layout detector. * Character codes > 127 now in ISO_8859_1:: format. * Added new option '-i, --invert'. 2003-07-19 Antonio Diaz Diaz * Version 0.3 released. * ORF file feature added. * Recursive 'layout detector' added. Copyright (C) 2003-2014 Antonio Diaz Diaz. This file is a collection of facts, and thus it is not copyrightable, but just in case, you have unlimited permission to copy, distribute and modify it. ocrad-0.24/INSTALL000066400000000000000000000032641241541103500135610ustar00rootroot00000000000000Requirements ------------ You will need a C++ compiler. I use gcc 4.9.1 and 3.3.6, but the code should compile with any standards compliant compiler. Gcc is available at http://gcc.gnu.org. Procedure --------- 1. Unpack the archive if you have not done so already: tar -xf ocrad[version].tar.lz or lzip -cd ocrad[version].tar.lz | tar -xf - This creates the directory ./ocrad[version] containing the source from the main archive. 2. Change to ocrad directory and run configure. (Try 'configure --help' for usage instructions). cd ocrad[version] ./configure 3. Run make. make 4. Optionally, type 'make check' to run the tests that come with ocrad. 5. Type 'make install' to install the program, the library and any data files and documentation. You can install only the program, the info manual or the man page by typing 'make install-bin', 'make install-info' or 'make install-man' respectively. Another way ----------- You can also compile ocrad into a separate directory. To do this, you must use a version of 'make' that supports the 'VPATH' variable, such as GNU 'make'. 'cd' to the directory where you want the object files and executables to go and run the 'configure' script. 'configure' automatically checks for the source code in '.', in '..' and in the directory that 'configure' is in. 'configure' recognizes the option '--srcdir=DIR' to control where to look for the sources. Usually 'configure' can determine that directory automatically. After running 'configure', you can run 'make' and 'make install' as explained above. Copyright (C) 2003-2014 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute and modify it. ocrad-0.24/Makefile.in000066400000000000000000000126101241541103500145700ustar00rootroot00000000000000 DISTNAME = $(pkgname)-$(pkgversion) AR = ar INSTALL = install INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_DATA = $(INSTALL) -m 644 INSTALL_DIR = $(INSTALL) -d -m 755 SHELL = /bin/sh lib_objs = ocradlib.o ocr_objs = common.o segment.o mask.o rational.o rectangle.o track.o \ ucs.o page_image.o page_image_io.o \ bitmap.o blob.o profile.o feats.o feats_test0.o feats_test1.o \ character.o character_r11.o character_r12.o character_r13.o \ textline.o textline_r2.o textblock.o textpage.o objs = arg_parser.o main.o .PHONY : all install install-bin install-info install-man install-strip \ uninstall uninstall-bin uninstall-info uninstall-man \ doc info man check dist clean distclean all : $(progname) lib$(libname).a lib$(libname).a: $(ocr_objs) $(lib_objs) $(AR) -rcs $@ $(ocr_objs) $(lib_objs) $(progname) : $(ocr_objs) $(objs) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(ocr_objs) $(objs) ocradcheck : ocradcheck.o lib$(libname).a $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ ocradcheck.o lib$(libname).a ocradcheck.o : ocradcheck.cc $(CXX) $(CXXFLAGS) $(CPPFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $< main.o : main.cc $(CXX) $(CXXFLAGS) $(CPPFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $< %.o : %.cc $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< $(lib_objs) : Makefile ocradlib.h $(ocr_objs) : Makefile bitmap.h blob.h common.h rectangle.h ucs.h $(objs) : Makefile arg_parser.h character.o : segment.h character.h profile.h feats.h character_r11.o : segment.h character.h profile.h feats.h character_r12.o : segment.h character.h profile.h feats.h character_r13.o : segment.h character.h profile.h feats.h feats.o : segment.h profile.h feats.h feats_test0.o : segment.h profile.h feats.h feats_test1.o : segment.h profile.h feats.h main.o : common.h rational.h rectangle.h page_image.h textpage.h mask.o : segment.h mask.h ocradlib.o : common.h rectangle.h ucs.h track.h bitmap.h blob.h character.h page_image.h textline.h textblock.h textpage.h page_image.o : ocradlib.h rational.h segment.h mask.h track.h page_image.h page_image_io.o : rational.h page_image.h profile.o : profile.h rational.o : rational.h segment.o : segment.h textblock.o : rational.h track.h character.h page_image.h textline.h textblock.h textline.o : histogram.h rational.h track.h character.h page_image.h textline.h textline_r2.o : track.h character.h textline.h textpage.o : segment.h mask.h track.h character.h page_image.h textline.h textblock.h textpage.h track.o : track.h ocradcheck.o : Makefile ocradlib.h doc : info man info : $(VPATH)/doc/$(pkgname).info $(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texi cd $(VPATH)/doc && makeinfo $(pkgname).texi man : $(VPATH)/doc/$(progname).1 $(VPATH)/doc/$(progname).1 : $(progname) help2man -n 'command line text recognition tool' -o $@ ./$(progname) Makefile : $(VPATH)/configure $(VPATH)/Makefile.in ./config.status check : all ocradcheck @$(VPATH)/testsuite/check.sh $(VPATH)/testsuite $(pkgversion) install : install-bin install-info install-man install-bin : all if [ ! -d "$(DESTDIR)$(bindir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(bindir)" ; fi if [ ! -d "$(DESTDIR)$(includedir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(includedir)" ; fi if [ ! -d "$(DESTDIR)$(libdir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(libdir)" ; fi $(INSTALL_PROGRAM) ./$(progname) "$(DESTDIR)$(bindir)/$(progname)" $(INSTALL_DATA) $(VPATH)/$(libname)lib.h "$(DESTDIR)$(includedir)/$(libname)lib.h" $(INSTALL_DATA) ./lib$(libname).a "$(DESTDIR)$(libdir)/lib$(libname).a" install-info : if [ ! -d "$(DESTDIR)$(infodir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(infodir)" ; fi $(INSTALL_DATA) $(VPATH)/doc/$(pkgname).info "$(DESTDIR)$(infodir)/$(pkgname).info" -install-info --info-dir="$(DESTDIR)$(infodir)" "$(DESTDIR)$(infodir)/$(pkgname).info" install-man : if [ ! -d "$(DESTDIR)$(mandir)/man1" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1" ; fi $(INSTALL_DATA) $(VPATH)/doc/$(progname).1 "$(DESTDIR)$(mandir)/man1/$(progname).1" install-strip : all $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install uninstall : uninstall-bin uninstall-info uninstall-man uninstall-bin : -rm -f "$(DESTDIR)$(bindir)/$(progname)" -rm -f "$(DESTDIR)$(includedir)/$(libname)lib.h" -rm -f "$(DESTDIR)$(libdir)/lib$(libname).a" uninstall-info : -install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(pkgname).info" -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info" uninstall-man : -rm -f "$(DESTDIR)$(mandir)/man1/$(progname).1" dist : doc ln -sf $(VPATH) $(DISTNAME) tar -Hustar --owner=root --group=root -cvf $(DISTNAME).tar \ $(DISTNAME)/AUTHORS \ $(DISTNAME)/COPYING \ $(DISTNAME)/ChangeLog \ $(DISTNAME)/INSTALL \ $(DISTNAME)/Makefile.in \ $(DISTNAME)/NEWS \ $(DISTNAME)/README \ $(DISTNAME)/configure \ $(DISTNAME)/doc/$(progname).1 \ $(DISTNAME)/doc/$(pkgname).info \ $(DISTNAME)/doc/$(pkgname).texi \ $(DISTNAME)/testsuite/check.sh \ $(DISTNAME)/testsuite/test.pbm \ $(DISTNAME)/testsuite/test.txt \ $(DISTNAME)/testsuite/test_utf8.txt \ $(DISTNAME)/ocrad.png \ $(DISTNAME)/*.h \ $(DISTNAME)/*.cc rm -f $(DISTNAME) lzip -v -9 $(DISTNAME).tar clean : -rm -f $(progname) $(objs) -rm -f ocradcheck ocradcheck.o $(ocr_objs) $(lib_objs) *.a distclean : clean -rm -f Makefile config.status *.tar *.tar.lz ocrad-0.24/NEWS000066400000000000000000000010361241541103500132220ustar00rootroot00000000000000Changes in version 0.24: Character recognition has been improved. (merged 'LZ', 'TZ' and 'rt', '9' vs 'g', 'A' vs 'R', 'C' vs '(', 'G', 'J', 'L', 's', 'x', 'z'). Several filters can be now applied in sequence using more than one "--filter" option. The new filters "same_height", "upper_num" and "upper_num_only", have been added. The description of "OCRAD_result_blocks" in the manual has been fixed. The license has been changed to GPL version 2 or later for better GPL compatibility. GPLv2 and GPLv2+ programs may use the library now. ocrad-0.24/README000066400000000000000000000040641241541103500134070ustar00rootroot00000000000000Description GNU Ocrad is an OCR (Optical Character Recognition) program and library based on a feature extraction method. It reads images in pbm (bitmap), pgm (greyscale) or ppm (color) formats and produces text in byte (8-bit) or UTF-8 formats. The pbm, pgm and ppm formats are collectively known as pnm. Ocrad includes a layout analyser able to separate the columns or blocks of text normally found on printed pages. See the file INSTALL for compilation and installation instructions. Try "ocrad --help" for usage instructions. Caveats. For best results, the characters should be at least 20 pixels high. If they are smaller, try the --scale option. Scanning the image at 300 dpi usually produces a character size good enough for ocrad. Merged characters are always a problem. Try to avoid them. Very bold or very light (broken) characters are also a problem. Always see with your own eyes the pnm file before blaming ocrad for the results. Remember the saying, "garbage in, garbage out". Ideas, comments, patches, donations (hardware, money, etc), etc, are welcome. --------------------------- Debug levels ( option -D ) 100 - Show raw block list. 99 - Show recursive block list. 98 - Show main block list. 96..97 - reserved. 95 - Show all blocks from every character before recognition. 94 - Show main black blocks from every character before recognition. 90..93 - reserved. 89 - Show all blocks from every character. 88 - Show main black blocks from every character. 87 - Show guess list for every character. 86 - Show best guess for every character. 80..85 - reserved. 78..79 - reserved. 7X - X = 0 Show page as bitmap. X = 1 Show page as bitmap with marked zones. X = 2 Show page as bitmap with marked lines. X = 4 Show page as bitmap with marked characters. Copyright (C) 2003-2014 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute and modify it. The file Makefile.in is a data file used by configure to produce the Makefile. It has the same copyright owner and permissions that configure itself. ocrad-0.24/arg_parser.cc000066400000000000000000000144131241541103500151620ustar00rootroot00000000000000/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) Copyright (C) 2006-2014 Antonio Diaz Diaz. This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see . As a special exception, you may use this file as part of a free software library without restriction. Specifically, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other files to produce an executable, this file does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ #include #include #include #include "arg_parser.h" bool Arg_parser::parse_long_option( const char * const opt, const char * const arg, const Option options[], int & argind ) { unsigned len; int index = -1; bool exact = false, ambig = false; for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ; // Test all long options for either exact match or abbreviated matches. for( int i = 0; options[i].code != 0; ++i ) if( options[i].name && std::strncmp( options[i].name, &opt[2], len ) == 0 ) { if( std::strlen( options[i].name ) == len ) // Exact match found { index = i; exact = true; break; } else if( index < 0 ) index = i; // First nonexact match found else if( options[index].code != options[i].code || options[index].has_arg != options[i].has_arg ) ambig = true; // Second or later nonexact match found } if( ambig && !exact ) { error_ = "option '"; error_ += opt; error_ += "' is ambiguous"; return false; } if( index < 0 ) // nothing found { error_ = "unrecognized option '"; error_ += opt; error_ += '\''; return false; } ++argind; data.push_back( Record( options[index].code ) ); if( opt[len+2] ) // '--=' syntax { if( options[index].has_arg == no ) { error_ = "option '--"; error_ += options[index].name; error_ += "' doesn't allow an argument"; return false; } if( options[index].has_arg == yes && !opt[len+3] ) { error_ = "option '--"; error_ += options[index].name; error_ += "' requires an argument"; return false; } data.back().argument = &opt[len+3]; return true; } if( options[index].has_arg == yes ) { if( !arg || !arg[0] ) { error_ = "option '--"; error_ += options[index].name; error_ += "' requires an argument"; return false; } ++argind; data.back().argument = arg; return true; } return true; } bool Arg_parser::parse_short_option( const char * const opt, const char * const arg, const Option options[], int & argind ) { int cind = 1; // character index in opt while( cind > 0 ) { int index = -1; const unsigned char c = opt[cind]; if( c != 0 ) for( int i = 0; options[i].code; ++i ) if( c == options[i].code ) { index = i; break; } if( index < 0 ) { error_ = "invalid option -- '"; error_ += c; error_ += '\''; return false; } data.push_back( Record( c ) ); if( opt[++cind] == 0 ) { ++argind; cind = 0; } // opt finished if( options[index].has_arg != no && cind > 0 && opt[cind] ) { data.back().argument = &opt[cind]; ++argind; cind = 0; } else if( options[index].has_arg == yes ) { if( !arg || !arg[0] ) { error_ = "option requires an argument -- '"; error_ += c; error_ += '\''; return false; } data.back().argument = arg; ++argind; cind = 0; } } return true; } Arg_parser::Arg_parser( const int argc, const char * const argv[], const Option options[], const bool in_order ) { if( argc < 2 || !argv || !options ) return; std::vector< std::string > non_options; // skipped non-options int argind = 1; // index in argv while( argind < argc ) { const unsigned char ch1 = argv[argind][0]; const unsigned char ch2 = ch1 ? argv[argind][1] : 0; if( ch1 == '-' && ch2 ) // we found an option { const char * const opt = argv[argind]; const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0; if( ch2 == '-' ) { if( !argv[argind][2] ) { ++argind; break; } // we found "--" else if( !parse_long_option( opt, arg, options, argind ) ) break; } else if( !parse_short_option( opt, arg, options, argind ) ) break; } else { if( !in_order ) non_options.push_back( argv[argind++] ); else { data.push_back( Record() ); data.back().argument = argv[argind++]; } } } if( error_.size() ) data.clear(); else { for( unsigned i = 0; i < non_options.size(); ++i ) { data.push_back( Record() ); data.back().argument.swap( non_options[i] ); } while( argind < argc ) { data.push_back( Record() ); data.back().argument = argv[argind++]; } } } Arg_parser::Arg_parser( const char * const opt, const char * const arg, const Option options[] ) { if( !opt || !opt[0] || !options ) return; if( opt[0] == '-' && opt[1] ) // we found an option { int argind = 1; // dummy if( opt[1] == '-' ) { if( opt[2] ) parse_long_option( opt, arg, options, argind ); } else parse_short_option( opt, arg, options, argind ); if( error_.size() ) data.clear(); } else { data.push_back( Record() ); data.back().argument = opt; } } ocrad-0.24/arg_parser.h000066400000000000000000000076331241541103500150320ustar00rootroot00000000000000/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) Copyright (C) 2006-2014 Antonio Diaz Diaz. This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see . As a special exception, you may use this file as part of a free software library without restriction. Specifically, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other files to produce an executable, this file does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ /* Arg_parser reads the arguments in 'argv' and creates a number of option codes, option arguments and non-option arguments. In case of error, 'error' returns a non-empty error message. 'options' is an array of 'struct Option' terminated by an element containing a code which is zero. A null name means a short-only option. A code value outside the unsigned char range means a long-only option. Arg_parser normally makes it appear as if all the option arguments were specified before all the non-option arguments for the purposes of parsing, even if the user of your program intermixed option and non-option arguments. If you want the arguments in the exact order the user typed them, call 'Arg_parser' with 'in_order' = true. The argument '--' terminates all options; any following arguments are treated as non-option arguments, even if they begin with a hyphen. The syntax for optional option arguments is '-' (without whitespace), or '--='. */ class Arg_parser { public: enum Has_arg { no, yes, maybe }; struct Option { int code; // Short option letter or code ( code != 0 ) const char * name; // Long option name (maybe null) Has_arg has_arg; }; private: struct Record { int code; std::string argument; explicit Record( const int c = 0 ) : code( c ) {} }; std::string error_; std::vector< Record > data; bool parse_long_option( const char * const opt, const char * const arg, const Option options[], int & argind ); bool parse_short_option( const char * const opt, const char * const arg, const Option options[], int & argind ); public: Arg_parser( const int argc, const char * const argv[], const Option options[], const bool in_order = false ); // Restricted constructor. Parses a single token and argument (if any) Arg_parser( const char * const opt, const char * const arg, const Option options[] ); const std::string & error() const { return error_; } // The number of arguments parsed (may be different from argc) int arguments() const { return data.size(); } // If code( i ) is 0, argument( i ) is a non-option. // Else argument( i ) is the option's argument (or empty). int code( const int i ) const { if( i >= 0 && i < arguments() ) return data[i].code; else return 0; } const std::string & argument( const int i ) const { if( i >= 0 && i < arguments() ) return data[i].argument; else return error_; } }; ocrad-0.24/bitmap.cc000066400000000000000000000315501241541103500143120ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "bitmap.h" // Creates a blank Bitmap Bitmap::Bitmap( const int l, const int t, const int r, const int b ) : Rectangle( l, t, r, b ), data( height() ) { for( int row = 0; row < height(); ++row ) data[row].resize( width(), false ); } // Creates a Bitmap from part of another Bitmap Bitmap::Bitmap( const Bitmap & source, const Rectangle & re ) : Rectangle( re ), data( re.height() ) { if( !source.includes( re ) ) Ocrad::internal_error( "bad parameter building a Bitmap from part of another one." ); const int ldiff = left()-source.left(); const int tdiff = top()-source.top(); for( int row = 0; row < height(); ++row ) { data[row].resize( width() ); std::vector< uint8_t > & datarow = data[row]; const std::vector< uint8_t > & datarow2 = source.data[row+tdiff]; for( int col = 0; col < width(); ++col ) datarow[col] = datarow2[col+ldiff]; } } void Bitmap::left( const int l ) { if( l == left() ) return; if( l < left() ) for( int row = height() - 1; row >= 0 ; --row ) data[row].insert( data[row].begin(), left() - l, false ); else for( int row = height() - 1; row >= 0 ; --row ) data[row].erase( data[row].begin(), data[row].begin() + ( l - left() ) ); Rectangle::left( l ); } void Bitmap::top( const int t ) { if( t == top() ) return; if( t < top() ) data.insert( data.begin(), top() - t, std::vector< uint8_t >( width(), false ) ); else data.erase( data.begin(), data.begin() + ( t - top() ) ); Rectangle::top( t ); } void Bitmap::right( const int r ) { if( r == right() ) return; Rectangle::right( r ); for( int row = height() - 1; row >= 0 ; --row ) data[row].resize( width(), false ); } void Bitmap::bottom( const int b ) { if( b == bottom() ) return; int old_height = height(); Rectangle::bottom( b ); data.resize( height() ); for( int row = old_height; row < height(); ++row ) data[row].resize( width(), false ); } void Bitmap::add_bitmap( const Bitmap & bm ) { add_rectangle( bm ); for( int row = bm.top(); row <= bm.bottom(); ++row ) for( int col = bm.left(); col <= bm.right(); ++col ) if( bm.get_bit( row, col ) ) set_bit( row, col, true ); } void Bitmap::add_point( const int row, const int col ) { if( col > right() ) right( col ); else if( col < left() ) left( col ); if( row > bottom() ) bottom( row ); else if( row < top() ) top( row ); set_bit( row, col, true ); } void Bitmap::add_rectangle( const Rectangle & re ) { if( re.left() < left() ) left( re.left() ); if( re.top() < top() ) top( re.top() ); if( re.right() > right() ) right( re.right() ); if( re.bottom() > bottom() ) bottom( re.bottom() ); } // Returns false if bitmap is empty // bool Bitmap::adjust_height() { int row1, row2; for( row1 = top(); row1 <= bottom(); ++row1 ) for( int col = left(); col <= right(); ++col ) if( get_bit( row1, col ) ) goto L1; L1: for( row2 = bottom(); row2 >= row1; --row2 ) for( int col = left(); col <= right(); ++col ) if( get_bit( row2, col ) ) goto L2; L2: if( row1 > row2 ) return false; if( row1 > top() ) top( row1 ); if( row2 < bottom() ) bottom( row2 ); return true; } // Returns false if bitmap is empty // bool Bitmap::adjust_width() { int col1, col2; for( col1 = left(); col1 <= right(); ++col1 ) for( int row = top(); row <= bottom(); ++row ) if( get_bit( row , col1 ) ) goto L1; L1: for( col2 = right(); col2 >= col1; --col2 ) for( int row = top(); row <= bottom(); ++row ) if( get_bit( row , col2) ) goto L2; L2: if( col1 >= col2 ) return false; if( col1 > left() ) left( col1 ); if( col2 < right() ) right( col2 ); return true; } // Return the total filled area of this Bitmap // int Bitmap::area() const { int a = 0; for( int row = top(); row <= bottom(); ++row ) for( int col = left(); col <= right(); ++col ) if( get_bit( row, col ) ) ++a; return a; } // Return the central octagon filled area of this Bitmap // int Bitmap::area_octagon() const { int a = 0; int bevel = ( 29 * std::min( height(), width() ) ) / 100; int l = left() + bevel; int r = right() - bevel; for( int i = 0; i < bevel; ++i ) for( int row = top() + i, col = l - i; col <= r + i; ++col ) if( get_bit( row, col ) ) ++a; for( int row = top() + bevel; row <= bottom() - bevel; ++row ) for( int col = left(); col <= right(); ++col ) if( get_bit( row, col ) ) ++a; for( int i = bevel - 1; i >= 0; --i ) for( int row = bottom() - i, col = l - i; col <= r + i; ++col ) if( get_bit( row, col ) ) ++a; return a; } // Return the size of the central octagon of this blob // int Bitmap::size_octagon() const { int bevel = ( 29 * std::min( height(), width() ) ) / 100; return size() - ( 2 * bevel * ( bevel + 1 ) ); } int Bitmap::seek_left( const int row, const int col, const bool black ) const { int c = col; while( c > left() && get_bit( row, c - 1 ) != black ) --c; return c; } int Bitmap::seek_top( const int row, const int col, const bool black ) const { int r = row; while( r > top() && get_bit( r - 1, col ) != black ) --r; return r; } int Bitmap::seek_right( const int row, const int col, const bool black ) const { int c = col; while( c < right() && get_bit( row, c + 1 ) != black ) ++c; return c; } int Bitmap::seek_bottom( const int row, const int col, const bool black ) const { int r = row; while( r < bottom() && get_bit( r + 1, col ) != black ) ++r; return r; } bool Bitmap::escape_left( int row, int col ) const { if( get_bit( row, col ) ) return false; int u, d; for( u = row; u > top() + 1; --u ) if( get_bit( u - 1, col ) ) break; for( d = row; d < bottom() - 1; ++d ) if( get_bit( d + 1, col ) ) break; while( u <= d && --col >= left() ) { if( u > top() + 1 && !get_bit( u, col ) ) --u; if( d < bottom() - 1 && !get_bit( d, col ) ) ++d; while( u <= d && get_bit( u, col ) ) ++u; while( u <= d && get_bit( d, col ) ) --d; } return ( col < left() ); } bool Bitmap::escape_top( int row, int col ) const { if( get_bit( row, col ) ) return false; int l, r; for( l = col; l > left() + 1; --l ) if( get_bit( row, l - 1 ) ) break; for( r = col; r < right() - 1; ++r ) if( get_bit( row, r + 1 ) ) break; while( l <= r && --row >= top() ) { if( l > left() + 1 && !get_bit( row, l ) ) --l; if( r < right() - 1 && !get_bit( row, r ) ) ++r; while( l <= r && get_bit( row, l ) ) ++l; while( l <= r && get_bit( row, r ) ) --r; } return ( row < top() ); } bool Bitmap::escape_right( int row, int col ) const { if( get_bit( row, col ) ) return false; int u, d; for( u = row; u > top() + 1; --u ) if( get_bit( u - 1, col ) ) break; for( d = row; d < bottom() - 1; ++d ) if( get_bit( d + 1, col ) ) break; while( u <= d && ++col <= right() ) { if( u > top() + 1 && !get_bit( u, col ) ) --u; if( d < bottom() - 1 && !get_bit( d, col ) ) ++d; while( u <= d && get_bit( u, col ) ) ++u; while( u <= d && get_bit( d, col ) ) --d; } return ( col > right() ); } bool Bitmap::escape_bottom( int row, int col ) const { if( get_bit( row, col ) ) return false; int l, r; for( l = col; l > left() + 1; --l ) if( get_bit( row, l - 1 ) ) break; for( r = col; r < right() - 1; ++r ) if( get_bit( row, r + 1 ) ) break; while( l <= r && ++row <= bottom() ) { if( l > left() + 1 && !get_bit( row, l ) ) --l; if( r < right() - 1 && !get_bit( row, r ) ) ++r; while( l <= r && get_bit( row, l ) ) ++l; while( l <= r && get_bit( row, r ) ) --r; } return ( row > bottom() ); } int Bitmap::follow_top( int row, int col ) const { if( !get_bit( row, col ) ) return row; std::vector< uint8_t > array; array.reserve( width() ); int c; for( c = col; c > left() && get_bit( row, c - 1 ); --c ) ; if( c > left() ) array.resize( c - left(), false ); for( c = col; c < right() && get_bit( row, c + 1 ); ++c ) ; array.resize( c - left() + 1, true ); if( c < right() ) array.resize( width(), false ); while( --row >= top() ) { bool alive = false; for( int i = 0; i < width(); ++i ) if( array[i] ) { if( !get_bit( row, left() + i ) ) array[i] = false; else alive = true; } if( !alive ) break; for( int i = 1; i < width(); ++i ) if( array[i-1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; for( int i = width() - 2; i >= 0; --i ) if( array[i+1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; } return row + 1; } int Bitmap::follow_bottom( int row, int col ) const { if( !get_bit( row, col ) ) return row; std::vector< uint8_t > array; array.reserve( width() ); int c; for( c = col; c > left() && get_bit( row, c - 1 ); --c ) ; if( c > left() ) array.resize( c - left(), false ); for( c = col; c < right() && get_bit( row, c + 1 ); ++c ) ; array.resize( c - left() + 1, true ); if( c < right() ) array.resize( width(), false ); while( ++row <= bottom() ) { bool alive = false; for( int i = 0; i < width(); ++i ) if( array[i] ) { if( !get_bit( row, left() + i ) ) array[i] = false; else alive = true; } if( !alive ) break; for( int i = 1; i < width(); ++i ) if( array[i-1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; for( int i = width() - 2; i >= 0; --i ) if( array[i+1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; } return row - 1; } // Looks for an inverted-U-shaped curve near the top, then tests which of // the vertical bars goes deeper // bool Bitmap::top_hook( int *hdiff ) const { int row, lcol = 0, rcol = 0, black_section = 0, wmax = 0; for( row = top() + 1; row < vcenter(); ++row ) { int l = -1, r = -2; bool prev_black = false; black_section = 0; for( int col = left(); col <= right(); ++col ) { bool black = get_bit( row, col ); if( black ) { if( !prev_black && ++black_section == 2 ) rcol = col; r = col; if( l < 0 ) l = col; } else if( prev_black && black_section == 1 ) lcol = col - 1; prev_black = black; } r = r - l + 1; if( 10 * r <= 9 * wmax ) return false; if( r > wmax ) wmax = r ; if( black_section >= 2 ) break; } if( black_section != 2 ) return false; if( escape_top( row, lcol + 1 ) ) return false; int lrow = follow_bottom( row, lcol ), rrow = follow_bottom( row, rcol ); if( lrow <= row || rrow <= row ) return false; if( hdiff ) *hdiff = lrow - rrow; return true; } // Looks for an U-shaped curve near the bottom, then tests which of // the vertical bars is taller // bool Bitmap::bottom_hook( int *hdiff ) const { int row, lcol = 0, rcol = 0, black_section = 0, wmax = 0; for( row = bottom(); row > vpos( 80 ); --row ) { int l, r; for( l = left(); l <= right(); ++l ) if( get_bit( row, l ) ) break; for( r = right(); r > l; --r ) if( get_bit( row, r ) ) break; const int w = r - l + 1; if( w > wmax ) wmax = w; if( 4 * w >= width() ) { int i; for( i = l + 1; i < r; ++i ) if( !get_bit( row, i ) ) break; if( i >= r ) break; } } if( row > vpos( 80 ) ) while( --row > vcenter() ) { int l = -1, r = -2; bool prev_black = false; black_section = 0; for( int col = left(); col <= right(); ++col ) { bool black = get_bit( row, col ); if( black ) { if( !prev_black && ++black_section == 2 ) rcol = col; r = col; if( l < 0 ) l = col; } else if( prev_black && black_section == 1 ) lcol = col - 1; prev_black = black; } const int w = r - l + 1; if( black_section > 2 || 10 * w <= 8 * wmax ) break; if( w > wmax ) wmax = w; if( black_section == 2 && rcol - lcol >= 2 ) { if( escape_bottom( row, lcol + 1 ) ) break; if( hdiff ) *hdiff = follow_top( row, lcol ) - follow_top( row, rcol ); return true; } } return false; } ocrad-0.24/bitmap.h000066400000000000000000000051521241541103500141530ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Bitmap : public Rectangle { std::vector< std::vector< uint8_t > > data; // faster than bool public: // Creates a blank Bitmap Bitmap( const int l, const int t, const int r, const int b ); // Creates a Bitmap from part of another Bitmap Bitmap( const Bitmap & source, const Rectangle & re ); using Rectangle::left; using Rectangle::top; using Rectangle::right; using Rectangle::bottom; using Rectangle::height; using Rectangle::width; void left ( const int l ); void top ( const int t ); void right ( const int r ); void bottom( const int b ); void height( const int h ) { bottom( top() + h - 1 ); } void width ( const int w ) { right( left() + w - 1 ); } void add_bitmap( const Bitmap & bm ); void add_point( const int row, const int col ); void add_rectangle( const Rectangle & re ); bool adjust_height(); bool adjust_width(); bool get_bit( const int row, const int col ) const { return data[row-top()][col-left()]; } void set_bit( const int row, const int col, const bool bit ) { data[row-top()][col-left()] = bit; } int area() const; // 'area' means filled area int area_octagon() const; int size_octagon() const; int seek_left ( const int row, const int col, const bool black = true ) const; int seek_top ( const int row, const int col, const bool black = true ) const; int seek_right ( const int row, const int col, const bool black = true ) const; int seek_bottom( const int row, const int col, const bool black = true ) const; bool escape_left ( int row, int col ) const; bool escape_top ( int row, int col ) const; bool escape_right ( int row, int col ) const; bool escape_bottom( int row, int col ) const; int follow_top ( int row, int col ) const; int follow_bottom( int row, int col ) const; bool top_hook ( int *hdiff ) const; bool bottom_hook( int *hdiff ) const; }; ocrad-0.24/blob.cc000066400000000000000000000206271241541103500137570ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "common.h" #include "rectangle.h" #include "bitmap.h" #include "blob.h" namespace { void delete_hole( std::vector< Bitmap * > & holep_vector, std::vector< Bitmap * > & v1, std::vector< Bitmap * > & v2, Bitmap * const p, int i ) { std::replace( v1.begin() + i, v1.end(), p, (Bitmap *) 0 ); std::replace( v2.begin(), v2.begin() + i, p, (Bitmap *) 0 ); i = holep_vector.size(); while( --i >= 0 && holep_vector[i] != p ) ; if( i < 0 ) Ocrad::internal_error( "delete_hole, lost hole." ); holep_vector.erase( holep_vector.begin() + i ); delete p; } inline void join_holes( std::vector< Bitmap * > & holep_vector, std::vector< Bitmap * > & v1, std::vector< Bitmap * > & v2, Bitmap * p1, Bitmap * p2, int i ) { if( p1->top() > p2->top() ) { Bitmap * const temp = p1; p1 = p2; p2 = temp; std::replace( v2.begin(), v2.begin() + ( i + 1 ), p2, p1 ); } else std::replace( v1.begin() + i, v1.end(), p2, p1 ); i = holep_vector.size(); while( --i >= 0 && holep_vector[i] != p2 ) ; if( i < 0 ) Ocrad::internal_error( "join_holes, lost hole." ); holep_vector.erase( holep_vector.begin() + i ); p1->add_bitmap( *p2 ); delete p2; } void delete_outer_holes( const Rectangle & re, std::vector< Bitmap * > & holepv ) { for( unsigned i = holepv.size(); i > 0; ) { Bitmap & h = *holepv[--i]; if( !re.strictly_includes( h ) ) { delete &h; holepv.erase( holepv.begin() + i ); } } } } // end namespace Blob::Blob( const Blob & b ) : Bitmap( b ), holepv( b.holepv ) { for( unsigned i = 0; i < holepv.size(); ++i ) holepv[i] = new Bitmap( *b.holepv[i] ); } Blob & Blob::operator=( const Blob & b ) { if( this != &b ) { Bitmap::operator=( b ); for( unsigned i = 0; i < holepv.size(); ++i ) delete holepv[i]; holepv = b.holepv; for( unsigned i = 0; i < holepv.size(); ++i ) holepv[i] = new Bitmap( *b.holepv[i] ); } return *this; } Blob::~Blob() { for( unsigned i = 0; i < holepv.size(); ++i ) delete holepv[i]; } void Blob::left( const int l ) { const int d = l - left(); if( d ) { Bitmap::left( l ); if( d > 0 ) delete_outer_holes( *this, holepv ); } } void Blob::top( const int t ) { const int d = t - top(); if( d ) { Bitmap::top( t ); if( d > 0 ) delete_outer_holes( *this, holepv ); } } void Blob::right( const int r ) { const int d = r - right(); if( d ) { Bitmap::right( r ); if( d < 0 ) delete_outer_holes( *this, holepv ); } } void Blob::bottom( const int b ) { const int d = b - bottom(); if( d ) { Bitmap::bottom( b ); if( d < 0 ) delete_outer_holes( *this, holepv ); } } const Bitmap & Blob::hole( const int i ) const { if( i < 0 || i >= holes() ) Ocrad::internal_error( "hole, index out of bounds." ); return *holepv[i]; } int Blob::id( const int row, const int col ) const { if( this->includes( row, col ) ) { if( get_bit( row, col ) ) return 1; for( int i = 0; i < holes(); ++i ) if( holepv[i]->includes( row, col ) && holepv[i]->get_bit( row, col ) ) return -( i + 1 ); } return 0; } bool Blob::test_BD() const { const int wlimit = std::min( height(), width() ) / 2; int lb = wlimit, rt = wlimit; // index of first dot found for( int i = 0; i < wlimit; ++i ) if( id( bottom() - i, left() + i ) != 0 || id( bottom() - i, left() + i + 1 ) != 0 ) { lb = i; break; } for( int i = 0; i < wlimit; ++i ) if( id( top() + i, right() - i ) != 0 ) { rt = i; break; } return ( rt >= 2 && 3 * lb <= rt ); } bool Blob::test_Q() const { const int wlimit = std::min( height(), width() ) / 2; int ltwmax = 0, rbwmax = 0; int ltimin = wlimit, rbimin = wlimit; // index of first dot found for( int disp = 0; disp < width() / 4; ++disp ) { int ltw = 0, rbw = 0; for( int i = 0; i < wlimit; ++i ) { if( id( top() + i, left() + disp + i ) == 1 ) { ++ltw; if( ltimin > i ) ltimin = i; } if( id( bottom() - i, right() - disp - i ) == 1 ) { ++rbw; if( rbimin > i ) rbimin = i; } } if( ltwmax < ltw ) ltwmax = ltw; if( rbwmax < rbw ) rbwmax = rbw; } return ( ( ltimin > rbimin || rbimin == 0 ) && ( 2 * ltwmax < rbwmax || ( 2 * ltwmax == rbwmax && rbwmax >= 4 ) ) ); } void Blob::print( FILE * const outfile ) const { for( int row = top(); row <= bottom(); ++row ) { for( int col = left(); col <= right(); ++col ) { if( get_bit( row, col ) ) std::fprintf( outfile, " O" ); else std::fprintf( outfile, " ." ); } std::fputs( "\n", outfile ); } std::fputs( "\n", outfile ); } void Blob::fill_hole( const int i ) { if( i < 0 || i >= holes() ) Ocrad::internal_error( "fill_hole, index out of bounds." ); add_bitmap( *holepv[i] ); delete holepv[i]; holepv.erase( holepv.begin() + i ); } void Blob::find_holes() { for( unsigned i = 0; i < holepv.size(); ++i ) delete holepv[i]; holepv.clear(); if( height() < 3 || width() < 3 ) return; std::vector< Bitmap * > old_data( width(), (Bitmap *) 0 ); std::vector< Bitmap * > new_data( width(), (Bitmap *) 0 ); for( int row = top(); row <= bottom(); ++row ) { old_data.swap( new_data ); new_data[0] = get_bit( row, left() ) ? this : 0; for( int col = left() + 1; col < right(); ++col ) { const int dcol = col - left(); if( get_bit( row, col ) ) new_data[dcol] = this; // black pixel else // white pixel { Bitmap *p; Bitmap *lp = new_data[dcol-1]; Bitmap *tp = old_data[dcol]; if( lp == 0 || tp == 0 ) { p = 0; if( lp && lp != this ) delete_hole( holepv, old_data, new_data, lp, dcol ); else if( tp && tp != this ) delete_hole( holepv, old_data, new_data, tp, dcol ); } else if( lp != this ) { p = lp; p->add_point( row, col ); } else if( tp != this ) { p = tp; p->add_point( row, col ); } else { p = new Bitmap( col, row, col, row ); p->set_bit( row, col, true ); holepv.push_back( p ); } new_data[dcol] = p; if( p && lp != tp && lp != this && tp != this ) join_holes( holepv, old_data, new_data, lp, tp, dcol ); } } if( !get_bit( row, right() ) ) { Bitmap *lp = new_data[width()-2]; if( lp && lp != this ) delete_hole( holepv, old_data, new_data, lp, width() - 1 ); } } for( unsigned i = holepv.size(); i > 0; ) // FIXME noise holes removal { Bitmap & h = *holepv[--i]; if( this->strictly_includes( h ) && ( h.height() > 4 || h.width() > 4 || ( ( h.height() > 2 || h.width() > 2 ) && h.area() > 3 ) ) ) continue; delete &h; holepv.erase( holepv.begin() + i ); } /* while( holepv.size() > 3 ) { int smin = holepv[0]->size(); for( unsigned i = 1; i < holepv.size(); ++i ) if( holepv[i]->size() < smin ) smin = holepv[i]->size(); for( int i = holepv.size() - 1; i >= 0; --i ) if( holepv[i]->size() == smin ) holepv.erase( holepv.begin() + i ); } for( unsigned i = holepv.size(); i > 0; ) { Bitmap & h = *holepv[--i]; if( !this->strictly_includes( h ) ) { delete &h; holepv.erase( holepv.begin() + i ); } if( 20 * h.height() < height() && 16 * h.width() < width() ) fill_hole( i ); // else if( h.height() < 2 && h.width() < 2 && h.area() < 2 ) // { delete &h; holepv.erase( holepv.begin() + i ); } } */ } ocrad-0.24/blob.h000066400000000000000000000036311241541103500136150ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Blob : public Bitmap { std::vector< Bitmap * > holepv; // vector of holes public: Blob( const int l, const int t, const int r, const int b ) : Bitmap( l, t, r, b ) {} Blob( const Bitmap & source, const Rectangle & re ) : Bitmap( source, re ) {} Blob( const Blob & b ); Blob & operator=( const Blob & b ); ~Blob(); using Bitmap::left; using Bitmap::top; using Bitmap::right; using Bitmap::bottom; using Bitmap::height; using Bitmap::width; void left ( const int l ); void top ( const int t ); void right ( const int r ); void bottom( const int b ); void height( const int h ) { bottom( top() + h - 1 ); } void width ( const int w ) { right( left() + w - 1 ); } const Bitmap & hole( const int i ) const; int holes() const { return holepv.size(); } // id = 1 for blob dots, negative for hole dots, 0 otherwise int id( const int row, const int col ) const; bool is_abnormal() const { return height() < 10 || height() >= 5 * width() || width() >= 3 * height(); } bool test_BD() const; bool test_Q() const; void print( FILE * const outfile ) const; void fill_hole( const int i ); void find_holes(); }; ocrad-0.24/character.cc000066400000000000000000000266071241541103500150010ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" Character::Character( const Character & c ) : Rectangle( c ), blobpv( c.blobpv ), gv( c.gv ) { for( unsigned i = 0; i < blobpv.size(); ++i ) blobpv[i] = new Blob( *c.blobpv[i] ); } Character & Character::operator=( const Character & c ) { if( this != &c ) { Rectangle::operator=( c ); for( unsigned i = 0; i < blobpv.size(); ++i ) delete blobpv[i]; blobpv = c.blobpv; for( unsigned i = 0; i < blobpv.size(); ++i ) blobpv[i] = new Blob( *c.blobpv[i] ); gv = c.gv; } return *this; } Character::~Character() { for( unsigned i = 0; i < blobpv.size(); ++i ) delete blobpv[i]; } // Return the filled area of the main blobs only (no recursive) // int Character::area() const { int a = 0; for( int i = 0; i < blobs(); ++i ) a += blobpv[i]->area(); return a; } const Blob & Character::blob( const int i ) const { if( i < 0 || i >= blobs() ) Ocrad::internal_error( "const blob, index out of bounds" ); return *blobpv[i]; } Blob & Character::blob( const int i ) { if( i < 0 || i >= blobs() ) Ocrad::internal_error( "blob, index out of bounds" ); return *blobpv[i]; } Blob & Character::main_blob() { int imax = 0; for( int i = 1; i < blobs(); ++i ) if( blobpv[i]->size() > blobpv[imax]->size() ) imax = i; return *blobpv[imax]; } void Character::shift_blobp( Blob * const p ) { add_rectangle( *p ); int i = blobs() - 1; for( ; i >= 0; --i ) { Blob & bi = *blobpv[i]; if( p->vcenter() > bi.vcenter() ) break; if( p->vcenter() == bi.vcenter() && p->hcenter() >= bi.hcenter() ) break; } blobpv.insert( blobpv.begin() + ( i + 1 ), p ); } void Character::insert_guess( const int i, const int code, const int value ) { if( i < 0 || i > guesses() ) Ocrad::internal_error( "insert_guess, index out of bounds" ); gv.insert( gv.begin() + i, Guess( code, value ) ); } void Character::delete_guess( const int i ) { if( i < 0 || i >= guesses() ) Ocrad::internal_error( "delete_guess, index out of bounds" ); gv.erase( gv.begin() + i ); } bool Character::set_merged_guess( const int code1, const int right1, const int code2, const int blob_index ) { if( blob_index < 0 || blob_index >= blobs() ) return false; const Blob & b = *blobpv[blob_index]; if( b.left() <= right1 && right1 < b.right() ) { only_guess( -(blob_index + 1), left() ); add_guess( code1, right1 ); add_guess( code2, right() ); return true; } return false; } void Character::swap_guesses( const int i, const int j ) { if( i < 0 || i >= guesses() || j < 0 || j >= guesses() ) Ocrad::internal_error( "swap_guesses, index out of bounds" ); const int code = gv[i].code; gv[i].code = gv[j].code; gv[j].code = code; } const Character::Guess & Character::guess( const int i ) const { if( i < 0 || i >= guesses() ) Ocrad::internal_error( "guess, index out of bounds" ); return gv[i]; } bool Character::maybe( const int code ) const { for( int i = 0; i < guesses(); ++i ) if( code == gv[i].code ) return true; return false; } /* bool Character::maybe_digit() const { for( int i = 0; i < guesses(); ++i ) if( UCS::isdigit( gv[i].code ) ) return true; return false; } bool Character::maybe_letter() const { for( int i = 0; i < guesses(); ++i ) if( UCS::isalpha( gv[i].code ) ) return true; return false; } */ void Character::join( Character & c ) { for( int i = 0; i < c.blobs(); ++i ) shift_blobp( c.blobpv[i] ); c.blobpv.clear(); } unsigned char Character::byte_result() const { if( guesses() ) { const unsigned char ch = UCS::map_to_byte( gv[0].code ); if( ch ) return ch; } return '_'; } const char * Character::utf8_result() const { if( guesses() ) { const char * s = UCS::ucs_to_utf8( gv[0].code ); if( *s ) return s; } return "_"; } void Character::print( const Control & control ) const { if( guesses() ) { if( !control.utf8 ) { unsigned char ch = UCS::map_to_byte( gv[0].code ); if( ch ) std::putc( ch, control.outfile ); } else if( gv[0].code ) std::fputs( UCS::ucs_to_utf8( gv[0].code ), control.outfile ); } else std::putc( '_', control.outfile ); } void Character::dprint( const Control & control, const Rectangle & charbox, const bool graph, const bool recursive ) const { if( graph || recursive ) std::fprintf( control.outfile, "%d guesses ", guesses() ); for( int i = 0; i < guesses(); ++i ) { if( gv[i].code == '\t' ) std::fprintf( control.outfile, "guess '\\t', confidence %d ", gv[i].value ); else if( !control.utf8 ) { unsigned char ch = UCS::map_to_byte( gv[i].code ); if( ch ) std::fprintf( control.outfile, "guess '%c', confidence %d ", ch, gv[i].value ); } else std::fprintf( control.outfile, "guess '%s', confidence %d ", UCS::ucs_to_utf8( gv[i].code ), gv[i].value ); if( !graph && !recursive ) break; } std::fputs( "\n", control.outfile ); if( graph ) { std::fprintf( control.outfile, "left = %d, top = %d, right = %d, bottom = %d\n", left(), top(), right(), bottom() ); std::fprintf( control.outfile, "width = %d, height = %d, hcenter = %d, vcenter = %d, black area = %d%%\n", width(), height(), hcenter(), vcenter(), ( area() * 100 ) / size() ); if( blobs() >= 1 && blobs() <= 3 ) { const Blob & b = blob( blobs() - 1 ); Features f( b ); std::fprintf( control.outfile, "hbars = %d, vbars = %d\n", f.hbars(), f.vbars() ); } std::fputs( "\n", control.outfile ); const int minrow = std::min( top(), charbox.top() ); const int maxrow = std::max( bottom(), charbox.bottom() ); for( int row = minrow; row <= maxrow; ++row ) { bool istop = ( row == top() ); bool isvc = ( row == vcenter() ); bool isbot = ( row == bottom() ); bool iscbtop = ( row == charbox.top() ); bool iscbvc = ( row == charbox.vcenter() ); bool iscbbot = ( row == charbox.bottom() ); bool ish1top = false, ish1bot = false, ish2top = false, ish2bot = false; if( blobs() == 1 && blobpv[0]->holes() ) { const Blob & b = *blobpv[0]; ish1top = ( row == b.hole(0).top() ); ish1bot = ( row == b.hole(0).bottom() ); if( b.holes() > 1 ) { ish2top = ( row == b.hole(1).top() ); ish2bot = ( row == b.hole(1).bottom() ); } } for( int col = left(); col <= right(); ++col ) { char ch = ( isvc && col == hcenter() ) ? '+' : '.'; for( int i = 0; i < blobs(); ++i ) { int id = blobpv[i]->id( row, col ); if( id != 0 ) { if( id > 0 ) ch = (ch == '+') ? 'C' : 'O'; else ch = (ch == '+') ? '=' : '-'; break; } } std::fprintf( control.outfile, " %c", ch ); } if( istop ) std::fprintf( control.outfile, " top(%d)", row ); if( isvc ) std::fprintf( control.outfile, " vcenter(%d)", row ); if( isbot ) std::fprintf( control.outfile, " bottom(%d)", row ); if( iscbtop ) std::fprintf( control.outfile, " box.top(%d)", row ); if( iscbvc ) std::fprintf( control.outfile, " box.vcenter(%d)", row ); if( iscbbot ) std::fprintf( control.outfile, " box.bottom(%d)", row ); if( ish1top ) std::fprintf( control.outfile, " h1.top(%d)", row ); if( ish1bot ) std::fprintf( control.outfile, " h1.bottom(%d)", row ); if( ish2top ) std::fprintf( control.outfile, " h2.top(%d)", row ); if( ish2bot ) std::fprintf( control.outfile, " h2.bottom(%d)", row ); std::fputs( "\n", control.outfile ); } std::fputs( "\n\n", control.outfile ); } } void Character::xprint( const Control & control ) const { std::fprintf( control.exportfile, "%3d %3d %2d %2d; %d", left(), top(), width(), height(), guesses() ); for( int i = 0; i < guesses(); ++i ) if( !control.utf8 ) { unsigned char ch = UCS::map_to_byte( gv[i].code ); if( !ch ) ch = '_'; std::fprintf( control.exportfile, ", '%c'%d", ch, gv[i].value ); } else std::fprintf( control.exportfile, ", '%s'%d", UCS::ucs_to_utf8( gv[i].code ), gv[i].value ); std::fputs( "\n", control.exportfile ); } void Character::apply_filter( const Filter::Type filter ) { if( !guesses() ) return; const int code = gv[0].code; bool remove = false; switch( filter ) { case Filter::letters_only: remove = true; // fall through case Filter::letters: if( !UCS::isalpha( code ) && !UCS::isspace( code ) ) { for( int i = 1; i < guesses(); ++i ) if( UCS::isalpha( gv[i].code ) ) { swap_guesses( 0, i ); break; } if( gv[0].code == '+' && 2 * height() > 3 * width() ) { gv[0].code = 't'; break; } if( !UCS::isalpha( gv[0].code ) ) gv[0].code = UCS::to_nearest_letter( gv[0].code ); if( remove && !UCS::isalpha( gv[0].code ) ) clear_guesses(); } break; case Filter::numbers_only: remove = true; // fall through case Filter::numbers: if( !UCS::isdigit( code ) && !UCS::isspace( code ) ) { for( int i = 1; i < guesses(); ++i ) if( UCS::isdigit( gv[i].code ) ) { swap_guesses( 0, i ); break; } if( !UCS::isdigit( gv[0].code ) ) gv[0].code = UCS::to_nearest_digit( gv[0].code ); if( remove && !UCS::isdigit( gv[0].code ) ) clear_guesses(); } break; case Filter::same_height: break; // handled at line level case Filter::upper_num_only: remove = true; // fall through case Filter::upper_num: if( !UCS::isupper( code ) && !UCS::isdigit( code ) && !UCS::isspace( code ) ) { for( int i = 1; i < guesses(); ++i ) if( UCS::isupper( gv[i].code ) || UCS::isdigit( gv[i].code ) ) { swap_guesses( 0, i ); break; } if( !UCS::isupper( gv[0].code ) && !UCS::isdigit( gv[0].code ) ) gv[0].code = UCS::to_nearest_upper_num( gv[0].code ); if( remove && !UCS::isupper( gv[0].code ) && !UCS::isdigit( gv[0].code ) ) clear_guesses(); } break; } } ocrad-0.24/character.h000066400000000000000000000062151241541103500146340ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Character : public Rectangle { public: struct Guess { int code; int value; Guess( const int c, const int v ) : code( c ), value( v ) {} }; private: std::vector< Blob * > blobpv; // the blobs forming this Character std::vector< Guess > gv; // vector of possible char codes // and their associated values. // gv[0].code < 0 means further // processing is needed (merged chars) void recognize110( const Charset & charset, const Rectangle & charbox ); void recognize111( const Charset & charset, const Rectangle & charbox ); void recognize112( const Rectangle & charbox ); void recognize12( const Charset & charset, const Rectangle & charbox ); void recognize13( const Charset & charset, const Rectangle & charbox ); public: explicit Character( Blob * const p ) : Rectangle( *p ), blobpv( 1, p ) {} Character( const Rectangle & re, int code, int value ) : Rectangle( re ), gv( 1, Guess( code, value ) ) {} Character( const Character & c ); Character & operator=( const Character & c ); ~Character(); int area() const; const Blob & blob( const int i ) const; Blob & blob( const int i ); int blobs() const { return blobpv.size(); } Blob & main_blob(); void shift_blobp( Blob * const p ); void add_guess( const int code, const int value ) { gv.push_back( Guess( code, value ) ); } void clear_guesses() { gv.clear(); } void insert_guess( const int i, const int code, const int value ); void delete_guess( const int i ); void only_guess( const int code, const int value ) { gv.clear(); gv.push_back( Guess( code, value ) ); } bool set_merged_guess( const int code1, const int right1, const int code2, const int blob_index ); void swap_guesses( const int i, const int j ); const Guess & guess( const int i ) const; int guesses() const { return gv.size(); } bool maybe( const int code ) const; // bool maybe_digit() const; // bool maybe_letter() const; void join( Character & c ); unsigned char byte_result() const; const char * utf8_result() const; void print( const Control & control ) const; void dprint( const Control & control, const Rectangle & charbox, const bool graph, const bool recursive ) const; void xprint( const Control & control ) const; void recognize1( const Charset & charset, const Rectangle & charbox ); void apply_filter( const Filter::Type filter ); }; ocrad-0.24/character_r11.cc000066400000000000000000000343731241541103500154630ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" // First attempt at recognition without relying on context. // void Character::recognize1( const Charset & charset, const Rectangle & charbox ) { if( blobs() == 1 ) { const Blob & b = blob( 0 ); if( b.holes() == 0 ) recognize110( charset, charbox ); else if( b.holes() == 1 ) recognize111( charset, charbox ); else if( b.holes() == 2 ) recognize112( charbox ); } else if( blobs() == 2 ) recognize12( charset, charbox ); else if( blobs() == 3 ) recognize13( charset, charbox ); } // Recognizes 1 blob characters without holes. // 12357CEFGHIJKLMNSTUVWXYZcfhklmnrstuvwxyz // '()*+,-./<>@[\]^_`{|}~¬ // void Character::recognize110( const Charset & charset, const Rectangle & charbox ) { const Blob & b = blob( 0 ); Features f( b ); int code = f.test_easy( charbox ); if( code ) { if( code == '.' && b.width() > b.height() && b.v_includes( charbox.vcenter() ) ) { add_guess( code, 1 ); add_guess( '-', 0 ); return; } add_guess( code, 0 ); return; } if( b.height() < 5 || ( b.height() < 8 && b.width() < 6 ) || b.height() > 10 * b.width() || 5 * b.height() < b.width() ) return; code = f.test_EFIJLlT( charset, charbox ); if( code ) { add_guess( code, 0 ); return; } code = f.test_frst( charbox ); if( code ) { add_guess( code, 0 ); return; } code = f.test_G(); if( code ) { add_guess( code, 0 ); return; } code = f.test_c(); if( code ) { add_guess( code, 0 ); return; } if( charset.enabled( Charset::iso_8859_9 ) ) { code = f.test_s_cedilla(); if( code ) { add_guess( code, 0 ); return; } } code = f.test_235Esz( charset ); if( code ) { add_guess( code, 0 ); return; } code = f.test_HKMNUuvwYy( charbox ); if( code == 'u' && f.lp.istpit() ) // Looks for merged 'tr' { int col = b.seek_left( b.vcenter(), b.right() ); if( col < b.hpos( 90 ) && !b.escape_top( b.vcenter(), col ) ) { col = b.seek_left( b.vcenter(), col - 1, false ); while( --col > b.hpos( 40 ) && ( b.seek_top( b.vcenter(), col ) > b.top() || f.hp[col-b.left()] > b.height() / 10 ) ) ; if( col > b.hpos( 40 ) && col < b.right() && set_merged_guess( 't', col, 'r', 0 ) ) return; } } if( code == 'N' && b.width() > b.height() && b.top() >= charbox.top() && 4 * f.tp[f.tp.pos(50)] < b.height() ) { // Looks for merged 'rv' const int col = f.hp.iminimum(); if( col >= f.hp.pos( 40 ) && col < f.hp.pos( 50 ) && set_merged_guess( 'r', b.left() + col, 'v', 0 ) ) return; } if( code ) { add_guess( code, 0 ); return; } const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; if( f.bp.minima() <= 2 && ( f.bp.minima( b.height() / 8 + noise ) == 2 || ( b.height() >= 16 && f.bp.minima( b.height() / 8 ) == 2 ) ) ) { code = f.test_hknwx( charbox ); if( code == 'n' ) // Looks for '"' or merged 'rt' or 'fl' { if( b.bottom() <= charbox.vcenter() ) { add_guess( '"', 0 ); return; } if( b.width() > b.height() && 10 * f.lp[f.lp.pos(10)] < b.width() && !f.rp.increasing( f.rp.pos( 75 ) ) ) { const int rgap = f.rp[f.rp.pos(50)]; if( 10 * rgap > b.width() && !b.escape_top( b.vcenter(), b.right() ) ) return; // leave 'rr', 'TT', 'rz', 'FT' etc, for next pass } if( 2 * f.lp[f.lp.pos(10)] > b.width() && !f.rp.increasing( f.rp.pos( 75 ) ) ) { const int col = b.seek_left( b.vcenter(), b.right() ); if( col <= b.hpos( 95 ) && !b.escape_top( b.vcenter(), col ) && set_merged_guess( 'r', b.hcenter(), 't', 0 ) ) return; } if( f.rp.minima() == 1 && !f.rp.increasing( f.rp.pos( 75 ) ) ) { int dmax = 0; bool bar = false; for( int row = b.vpos( 60 ); row > b.vpos( 25 ); --row ) { int d = b.hcenter() - b.seek_left( row, b.hcenter() ); if( d > dmax ) dmax = d; else if( 2 * d < dmax && dmax > 2 ) bar = true; if( bar && Ocrad::similar( d, dmax, 25 ) ) { int col, limit = b.seek_right( b.vcenter(), b.hcenter() ); for( col = b.hcenter(); col <= limit; ++col ) if( b.seek_bottom( b.vcenter(), col ) < b.bottom() ) break; if( col > b.left() && col < b.right() && set_merged_guess( 'f', col - 1, 'l', 0 ) ) return; } } } } else if( code == 'h' ) // Looks for merged 'rf' or 'fi' { if( 2 * f.lp[f.lp.pos(10)] > b.width() ) { if( f.rp[f.rp.pos(70)] >= 2 && b.seek_top( b.vpos( 70 ), b.right() ) > b.top() ) { int col = 0, hmin = f.hp.range() + 1; for( int i = b.hpos( 40 ); i <= b.hpos( 60 ); ++i ) if( f.hp[i-b.left()] < hmin ) { hmin = f.hp[i-b.left()]; col = i; } if( col > b.left() && col < b.right() ) set_merged_guess( 'r', col - 1, 'f', 0 ); } return; } if( f.rp.isctip( 30 ) ) { set_merged_guess( 'f', b.hcenter(), 'i', 0 ); return; } } else if( code == 'k' ) // Looks for merged 'rt' { if( 2 * f.lp[f.lp.pos(10)] > b.width() && !f.rp.increasing( f.rp.pos( 75 ) ) && set_merged_guess( 'r', b.hcenter(), 't', 0 ) ) return; } if( code ) { add_guess( code, 0 ); return; } } if( f.bp.minima() == 3 ) { if( f.bp.minima( b.height() / 2 ) == 1 && f.tp.minima() == 3 && f.lp.minima() == 2 && f.rp.minima() == 2 ) { add_guess( '*', 0 ); return; } if( b.id( b.vcenter(), b.hcenter() ) == 0 && b.id( b.vcenter() - 1, b.hcenter() ) == 0 && b.id( b.vcenter() + 1, b.hcenter() ) == 0 && b.seek_left( b.vcenter(), b.hcenter() ) <= b.hpos( 25 ) ) { // Found merged 'rn' int row = b.vpos( 95 ); int col = b.seek_right( row, b.left() ); col = b.seek_right( row, col + 1, false ); col = b.seek_right( row, col + 1 ); if( col > b.left() && col < b.right() && set_merged_guess( 'r', col, 'n', 0 ) ) return; } if( f.tp.minima( b.height() / 3 ) == 1 ) add_guess( 'm', 0 ); return; } if( f.bp.minima() == 4 && f.tp.minima( b.height() / 3 ) == 1 ) { // Found merged 'rm' int row = b.vpos( 95 ); int col = b.seek_right( row, b.left() ); col = b.seek_right( row, col + 1, false ); col = b.seek_right( row, col + 1 ); if( col > b.left() && col < b.right() && set_merged_guess( 'r', col, 'm', 0 ) ) return; } if( f.tp.minima( b.height() / 4 ) == 3 ) { if( f.segments_in_row( b.vcenter() ) == 2 && f.segments_in_row( b.vpos( 80 ) ) == 3 ) return; // merged 'IX' int hdiff; if( !b.bottom_hook( &hdiff ) && ( f.segments_in_row( b.vcenter() ) < 4 || !b.escape_top( b.vcenter(), b.hcenter() ) ) ) add_guess( 'w', 0 ); return; } code = f.test_line( charbox ); if( code ) { add_guess( code, 0 ); return; } code = f.test_misc( charbox ); if( code ) { add_guess( code, 0 ); return; } } // Recognizes 1 blob characters with 1 hole. // 0469ADOPQRabdegopq# // void Character::recognize111( const Charset & charset, const Rectangle & charbox ) { const Blob & b = blob( 0 ); const Bitmap & h = b.hole( 0 ); if( !h.is_hcentred_in( b ) ) return; Features f( b ); int top_delta = h.top() - b.top(), bottom_delta = b.bottom() - h.bottom(); if( std::abs( top_delta - bottom_delta ) <= std::max( 2, h.height() / 4 ) || Ocrad::similar( top_delta, bottom_delta, 40, 2 ) ) { // hole is vertically centred int code = f.test_4ADQao( charset, charbox ); if( code ) { if( code == 'Q' && Ocrad::similar( top_delta, bottom_delta, 40, 2 ) ) add_guess( 'a', 1 ); add_guess( code, 0 ); } return; } if( top_delta < bottom_delta ) // hole is high { int code = f.test_49ARegpq( charbox ); if( code ) add_guess( code, 0 ); return; } if( top_delta > bottom_delta ) // hole is low { int code = f.test_6abd( charset ); if( code ) { add_guess( code, 0 ); if( code == UCS::SOACUTE ) { int row = h.top() - ( b.bottom() - h.bottom() ) - 1; if( row <= b.top() || row + 1 >= h.top() ) return; Blob & b1 = const_cast< Blob & >( b ); Blob b2( b ); b1.bottom( row ); b2.top( row + 1 ); blobpv.push_back( new Blob( b2 ) ); } } } } // Recognizes 1 blob characters with 2 holes. // 8BQg$& // void Character::recognize112( const Rectangle & charbox ) { const Blob & b = blob( 0 ); const Bitmap & h1 = b.hole( 0 ); // upper hole const Bitmap & h2 = b.hole( 1 ); // lower hole Profile lp( b, Profile::left ); Profile tp( b, Profile::top ); Profile rp( b, Profile::right ); Profile bp( b, Profile::bottom ); // Check for 'm' or 'w' with merged serifs if( 10 * std::abs( h2.vcenter() - h1.vcenter() ) <= b.height() && h1.is_vcentred_in( b ) && h2.is_vcentred_in( b ) ) { if( ( b.bottom() - h1.bottom() <= h1.top() - b.top() ) && ( b.bottom() - h2.bottom() <= h2.top() - b.top() ) && bp.isflats() ) { add_guess( 'm', 0 ); return; } if( 5 * std::abs( h1.bottom() - b.vcenter() ) <= b.height() && 5 * std::abs( h2.bottom() - b.vcenter() ) <= b.height() && tp.isflats() && bp.minima() == 2 ) { add_guess( 'w', 0 ); return; } return; } if( !h1.is_hcentred_in( b ) ) return; if( !h2.is_hcentred_in( b ) ) return; if( h1.left() > b.hcenter() && h2.left() > b.hcenter() ) return; if( h1.right() < b.hpos( 40 ) && h2.right() < b.hpos( 40 ) ) return; if( h1.top() > b.vcenter() || h2.bottom() < b.vcenter() ) return; const int a1 = h1.area(); const int a2 = h2.area(); { const int w = b.right() - std::min( b.hcenter(), std::min( h1.hcenter(), h2.hcenter() ) ); for( int i = h1.bottom() - b.top() + 1; i < h2.top() - b.top(); ++i ) if( rp[i] > w ) { add_guess( 'g', 2 ); return; } } if( Ocrad::similar( a1, a2, 50 ) ) // I don't like this { if( h1.bottom() > b.vcenter() && h2.top() < b.vcenter() && h1.h_overlaps( h2 ) && !h1.h_includes( h2 ) ) { add_guess( '0', 0 ); return; } if( h1.bottom() <= h2.top() ) { int hdiff; if( b.bottom_hook( &hdiff ) && hdiff > b.height() / 2 ) if( b.top_hook( &hdiff ) && hdiff > b.height() / 2 ) { add_guess( 's', 0 ); return; } if( lp.isflats() && ( lp.istip() || ( lp.isflat() && b.test_BD() ) ) ) { add_guess( 'B', 0 ); return; } int col1 = h1.seek_left( h1.bottom(), h1.right() + 1 ) - 1; int col2 = h2.seek_right( h2.top(), h2.left() - 1 ) + 1; if( col1 <= col2 ) { if( lp.isconvex() || lp.ispit() ) add_guess( 'e', 1 ); else if( !rp.isctip() && tp.minima() == 1 ) add_guess( 'a', 1 ); if( bp.istpit() ) { add_guess( '$', 0 ); return; } } if( b.hcenter() > h1.hcenter() && b.hcenter() > h2.hcenter() && ( b.hcenter() >= h1.right() || b.hcenter() >= h2.right() ) ) { add_guess( '&', 0 ); return; } for( int row = h1.bottom() + 1; row < h2.top(); ++row ) if( !b.get_bit( row, hcenter() ) ) { add_guess( 'g', 0 ); return; } if( charbox.bottom() > h2.vcenter() && ( bp.isconvex() || ( bp.ispit() && tp.ispit() ) ) ) { if( b.top() >= charbox.top() && b.height() <= charbox.height() ) { if( ( lp.ispit() || lp.isconvex() ) && ( !rp.ispit() || h2.right() > h1.right() ) ) add_guess( 'e', 1 ); else if( b.right() - rp[rp.pos(50)] > h1.right() && !rp.isctip() ) add_guess( 'a', 1 ); } if( h1.bottom() > b.vcenter() && h1.top() > b.vpos( 30 ) ) add_guess( UCS::SEACUTE, 0 ); else add_guess( '8', 0 ); return; } if( lp.minima() == 2 && rp.minima() == 1 ) { if( charbox.vcenter() < h1.bottom() && charbox.bottom() < h2.bottom() ) add_guess( 'g', 0 ); else add_guess( 'a', 0 ); return; } if( charbox.vcenter() > h1.top() && ( charbox.vcenter() < h1.bottom() || charbox.bottom() < h2.vcenter() ) ) add_guess( 'g', 2 ); add_guess( 'B', 1 ); add_guess( 'a', 0 ); return; } } if( a1 > a2 && h1.h_overlaps( h2 ) ) { if( !h1.v_overlaps( h2 ) ) { if( h2.left() > b.hcenter() && h2.bottom() < b.bottom() - h1.height() ) add_guess( '9', 0 ); else add_guess( 'g', 0 ); return; } if( h1.h_includes( h2 ) ) { add_guess( 'Q', 0 ); return; } return; } if( a1 < a2 && tp.minima() == 1 ) { if( h1.h_overlaps( h2 ) ) { if( rp.minima() == 1 ) { if( 2 * h1.height() > h2.height() && 2 * h1.width() > h2.width() && 3 * h2.width() >= b.width() && !lp.isctip() ) { if( lp.ispit() && lp.isconvex() ) add_guess( '6', 0 ); else add_guess( 'B', 0 ); } else if( h2.right() < b.hcenter() ) add_guess( '&', 0 ); else add_guess( 'a', 0 ); return; } if( !h1.v_overlaps( h2 ) && h1.right() < b.hcenter() && h1.top() > b.top() + h1.height() ) { add_guess( '6', 0 ); return; } } if( h1.bottom() < h2.top() ) { add_guess( '&', 0 ); return; } } } ocrad-0.24/character_r12.cc000066400000000000000000000226141241541103500154570ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" // Recognizes 2 blob characters. // ijÑñ!%:;=?|¡ª±º¿ÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛáéíóúàèìòùâêîôûÅå // void Character::recognize12( const Charset & charset, const Rectangle & charbox ) { const Blob & b1 = blob( 0 ); // upper blob const Blob & b2 = blob( 1 ); // lower blob int a1 = b1.area(); int a2 = b2.area(); Features f1( b1 ); Features f2( b2 ); if( Ocrad::similar( a1, a2, 10 ) ) { if( !b1.holes() && !b2.holes() && 2 * a1 > b1.size() && 2 * a2 > b2.size() ) { if( width() > height() || Ocrad::similar( width(), height(), 40 ) ) { add_guess( '=', 0 ); return; } if( Ocrad::similar( b1.width(), b1.height(), 20, 2 ) && Ocrad::similar( b2.width(), b2.height(), 20, 2 ) ) add_guess( ':', 0 ); return; } return; } if( Ocrad::similar( a1, a2, 60 ) ) { if( f1.test_solid( charbox ) == '.' ) { if( f2.test_solid( charbox ) == '.' ) { add_guess( ':', 0 ); return; } if( b2.height() > b1.height() && b2.top() > charbox.vcenter() ) { add_guess( ';', 0 ); return; } } if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) { int code = f2.test_solid( charbox ); if( code == '-' || code == '_' ) { add_guess( UCS::PLUSMIN, 0 ); return; } } if( b1.includes_hcenter( b2 ) && b2.includes_hcenter( b1 ) ) { if( b1.holes() && b2.holes() ) { add_guess( 'g', 0 ); return; } } if( b1.hcenter() < b2.hcenter() ) // Looks for merged 'fi' { if( b2.height() > b2.width() && b1.hcenter() < b2.left() && b1.includes_hcenter( b2 ) && 4 * b1.height() > 5 * b2.height() && Ocrad::similar( b1.bottom()-b1.top(), b2.bottom()-b1.top(), 10 ) ) { Character c2( new Blob( b2 ) ); c2.recognize1( charset, charbox ); if( ( c2.maybe('l') || c2.maybe('|') ) && set_merged_guess( 'f', b2.left() - 1, 'i', 0 ) ) return; } } } if( a1 > a2 && b1.hcenter() < b2.hcenter() && 2 * b1.height() > 3 * b2.height() && b1.holes() == 1 && b2.holes() == 1 && Ocrad::similar( b2.width(), b2.height(), 50 ) ) { add_guess( '%', 0 ); return; } if( a1 < a2 ) { { int code = f1.test_solid( charbox ); //FIXME all this if( code == '-' && 2 * b1.height() > b1.width() ) code = '.'; else if( code == '\'' || code == '|' ) code = '.'; if( !code && !b1.holes() && 2 * b1.height() < b2.height() && b1.width() <= b2.width() ) { if( 10 * a1 >= 7 * b1.height() * b1.width() ) code = '.'; else code = '\''; } if( !b2.holes() && ( code == '.' || code == '\'' ) ) { // Looks for merged 'ri' or 'rí' if( f2.bp.minima( b2.height() / 4 ) == 2 && b2.top() > b1.bottom() && b2.hcenter() < b1.left() ) { Character c2( new Blob( b2 ) ); c2.recognize1( charset, charbox ); if( c2.maybe('n') ) { if( code == '.' && ( b1.left() < b2.hcenter() || b1.right() > b2.right() ) ) { add_guess( 'n', 0 ); return; } // FIXME remove dot int col, limit = b2.seek_right( b2.vcenter(), b2.hcenter() ); for( col = b2.hcenter(); col <= limit; ++col ) if( b2.seek_bottom( b2.vcenter(), col ) < b2.bottom() ) break; if( b2.left() < col && col < b2.right() ) { if( charset.enabled( Charset::iso_8859_9 ) && f2.rp.istip() ) set_merged_guess( 'T', col - 1, UCS::CIDOT, 1 ); else { const int code2 = ( ( code == '.' ) ? 'i' : (int)UCS::SIACUTE ); set_merged_guess( 'r', col - 1, code2, 1 ); } return; } } } if( code == '.' && f2.bp.minima( b2.height() / 4 ) == 1 && b1.bottom() <= b2.top() && f2.rp.minima( b2.width() / 2 ) <= 2 ) { int hdiff; if( b2.bottom_hook( &hdiff ) && std::abs( hdiff ) >= b2.height() / 2 ) { if( hdiff > 0 && f2.rp.increasing( f2.rp.pos( 80 ) ) ) { add_guess( 'j', 0 ); return; } if( hdiff < 0 ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( -4 * hdiff <= 3 * b2.height() && f2.wp.max() > 2 * f1.wp.max() && f2.lp.minima() == 1 && 2 * f2.bp[0] < b2.height() ) { add_guess( UCS::IQUEST, 0 ); return; } add_guess( 'i', 0 ); return; } } if( f2.tp.minima() == 1 ) { const bool maybe_j = ( b2.height() > charbox.height() && b2.vpos( 80 ) > charbox.bottom() ); if( Ocrad::similar( f1.wp.max(), f2.wp.max(), 20 ) ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( !f2.lp.isctip() && f2.wp.max() >= f1.wp.max() && ( 3 * f2.wp[f2.wp.pos(10)] < 2 * f1.wp.max() || ( b1.left() <= b2.left() && b2.vpos( 80 ) > charbox.bottom() ) ) ) { add_guess( UCS::IEXCLAM, 0 ); return; } if( maybe_j ) add_guess( 'j', 0 ); else add_guess( 'i', 0 ); return; } if( 3 * f2.wp.max() > 4 * f1.wp.max() && b2.seek_bottom( b2.vcenter(), b2.hpos( 10 ) ) < b2.bottom() && f2.rp.increasing( f2.rp.pos( 75 ) ) && ( b1.left() >= b2.hcenter() || b2.seek_top( b2.vcenter(), b2.hpos( 10 ) ) <= b2.top() ) ) { add_guess( 'j', 0 ); return; } if( charset.enabled( Charset::iso_8859_9 ) && f2.rp.istip() ) { add_guess( UCS::CIDOT, 0 ); return; } if( maybe_j ) add_guess( 'j', 0 ); else add_guess( 'i', 0 ); return; } } } } if( ( !b1.holes() && ( b1.bottom() < b2.vcenter() || 2 * a1 < a2 ) ) || ( b1.holes() == 1 && b1.bottom() < b2.top() && b2.top() - b1.bottom() < b1.height() ) ) { Character c( new Blob( b2 ) ); c.recognize1( charset, charbox ); if( c.guesses() ) { int code = c.guess( 0 ).code; if( b1.holes() == 1 ) { if( code == 'a' ) code = UCS::SARING; else if( code == 'A' ) code = UCS::CARING; else code = 0; } else if( code == 'u' && 5 * b1.width() <= b2.width() && 5 * b1.height() <= b2.width() ) return; else if( b1.bottom() < b2.vcenter() ) { int atype = '\''; if( UCS::isvowel( code ) && 2 * b1.width() > 3 * b1.height() && !f1.tp.iscpit() && f1.hp.iscpit() ) atype = ':'; else if( f1.bp.minima() == 2 || f1.bp.istip() ) atype = '^'; else if( std::min( b1.height(), b1.width() ) >= 5 && ( f1.rp.decreasing() || f1.tp.increasing() ) && ( f1.bp.decreasing() || f1.lp.increasing() ) ) atype = '`'; code = UCS::compose( code, atype ); } if( code != c.guess( 0 ).code && charset.only( Charset::ascii ) ) { if( UCS::base_letter( code ) == 'i' ) code = 'i'; else code = c.guess( 0 ).code; } if( code ) add_guess( code, 0 ); } } return; } if( b1.bottom() <= b2.top() ) { const int code = f2.test_solid( charbox ); if( !b1.holes() && ( code == '.' || ( code && Ocrad::similar( b2.height(), b2.width(), 50 ) ) ) ) { if( Ocrad::similar( b1.width(), b2.width(), 50 ) && !f1.lp.isctip() ) { add_guess( '!', 0 ); return; } if( f1.bp.minima() == 1 ) add_guess( '?', 0 ); return; } if( code == '-' || code == '_' ) if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) { if( b1.holes() == 1 ) { const Bitmap & h = b1.hole( 0 ); if( b2.width() >= h.width() && b2.top() - b1.bottom() < h.height() ) { if( Ocrad::similar( h.left() - b1.left(), b1.right() - h.right(), 40 ) ) { add_guess( UCS::MASCORD, 0 ); return; } add_guess( UCS::FEMIORD, 0 ); return; } } } } } ocrad-0.24/character_r13.cc000066400000000000000000000045201241541103500154540ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" // Recognizes 3 blob characters. // %ÄËÏÖÜäëïöüÿ÷ // void Character::recognize13( const Charset & charset, const Rectangle & charbox ) { const Blob & b1 = blob( 0 ); const Blob & b2 = blob( 1 ); const Blob & b3 = blob( 2 ); // lower blob Character c( new Blob( b3 ) ); int code = 0; c.recognize1( charset, charbox ); if( c.guesses() ) { if( c.maybe('.') || ( c.height() < 2 * c.width() && c.maybe(',') && 2 * b3.area() >= b3.size() ) ) { if( b1.bottom() <= b2.top() && b2.bottom() <= b3.top() ) { if( b2.width() >= 2 * b2.height() ) code = UCS::DIV; } else if( b1.top() < b3.top() && b2.top() < b3.top() ) code = '%'; } else if( std::max( b1.width(), b2.width() ) < b3.width() && Ocrad::similar( b1.height(), b2.height(), 20, 2 ) && 2 * std::max( b1.height(), b2.height() ) < b3.height() ) code = UCS::compose( c.guess( 0 ).code, ':' ); else if( c.maybe('o') ) { if( ( b1.hcenter() < b2.hcenter() && b1.holes() == 1 && !b2.holes() ) || ( b2.hcenter() < b1.hcenter() && b2.holes() == 1 && !b1.holes() ) ) code = '%'; } } if( charset.only( Charset::ascii ) ) { if( code == UCS::DIV ) code = '%'; else code = UCS::base_letter( code ); } if( code ) add_guess( code, 0 ); } ocrad-0.24/common.cc000066400000000000000000000121051241541103500143210ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" namespace { const int charsets = 3; const Charset::Value charset_value[charsets] = { Charset::ascii, Charset::iso_8859_9, Charset::iso_8859_15 }; const char * const charset_name[charsets] = { "ascii", "iso-8859-9", "iso-8859-15" }; struct F_entry { const char * const name; Filter::Type type; }; const F_entry F_table[] = { { "letters", Filter::letters }, { "letters_only", Filter::letters_only }, { "numbers", Filter::numbers }, { "numbers_only", Filter::numbers_only }, { "same_height", Filter::same_height }, { "upper_num", Filter::upper_num }, { "upper_num_only", Filter::upper_num_only }, { 0, (Filter::Type)0 } }; struct T_entry { const char * const name; Transformation::Type type; }; const T_entry T_table[] = { { "none", Transformation::none }, { "rotate90", Transformation::rotate90 }, { "rotate180", Transformation::rotate180 }, { "rotate270", Transformation::rotate270 }, { "mirror_lr", Transformation::mirror_lr }, { "mirror_tb", Transformation::mirror_tb }, { "mirror_d1", Transformation::mirror_d1 }, { "mirror_d2", Transformation::mirror_d2 }, { 0, Transformation::none } }; } // end namespace int verbosity = 0; void Ocrad::internal_error( const char * const msg ) { std::fprintf( stderr, "ocrad: internal error: %s\n", msg ); std::exit( 3 ); } bool Ocrad::similar( const int a, const int b, const int percent_dif, const int abs_dif ) { int difference = std::abs( a - b ); if( percent_dif > 0 && difference <= abs_dif ) return true; int max_abs = std::max( std::abs( a ), std::abs( b ) ); return ( difference * 100 <= max_abs * percent_dif ); } bool Charset::enable( const char * const name ) { for( int i = 0; i < charsets; ++i ) if( std::strcmp( name, charset_name[i] ) == 0 ) { charset_ |= charset_value[i]; return true; } return false; } bool Charset::enabled( const Value cset ) const { if( !charset_ ) return cset == iso_8859_15; // default charset return charset_ & cset; } bool Charset::only( const Value cset ) const { if( !charset_ ) return cset == iso_8859_15; // default charset return charset_ == cset; } void Charset::show_error( const char * const program_name, const char * const arg ) const { if( verbosity >= 0 ) { if( arg && std::strcmp( arg, "help" ) != 0 ) std::fprintf( stderr,"%s: bad charset '%s'\n", program_name, arg ); std::fputs( "Valid charset names:", stderr ); for( int i = 0; i < charsets; ++i ) std::fprintf( stderr, " %s", charset_name[i] ); std::fputs( "\n", stderr ); } } bool Transformation::set( const char * const name ) { for( int i = 0; T_table[i].name != 0; ++i ) if( std::strcmp( name, T_table[i].name ) == 0 ) { type_ = T_table[i].type; return true; } return false; } void Transformation::show_error( const char * const program_name, const char * const arg ) const { if( verbosity >= 0 ) { if( arg && std::strcmp( arg, "help" ) != 0 ) std::fprintf( stderr,"%s: bad bitmap trasformation '%s'\n", program_name, arg ); std::fputs( "Valid transformation names:", stderr ); for( int i = 0; T_table[i].name != 0; ++i ) std::fprintf( stderr, " %s", T_table[i].name ); std::fputs( "\nRotations are made counter-clockwise.\n", stderr ); } } bool Control::add_filter( const char * const program_name, const char * const name ) { for( int i = 0; F_table[i].name != 0; ++i ) if( std::strcmp( name, F_table[i].name ) == 0 ) { filters.push_back( F_table[i].type ); return true; } if( verbosity >= 0 ) { if( name && std::strcmp( name, "help" ) != 0 ) std::fprintf( stderr,"%s: bad filter '%s'\n", program_name, name ); std::fputs( "Valid filter names:", stderr ); for( int i = 0; F_table[i].name != 0; ++i ) std::fprintf( stderr, " %s", F_table[i].name ); std::fputs( "\n", stderr ); } return false; } bool Control::set_format( const char * const name ) { if( std::strcmp( name, "byte" ) == 0 ) { utf8 = false; return true; } if( std::strcmp( name, "utf8" ) == 0 ) { utf8 = true; return true; } return false; } ocrad-0.24/common.h000066400000000000000000000044061241541103500141700ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int verbosity; namespace Ocrad { void internal_error( const char * const msg ); bool similar( const int a, const int b, const int percent_dif, const int abs_dif = 1 ); } // end namespace Ocrad class Charset { int charset_; public: enum Value { ascii = 1, iso_8859_9 = 2, iso_8859_15 = 4 }; Charset() : charset_( 0 ) {} bool enable( const char * const name ); bool enabled( const Value cset ) const; bool only( const Value cset ) const; void show_error( const char * const program_name, const char * const arg ) const; }; class Transformation { public: enum Type { none, rotate90, rotate180, rotate270, mirror_lr, mirror_tb, mirror_d1, mirror_d2 }; private: Type type_; public: Transformation() : type_( none ) {} bool set( const char * const name ); Type type() const { return type_; } void show_error( const char * const program_name, const char * const arg ) const; }; namespace Filter { enum Type { letters, letters_only, numbers, numbers_only, same_height, upper_num, upper_num_only }; } // end namespace Filter struct Control { Charset charset; std::vector< Filter::Type > filters; FILE * outfile, * exportfile; int debug_level; char filetype; bool utf8; Control() : outfile( stdout ), exportfile( 0 ), debug_level( 0 ), filetype( '4' ), utf8( false ) {} bool add_filter( const char * const program_name, const char * const name ); bool set_format( const char * const name ); }; ocrad-0.24/configure000077500000000000000000000140461241541103500144370ustar00rootroot00000000000000#! /bin/sh # configure script for GNU Ocrad - Optical Character Recognition program # Copyright (C) 2003-2014 Antonio Diaz Diaz. # # This configure script is free software: you have unlimited permission # to copy, distribute and modify it. pkgname=ocrad pkgversion=0.24 progname=ocrad libname=ocrad srctrigger=doc/${pkgname}.texi # clear some things potentially inherited from environment. LC_ALL=C export LC_ALL srcdir= prefix=/usr/local exec_prefix='$(prefix)' bindir='$(exec_prefix)/bin' datarootdir='$(prefix)/share' includedir='${prefix}/include' infodir='$(datarootdir)/info' libdir='${exec_prefix}/lib' mandir='$(datarootdir)/man' CXX=g++ CPPFLAGS= CXXFLAGS='-Wall -W -O2' LDFLAGS= # checking whether we are using GNU C++. ${CXX} --version > /dev/null 2>&1 if [ $? != 0 ] ; then CXX=c++ CXXFLAGS='-W -O2' fi # Loop over all args args= no_create= while [ $# != 0 ] ; do # Get the first arg, and shuffle option=$1 ; arg2=no shift # Add the argument quoted to args args="${args} \"${option}\"" # Split out the argument for options that take them case ${option} in *=*) optarg=`echo ${option} | sed -e 's,^[^=]*=,,;s,/$,,'` ;; esac # Process the options case ${option} in --help | -h) echo "Usage: configure [options]" echo echo "Options: [defaults in brackets]" echo " -h, --help display this help and exit" echo " -V, --version output version information and exit" echo " --srcdir=DIR find the sources in DIR [. or ..]" echo " --prefix=DIR install into DIR [${prefix}]" echo " --exec-prefix=DIR base directory for arch-dependent files [${exec_prefix}]" echo " --bindir=DIR user executables directory [${bindir}]" echo " --datarootdir=DIR base directory for doc and data [${datarootdir}]" echo " --includedir=DIR C header files [${includedir}]" echo " --infodir=DIR info files directory [${infodir}]" echo " --libdir=DIR object code libraries [${libdir}]" echo " --mandir=DIR man pages directory [${mandir}]" echo " CXX=COMPILER C++ compiler to use [${CXX}]" echo " CPPFLAGS=OPTIONS command line options for the preprocessor [${CPPFLAGS}]" echo " CXXFLAGS=OPTIONS command line options for the C++ compiler [${CXXFLAGS}]" echo " LDFLAGS=OPTIONS command line options for the linker [${LDFLAGS}]" echo exit 0 ;; --version | -V) echo "Configure script for GNU ${pkgname} version ${pkgversion}" exit 0 ;; --srcdir) srcdir=$1 ; arg2=yes ;; --prefix) prefix=$1 ; arg2=yes ;; --exec-prefix) exec_prefix=$1 ; arg2=yes ;; --bindir) bindir=$1 ; arg2=yes ;; --datarootdir) datarootdir=$1 ; arg2=yes ;; --includedir) includedir=$1 ; arg2=yes ;; --infodir) infodir=$1 ; arg2=yes ;; --libdir) libdir=$1 ; arg2=yes ;; --mandir) mandir=$1 ; arg2=yes ;; --srcdir=*) srcdir=${optarg} ;; --prefix=*) prefix=${optarg} ;; --exec-prefix=*) exec_prefix=${optarg} ;; --bindir=*) bindir=${optarg} ;; --datarootdir=*) datarootdir=${optarg} ;; --includedir=*) includedir=${optarg} ;; --infodir=*) infodir=${optarg} ;; --libdir=*) libdir=${optarg} ;; --mandir=*) mandir=${optarg} ;; --no-create) no_create=yes ;; CXX=*) CXX=${optarg} ;; CPPFLAGS=*) CPPFLAGS=${optarg} ;; CXXFLAGS=*) CXXFLAGS=${optarg} ;; LDFLAGS=*) LDFLAGS=${optarg} ;; --*) echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;; *=* | *-*-*) ;; *) echo "configure: unrecognized option: '${option}'" 1>&2 echo "Try 'configure --help' for more information." 1>&2 exit 1 ;; esac # Check if the option took a separate argument if [ "${arg2}" = yes ] ; then if [ $# != 0 ] ; then args="${args} \"$1\"" ; shift else echo "configure: Missing argument to '${option}'" 1>&2 exit 1 fi fi done # Find the source files, if location was not specified. srcdirtext= if [ -z "${srcdir}" ] ; then srcdirtext="or . or .." ; srcdir=. if [ ! -r "${srcdir}/${srctrigger}" ] ; then srcdir=.. ; fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then ## the sed command below emulates the dirname command srcdir=`echo $0 | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` fi fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then echo "configure: Can't find sources in ${srcdir} ${srcdirtext}" 1>&2 echo "configure: (At least ${srctrigger} is missing)." 1>&2 exit 1 fi # Set srcdir to . if that's what it is. if [ "`pwd`" = "`cd "${srcdir}" ; pwd`" ] ; then srcdir=. ; fi echo if [ -z "${no_create}" ] ; then echo "creating config.status" rm -f config.status cat > config.status << EOF #! /bin/sh # This file was generated automatically by configure. Do not edit. # Run this file to recreate the current configuration. # # This script is free software: you have unlimited permission # to copy, distribute and modify it. exec /bin/sh $0 ${args} --no-create EOF chmod +x config.status fi echo "creating Makefile" echo "VPATH = ${srcdir}" echo "prefix = ${prefix}" echo "exec_prefix = ${exec_prefix}" echo "bindir = ${bindir}" echo "datarootdir = ${datarootdir}" echo "includedir = ${includedir}" echo "infodir = ${infodir}" echo "libdir = ${libdir}" echo "mandir = ${mandir}" echo "CXX = ${CXX}" echo "CPPFLAGS = ${CPPFLAGS}" echo "CXXFLAGS = ${CXXFLAGS}" echo "LDFLAGS = ${LDFLAGS}" rm -f Makefile cat > Makefile << EOF # Makefile for GNU Ocrad - Optical Character Recognition program # Copyright (C) 2003-2014 Antonio Diaz Diaz. # This file was generated automatically by configure. Do not edit. # # This Makefile is free software: you have unlimited permission # to copy, distribute and modify it. pkgname = ${pkgname} pkgversion = ${pkgversion} progname = ${progname} libname = ${libname} VPATH = ${srcdir} prefix = ${prefix} exec_prefix = ${exec_prefix} bindir = ${bindir} datarootdir = ${datarootdir} includedir = ${includedir} infodir = ${infodir} libdir = ${libdir} mandir = ${mandir} CXX = ${CXX} CPPFLAGS = ${CPPFLAGS} CXXFLAGS = ${CXXFLAGS} LDFLAGS = ${LDFLAGS} EOF cat "${srcdir}/Makefile.in" >> Makefile echo "OK. Now you can run make." ocrad-0.24/doc/000077500000000000000000000000001241541103500132705ustar00rootroot00000000000000ocrad-0.24/doc/ocrad.1000066400000000000000000000055261241541103500144520ustar00rootroot00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1. .TH OCRAD "1" "October 2014" "ocrad 0.24" "User Commands" .SH NAME ocrad \- command line text recognition tool .SH SYNOPSIS .B ocrad [\fI\,options\/\fR] [\fI\,files\/\fR] .SH DESCRIPTION GNU Ocrad is an OCR (Optical Character Recognition) program based on a feature extraction method. It reads images in pbm (bitmap), pgm (greyscale) or ppm (color) formats and produces text in byte (8\-bit) or UTF\-8 formats. The pbm, pgm and ppm formats are collectively known as pnm. .PP Ocrad includes a layout analyser able to separate the columns or blocks of text normally found on printed pages. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR display this help and exit .TP \fB\-V\fR, \fB\-\-version\fR output version information and exit .TP \fB\-a\fR, \fB\-\-append\fR append text to output file .TP \fB\-c\fR, \fB\-\-charset=\fR try '\-\-charset=help' for a list of names .TP \fB\-e\fR, \fB\-\-filter=\fR try '\-\-filter=help' for a list of names .TP \fB\-f\fR, \fB\-\-force\fR force overwrite of output file .TP \fB\-F\fR, \fB\-\-format=\fR output format (byte, utf8) .TP \fB\-i\fR, \fB\-\-invert\fR invert image levels (white on black) .TP \fB\-l\fR, \fB\-\-layout\fR perform layout analysis .TP \fB\-o\fR, \fB\-\-output=\fR place the output into .TP \fB\-q\fR, \fB\-\-quiet\fR suppress all messages .TP \fB\-s\fR, \fB\-\-scale\fR=\fI\,[\-]\/\fR scale input image by [1/] .TP \fB\-t\fR, \fB\-\-transform=\fR try '\-\-transform=help' for a list of names .TP \fB\-T\fR, \fB\-\-threshold=\fR threshold for binarization (0\-100%) .TP \fB\-u\fR, \fB\-\-cut=\fR cut input image by given rectangle .TP \fB\-v\fR, \fB\-\-verbose\fR be verbose .TP \fB\-x\fR, \fB\-\-export=\fR export results in ORF format to .PP If no files are specified, ocrad reads the image from standard input. If the \fB\-o\fR option is not specified, ocrad sends text to standard output. .PP Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (eg, bug) which caused ocrad to panic. .SH "REPORTING BUGS" Report bugs to bug\-ocrad@gnu.org .br Ocrad home page: http://www.gnu.org/software/ocrad/ocrad.html .br General help using GNU software: http://www.gnu.org/gethelp .SH COPYRIGHT Copyright \(co 2014 Antonio Diaz Diaz. License GPLv2+: GNU GPL version 2 or later .br This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH "SEE ALSO" The full documentation for .B ocrad is maintained as a Texinfo manual. If the .B info and .B ocrad programs are properly installed at your site, the command .IP .B info ocrad .PP should give you access to the complete manual. ocrad-0.24/doc/ocrad.info000066400000000000000000000533731241541103500152500ustar00rootroot00000000000000This is ocrad.info, produced by makeinfo version 4.13+ from ocrad.texi. INFO-DIR-SECTION GNU Packages START-INFO-DIR-ENTRY * Ocrad: (ocrad). The GNU OCR program END-INFO-DIR-ENTRY  File: ocrad.info, Node: Top, Next: Character sets, Up: (dir) GNU Ocrad Manual **************** This manual is for GNU Ocrad (version 0.24, 3 October 2014). GNU Ocrad is an OCR (Optical Character Recognition) program and library based on a feature extraction method. It reads images in pbm (bitmap), pgm (greyscale) or ppm (color) formats and produces text in byte (8-bit) or UTF-8 formats. The pbm, pgm and ppm formats are collectively known as pnm. Ocrad includes a layout analyser able to separate the columns or blocks of text normally found on printed pages. * Menu: * Character sets:: Input charsets and output formats * Invoking ocrad:: Command line interface * Library version:: Checking library version * Library functions:: Descriptions of the library functions * Library error codes:: Meaning of codes returned by functions * Image format conversion:: How to convert other formats to pnm * Algorithm:: How ocrad does its job * OCR results file:: Description of the ORF file format * Problems:: Reporting bugs * Concept index:: Index of concepts Copyright (C) 2003-2014 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute and modify it.  File: ocrad.info, Node: Character sets, Next: Invoking ocrad, Prev: Top, Up: Top 1 Character sets **************** The character set internally used by ocrad is ISO 10646, also known as UCS (Universal Character Set), which can represent over two thousand million characters (2^31). As it is unpractical to try to recognize one among so many different characters, you can tell ocrad what character sets to recognize. You do this with the '--charset' option. If the input page contains characters from only one character set, say 'ISO-8859-15', you can use the default 'byte' output format. But in a page with 'ISO-8859-9' and 'ISO-8859-15' characters, you can't tell if a code of 0xFD represents a 'latin small letter i dotless' or a 'latin small letter y with acute'. You should use '--format=utf8' instead. Of course, you may request UTF-8 output in any case. NOTE: 10^9 is a thousand millions, a billion is a million millions (million^2), a trillion is a million million millions (million^3), and so on. Please, don't "embrace and extend" the meaning of prefixes, making communication among all people difficult. Thanks.  File: ocrad.info, Node: Invoking ocrad, Next: Library version, Prev: Character sets, Up: Top 2 Invoking ocrad **************** The format for running ocrad is: ocrad [OPTIONS] [FILES] Ocrad supports the following options: '-h' '--help' Print an informative help message describing the options and exit. 'ocrad --verbose --help' describes also hidden options. '-V' '--version' Print the version number of ocrad on the standard output and exit. '-a' '--append' Append generated text to the output file instead of overwriting it. '-c NAME' '--charset=NAME' Enable recognition of the characters belonging to the given character set. You can repeat this option multiple times with different names for processing a page with characters from different character sets. If no charset is specified, 'iso-8859-15' (latin9) is assumed. Try '--charset=help' for a list of valid charset names. '-e NAME' '--filter=NAME' Pass the output text through the given postprocessing filter. Several filters can be applied in sequence using more than one '--filter' option. The filters are applied in the order they appear on the command line. '--filter=letters' forces every character that resembles a letter to be recognized as a letter. Other characters will be output without change. '--filter=letters_only', same as '--filter=letters', but other characters will be discarded. '--filter=numbers' forces every character that resembles a number to be recognized as a number. Other characters will be output without change. '--filter=numbers_only', same as '--filter=numbers' but other characters will be discarded. '--filter=same_height' discards any character (or noise) whose height differs in more than 13 percent from the median height of the characters in the line. '--filter=upper_num' forces every character that resembles a uppercase letter or a number to be recognized as such. Other characters will be output without change. '--filter=upper_num_only', same as '--filter=upper_num', but other characters will be discarded. Try '--filter=help' for a list of valid filter names. '-f' '--force' Force overwrite of output files. '-F NAME' '--format=NAME' Select the output format. The valid names are 'byte' and 'utf8'. If no output format is specified, 'byte' (8 bit) is assumed. '-i' '--invert' Invert image levels (white on black). '-l' '--layout' Enable page layout analysis. Ocrad is able to separate blocks of text of arbitrary shape as long as they are clearly delimited by white space. '-o FILE' '--output=FILE' Place the output into FILE instead of into the standard output. '-q' '--quiet' Quiet operation. '-s VALUE' '--scale=VALUE' Scale up the input image by VALUE before layout analysis and recognition. If VALUE is negative, the input image is scaled down by -VALUE. '-t NAME' '--transform=NAME' Perform given transformation (rotation or mirroring) on the input image before scaling, layout analysis and recognition. Try '--transform=help' for a list of valid transformation names. '-T VALUE' '--threshold=VALUE' Set binarization threshold for pgm or ppm files or for '--scale' option (only for scaled down images). VALUE should be a rational number between 0 and 1, and may be given as a percentage (50%), a fraction (1/2), or a decimal value (0.5). Image values greater than threshold are converted to white. The default value is 0.5. '-u LEFT,TOP,WIDTH,HEIGHT' '--cut=LEFT,TOP,WIDTH,HEIGHT' Cut the input image by the rectangle defined by LEFT, TOP, WIDTH and HEIGHT. Values may be relative to the image size (-1.0 <= value <= +1.0), or absolute (abs( value ) > 1). Negative values of LEFT, TOP are relative to the right-bottom corner of the image. Values of WIDTH and HEIGHT must be positive. Absolute and relative values can be mixed. For example 'ocrad --cut 700,960,1,1' will extract from '700,960' to the right-bottom corner of the image. The cutting is performed before any other transformation (rotation or mirroring) on the input image, and before scaling, layout analysis and recognition. '-v' '--verbose' Verbose mode. '-x FILE' '--export=FILE' Write (export) OCR results file to FILE (*note OCR results file::). '-x -' writes to stdout, overriding text output except if output has been also redirected with the '-o' option. Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (eg, bug) which caused ocrad to panic.  File: ocrad.info, Node: Library version, Next: Library functions, Prev: Invoking ocrad, Up: Top 3 Library version ***************** -- Function: const char * OCRAD_version ( void ) Returns the library version as a string. -- Constant: const char * OCRAD_version_string This constant is defined in the header file 'ocradlib.h'. The application should compare OCRAD_version and OCRAD_version_string for consistency. If the first character differs, the library code actually used may be incompatible with the 'ocradlib.h' header file used by the application. if( OCRAD_version()[0] != OCRAD_version_string[0] ) error( "bad library version" );  File: ocrad.info, Node: Library functions, Next: Library error codes, Prev: Library version, Up: Top 4 Library functions ******************* These are the OCRAD library functions. In case of error, all of them return -1 or a null pointer, except 'OCRAD_open' whose return value must be verified by calling 'OCRAD_get_errno' before using it. -- Function: struct OCRAD_Descriptor * OCRAD_open ( void ) Initializes the internal library state and returns a pointer that can only be used as the OCRDES argument for the other OCRAD functions, or a null pointer if the descriptor could not be allocated. The returned pointer must be verified by calling 'OCRAD_get_errno' before using it. If 'OCRAD_get_errno' does not return 'OCRAD_ok', the returned pointer must not be used and should be freed with 'OCRAD_close' to avoid memory leaks. -- Function: int OCRAD_close ( struct OCRAD_Descriptor * const OCRDES ) Frees all dynamically allocated data structures for this descriptor. After a call to 'OCRAD_close', OCRDES can no more be used as an argument to any OCRAD function. -- Function: enum OCRAD_Errno OCRAD_get_errno ( struct OCRAD_Descriptor * const OCRDES ) Returns the current error code for OCRDES (*note Library error codes::). -- Function: int OCRAD_set_image ( struct OCRAD_Descriptor * const OCRDES, const struct OCRAD_Pixmap * const IMAGE, const bool INVERT ) Loads IMAGE into the internal buffer. If INVERT is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. -- Function: int OCRAD_set_image_from_file ( struct OCRAD_Descriptor * const OCRDES, const char * const FILENAME, const bool INVERT ) Loads a image from the file FILENAME into the internal buffer. If INVERT is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. -- Function: int OCRAD_set_utf8_format ( struct OCRAD_Descriptor * const OCRDES, const bool UTF8 ) Set the output format to 'byte' (if UTF8=false) or to 'utf8'. By default ocrad produces 'byte' (8 bit) output. -- Function: int OCRAD_set_threshold ( struct OCRAD_Descriptor * const OCRDES, const int THRESHOLD ) Set binarization threshold for greymap or RGB images. THRESHOLD values between 0 and 255 set a fixed threshold. A value of -1 sets an automatic threshold. Pixel values greater than the resulting threshold are converted to white. The default threshold value if this function is not called is 127. -- Function: int OCRAD_scale ( struct OCRAD_Descriptor * const OCRDES, const int VALUE ) Scale up the image in the internal buffer by VALUE. If VALUE is negative, the image is scaled down by -VALUE. -- Function: int OCRAD_recognize ( struct OCRAD_Descriptor * const OCRDES, const bool LAYOUT ) Recognize the image loaded in the internal buffer and produce text results which can be later retrieved with the 'OCRAD_result' functions. The same image can be recognized as many times as desired, for example setting a new threshold each time for 3D greymap recognition. Every time this function is called, the produced text results replace any previous ones. If LAYOUT is true, page layout analysis is enabled, probably producing more than one text block. -- Function: int OCRAD_result_blocks ( struct OCRAD_Descriptor * const OCRDES ) Returns the number of text blocks found in the image, or 0 if no text was found. The returned value is usually 1, but can be larger if layout analysis was requested. -- Function: int OCRAD_result_lines ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM ) Returns the number of text lines contained in the given text block. -- Function: int OCRAD_result_chars_total ( struct OCRAD_Descriptor * const OCRDES ) Returns the total number of text characters contained in the recognized image. -- Function: int OCRAD_result_chars_block ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM ) Returns the number of text characters contained in the given text block. -- Function: int OCRAD_result_chars_line ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM, const int LINENUM ) Returns the number of text characters contained in the given text line. -- Function: const char * OCRAD_result_line ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM, const int LINENUM ) Returns the line of text specified by BLOCKNUM and LINENUM. -- Function: int OCRAD_result_first_character ( struct OCRAD_Descriptor * const OCRDES ) Returns the byte result for the first character in the image. Returns 0 if the image has no characters or if the first character could not be recognized. This function is a convenient short cut to the result for images containing a single character.  File: ocrad.info, Node: Library error codes, Next: Image format conversion, Prev: Library functions, Up: Top 5 Library error codes ********************* Most library functions return -1 or a null pointer to indicate that they have failed. But this return value only tells you that an error has occurred. To find out what kind of error it was, you need to verify the error code by calling 'OCRAD_get_errno'. Library functions do not change the value returned by 'OCRAD_get_errno' when they succeed; thus, the value returned by 'OCRAD_get_errno' after a successful call is not necessarily OCRAD_ok, and you should not use 'OCRAD_get_errno' to determine whether a call failed. If the call failed, then you can examine 'OCRAD_get_errno'. The error codes are defined in the header file 'ocradlib.h'. -- Constant: enum OCRAD_Errno OCRAD_ok The value of this constant is 0 and is used to indicate that there is no error. -- Constant: enum OCRAD_Errno OCRAD_bad_argument At least one of the arguments passed to the library function was invalid. -- Constant: enum OCRAD_Errno OCRAD_mem_error No memory available. The system cannot allocate more virtual memory because its capacity is full. -- Constant: enum OCRAD_Errno OCRAD_sequence_error A library function was called in the wrong order. For example 'OCRAD_result_line' was called before 'OCRAD_recognize'. -- Constant: enum OCRAD_Errno OCRAD_library_error A bug was detected in the library. Please, report it (*note Problems::).  File: ocrad.info, Node: Image format conversion, Next: Algorithm, Prev: Library error codes, Up: Top 6 Image format conversion ************************* There are a lot of image formats, but ocrad is able to decode only three of them; pbm, pgm and ppm. In this chapter you will find command examples and advice about how to convert image files to a format that ocrad can manage. '.png' Portable Network Graphics file. Use the command 'pngtopnm filename.png | ocrad'. In some cases, like the ocrad.png icon, you have to invert the image with the '-i' option: 'pngtopnm filename.png | ocrad -i'. '.ps' '.pdf' Postscript or Portable Document Format file. Use the command 'gs -sPAPERSIZE=a4 -sDEVICE=pnmraw -r300 -dNOPAUSE -dBATCH -sOutputFile=- -q filename.ps | ocrad'. You may also use the command 'pstopnm -stdout -dpi=300 -pgm filename.ps | ocrad', but it seems not to work with pdf files. Also old versions of 'pstopnm' don't recognize the '-dpi' option and produce an image too small for OCR. '.tiff' TIFF file. Use the command 'tifftopnm filename.tiff | ocrad'. '.jpg' JPEG file. Use the command 'djpeg -greyscale -pnm filename.jpg | ocrad'. JPEG is a lossy format and is in general not recommended for text images. '.pnm.gz' Pnm file compressed with gzip. Use the command 'gzip -cd filename.pnm.gz | ocrad' '.pnm.lz' Pnm file compressed with lzip. Use the command 'lzip -cd filename.pnm.lz | ocrad'  File: ocrad.info, Node: Algorithm, Next: OCR results file, Prev: Image format conversion, Up: Top 7 Algorithm *********** Ocrad is mainly a research project. Many of the algorithms ocrad uses are ad hoc, and will change in successive releases as I myself gain understanding about OCR issues. The overall working of ocrad may be described as follows: 1) Read the image. 2) Optionally, perform some transformations (cut, rotate, scale, etc). 3) Optionally, perform layout detection. 4) Remove frames and pictures. 5) Detect characters and group them in lines. 6) Recognize characters (very ad hoc; one algorithm per character). 7) Correct some ambiguities (transform l.OOO into 1.000, etc). 8) Output result. Ocrad recognizes characters by its shape, and the reason it is so fast is that it does not compare the shape of every character against some sort of database of shapes and then chooses the best match. Instead of this, ocrad only compares the shape differences that are relevant to choose between two character categories, mostly like a binary search. As there is no such thing as a free lunch, this approach has some drawbacks. It makes ocrad very sensitive to character defects, and makes difficult to modify ocrad to recognize new characters. For best results, the characters should be at least 20 pixels high. If they are smaller, try the -scale option. Scanning the image at 300 dpi usually produces a character size good enough for ocrad.  File: ocrad.info, Node: OCR results file, Next: Problems, Prev: Algorithm, Up: Top 8 OCR results file ****************** Calling ocrad with option '-x' produces an OCR results file (ORF), that is, a parsable file containing the OCR results. The ORF format is as follows: - Any line beginning with '#' is a comment line. - The first non-comment line has the form 'source file FILENAME', where FILENAME is the name of the file being processed ('-' for stdin). This is the only line guaranteed to exist for every input file read without errors. If the file, or any block or line, has no text, the corresponding part in the ORF file will be missing. - The second non-comment line has the form 'total text blocks N', where N is the total number of text blocks in the source image. For each text block in the source image, the following data follows: - A line in the form 'text block I X Y W H'. Where I is the block number and X Y W H are the block position and size as described below for character boxes. - A line in the form 'lines N'. Where N is the number of lines in this block. For each line in every text block, the following data follows: - A line in the form 'line I chars N height H', where I is the line number, N is the number of characters in this line, and H is the mean height of the characters in this line (in pixels). - N lines (one for every character) in the form 'X Y W H; G[, 'C'V]...', where: X is the left border (x-coordinate) of the char bounding box in the source image (in pixels). Y is the top border (y-coordinate). W is the width of the bounding box. H is the height of the bounding box. G is the number of different recognition guesses for this character. The result characters follow after the number of guesses in the form of a comma-separated list of pairs. Every pair is formed by the actual recognised char C enclosed in single quotes, followed by the confidence value V, without space between them. The higher the value of confidence, the more confident is the result. Running './ocrad -x test.orf examples/test.pbm' in the source directory will give you an example ORF file.  File: ocrad.info, Node: Problems, Next: Concept index, Prev: OCR results file, Up: Top 9 Reporting bugs **************** There are probably bugs in ocrad. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. If you find a bug in GNU Ocrad, please send electronic mail to . Include the version number, which you can find by running 'ocrad --version'.  File: ocrad.info, Node: Concept index, Prev: Problems, Up: Top Concept index ************* [index] * Menu: * algorithm: Algorithm. (line 6) * bugs: Problems. (line 6) * getting help: Problems. (line 6) * image format conversion: Image format conversion. (line 6) * input charsets: Character sets. (line 6) * invoking: Invoking ocrad. (line 6) * library error codes: Library error codes. (line 6) * library functions: Library functions. (line 6) * library version: Library version. (line 6) * OCR results file: OCR results file. (line 6) * options: Invoking ocrad. (line 6) * output format: Character sets. (line 6) * usage: Invoking ocrad. (line 6) * version: Invoking ocrad. (line 6)  Tag Table: Node: Top196 Node: Character sets1544 Node: Invoking ocrad2689 Node: Library version7543 Node: Library functions8222 Node: Library error codes13340 Node: Image format conversion14891 Node: Algorithm16415 Node: OCR results file17892 Node: Problems20160 Node: Concept index20696  End Tag Table  Local Variables: coding: iso-8859-15 End: ocrad-0.24/doc/ocrad.texi000066400000000000000000000520251241541103500152570ustar00rootroot00000000000000\input texinfo @c -*-texinfo-*- @c %**start of header @setfilename ocrad.info @documentencoding ISO-8859-15 @settitle GNU Ocrad Manual @finalout @c %**end of header @set UPDATED 3 October 2014 @set VERSION 0.24 @dircategory GNU Packages @direntry * Ocrad: (ocrad). The GNU OCR program @end direntry @ifnothtml @titlepage @title GNU Ocrad @subtitle The GNU OCR Program @subtitle for GNU Ocrad version @value{VERSION}, @value{UPDATED} @author by Antonio Diaz Diaz @page @vskip 0pt plus 1filll @end titlepage @contents @end ifnothtml @node Top @top This manual is for GNU Ocrad (version @value{VERSION}, @value{UPDATED}). @sp 1 GNU Ocrad is an OCR (Optical Character Recognition) program and library based on a feature extraction method. It reads images in pbm (bitmap), pgm (greyscale) or ppm (color) formats and produces text in @w{byte (8-bit)} or UTF-8 formats. The pbm, pgm and ppm formats are collectively known as pnm. Ocrad includes a layout analyser able to separate the columns or blocks of text normally found on printed pages. @menu * Character sets:: Input charsets and output formats * Invoking ocrad:: Command line interface * Library version:: Checking library version * Library functions:: Descriptions of the library functions * Library error codes:: Meaning of codes returned by functions * Image format conversion:: How to convert other formats to pnm * Algorithm:: How ocrad does its job * OCR results file:: Description of the ORF file format * Problems:: Reporting bugs * Concept index:: Index of concepts @end menu @sp 1 Copyright @copyright{} 2003-2014 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute and modify it. @node Character sets @chapter Character sets @cindex input charsets @cindex output format The character set internally used by ocrad is ISO 10646, also known as UCS (Universal Character Set), which can represent over two thousand million characters (2^31). As it is unpractical to try to recognize one among so many different characters, you can tell ocrad what character sets to recognize. You do this with the @samp{--charset} option. If the input page contains characters from only one character set, say @w{@samp{ISO-8859-15}}, you can use the default @samp{byte} output format. But in a page with @w{@samp{ISO-8859-9}} and @w{@samp{ISO-8859-15}} characters, you can't tell if a code of 0xFD represents a 'latin small letter i dotless' or a 'latin small letter y with acute'. You should use @w{@samp{--format=utf8}} instead.@* Of course, you may request UTF-8 output in any case. @sp 1 NOTE: 10^9 is a thousand millions, a billion is a million millions (million^2), a trillion is a million million millions (million^3), and so on. Please, don't "embrace and extend" the meaning of prefixes, making communication among all people difficult. Thanks. @node Invoking ocrad @chapter Invoking ocrad @cindex invoking @cindex options @cindex usage @cindex version The format for running ocrad is: @example ocrad [@var{options}] [@var{files}] @end example Ocrad supports the following options: @table @samp @item -h @itemx --help Print an informative help message describing the options and exit. @w{@samp{ocrad --verbose --help}} describes also hidden options. @item -V @itemx --version Print the version number of ocrad on the standard output and exit. @item -a @itemx --append Append generated text to the output file instead of overwriting it. @item -c @var{name} @itemx --charset=@var{name} Enable recognition of the characters belonging to the given character set. You can repeat this option multiple times with different names for processing a page with characters from different character sets.@* If no charset is specified, @w{@samp{iso-8859-15}} (latin9) is assumed.@* Try @w{@samp{--charset=help}} for a list of valid charset names. @item -e @var{name} @itemx --filter=@var{name} Pass the output text through the given postprocessing filter. Several filters can be applied in sequence using more than one @samp{--filter} option. The filters are applied in the order they appear on the command line. @w{@samp{--filter=letters}} forces every character that resembles a letter to be recognized as a letter. Other characters will be output without change.@* @w{@samp{--filter=letters_only}}, same as @w{@samp{--filter=letters}}, but other characters will be discarded.@* @w{@samp{--filter=numbers}} forces every character that resembles a number to be recognized as a number. Other characters will be output without change.@* @w{@samp{--filter=numbers_only}}, same as @w{@samp{--filter=numbers}} but other characters will be discarded.@* @w{@samp{--filter=same_height}} discards any character (or noise) whose height differs in more than 13 percent from the median height of the characters in the line.@* @w{@samp{--filter=upper_num}} forces every character that resembles a uppercase letter or a number to be recognized as such. Other characters will be output without change.@* @w{@samp{--filter=upper_num_only}}, same as @w{@samp{--filter=upper_num}}, but other characters will be discarded.@* Try @w{@samp{--filter=help}} for a list of valid filter names. @item -f @itemx --force Force overwrite of output files. @item -F @var{name} @itemx --format=@var{name} Select the output format. The valid names are @samp{byte} and @samp{utf8}.@* If no output format is specified, @samp{byte} (8 bit) is assumed. @item -i @itemx --invert Invert image levels (white on black). @item -l @itemx --layout Enable page layout analysis. Ocrad is able to separate blocks of text of arbitrary shape as long as they are clearly delimited by white space. @item -o @var{file} @itemx --output=@var{file} Place the output into @var{file} instead of into the standard output. @item -q @itemx --quiet Quiet operation. @item -s @var{value} @itemx --scale=@var{value} Scale up the input image by @var{value} before layout analysis and recognition. If @var{value} is negative, the input image is scaled down by @var{-value}. @item -t @var{name} @itemx --transform=@var{name} Perform given transformation (rotation or mirroring) on the input image before scaling, layout analysis and recognition.@* Try @w{@samp{--transform=help}} for a list of valid transformation names. @item -T @var{value} @itemx --threshold=@var{value} Set binarization threshold for pgm or ppm files or for @samp{--scale} option (only for scaled down images). @var{value} should be a rational number between 0 and 1, and may be given as a percentage (50%), a fraction (1/2), or a decimal value (0.5). Image values greater than threshold are converted to white. The default value is 0.5. @item -u @var{left},@var{top},@var{width},@var{height} @itemx --cut=@var{left},@var{top},@var{width},@var{height} Cut the input image by the rectangle defined by @var{left}, @var{top}, @var{width} and @var{height}. Values may be relative to the image size @w{(-1.0 <= value <= +1.0)}, or absolute @w{(abs( value ) > 1)}. Negative values of @var{left}, @var{top} are relative to the right-bottom corner of the image. Values of @var{width} and @var{height} must be positive. Absolute and relative values can be mixed. For example @w{@samp{ocrad --cut 700,960,1,1}} will extract from @samp{700,960} to the right-bottom corner of the image.@* The cutting is performed before any other transformation (rotation or mirroring) on the input image, and before scaling, layout analysis and recognition. @item -v @itemx --verbose Verbose mode. @item -x @var{file} @itemx --export=@var{file} Write (export) OCR results file to @var{file} (@pxref{OCR results file}). @w{@samp{-x -}} writes to stdout, overriding text output except if output has been also redirected with the @samp{-o} option. @end table Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (eg, bug) which caused ocrad to panic. @node Library version @chapter Library version @cindex library version @deftypefun {const char *} OCRAD_version ( void ) Returns the library version as a string. @end deftypefun @deftypevr Constant {const char *} OCRAD_version_string This constant is defined in the header file @samp{ocradlib.h}. @end deftypevr The application should compare OCRAD_version and OCRAD_version_string for consistency. If the first character differs, the library code actually used may be incompatible with the @samp{ocradlib.h} header file used by the application. @example if( OCRAD_version()[0] != OCRAD_version_string[0] ) error( "bad library version" ); @end example @node Library functions @chapter Library functions @cindex library functions These are the OCRAD library functions. In case of error, all of them return -1 or a null pointer, except @samp{OCRAD_open} whose return value must be verified by calling @samp{OCRAD_get_errno} before using it. @deftypefun {struct OCRAD_Descriptor *} OCRAD_open ( void ) Initializes the internal library state and returns a pointer that can only be used as the @var{ocrdes} argument for the other OCRAD functions, or a null pointer if the descriptor could not be allocated. The returned pointer must be verified by calling @samp{OCRAD_get_errno} before using it. If @samp{OCRAD_get_errno} does not return @samp{OCRAD_ok}, the returned pointer must not be used and should be freed with @samp{OCRAD_close} to avoid memory leaks. @end deftypefun @deftypefun int OCRAD_close ( struct OCRAD_Descriptor * const @var{ocrdes} ) Frees all dynamically allocated data structures for this descriptor. After a call to @samp{OCRAD_close}, @var{ocrdes} can no more be used as an argument to any OCRAD function. @end deftypefun @deftypefun {enum OCRAD_Errno} OCRAD_get_errno ( struct OCRAD_Descriptor * const @var{ocrdes} ) Returns the current error code for @var{ocrdes} (@pxref{Library error codes}). @end deftypefun @deftypefun int OCRAD_set_image ( struct OCRAD_Descriptor * const @var{ocrdes}, const struct OCRAD_Pixmap * const @var{image}, const bool @var{invert} ) Loads @var{image} into the internal buffer. If @var{invert} is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. @end deftypefun @deftypefun int OCRAD_set_image_from_file ( struct OCRAD_Descriptor * const @var{ocrdes}, const char * const @var{filename}, const bool @var{invert} ) Loads a image from the file @var{filename} into the internal buffer. If @var{invert} is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. @end deftypefun @deftypefun int OCRAD_set_utf8_format ( struct OCRAD_Descriptor * const @var{ocrdes}, const bool @var{utf8} ) Set the output format to @samp{byte} (if @var{utf8}=false) or to @samp{utf8}. By default ocrad produces @samp{byte} (8 bit) output. @end deftypefun @deftypefun int OCRAD_set_threshold ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{threshold} ) Set binarization threshold for greymap or RGB images. @var{threshold} values between 0 and 255 set a fixed threshold. A value of -1 sets an automatic threshold. Pixel values greater than the resulting threshold are converted to white. The default threshold value if this function is not called is 127. @end deftypefun @deftypefun int OCRAD_scale ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{value} ) Scale up the image in the internal buffer by @var{value}. If @var{value} is negative, the image is scaled down by @var{-value}. @end deftypefun @deftypefun int OCRAD_recognize ( struct OCRAD_Descriptor * const @var{ocrdes}, const bool @var{layout} ) Recognize the image loaded in the internal buffer and produce text results which can be later retrieved with the @samp{OCRAD_result} functions. The same image can be recognized as many times as desired, for example setting a new threshold each time for 3D greymap recognition. Every time this function is called, the produced text results replace any previous ones. If @var{layout} is true, page layout analysis is enabled, probably producing more than one text block. @end deftypefun @deftypefun int OCRAD_result_blocks ( struct OCRAD_Descriptor * const @var{ocrdes} ) Returns the number of text blocks found in the image, or 0 if no text was found. The returned value is usually 1, but can be larger if layout analysis was requested. @end deftypefun @deftypefun int OCRAD_result_lines ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum} ) Returns the number of text lines contained in the given text block. @end deftypefun @deftypefun int OCRAD_result_chars_total ( struct OCRAD_Descriptor * const @var{ocrdes} ) Returns the total number of text characters contained in the recognized image. @end deftypefun @deftypefun int OCRAD_result_chars_block ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum} ) Returns the number of text characters contained in the given text block. @end deftypefun @deftypefun int OCRAD_result_chars_line ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum}, const int @var{linenum} ) Returns the number of text characters contained in the given text line. @end deftypefun @deftypefun {const char *} OCRAD_result_line ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum}, const int @var{linenum} ) Returns the line of text specified by @var{blocknum} and @var{linenum}. @end deftypefun @deftypefun int OCRAD_result_first_character ( struct OCRAD_Descriptor * const @var{ocrdes} ) Returns the byte result for the first character in the image. Returns 0 if the image has no characters or if the first character could not be recognized. This function is a convenient short cut to the result for images containing a single character. @end deftypefun @node Library error codes @chapter Library error codes @cindex library error codes Most library functions return -1 or a null pointer to indicate that they have failed. But this return value only tells you that an error has occurred. To find out what kind of error it was, you need to verify the error code by calling @samp{OCRAD_get_errno}. Library functions do not change the value returned by @samp{OCRAD_get_errno} when they succeed; thus, the value returned by @samp{OCRAD_get_errno} after a successful call is not necessarily OCRAD_ok, and you should not use @samp{OCRAD_get_errno} to determine whether a call failed. If the call failed, then you can examine @samp{OCRAD_get_errno}. The error codes are defined in the header file @samp{ocradlib.h}. @deftypevr Constant {enum OCRAD_Errno} OCRAD_ok The value of this constant is 0 and is used to indicate that there is no error. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_bad_argument At least one of the arguments passed to the library function was invalid. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_mem_error No memory available. The system cannot allocate more virtual memory because its capacity is full. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_sequence_error A library function was called in the wrong order. For example @samp{OCRAD_result_line} was called before @samp{OCRAD_recognize}. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_library_error A bug was detected in the library. Please, report it (@pxref{Problems}). @end deftypevr @node Image format conversion @chapter Image format conversion @cindex image format conversion There are a lot of image formats, but ocrad is able to decode only three of them; pbm, pgm and ppm. In this chapter you will find command examples and advice about how to convert image files to a format that ocrad can manage. @table @samp @item .png Portable Network Graphics file. Use the command @w{@code{pngtopnm filename.png | ocrad}}.@* In some cases, like the ocrad.png icon, you have to invert the image with the @samp{-i} option: @w{@code{pngtopnm filename.png | ocrad -i}}. @item .ps @itemx .pdf Postscript or Portable Document Format file. Use the command @w{@code{gs -sPAPERSIZE=a4 -sDEVICE=pnmraw -r300 -dNOPAUSE -dBATCH -sOutputFile=- -q filename.ps | ocrad}}.@* You may also use the command @w{@code{pstopnm -stdout -dpi=300 -pgm filename.ps | ocrad}},@* but it seems not to work with pdf files. Also old versions of @code{pstopnm} don't recognize the @samp{-dpi} option and produce an image too small for OCR. @item .tiff TIFF file. Use the command@* @w{@code{tifftopnm filename.tiff | ocrad}}. @item .jpg JPEG file. Use the command @w{@code{djpeg -greyscale -pnm filename.jpg | ocrad}}.@* JPEG is a lossy format and is in general not recommended for text images. @item .pnm.gz Pnm file compressed with gzip. Use the command @w{@code{gzip -cd filename.pnm.gz | ocrad}} @item .pnm.lz Pnm file compressed with lzip. Use the command @w{@code{lzip -cd filename.pnm.lz | ocrad}} @end table @node Algorithm @chapter Algorithm @cindex algorithm Ocrad is mainly a research project. Many of the algorithms ocrad uses are ad hoc, and will change in successive releases as I myself gain understanding about OCR issues. The overall working of ocrad may be described as follows:@* 1) Read the image.@* 2) Optionally, perform some transformations (cut, rotate, scale, etc).@* 3) Optionally, perform layout detection.@* 4) Remove frames and pictures.@* 5) Detect characters and group them in lines.@* 6) Recognize characters (very ad hoc; one algorithm per character).@* 7) Correct some ambiguities (transform l.OOO into 1.000, etc).@* 8) Output result. @sp 1 Ocrad recognizes characters by its shape, and the reason it is so fast is that it does not compare the shape of every character against some sort of database of shapes and then chooses the best match. Instead of this, ocrad only compares the shape differences that are relevant to choose between two character categories, mostly like a binary search. As there is no such thing as a free lunch, this approach has some drawbacks. It makes ocrad very sensitive to character defects, and makes difficult to modify ocrad to recognize new characters. For best results, the characters should be at least 20 pixels high. If they are smaller, try the --scale option. Scanning the image at 300 dpi usually produces a character size good enough for ocrad. @node OCR results file @chapter OCR results file @cindex OCR results file Calling ocrad with option @samp{-x} produces an OCR results file (ORF), that is, a parsable file containing the OCR results. The ORF format is as follows: @itemize @minus @item Any line beginning with @samp{#} is a comment line. @item The first non-comment line has the form @w{@samp{source file @var{filename}}}, where @var{filename} is the name of the file being processed (@samp{-} for stdin). This is the only line guaranteed to exist for every input file read without errors. If the file, or any block or line, has no text, the corresponding part in the ORF file will be missing. @item The second non-comment line has the form @w{@samp{total text blocks @var{n}}}, where @var{n} is the total number of text blocks in the source image. @end itemize @noindent For each text block in the source image, the following data follows: @itemize @minus @item A line in the form @w{@samp{text block @var{i} @var{x y w h}}}. Where @var{i} is the block number and @var{x y w h} are the block position and size as described below for character boxes. @item A line in the form @samp{lines @var{n}}. Where @var{n} is the number of lines in this block. @end itemize @noindent For each line in every text block, the following data follows: @itemize @minus @item A line in the form @samp{line @var{i} chars @var{n} height @var{h}}, where @var{i} is the line number, @var{n} is the number of characters in this line, and @var{h} is the mean height of the characters in this line (in pixels). @item N lines (one for every character) in the form @w{@samp{@var{x} @var{y} @var{w} @var{h}; @var{g}[, '@var{c}'@var{v}]...}}, where:@* @var{x} is the left border (x-coordinate) of the char bounding box in the source image (in pixels).@* @var{y} is the top border (y-coordinate).@* @var{w} is the width of the bounding box.@* @var{h} is the height of the bounding box.@* @var{g} is the number of different recognition guesses for this character.@* The result characters follow after the number of guesses in the form of a comma-separated list of pairs. Every pair is formed by the actual recognised char @var{c} enclosed in single quotes, followed by the confidence value @var{v}, without space between them. The higher the value of confidence, the more confident is the result. @end itemize Running @code{./ocrad -x test.orf examples/test.pbm} in the source directory will give you an example ORF file. @node Problems @chapter Reporting bugs @cindex bugs @cindex getting help There are probably bugs in ocrad. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. If you find a bug in GNU Ocrad, please send electronic mail to @email{bug-ocrad@@gnu.org}. Include the version number, which you can find by running @w{@samp{ocrad --version}}. @node Concept index @unnumbered Concept index @printindex cp @bye ocrad-0.24/feats.cc000066400000000000000000000242221241541103500141360ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "profile.h" #include "feats.h" Features::Features( const Blob & b_ ) : b( b_ ), hbar_initialized( false ), vbar_initialized( false ), lp( b, Profile::left ), tp( b, Profile::top ), rp( b, Profile::right ), bp( b, Profile::bottom ), hp( b, Profile::height ), wp( b, Profile::width ) {} int Features::hbars() const { if( !hbar_initialized ) { hbar_initialized = true; const int limit = ( wp.max() + 1 ) / 2; int state = 0, begin = 0, l = 0, r = 0; std::vector< int > count( b.height(), 0 ); for( int row = b.top(); row <= b.bottom(); ++row ) { int col, c = 0, lt = 0, rt = 0, x = 0; int & maxcount = count[row-b.top()]; for( col = b.left(); col <= b.right(); ++col ) { if( b.get_bit( row, col ) ) { ++c; x = col; if( col < b.right() ) continue; } if( c > maxcount ) { maxcount = c; rt = x; lt = rt - c + 1; } c = 0; } switch( state ) { case 0: if( maxcount > limit ) { state = 1; begin = row; l = lt; r = rt; } else break; case 1: if( maxcount > limit ) { if( lt < l ) l = lt; if( rt > r ) r = rt; if( row < b.bottom() ) break; } state = 0; int end = ( maxcount <= limit ) ? row - 1 : row; const int width = r - l + 1; while( begin <= end && 3 * count[begin-b.top()] < 2 * width ) ++begin; while( begin <= end && 3 * count[end-b.top()] < 2 * width ) --end; const int height = end - begin + 1; if( height < 1 || 2 * height > 3 * width ) break; hbar_.push_back( Rectangle( l, begin, r, end ) ); break; } } while( hbar_.size() > 3 ) // remove noise hbars { int wmin = hbar_[0].width(); for( unsigned i = 1; i < hbar_.size(); ++i ) if( hbar_[i].width() < wmin ) wmin = hbar_[i].width(); for( int i = hbar_.size() - 1; i >= 0; --i ) if( hbar_[i].width() == wmin ) hbar_.erase( hbar_.begin() + i ); } } return hbar_.size(); } int Features::vbars() const // FIXME small gaps not detected { if( !vbar_initialized ) { vbar_initialized = true; int state = 0, begin = 0, limit = b.height(); limit -= ( b.height() < 40 ) ? 3 : b.height() / 10; for( int col = b.left(); col <= b.right(); ++col ) { int c = 0, c2 = 0, count = 0; for( int row = b.top() + 1; row < b.bottom(); ++row ) { if( b.get_bit( row, col ) ) { ++c; if( row < b.bottom() - 1 ) continue; } else if( ( col > b.left() && b.get_bit( row, col - 1 ) ) || ( col < b.right() && b.get_bit( row, col + 1 ) ) ) { ++c; ++c2; if( row < b.bottom() - 1 ) continue; } if( c > count ) { count = c; } c = 0; } if( ( count - c2 ) * 3 < limit * 2 ) count = 0; switch( state ) { case 0: if( count >= limit ) { state = 3; begin = col; } else if( count * 4 >= limit * 3 ) { state = 2; begin = col; } else if( count * 3 >= limit * 2 ) { state = 1; begin = col; } break; case 1: if( count >= limit ) state = 3; else if( count * 4 >= limit * 3 ) state = 2; else if( count * 3 < limit * 2 ) state = 0; else begin = col; break; case 2: if( count >= limit ) state = 3; else if( count * 3 < limit * 2 ) state = 0; else if( count * 4 < limit * 3 ) state = 1; break; case 3: if( count * 3 < limit * 2 || col == b.right() ) { int end = ( count * 3 < limit * 2 ) ? col - 1 : col; vbar_.push_back( Rectangle( begin, b.top(), end, b.bottom() ) ); state = 0; } } } } return vbar_.size(); } // return the number of vertical traces crossing every row // int Features::segments_in_row( const int row ) const { if( row_scan.empty() ) { int l = -1; // begin of segment. -1 means no segment row_scan.resize( b.height() ); for( int row = b.top(); row <= b.bottom(); ++row ) for( int col = b.left(); col <= b.right(); ++col ) { bool black = b.get_bit( row, col ); if( l < 0 && black ) l = col; // begin of segment if( l >= 0 && ( !black || col == b.right() ) ) // end of segment { row_scan[row-b.top()].push_back( Csegment( l, col - !black ) ); l = -1; } } } return row_scan[row-b.top()].size(); } // return the number of horizontal traces crossing every column // int Features::segments_in_col( const int col ) const { if( col_scan.empty() ) { int t = -1; // begin of segment. -1 means no segment col_scan.resize( b.width() ); for( int col = b.left(); col <= b.right(); ++col ) for( int row = b.top(); row <= b.bottom(); ++row ) { bool black = b.get_bit( row, col ); if( t < 0 && black ) t = row; // begin of segment if( t >= 0 && ( !black || row == b.bottom() ) ) // end of segment { col_scan[col-b.left()].push_back( Csegment( t, row - !black ) ); t = -1; } } } return col_scan[col-b.left()].size(); } // return the column segment containing the point (row,col) if any // Csegment Features::col_segment( const int row, const int col ) const { const int segments = segments_in_col( col ); for( int i = 0; i < segments; ++i ) if( col_scan[col-b.left()][i].includes( row ) ) return col_scan[col-b.left()][i]; return Csegment(); } int Features::test_misc( const Rectangle & charbox ) const { if( bp.minima() == 1 ) { if( hbars() == 1 && hbar(0).top() <= b.top() + ( b.height() / 10 ) && 4 * hbar(0).height() <= b.height() && 5 * hbar(0).width() >= 4 * b.width() && rp[hbar(0).bottom()-b.top()+2] - rp[hbar(0).bottom()-b.top()] < b.width() / 4 && rp.increasing( hbar(0).vcenter() - b.top() + 1 ) ) return '7'; if( b.height() > b.width() && rp.increasing() && !tp.decreasing() && b.seek_left( b.vcenter(), b.hcenter() ) <= b.left() ) return '7'; } if( tp.minima( b.height() / 4 ) == 1 && bp.minima( b.height() / 4 ) == 1 ) { if( b.height() > 2 * b.width() && rp.increasing() && tp.decreasing() && lp.iscpit( 25 ) ) return '1'; if( hbars() == 1 || ( hbars() == 2 && hbar(1).bottom() >= b.bottom() - 1 && 3 * hbar(0).width() > 4 * hbar(1).width() ) ) if( 3 * hbar(0).height() < b.height() && hbar(0).top() <= b.top() + 1 ) { int i = lp.pos( 40 ); if( 3 * wp[i] < b.width() && 5 * lp[i] > b.width() && 5 * rp[i] > b.width() ) return 'T'; } if( 3 * b.height() > 4 * b.width() && vbars() == 1 && vbar(0).width() >= 2 ) { const int lg = vbar(0).left() - b.left(); const int rg = b.right() - vbar(0).right(); if( 2 * lg < b.width() && 2 * rg < b.width() && Ocrad::similar( lg, rg, 40 ) && 4 * bp[bp.pos(25)] > 3 * b.height() && 4 * tp[tp.pos(75)] > 3 * b.height() ) return 'l'; } if( 5 * b.height() >= 4 * charbox.height() && b.height() > wp.max() && 3 * wp[wp.pos(50)] < b.width() ) { if( hbars() == 1 && hbar(0).bottom() >= b.bottom() - 1 && hbar(0).top() > b.vpos( 75 ) && Ocrad::similar( lp[lp.pos(50)], rp[rp.pos(50)], 20, 2 ) ) return 'l'; if( hbars() == 2 && hbar(0).bottom() < b.vpos( 25 ) && hbar(1).top() > b.vpos( 75 ) && hbar(1).bottom() >= b.bottom() - 1 /*&& 3 * hbar(0).width() < 4 * hbar(1).width()*/ ) { if( hbar(0).right() <= hbar(1).hcenter() ) return 0; if( 3 * hbar(0).width() <= 2 * hbar(1).width() || b.height() >= 3 * wp.max() ) return 'l'; return 'I'; } } if( ( hbars() == 2 || hbars() == 3 ) && hbar(0).top() <= b.top() + 1 && hbar(1).includes_vcenter( b ) && 3 * hbar(0).width() > 4 * hbar(1).width() && ( hbars() == 2 || ( hbar(2).bottom() >= b.bottom() - 1 && 3 * hbar(0).width() > 4 * hbar(2).width() ) ) ) return 'F'; if( b.height() > 3 * wp.max() ) { if( rp.istip() && lp.ispit() ) { if( lp.istpit() ) return '{'; else return '('; } if( lp.istip() && rp.ispit() ) { if( rp.istpit() ) return '}'; else return ')'; } if( b.width() > 2 * wp.max() && rp.isconvex() ) return ')'; } if( b.height() > 2 * b.width() && 5 * b.height() >= 4 * charbox.height() && lp.max() + rp.max() < b.width() ) { if( 5 * rp[rp.pos(50)] > 2 * b.width() ) { const int row = b.seek_top( b.vpos( 75 ), b.hpos( 75 ) ); if( ( b.top() < charbox.top() || b.bottom() <= charbox.bottom() + ( b.height() / 5 ) ) && row <= b.top() ) return 'L'; if( row > b.top() && b.seek_bottom( b.vpos( 75 ), b.hpos( 75 ) ) < b.bottom() ) return '['; } return '|'; } } return 0; } ocrad-0.24/feats.h000066400000000000000000000045261241541103500140050ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Features { const Blob & b; // Blob to witch these features belong mutable bool hbar_initialized, vbar_initialized; mutable std::vector< Rectangle > hbar_, vbar_; mutable std::vector< std::vector< Csegment > > row_scan, col_scan; Features( const Features & ); // declared as private void operator=( const Features & ); // declared as private public: mutable Profile lp, tp, rp, bp, hp, wp; explicit Features( const Blob & b_ ); // const Blob & blob() const { return b; } const Rectangle & hbar( const int i ) const { return hbar_[i]; } const Rectangle & vbar( const int i ) const { return vbar_[i]; } int hbars() const; int vbars() const; int segments_in_row( const int row ) const; int segments_in_col( const int col ) const; Csegment col_segment( const int row, const int col ) const; int test_235Esz( const Charset & charset ) const; int test_49ARegpq( const Rectangle & charbox ) const; int test_4ADQao( const Charset & charset, const Rectangle & charbox ) const; int test_6abd( const Charset & charset ) const; int test_EFIJLlT( const Charset & charset, const Rectangle & charbox ) const; int test_c() const; int test_frst( const Rectangle & charbox ) const; int test_G() const; int test_HKMNUuvwYy( const Rectangle & charbox ) const; int test_hknwx( const Rectangle & charbox ) const; int test_s_cedilla() const; bool test_comma() const; int test_easy( const Rectangle & charbox ) const; int test_line( const Rectangle & charbox ) const; int test_solid( const Rectangle & charbox ) const; int test_misc( const Rectangle & charbox ) const; }; ocrad-0.24/feats_test0.cc000066400000000000000000001017131241541103500152560ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "profile.h" #include "feats.h" // Looks for three black sections in column hcenter() ± n, then tests if // upper and lower gaps are open to the right or to the left // int Features::test_235Esz( const Charset & charset ) const { const int csize = 3; const int ucoff[csize] = { 0, -1, +1 }; const int lcoff[3*csize] = { 0, -1, +1, -1, 0, +1, +1, 0, -1 }; if( b.width() < 9 || b.height() > 3 * b.width() || bp.minima( b.height() / 2 ) > 1 ) return 0; const int noise = ( std::min( b.height(), b.width() ) / 15 ) + 1; int lrow1 = 0, urow2 = 0, lrow2 = 0, urow3 = 0; int lcol1 = 0, ucol2 = 0, lcol2 = 0, ucol3 = 0; bool done = false; for( int i = 0; i < csize && !done; ++i ) { const int ucol = b.hcenter() + ( noise * ucoff[i] ); int row = b.top() + tp[ucol-b.left()]; while( ++row < b.bottom() && b.get_bit( row, ucol ) ) ; if( row <= b.vpos( 30 ) ) { lrow1 = row; lcol1 = ucol; } else continue; while( ++row < b.bottom() && !b.get_bit( row, ucol ) ) ; if( row < b.bottom() ) { urow2 = row - 1; ucol2 = ucol; for( int j = 0; j < csize && !done; ++j ) { row = urow2 + 1; const int lcol = b.hcenter() + ( noise * lcoff[(csize*i)+j] ); if( ucol != lcol ) { const int d = ( ucol > lcol ) ? +1 : -1; int c = lcol; while( c != ucol && b.get_bit( row, c ) ) c += d; if( c != ucol ) continue; } while( ++row < b.bottom() && b.get_bit( row, lcol ) ) ; if( row < b.bottom() ) { lrow2 = row; lcol2 = lcol; } else continue; while( ++row <= b.bottom() && !b.get_bit( row, lcol ) ) ; if( row <= b.bottom() && row > b.vpos( 70 ) ) { urow3 = row - 1; ucol3 = lcol; done = true; } } } } if( !done ) return 0; const bool bopen = b.escape_bottom( urow3, ucol3 ); const bool topen = b.escape_top( lrow1, lcol1 ); const bool tbopen = bopen && topen; const int ascode = ( b.get_bit( b.vcenter(), b.hcenter() ) ) ? '*' : 0; if( b.escape_left( lrow2, lcol2 ) ) { if( b.escape_left( urow2, ucol2 ) ) { if( tbopen ) return ascode; if( !bopen && !topen && b.height() <= 3 * b.width() ) { const int lm = lp.minima(), rm = rp.minima(); if( ( lm == 3 || lm == 2 ) && ( rm == 2 || ( rm == 1 && rp.iminimum() < rp.pos( 80 ) ) ) ) return '3'; } } else if( b.escape_right( urow2, ucol2 ) ) { if( tbopen ) return ascode; if( rp[lrow1 + 1 - b.top()] >= lcol1 - b.left() && ( lp[lrow2 + 1 - b.top()] < lcol2 - b.left() || lp[urow3 - 1 - b.top()] < ucol3 - b.left() ) ) { int c = 0, hdiff; if( !b.top_hook( &hdiff ) || 5 * hdiff >= 4 * b.height() ) ++c; if( 2 * lp[lrow2 - b.top()] < lcol2 - b.left() ) ++c; if( !tp.isconvex() || ( !tp.ispit() && bp.ispit() ) ) ++c; if( c >= 2 ) return '5'; } if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( urow2 > b.vpos( 55 ) && b.seek_right( urow2 - 1, ucol2 ) < b.right() ) { if( urow2 > b.vpos( 63 ) ) return UCS::CCCEDI; else return UCS::SCCEDI; } return 's'; } } else if( b.escape_right( lrow2, lcol2 ) ) { if( b.escape_right( urow2, ucol2 ) ) { if( tbopen ) return ascode; if( bp.minima( b.height() / 5 ) == 1 ) { if( 8 * lp[((lrow2+urow3)/2)-b.top()] >= b.width() && b.escape_top( ( lrow1 + urow2 ) / 2, b.left() ) && !b.escape_top( ( lrow2 + urow3 ) / 2, b.left() ) ) return 'f'; if( rp.minima( b.width() / 8 ) < 3 && b.escape_bottom( urow3, ucol3 ) ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( 2 * lp[lp.pos(95)] > rp[rp.pos(95)] ) { if( urow2 > b.vpos( 63 ) ) return UCS::CCCEDI; else return UCS::SCCEDI; } return 'F'; } else if( lrow1 < urow2 && urow2 + 2 < lrow2 && lrow2 < urow3 && urow2 <= b.vcenter() && lrow2 >= b.vcenter() ) return 'E'; } } else if( b.escape_left( urow2, ucol2 ) ) { if( !tbopen && ( 2 * lp[lp.pos(50)] ) + 2 >= b.width() && ( tp.isconvex() || ( tp.ispit() && !bp.ispit() ) ) ) return '2'; if( 2 * b.height() <= 5 * wp.max() && bp[bp.pos(75)] <= b.height() / 10 && Ocrad::similar( wp.max( 0, wp.pos(30) ), wp.max( wp.pos(70) ), 20 ) ) return 'z'; } } return 0; } int Features::test_EFIJLlT( const Charset & charset, const Rectangle & charbox ) const { if( tp.minima( b.height() / 4 ) != 1 || bp.minima( b.height() / 4 ) != 1 ) return 0; const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; { const bool maybe_j = ( 2 * ( lp[lp.pos(50)] + noise ) >= b.width() ); const int col = b.hpos( maybe_j ? 25 : 75 ); int row = b.seek_top( b.vcenter(), col ); if( row <= b.top() || ( row < b.vpos( 25 ) && b.escape_top( row, col ) ) ) { int hdiff; if( b.bottom_hook( &hdiff ) ) { if( maybe_j && hdiff > b.height() / 2 && rp.increasing( rp.pos( 80 ), 1 ) && !rp.decreasing() ) return 'J'; if( !maybe_j && -hdiff > b.height() / 2 ) { if( 5 * lp[lp.pos(80)] >= 2 * b.width() ) return 'v'; // broken 'v' if( col > b.hcenter() ) return 'L'; } } } } const int vnoise = ( b.height() / 30 ) + 1; const int topmax = b.top() + vnoise; const int botmin = b.bottom() - vnoise; if( vbars() == 1 && vbar(0).width() >= 2 && 2 * vbar(0).width() <= b.width() ) { if( std::abs( vbar(0).hcenter() - b.hcenter() ) <= noise && std::abs( (vbar(0).left() - b.left()) - (b.right() - vbar(0).right()) ) <= 2 * noise ) { if( hbars() == 1 && 4 * hbar(0).height() <= b.height() ) { if( ( hbar(0).top() <= topmax || hbar(0).bottom() < b.vpos( 15 ) ) && hbar(0).width() >= wp[wp.pos(75)] + wp[wp.pos(80)] ) return 'T'; if( std::abs( hbar(0).vcenter() - b.vcenter() ) <= vnoise && hbar(0).width() >= b.width() && Ocrad::similar( b.height(), b.width(), 50 ) ) return '+'; } if( hbars() == 2 && hbar(0).top() <= topmax && 4 * hbar(0).height() <= b.height() && hbar(1).bottom() >= botmin && 4 * hbar(1).height() <= b.height() && 3 * hbar(0).width() > 4 * hbar(1).width() ) return 'T'; } } if( vbars() == 1 && vbar(0).width() >= 2 && 2 * vbar(0).width() <= b.width() ) { if( vbar(0).right() <= b.hcenter() ) { if( ( hbars() == 2 || hbars() == 3 ) && hbar(0).top() <= topmax && hbar(0).width() + 1 >= hbar(1).width() && 2 * hbar(1).width() >= 3 * vbar(0).width() && vbar(0).h_overlaps( hbar(1) ) ) { if( hbars() == 3 && Ocrad::similar( hbar(0).width(), hbar(2).width(), 10, 2 ) && 10 * hbar(2).width() >= 9 * hbar(1).width() && hbar(0).left() <= hbar(1).left() + 1 ) return 'E'; if( ( hbars() == 2 || hbar(0).width() > hbar(2).width() ) && ( hbar(1).includes_vcenter( b ) || ( 3 * hbar(1).width() > 2 * hbar(0).width() && 10 * lp[vnoise] < b.width() && hbar(1).top() > b.vpos( 30 ) && hbar(1).bottom() < b.vpos( 60 ) ) ) ) return 'F'; } if( hbars() == 2 && hbar(1).bottom() >= botmin && b.height() > b.width() && hbar(1).width() > hbar(0).width() && std::abs( vbar(0).hcenter() - hbar(0).hcenter() ) <= 1 && rp.iminimum() > rp.pos( 70 ) ) return 'L'; if( hbars() == 1 && Ocrad::similar( hbar(0).width(), b.width(), 10 ) && vbar(0).left() <= b.hpos( 30 ) ) { if( hbar(0).bottom() >= botmin && b.escape_top( b.vcenter(), b.hpos( 75 ) ) ) return 'L'; if( hbar(0).top() <= topmax && 2 * wp[wp.pos(50)] >= b.width() && 4 * wp[wp.pos(75)] < b.width() && b.escape_right( b.vpos( 25 ), b.hcenter() ) ) return 'F'; } } if( vbar(0).left() > b.hcenter() && hbars() == 1 ) { if( vbar(0).right() >= b.hpos( 90 ) && hbar(0).bottom() >= botmin && Ocrad::similar( hbar(0).width(), b.width(), 10 ) && b.bottom() > charbox.vpos( 90 ) && b.escape_top( b.vcenter(), b.hpos( 25 ) ) ) { if( b.height() > b.width() ) return 'J'; else return 0; } if( hbar(0).top() <= topmax && hbar(0).width() + 1 >= b.width() ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( b.width() > b.height() ) return UCS::NOT; return 0; } } } if( vbars() == 1 && vbar(0).width() >= 2 && tp.minima() == 1 && bp.minima() == 1 ) { if( 3 * b.height() > 4 * b.width() && Ocrad::similar( vbar(0).left() - b.left(), b.right() - vbar(0).right(), 30, 2 * noise ) ) { if( b.height() <= 3 * wp.max() && rp.istip() && lp.istip() ) { if( b.height() <= 3 * b.width() && lp[lp.pos(40)] > lp[lp.pos(60)] + noise && rp[rp.pos(60)] > rp[rp.pos(40)] + noise ) return 'z'; return 'I'; } if( rp.isflats() && ( lp.istip() || lp.isflats() || ( lp.isctip() && lp.minima() == 2 && lp.iminimum() < lp.pos( 30 ) && lp.iminimum(1) > lp.pos( 80 ) ) ) ) return 'l'; if( b.height() > 3 * wp.max() ) { if( rp.istip() && lp.ispit() && Ocrad::similar( lp.iminimum(), lp.pos( 50 ), 10 ) ) { if( lp.istpit() ) return '{'; else return '('; } if( lp.istip() && rp.ispit() && Ocrad::similar( rp.iminimum(), rp.pos( 50 ), 10 ) ) { if( rp.istpit() ) return '}'; else return ')'; } if( rp.isflats() && 2 * vbar(0).size() >= b.area() ) return 'l'; } if( 2 * b.height() > 3 * b.width() && lp.minima() <= 2 ) if( rp.isflats() || rp.minima() == 1 ) if( vbar(0).right() >= b.hpos( 70 ) || b.escape_top( b.vpos( 75 ), std::min( b.right(), vbar(0).right() + 1 ) ) ) for( int i = vbar(0).left() - 1; i > b.left(); --i ) if( b.seek_bottom( b.vpos( 75 ), i ) < b.bottom() && bp[i-b.left()] <= noise ) return 'l'; } if( vbar(0).right() >= b.right() - 1 ) { if( lp.istip() && b.height() > 2 * b.width() ) { if( 2 * vbar(0).width() <= wp.max() && lp[lp.pos(50)] >= b.width() / 2 ) return ']'; if( b.height() >= 3 * b.width() ) return 'l'; } if( 2 * b.height() >= 3 * b.width() && vbar(0).height() >= 3 * vbar(0).width() && lp.istpit() && lp.minima() == 1 ) { const int i = lp.iminimum(); if( i > lp.pos( 10 ) && i < lp.pos( 40 ) ) return '1'; } } } if( hbars() == 1 && hbar(0).width() >= b.width() && std::abs( hbar(0).vcenter() - b.vcenter() ) <= vnoise && Ocrad::similar( b.height(), b.width(), 50 ) && tp.isupit() && bp.isupit() ) return '+'; return 0; } int Features::test_c() const { if( lp.isconvex() || lp.ispit() ) { int urow = b.seek_top( b.vcenter(), b.hcenter() ); int lrow = b.seek_bottom( b.vcenter(), b.hcenter() ); if( b.height() > 2 * b.width() && 3 * wp.max() <= 2 * b.width() ) { if( lp.isconvex() ) return '('; else return 0; } if( urow > b.top() && lrow < b.bottom() && rp.isctip() && ( bp.ispit() || tp.ispit() || ( bp.isltip() && tp.isltip() ) ) && b.escape_right( b.vcenter(), b.hcenter() ) ) return 'c'; } if( b.height() > 2 * b.width() && rp.isconvex() ) { int urow = b.seek_top( b.vcenter(), b.hcenter() ); int lrow = b.seek_bottom( b.vcenter(), b.hcenter() ); if( 3 * wp.max() <= 2 * b.width() || ( 2 * lp[urow-b.top()] >= b.width() && 2 * lp[lrow-b.top()] >= b.width() ) ) return ')'; } return 0; } int Features::test_frst( const Rectangle & charbox ) const { if( bp.minima( b.height() / 4 ) != 1 || tp.minima( b.height() / 2 ) != 1 || bp.minima( b.height() / 2 ) != 1 ) return 0; const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; const bool maybe_slanted_r = ( tp.minima( b.height() / 4 ) != 1 ); bool maybe_t = true; if( !maybe_slanted_r ) { int b_hdiff = 0, t_hdiff = 0; if( b.bottom_hook( &b_hdiff ) ) { if( -2 * b_hdiff > b.height() ) { if( b.height() >= 3 * wp.max() && !lp.ispit() && ( hbars() == 0 || hbar(0).bottom() < b.vpos( 20 ) ) ) return 'l'; if( 2 * wp[wp.pos(6)] < b.width() && hbars() >= 1 && hbars() <= 2 && hbar(0).top() >= b.vpos( 15 ) && hbar(0).bottom() < b.vcenter() && Ocrad::similar( hbar(0).width(), wp.max(), 10 ) ) return 't'; } } if( b.top_hook( &t_hdiff ) ) { if( 3 * t_hdiff > 2 * b.height() && b.height() > 2 * wp.max() && tp.iminimum() > tp.pos( 50 ) && bp.iminimum() <= bp.pos( 50 ) && ( !b_hdiff || rp.increasing( rp.pos( 50 ) ) ) ) return 'f'; if( 2 * b_hdiff > b.height() && 2 * t_hdiff > b.height() ) return 0; // recognized 's' or SCCEDI maybe_t = false; } } if( 2 * rp[rp.pos(50)] > b.width() && 2 * bp[bp.pos(50)] > b.height() && tp.isctip() ) return 'r'; if( maybe_slanted_r || vbars() != 1 || vbar(0).width() < 2 ) return 0; if( vbar(0).hcenter() <= b.hcenter() ) { const int col = b.right() - rp[rp.pos(50)] + 2; if( col < b.right() ) { const int row = b.seek_bottom( b.vcenter(), col ); if( row >= b.bottom() || b.escape_bottom( row - 1, col ) ) { if( rp.minima() == 3 ) { if( rp.minima( b.width() / 8 ) < 3 ) return 'f'; else return 0; } if( Ocrad::similar( b.height(), b.width(), 40 ) ) { if( tp.minima( b.height() / 8 ) == 2 && bp.minima( b.height() / 8 ) == 2 ) return 'x'; int row2 = b.vpos( 75 ); int col2 = b.seek_right( row2, b.hcenter(), false ) + 1; if( b.seek_right( row2, col2 ) >= b.right() ) { if( lp.isconvex() && ( col > b.hpos( 60 ) || row < b.bottom() ) ) return 0; if( ( hbars() == 1 || ( hbars() == 2 && hbar(1).bottom() >= b.bottom() - 1 && 2 * hbar(0).width() > 3 * hbar(1).width() ) ) && hbar(0).top() <= b.top() + 1 && 4 * hbar(0).height() <= b.height() && 4 * lp[lp.pos(50)] >= b.width() ) return 'T'; return 'r'; } } } if( Ocrad::similar( b.height(), b.width(), 40 ) && segments_in_row( b.vpos( 15 ) ) == 3 && segments_in_row( b.vpos( 85 ) ) == 3 && b.seek_right( row - 1, col ) < b.right() && lp.isctip() ) return 'x'; } if( 3 * b.height() > 4 * b.width() && vbar(0).left() > b.left() && rp.minima() <= 2 ) { const int col = b.right() - std::max( 0, rp[rp.pos(50)] - 1 ); if( !b.escape_bottom( b.vcenter(), col ) ) { if( 3 * wp[wp.pos(6)] < 2 * b.width() && tp.ispit() && lp.iminimum() < lp.pos( 40 ) ) return 't'; else return 0; } else if( 2 * wp.max() > b.width() ) { if( rp.iminimum() < rp.pos( 20 ) ) { if( rp.increasing( rp.pos( 20 ) ) || bp.increasing() || tp.minima( noise ) == 2 || ( rp.minima() == 1 && ( b.height() < charbox.height() || tp.iminimum() > tp.pos( 50 ) ) ) ) { if( b.height() <= 3 * wp.max() ) return 'r'; else return 0; } else if( 3 * b.height() >= 5 * b.width() && !rp.istip() ) return 'f'; } else { if( maybe_t && !rp.isconvex() && bp.minima( b.height() / 3 ) == 1 ) return 't'; else return 0; } } } if( b.seek_bottom( b.vcenter(), b.hpos( 60 ) + 1 ) >= b.bottom() ) { if( rp.minima() == 2 ) return 'f'; else return 'r'; } if( vbar(0).right() <= b.hcenter() && hbars() == 1 && hbar(0).bottom() >= b.bottom() - 1 && lp.istip() && rp.istip() && !b.escape_top( b.vcenter(), b.hpos( 75 ) ) ) return 'r'; } return 0; } int Features::test_G() const { if( lp.isconvex() || lp.ispit() ) { int col = 0, row = 0; for( int i = rp.pos( 60 ); i >= rp.pos( 30 ); --i ) if( rp[i] > col ) { col = rp[i]; row = i; } if( col == 0 ) return 0; row += b.top(); col = b.right() - col + 1; if( col <= b.left() || col >= b.hcenter() ) return 0; col = ( col + b.hcenter() ) / 2; row = b.seek_bottom( row, col ); if( row < b.bottom() && b.escape_right( row, col ) && !b.escape_bottom( row, b.hcenter() ) ) { const int noise = std::max( 2, b.height() / 20 ); int lrow, urow; for( lrow = row - 1 ; lrow > b.top(); --lrow ) if( b.seek_right( lrow, b.hcenter() ) >= b.right() ) break; for( urow = lrow - 1 ; urow > b.top(); --urow ) if( b.seek_right( urow, b.hcenter() ) < b.right() ) break; lrow += noise; if( lrow < row && urow > b.top() ) { urow -= std::min( noise, ( urow - b.top() ) / 2 ); int uwidth = b.seek_left( urow, b.right() ) - b.seek_right( urow, b.hcenter() ); int lwidth = b.seek_left( lrow, b.right() ) - b.seek_right( lrow, b.hcenter() ); if( lrow - noise <= b.vcenter() || lwidth > uwidth + noise ) return 'G'; } } } return 0; } // Common feature: U-shaped top of character // int Features::test_HKMNUuvwYy( const Rectangle & charbox ) const { if( tp.minima( b.height() / 5 ) == 2 && tp.minima( b.height() / 4 ) == 2 && tp.minima( b.height() / 2 ) <= 3 && tp.isctip() ) { const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; const int m5 = bp.minima( b.height() / 5 ); if( 2 * b.height() >= b.width() && b.height() >= 10 && ( m5 == 1 || ( m5 == 2 && Ocrad::similar( bp.iminimum(), bp.pos( 50 ), 10 ) ) ) ) { const int stem = std::min( tp.range() + ( b.height() / 10 ), wp.pos(90) ); const bool maybe_Y = ( 5 * tp.range() <= 3 * b.height() || ( stem <= wp.pos(75) && 5 * wp[stem] <= b.width() ) ); const int lg = lp.min( lp.pos( 90 ) ); if( lg > 1 && bp.isvpit() && tp.minima( b.height() / 2 ) == 2 && lp[lp.pos(75)] <= lg && ( !maybe_Y || 3 * wp[stem] > b.width() || wp[stem] > wp[wp.pos(90)] + 1 ) ) return 'v'; int hdiff; if( b.bottom_hook( &hdiff ) ) { if( std::abs( hdiff ) <= b.height() / 8 ) { if( segments_in_row( b.vpos( 30 ) ) >= 3 ) return 'v'; if( bp.isconvex() ) { if( 9 * wp[wp.pos(30)] > 10 * wp[wp.pos(50)] && 9 * wp[wp.pos(50)] > 10 * wp[wp.pos(70)] ) return 'v'; else return 'u'; } } if( hdiff > b.height() / 2 ) { if( bp.minima( b.height() / 2 ) == 1 ) return 'y'; else return 0; } } const int rg = rp.min( rp.pos( 90 ) ); const int lg2 = lp.max( lp.pos( 70 ), lp.pos( 90 ) ); const int rg2 = rp.max( rp.pos( 70 ), rp.pos( 90 ) ); const int lc = ( lg + ( 2 * ( lp.limit() - rg ) ) ) / 3; const int lc2 = ( lg2 + lp.limit() - rg2 ) / 2; if( bp.ispit() && maybe_Y ) { int row2 = b.top(); while( row2 < b.bottom() && segments_in_row( row2 ) != 2 ) ++row2; int row1 = row2 + 1; while( row1 < b.bottom() && segments_in_row( row1 ) != 1 ) ++row1; if( row1 < b.bottom() ) row1 += wp[row1-b.top()] / 4; if( row1 < b.bottom() && wp[row1-b.top()] < b.width() ) { const int w1 = wp[row1-b.top()]; int row0 = w1 * ( row1 - row2 ) / ( b.width() - w1 ) + row1; if( row0 < b.bottom() && 2 * wp[wp.pos(70)] < b.width() && ( Ocrad::similar( lg, rg, 20 ) || ( lg > 1 && lg < rg && lc >= lc2 && !rp.increasing() ) ) ) return 'Y'; } } if( b.escape_top( b.vpos( 60 ), b.hcenter() ) && !lp.istip() && ( 4 * b.height() >= 3 * b.width() || segments_in_col( b.hpos( 75 ) ) <= 2 ) ) return 'u'; if( lg < rg + 1 && !lp.increasing( lp.pos( 50 ) ) && ( 2 * lg < rg || b.vpos( 90 ) >= charbox.bottom() ) && ( tp.minima( b.height()/2 ) == 1 || lp.imaximum() > b.height()/2 ) ) return 'y'; if( lg > 1 && bp.ispit() && tp.minima( b.height() / 3 ) == 2 ) return 'v'; if( lg <= 1 && 2 * ( b.width() - rg - lg ) < b.width() && rp.increasing() && tp.minima( b.height() / 2 ) == 2 ) return 'v'; return 0; } if( 2 * b.height() >= b.width() && b.height() >= 9 && bp.minima() == 2 && bp.isctip() ) { const int th = std::max( b.height() / 4, bp[bp.pos(50)] + noise ); if( bp.minima( th ) == 3 ) return 'M'; const int lg = lp[lp.pos(50)]; const int rg = rp[rp.pos(50)]; if( Ocrad::similar( lg, rg, 80, 2 ) && 4 * lg < b.width() && 4 * rg < b.width() ) { if( lg > 1 && rg > 1 && lp.increasing() && rp.increasing() && 5 * tp[tp.pos(50)] > b.height() ) return 'w'; if( hbars() == 1 && 5 * ( hbar(0).height() - 1 ) < b.height() && hbar(0).top() >= b.vpos( 30 ) && hbar(0).bottom() <= b.vpos( 60 ) && 10 * hbar(0).width() > 9 * wp[hbar(0).vcenter()-b.top()] && Ocrad::similar( col_segment( hbar(0).vcenter(), hbar(0).hcenter() ).size(), hbar(0).height(), 30, 2 ) ) { if( 9 * hbar(0).width() <= 10 * wp[wp.pos(50)] ) return 'H'; return 0; } if( segments_in_row( b.vpos( 60 ) ) == 4 || segments_in_row( b.vpos( 70 ) ) == 4 ) { if( 2 * tp[tp.pos(50)] > b.height() ) return 'M'; return 'w'; } if( ( vbars() <= 2 || ( vbars() == 3 && b.height() >= b.width() ) ) && tp.minima( b.height() / 2 ) <= 2 && tp.minima( ( 2 * b.height() ) / 5 ) <= 2 && !lp.istpit() && 4 * std::abs( rp[rp.pos(20)] - rp[rp.pos(80)] ) <= b.width() ) { const int row = b.top() + tp[tp.pos(50)]; if( row > b.vcenter() ) { Rectangle r( b.left(), b.top(), b.hcenter(), b.bottom() ); Bitmap bm( b, r ); int hdiff; if( bm.bottom_hook( &hdiff ) && -2 * hdiff > bm.height() ) return 'u'; } if( row > b.vpos( 10 ) || vbars() >= 2 ) return 'N'; } return 0; } if( 3 * lg < 2 * rg && lg < b.width() / 4 && rg > b.width() / 4 && rp.isctip() && tp.minima( b.height() / 8 ) == 2 ) return 'K'; return 0; } if( bp.minima() <= 2 && 2 * b.width() > 5 * b.height() ) return '~'; if( bp.minima() == 3 && ( hbars() == 0 || ( hbars() == 1 && hbar(0).top() >= b.vpos( 20 ) ) ) ) return 'M'; } return 0; } // Looks for the nearest frontier in column hcenter(), then tests if // gap is open downwards (except for 'x') // int Features::test_hknwx( const Rectangle & charbox ) const { const int m8 = tp.minima( b.height() / 8 ); if( m8 == 2 && bp.minima( b.height() / 2 ) == 1 && ( ( lp.isctip() && rp.isctip() ) || ( lp.isconcave() && rp.isconcave() ) ) ) return 'x'; if( b.width() >= b.height() && tp.ispit() && ( b.bottom() < charbox.vcenter() || ( lp.decreasing() && rp.decreasing() ) ) ) return '^'; int col = 0, row = 0; for( int i = bp.pos( 40 ); i <= bp.pos( 60 ); ++i ) if( bp[i] > row ) { row = bp[i]; col = i; } row = b.bottom() - row + 1; col += b.left(); if( row > b.vpos( 90 ) || row <= b.top() ) return 0; // FIXME follow gap up { int c = col; col = b.seek_right( row, col ); if( col > c ) --col; row = b.seek_top( row, col ); } const int urow = b.seek_top( row - 1, col, false ); if( urow > b.vpos( 20 ) || 3 * tp[tp.pos(60)] > b.height() ) { const int m5 = tp.minima( b.height() / 5 ); if( m5 == 3 && segments_in_row( b.vcenter() ) == 2 && segments_in_row( b.vpos( 80 ) ) == 3 ) return 0; // merged 'IX' if( ( m5 == 2 || m5 == 3 ) && tp.minima() >= 2 && rp[rp.pos(25)] <= b.width() / 4 && ( !lp.istpit() || rp.minima() == 1 ) ) return 'w'; if( m5 == 1 && m8 == 1 && 4 * tp.max( tp.pos(40), tp.pos(60) ) < 3 * b.height() ) { if( rp.isctip( 66 ) ) return 'k'; else return 'h'; } return 0; } if( Ocrad::similar( b.height(), b.width(), 40 ) && row > b.vcenter() && urow < b.vcenter() && tp.minima( b.height() / 5 ) == 2 && bp.minima( urow + 1 ) == 3 ) return 'w'; if( urow <= b.vpos( 20 ) && tp.minima( b.height() / 4 ) == 1 && Ocrad::similar( b.height(), b.width(), 40 ) && ( 8 * ( rp[rp.pos(50)] - 1 ) <= b.width() || tp[tp.pos(99)] > b.height() / 2 ) ) return 'n'; return 0; } // Looks for four black sections in column hcenter() ± 1, then tests if // upper gap is open to the right and lower gaps are open to the left // int Features::test_s_cedilla() const { int urow2 = 0, urow3 = 0, urow4 = 0, col, black_section = 0; for( col = b.hcenter() - 1; col <= b.hcenter() + 1; ++col ) { bool prev_black = false; for( int row = b.top(); row <= b.bottom(); ++row ) { bool black = b.get_bit( row, col ); if( black && !prev_black ) { if( ++black_section == 2 ) urow2 = row - 1; else if( black_section == 3 ) urow3 = row - 1; else if( black_section == 4 ) urow4 = row - 1; } prev_black = black; } if( black_section == 4 && urow2 < b.vpos( 50 ) && urow4 >= b.vpos( 70 ) ) break; black_section = 0; } if( black_section == 4 && b.escape_right( urow2, col ) && b.escape_left( urow3, col ) && b.escape_left( urow4, col ) ) return UCS::SSCEDI; return 0; } bool Features::test_comma() const { if( b.holes() || b.height() <= b.width() || b.height() > 3 * b.width() ) return false; if( b.width() >= 3 && b.height() >= 3 ) { int upper_area = 0; for( int row = b.top(); row < b.top() + b.width(); ++row ) for( int col = b.left(); col <= b.right(); ++col ) if( b.get_bit( row, col ) ) ++upper_area; if( upper_area < (b.width() - 2) * (b.width() - 2) ) return false; int count1 = 0, count2 = 0; for( int col = b.left(); col <= b.right(); ++col ) { if( b.get_bit( b.top() + 1, col ) ) ++count1; if( b.get_bit( b.bottom() - 1, col ) ) ++count2; } if( count1 <= count2 ) return false; } return true; } int Features::test_easy( const Rectangle & charbox ) const { int code = test_solid( charbox ); if( code ) return code; if( b.top() >= charbox.vcenter() && test_comma() ) return ','; if( b.bottom() <= charbox.vcenter() && b.height() > b.width() && bp.minima() == 1 ) { if( tp.iminimum() < tp.pos( 50 ) && bp.iminimum() > bp.pos( 50 ) ) return '`'; else return '\''; } if( 2 * b.height() > 3 * wp.max() && b.top() >= charbox.vcenter() && bp.minima() == 1 ) return ','; return 0; } // Recognizes single line, non-rectangular characters without holes. // '/<>C[\^`c // int Features::test_line( const Rectangle & charbox ) const { const int vnoise = ( b.height() / 30 ) + 1; const int topmax = b.top() + vnoise; const int botmin = b.bottom() - vnoise; const bool vbar_left = ( vbars() == 1 && vbar(0).width() >= 2 && vbar(0).left() <= b.hpos( 10 ) + 1 ); if( tp.minima() == 1 && bp.minima() == 1 && rp.istip() ) { if( vbar_left && b.height() > 2 * b.width() && 2 * rp[rp.pos(50)] > b.width() ) { int row = b.seek_top( b.vcenter(), b.hcenter() ); int col = b.seek_right( row, b.hcenter() ); if( col < b.right() ) { row = b.seek_bottom( b.vcenter(), b.hcenter() ); col = b.seek_right( row, b.hcenter() ); if( col < b.right() ) return 'C'; } } if( hbars() == 2 && hbar(0).top() <= topmax && 4 * hbar(0).height() <= b.height() && hbar(1).bottom() >= botmin && 4 * hbar(1).height() <= b.height() ) { if( vbar_left && b.height() > 2 * b.width() ) return '['; if( vbar_left || lp.ispit() ) return 'c'; } } int slope1, slope2; if( tp.minima() != 1 ) return 0; if( lp.minima() == 1 && rp.minima() == 1 && 2 * b.height() >= b.width() && lp.straight( &slope1 ) && rp.straight( &slope2 ) ) { if( slope1 < 0 && slope2 < 0 && bp.minima() == 2 ) return '^'; if( bp.minima() != 1 ) return 0; if( slope1 < 0 && slope2 > 0 ) { if( b.v_includes( charbox.vcenter() ) ) { if( 10 * b.area() < 3 * b.size() ) return '/'; if( b.height() > 2 * b.width() ) return 'l'; return 0; } if( b.top() >= charbox.vcenter() ) return ','; return '\''; } if( slope1 > 0 && slope2 < 0 ) { if( b.bottom() > charbox.vcenter() ) { if( ( 3 * b.width() > b.height() && b.height() > charbox.height() ) || 2 * b.width() >= b.height() ) return '\\'; else return 0; } return '`'; } return 0; } if( bp.minima() == 1 && 2 * b.width() >= b.height() && tp.straight( &slope1 ) && bp.straight( &slope2 ) ) { if( lp.minima() == 1 && rp.minima() == 1 ) { if( slope1 < 0 && slope2 > 0 ) { if( b.v_includes( charbox.vcenter() ) ) return '/'; if( b.top() >= charbox.vcenter() ) return ','; return '\''; } if( slope1 > 0 && slope2 < 0 ) { if( b.bottom() > charbox.vcenter() ) return '\\'; return '`'; } } else if( 2 * b.width() >= b.height() ) { if( slope1 < 0 && slope2 < 0 && lp.minima() == 1 && rp.minima() == 2 ) return '<'; if( slope1 > 0 && slope2 > 0 && lp.minima() == 2 && rp.minima() == 1 ) return '>'; } } return 0; } int Features::test_solid( const Rectangle & charbox ) const { if( b.holes() ) return 0; if( b.height() >= 5 && b.width() >= 5 ) { if( 2 * b.height() > b.width() && ( tp.minima() != 1 || bp.minima() != 1 ) ) return 0; if( b.height() < 2 * b.width() && ( lp.minima() != 1 || rp.minima() != 1 ) ) return 0; } int inner_area, inner_size, porosity = 0; if( b.width() >= 3 && b.height() >= 3 ) { const int vnoise = ( b.height() / 100 ) + 1; inner_size = ( b.width() - 2 ) * ( b.height() - 2 ); inner_area = 0; for( int row = b.top() + vnoise; row <= b.bottom() - vnoise; ++row ) { int holes = 0; // FIXME for( int col = b.left() + 1; col < b.right(); ++col ) { if( b.get_bit( row, col ) ) ++inner_area; else ++holes; } if( 5 * holes >= b.width() ) porosity += ( 5 * holes ) / b.width(); } if( inner_area * 100 < inner_size * 70 ) return 0; } else { inner_size = 0; inner_area = b.area(); } if( Ocrad::similar( b.height(), wp.max(), 20, 2 ) ) { const int n = std::min( b.height(), b.width() ); if( n >= 6 ) { int d = 0; for( int i = 0; i < n; ++i ) { if( b.get_bit( b.top() + i, b.left() + i ) ) ++d; if( b.get_bit( b.top() + i, b.right() - i ) ) --d; } if( 2 * std::abs( d ) >= n - 1 ) return 0; } if( ( !porosity && inner_area * 100 >= inner_size * 75 ) || ( b.width() >= 7 && b.height() >= 7 && ( 100 * b.area_octagon() >= 95 * b.size_octagon() || 100 * b.area_octagon() >= 95 * b.area() ) ) ) return '.'; return 0; } if( porosity > 1 || inner_area * 100 < inner_size * 85 || ( porosity && inner_area * 100 < inner_size * 95 ) ) return 0; if( b.width() > b.height() ) { if( b.top() > charbox.vpos( 90 ) || ( charbox.bottom() - b.bottom() < b.top() - charbox.vcenter() && b.width() >= 5 * b.height() ) ) return '_'; return '-'; } if( b.height() > b.width() ) { if( b.top() > charbox.vcenter() ) return ','; if( b.bottom() <= charbox.vcenter() ) return '\''; return '|'; } return 0; } ocrad-0.24/feats_test1.cc000066400000000000000000000212671241541103500152640ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "profile.h" #include "feats.h" // Tests if the lower half of character is open to the left, to the right, // and/or to the bottom // int Features::test_49ARegpq( const Rectangle & charbox ) const { const Bitmap & h = b.hole( 0 ); if( bp.minima( b.height() / 10 + 1 ) == 2 && bp.isctip() && tp.minima() == 1 ) { if( tp.isvpit() || rp.decreasing() || ( rp.decreasing( 1, rp.pos( 20 ) ) && lp.decreasing( 1, lp.pos( 20 ) ) ) ) return 'A'; if( hbars() == 2 && hbar(1).width() >= b.width() ) { const int i = hbar(1).top() - b.top(); const int j = hbar(1).bottom() - b.top(); if( rp.area( i, j ) <= lp.area( i, j ) ) return 'A'; } return 'R'; } int col = h.hcenter(); int row = b.seek_bottom( h.bottom(), col, false ) + 1; if( row >= b.vpos( 90 ) ) { col = h.left(); row = b.seek_bottom( h.bottom(), col, false ) + 1; } if( row >= b.bottom() ) return 0; if( b.escape_right( row, col ) ) { if( ( lp.ispit() && b.seek_bottom( row, h.right() ) < b.bottom() ) || ( lp.isconvex() && b.seek_bottom( row, h.hcenter() ) < b.bottom() ) ) return 'e'; if( bp.ispit() ) { int row2 = b.seek_bottom( row, h.right() ); if( row2 < b.vpos( 75 ) ) return 'g'; if( row2 < b.bottom() ) return 'e'; } return 'p'; } else if( b.escape_left( row, col ) ) { Profile hlp( h, Profile::left ); Profile htp( h, Profile::top ); Profile hwp( h, Profile::width ); if( vbars() == 1 && vbar(0).hcenter() > b.hcenter() && hlp.decreasing() && htp.decreasing() && hwp[hwp.pos(30)] < hwp[hwp.pos(70)] ) return '4'; if( rp.ispit() && rp.minima() == 1 && rp.iminimum() < rp.pos( 70 ) && tp.ispit() && charbox.bottom() > b.vpos( rp.isconvex() ? 80 : 90 ) ) return '9'; int hdiff; if( b.bottom_hook( &hdiff ) && hdiff > 0 ) { if( h.bottom() < b.vcenter() && h.right() + 2 <= b.right() && ( !b.get_bit( h.bottom() + 1, h.right() + 1 ) || !b.get_bit( h.bottom() + 1, h.right() + 2 ) || rp.isctip() ) ) return 's'; else return 'g'; } if( row > b.vpos( 85 ) && tp.ispit() ) return 'Q'; int row2 = b.seek_bottom( row, col ); if( row2 < b.bottom() && rp.increasing( ( ( row + ( 2 * row2 ) ) / 3 ) - b.top() ) ) return 'g'; if( bp.minima() == 1 ) { if( h.height() >= charbox.height() ) return 'Q'; if( h.right() < b.hcenter() && h.bottom() < b.vcenter() ) return '2'; return 'q'; } } return 0; } int Features::test_4ADQao( const Charset & charset, const Rectangle & charbox ) const { const Bitmap & h = b.hole( 0 ); int left_delta = h.left() - b.left(), right_delta = b.right() - h.right(); if( !lp.ispit() && lp.isflats() && rp.ispit() ) return 'D'; if( !rp.isconvex() ) { if( Ocrad::similar( left_delta, right_delta, 40 ) && tp.minima() == 2 && bp.minima() == 2 ) return '#'; if( tp.minima() == 1 && bp.minima() == 1 ) { int row = b.seek_bottom( h.bottom(), h.hcenter(), false ); if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( !lp.isconvex() && bp.isconvex() && b.seek_bottom( row, h.hcenter() ) < b.bottom() ) return UCS::SEACUTE; row = ( row + b.seek_bottom( row, h.hcenter() ) ) / 2; if( row < b.bottom() - 1 && !lp.isflats() && b.seek_left( row, h.hcenter() ) <= b.left() ) { if( wp[h.top()-b.top()] < wp[h.bottom()-b.top()] ) return '4'; return 'Q'; } } if( 2 * b.width() > 5 * h.width() ) { const int c = segments_in_row( h.vcenter() ); const int m = bp.minima(); if( c == 3 && h.top() < b.vcenter() && h.bottom() > b.vcenter() && 3 * h.height() >= b.height() && ( m == 3 || m == 2 ) && !lp.ispit() ) return 'm'; if( c == 3 && left_delta > right_delta && lp.ispit() && segments_in_col( h.hcenter() ) == 4 ) return '@'; if( c == 4 && Ocrad::similar( left_delta, right_delta, 40 ) && lp.ispit() ) return '@'; } } if( tp.minima() == 1 && bp.istip() && !rp.isctip( 66 ) ) return 'A'; if( Ocrad::similar( left_delta, right_delta, 50 ) ) { if( bp.minima() == 1 && rp.isconvex() && b.test_BD() ) return 'D'; if( bp.minima() > 1 || rp.minima() > 1 || b.test_Q() ) { if( 4 * h.size() >= b.size() || tp.ispit() || lp.ispit() ) return 'Q'; else return 0; } if( 3 * bp[bp.pos(100)] < b.height() && 5 * rp[rp.pos(55)] >= b.width() ) return 'a'; if( lp.istip() ) return 'n'; if( b.vpos( 80 ) < charbox.vcenter() ) return UCS::DEG; return 'o'; } if( left_delta > right_delta && rp.ispit() && tp.minima() == 1 && bp.minima() == 1 ) return 'D'; if( Ocrad::similar( left_delta, right_delta, 50 ) && ( bp.minima() > 1 || rp.minima() > 1 ) ) return 'a'; return 0; } // Tests if the upper half of character is open to the left, to the right, // and/or to the bottom // int Features::test_6abd( const Charset & charset ) const { const Bitmap & h = b.hole( 0 ); if( 3 * h.width() < b.width() && ( bp.minima( b.height() / 4 ) != 1 || tp.minima( h.vcenter() - b.top() ) != 1 ) ) return 0; int col = h.hcenter(); int row = b.seek_top( h.top(), col, false ) - 1; if( row <= b.top() ) { col = h.right(); if( b.right() - h.right() > h.width() ) ++col; row = b.seek_top( h.top(), col, false ) - 1; } if( row <= b.top() ) return 0; const int rcol = ( b.right() + h.right() ) / 2; const int urow = h.top() - ( b.bottom() - h.bottom() ); const bool oacute1 = ( ( b.seek_right( urow - 1, h.right() ) >= b.right() ) || ( b.seek_right( row, col ) >= b.right() ) ); if( b.escape_right( row, col ) ) { const int noise = ( b.width() / 30 ) + 1; const int c = lp[urow-b.top()]; const bool oacute2 = ( c > lp[h.top()-b.top()] + noise && urow <= b.top() + tp[std::min( c - 1, b.width() / 4 )] ); if( ( oacute1 && oacute2 ) && ( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) ) { const bool oacute3 = ( b.right() - rp[rp.pos(5)] >= h.right() || b.left() + lp[h.top()-b.top()] <= b.hpos( 5 ) ); if( oacute3 ) return UCS::SOACUTE; } if( !oacute2 && lp.ispit() && bp.ispit() ) { int row2 = b.seek_top( h.top(), h.right() + 1, false ) - 1; row2 = b.seek_top( row2, h.right() + 1 ); if( row2 > b.top() ) return '6'; } int row2 = b.seek_top( h.top(), rcol, false ) - 1; row2 = b.seek_top( row2, rcol ); if( row2 <= b.top() ) return 'b'; const int m = tp.minima( b.height() / 2 ); if( m == 1 && bp.minima() == 1 ) return 's'; if( m == 2 ) return 'k'; else return 0; } if( b.escape_left( row, col ) ) { const int col2 = std::max( h.left(), h.hpos( 10 ) ); int row2 = b.seek_top( h.top(), col2, false ) - 1; row2 = b.seek_top( row2, col2 ); if( row2 > b.top() ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) { int row3 = b.seek_top( row, col ); if( row > b.vcenter() && row3 > b.vpos( 20 ) ) return UCS::SAACUTE; if( oacute1 ) return UCS::SOGRAVE; } return 'a'; } if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( oacute1 ) return UCS::SOACUTE; return 'd'; } if( b.width() > 3 * h.width() && h.top() < b.vcenter() && segments_in_row( b.vcenter() ) == 3 && !lp.isconvex() ) return 'm'; int hdiff; if( b.top_hook( &hdiff ) && hdiff > 0 ) return 's'; return 0; } ocrad-0.24/histogram.h000066400000000000000000000032051241541103500146710ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Histogram { unsigned samples_; std::vector< int > distrib; public: Histogram() : samples_( 0 ) {} int samples() const { return samples_; } int size() const { return distrib.size(); } bool empty() const { return distrib.empty(); } int operator[]( const int i ) const { return distrib[i]; } void reset() { samples_ = 0; distrib.clear(); } void add_sample( const unsigned sample ) { if( sample < INT_MAX && samples_ < INT_MAX ) { if( sample >= distrib.size() ) distrib.resize( sample + 1 ); ++distrib[sample]; ++samples_; } } int median() const { unsigned l = 0, cum = 0; while( l < distrib.size() ) { cum += distrib[l]; if( 2 * cum >= samples_ ) break; else ++l; } unsigned r = l; while( true ) { if( 2 * cum > samples_ || r >= distrib.size() ) break; cum += distrib[r]; ++r; } return ( l + r ) / 2; } }; ocrad-0.24/main.cc000066400000000000000000000353621241541103500137670ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (eg, bug) which caused ocrad to panic. */ #include #include #include #include #include #include #include #include #if defined(__MSVCRT__) || defined(__OS2__) || defined(_MSC_VER) #include #include #include #endif #include "arg_parser.h" #include "common.h" #include "rational.h" #include "rectangle.h" #include "page_image.h" #include "textpage.h" namespace { const char * const Program_name = "GNU Ocrad"; const char * const program_name = "ocrad"; const char * const program_year = "2014"; const char * invocation_name = 0; struct Input_control { Transformation transformation; int scale; Rational threshold, ltwh[4]; bool copy, cut, invert, layout; Input_control() : scale( 0 ), threshold( -1 ), copy( false ), cut( false ), invert( false ), layout( false ) {} bool parse_cut_rectangle( const char * const s ); bool parse_threshold( const char * const s ); }; void show_error( const char * const msg, const int errcode = 0, const bool help = false ) { if( verbosity >= 0 ) { if( msg && msg[0] ) { std::fprintf( stderr, "%s: %s", program_name, msg ); if( errcode > 0 ) std::fprintf( stderr, ": %s.", std::strerror( errcode ) ); std::fputs( "\n", stderr ); } if( help ) std::fprintf( stderr, "Try '%s --help' for more information.\n", invocation_name ); } } bool Input_control::parse_cut_rectangle( const char * const s ) { int c = ltwh[0].parse( s ); // left if( c && s[c] == ',' && ltwh[0] >= -1 ) { int i = c + 1; c = ltwh[1].parse( &s[i] ); // top if( c && s[i+c] == ',' && ltwh[1] >= -1 ) { i += c + 1; c = ltwh[2].parse( &s[i] ); // width if( c && s[i+c] == ',' && ltwh[2] > 0 ) { i += c + 1; c = ltwh[3].parse( &s[i] ); // height if( c && ltwh[3] > 0 ) { cut = true; return true; } } } } show_error( "invalid cut rectangle.", 0, true ); return false; } bool Input_control::parse_threshold( const char * const s ) { Rational tmp; if( tmp.parse( s ) && tmp >= 0 && tmp <= 1 ) { threshold = tmp; return true; } show_error( "threshold out of limits (0.0 - 1.0).", 0, true ); return false; } void show_help() { std::printf( "GNU Ocrad is an OCR (Optical Character Recognition) program based on a\n" "feature extraction method. It reads images in pbm (bitmap), pgm\n" "(greyscale) or ppm (color) formats and produces text in byte (8-bit) or\n" "UTF-8 formats. The pbm, pgm and ppm formats are collectively known as pnm.\n" "\nOcrad includes a layout analyser able to separate the columns or blocks\n" "of text normally found on printed pages.\n" "\nUsage: %s [options] [files]\n", invocation_name ); std::printf( "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" " -a, --append append text to output file\n" " -c, --charset= try '--charset=help' for a list of names\n" " -e, --filter= try '--filter=help' for a list of names\n" " -f, --force force overwrite of output file\n" " -F, --format= output format (byte, utf8)\n" " -i, --invert invert image levels (white on black)\n" " -l, --layout perform layout analysis\n" " -o, --output= place the output into \n" " -q, --quiet suppress all messages\n" " -s, --scale=[-] scale input image by [1/]\n" " -t, --transform= try '--transform=help' for a list of names\n" " -T, --threshold= threshold for binarization (0-100%%)\n" " -u, --cut= cut input image by given rectangle\n" " -v, --verbose be verbose\n" " -x, --export= export results in ORF format to \n" ); if( verbosity >= 1 ) { std::printf( " -1..6 pnm output file type (debug)\n" " -C, --copy 'copy' input to output (debug)\n" " -D, --debug= (0-100) output intermediate data (debug)\n" ); } std::printf( "If no files are specified, ocrad reads the image from standard input.\n" "If the -o option is not specified, ocrad sends text to standard output.\n" "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n" "not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n" "invalid input file, 3 for an internal consistency error (eg, bug) which\n" "caused ocrad to panic.\n" "\nReport bugs to bug-ocrad@gnu.org\n" "Ocrad home page: http://www.gnu.org/software/ocrad/ocrad.html\n" "General help using GNU software: http://www.gnu.org/gethelp\n" ); } void show_version() { std::printf( "GNU %s %s\n", program_name, PROGVERSION ); std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); std::printf( "License GPLv2+: GNU GPL version 2 or later \n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" ); } const char * my_basename( const char * filename ) { const char * c = filename; while( *c ) { if( *c == '/' ) { filename = c + 1; } ++c; } return filename; } int process_file( FILE * const infile, const char * const infile_name, const Input_control & input_control, const Control & control ) { if( verbosity >= 1 ) std::fprintf( stderr, "processing file '%s'\n", infile_name ); try { Page_image page_image( infile, input_control.invert ); if( input_control.cut ) { if( page_image.cut( input_control.ltwh ) ) { if( verbosity >= 1 ) std::fprintf( stderr, "file cut to %dw x %dh\n", page_image.width(), page_image.height() ); } else { if( verbosity >= 1 ) std::fprintf( stderr, "file '%s' totally cut away\n", infile_name ); return 1; } } page_image.transform( input_control.transformation ); page_image.change_scale( input_control.scale ); page_image.threshold( input_control.threshold ); if( verbosity >= 1 ) { const Rational th( page_image.threshold(), page_image.maxval() ); std::fprintf( stderr, "maxval = %d, threshold = %d (%s)\n", page_image.maxval(), page_image.threshold(), th.to_decimal( 1, -3 ).c_str() ); } if( input_control.copy ) { if( control.outfile ) page_image.save( control.outfile, control.filetype ); return 0; } Textpage textpage( page_image, my_basename( infile_name ), control, input_control.layout ); if( control.debug_level == 0 ) { if( control.outfile ) textpage.print( control ); if( control.exportfile ) textpage.xprint( control ); } } catch( Page_image::Error e ) { show_error( e.msg ); return 2; } if( verbosity >= 1 ) std::fputs( "\n", stderr ); return 0; } } // end namespace // 'infile' contains the scanned image (in pnm format) to be converted // to text. // 'outfile' is the destination for the text version of the scanned // image. (or for a pnm file if debugging). // 'exportfile' is the Ocr Results File. // int main( const int argc, const char * const argv[] ) { Input_control input_control; Control control; const char * outfile_name = 0, * exportfile_name = 0; bool append = false, force = false; invocation_name = argv[0]; const Arg_parser::Option options[] = { { '1', 0, Arg_parser::no }, { '2', 0, Arg_parser::no }, { '3', 0, Arg_parser::no }, { '4', 0, Arg_parser::no }, { '5', 0, Arg_parser::no }, { '6', 0, Arg_parser::no }, { 'a', "append", Arg_parser::no }, { 'c', "charset", Arg_parser::yes }, { 'C', "copy", Arg_parser::no }, { 'D', "debug", Arg_parser::yes }, { 'e', "filter", Arg_parser::yes }, { 'f', "force", Arg_parser::no }, { 'F', "format", Arg_parser::yes }, { 'h', "help", Arg_parser::no }, { 'i', "invert", Arg_parser::no }, { 'l', "layout", Arg_parser::no }, { 'o', "output", Arg_parser::yes }, { 'q', "quiet", Arg_parser::no }, { 's', "scale", Arg_parser::yes }, { 't', "transform", Arg_parser::yes }, { 'T', "threshold", Arg_parser::yes }, { 'u', "cut", Arg_parser::yes }, { 'v', "verbose", Arg_parser::no }, { 'V', "version", Arg_parser::no }, { 'x', "export", Arg_parser::yes }, { 0 , 0, Arg_parser::no } }; const Arg_parser parser( argc, argv, options ); if( parser.error().size() ) // bad option { show_error( parser.error().c_str(), 0, true ); return 1; } int argind = 0; for( ; argind < parser.arguments(); ++argind ) { const int code = parser.code( argind ); if( !code ) break; // no more options const char * const arg = parser.argument( argind ).c_str(); switch( code ) { case '1': case '2': case '3': case '4': case '5': case '6': control.filetype = code; break; case 'a': append = true; break; case 'c': if( !control.charset.enable( arg ) ) { control.charset.show_error( program_name, arg ); return 1; } break; case 'C': input_control.copy = true; break; case 'D': control.debug_level = std::strtol( arg, 0, 0 ); break; case 'e': if( !control.add_filter( program_name, arg ) ) return 1; break; case 'f': force = true; break; case 'F': if( !control.set_format( arg ) ) { show_error( "bad output format.", 0, true ); return 1; } break; case 'h': show_help(); return 0; case 'i': input_control.invert = true; break; case 'l': input_control.layout = true; break; case 'o': outfile_name = arg; break; case 'q': verbosity = -1; break; case 's': input_control.scale = std::strtol( arg, 0, 0 ); break; case 't': if( !input_control.transformation.set( arg ) ) { input_control.transformation.show_error( program_name, arg ); return 1; } break; case 'T': if( !input_control.parse_threshold( arg ) ) return 1; break; case 'u': if( !input_control.parse_cut_rectangle( arg ) ) return 1; break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; case 'x': exportfile_name = arg; break; default : Ocrad::internal_error( "uncaught option." ); } } // end process options #if defined(__MSVCRT__) || defined(__OS2__) || defined(_MSC_VER) setmode( fileno( stdin ), O_BINARY ); setmode( fileno( stdout ), O_BINARY ); #endif if( outfile_name && std::strcmp( outfile_name, "-" ) != 0 ) { if( append ) control.outfile = std::fopen( outfile_name, "a" ); else if( force ) control.outfile = std::fopen( outfile_name, "w" ); else if( ( control.outfile = std::fopen( outfile_name, "wx" ) ) == 0 ) { if( verbosity >= 0 ) std::fprintf( stderr, "Output file %s already exists.\n", outfile_name ); return 1; } if( !control.outfile ) { if( verbosity >= 0 ) std::fprintf( stderr, "Cannot open %s\n", outfile_name ); return 1; } } if( exportfile_name && control.debug_level == 0 && !input_control.copy ) { if( std::strcmp( exportfile_name, "-" ) == 0 ) { control.exportfile = stdout; if( !outfile_name ) control.outfile = 0; } else { control.exportfile = std::fopen( exportfile_name, "w" ); if( !control.exportfile ) { if( verbosity >= 0 ) std::fprintf( stderr, "Cannot open %s\n", exportfile_name ); return 1; } } std::fprintf( control.exportfile, "# Ocr Results File. Created by %s version %s\n", Program_name, PROGVERSION ); } // process any remaining command line arguments (input files) FILE * infile = (argind < parser.arguments()) ? 0 : stdin; const char *infile_name = "-"; int retval = 0; while( true ) { while( infile != stdin ) { if( infile ) std::fclose( infile ); if( argind >= parser.arguments() ) { infile = 0; break; } infile_name = parser.argument( argind++ ).c_str(); if( std::strcmp( infile_name, "-" ) == 0 ) infile = stdin; else infile = std::fopen( infile_name, "rb" ); if( infile ) break; if( verbosity >= 0 ) std::fprintf( stderr, "Cannot open %s\n", infile_name ); if( retval == 0 ) retval = 1; } if( !infile ) break; int tmp = process_file( infile, infile_name, input_control, control ); if( infile == stdin ) { if( tmp <= 1 ) { int ch; do ch = std::fgetc( infile ); while( std::isspace( ch ) ); std::ungetc( ch, infile ); } if( tmp > 1 || std::feof( infile ) || std::ferror( infile ) ) infile = 0; } if( tmp > retval ) retval = tmp; if( control.outfile ) std::fflush( control.outfile ); if( control.exportfile ) std::fflush( control.exportfile ); } if( control.outfile ) std::fclose( control.outfile ); if( control.exportfile ) std::fclose( control.exportfile ); return retval; } ocrad-0.24/mask.cc000066400000000000000000000075461241541103500140010ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "rectangle.h" #include "segment.h" #include "mask.h" int Mask::left( const int row ) const { if( top() <= row && row <= bottom() && data[row-top()].valid() ) return data[row-top()].left; return -1; } int Mask::right( const int row ) const { if( top() <= row && row <= bottom() && data[row-top()].valid() ) return data[row-top()].right; return -1; } void Mask::top( const int t ) { if( t == top() ) return; if( t < top() ) data.insert( data.begin(), top() - t, Csegment() ); else data.erase( data.begin(), data.begin() + ( t - top() ) ); Rectangle::top( t ); } void Mask::bottom( const int b ) { if( b != bottom() ) { Rectangle::bottom( b ); data.resize( height() ); } } void Mask::add_mask( const Mask & m ) { if( m.top() < top() ) top( m.top() ); if( m.bottom() > bottom() ) bottom( m.bottom() ); for( int i = m.top(); i <= m.bottom(); ++i ) { Csegment & seg = data[i-top()]; seg.add_csegment( m.data[i-m.top()] ); if( seg.left < left() ) left( seg.left ); if( seg.right > right() ) right( seg.right ); } } void Mask::add_point( const int row, const int col ) { if( row < top() ) top( row ); else if( row > bottom() ) bottom( row ); data[row-top()].add_point( col ); if( col < left() ) left( col ); else if( col > right() ) right( col ); } void Mask::add_rectangle( const Rectangle & re ) { if( re.top() < top() ) top( re.top() ); if( re.bottom() > bottom() ) bottom( re.bottom() ); const Csegment rseg( re.left(), re.right() ); for( int i = re.top(); i <= re.bottom(); ++i ) { Csegment & seg = data[i-top()]; seg.add_csegment( rseg ); if( seg.left < left() ) left( seg.left ); if( seg.right > right() ) right( seg.right ); } } bool Mask::includes( const Rectangle & re ) const { if( re.top() < top() || re.bottom() > bottom() ) return false; const Csegment seg( re.left(), re.right() ); for( int i = re.top(); i <= re.bottom(); ++i ) if( !data[i-top()].includes( seg ) ) return false; return true; } bool Mask::includes( const int row, const int col ) const { return ( row >= top() && row <= bottom() && data[row-top()].includes( col ) ); } int Mask::distance( const Rectangle & re ) const { const Csegment seg( re.left(), re.right() ); int mindist = INT_MAX; for( int i = bottom(); i >= top(); --i ) { const int vd = re.v_distance( i ); if( vd >= mindist ) { if( i < re.top() ) break; else continue; } const int hd = data[i-top()].distance( seg ); if( hd >= mindist ) continue; const int d = Rectangle::hypoti( hd, vd ); if( d < mindist ) mindist = d; } return mindist; } int Mask::distance( const int row, const int col ) const { int mindist = INT_MAX; for( int i = bottom(); i >= top(); --i ) { const int vd = std::abs( i - row ); if( vd >= mindist ) { if( i < row ) break; else continue; } const int hd = data[i-top()].distance( col ); if( hd >= mindist ) continue; const int d = Rectangle::hypoti( hd, vd ); if( d < mindist ) mindist = d; } return mindist; } ocrad-0.24/mask.h000066400000000000000000000034171241541103500136340ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Mask : public Rectangle { std::vector< Csegment > data; // csegment in each line public: // Creates a rectangular Mask explicit Mask( const Rectangle & re ) : Rectangle( re ), data( height(), Csegment( re.left(), re.right() ) ) {} Mask( const int l, const int t, const int r, const int b ) : Rectangle( l, t, r, b ), data( height(), Csegment( l, r ) ) {} using Rectangle::left; using Rectangle::top; using Rectangle::right; using Rectangle::bottom; using Rectangle::height; int left ( const int row ) const; int right( const int row ) const; void top ( const int t ); void bottom( const int b ); void height( const int h ) { bottom( top() + h - 1 ); } void add_mask( const Mask & m ); void add_point( const int row, const int col ); void add_rectangle( const Rectangle & re ); bool includes( const Rectangle & re ) const; bool includes( const int row, const int col ) const; int distance( const Rectangle & re ) const; int distance( const int row, const int col ) const; }; ocrad-0.24/ocrad.png000066400000000000000000000003331241541103500143200ustar00rootroot00000000000000‰PNG  IHDR0¼ý~Ü¢IDATX…í–Ý€ …¥õþ¯L7±~DE›‹oë";ëˆÇ°”‚àßÀó‘‘@>f­Ós{gÆ™Zëx0%<'XÕnZ¹àºìa £–¡TçK!uØm^ó] ‘À(ÔmzŸSwšPÀ»Ún·É ä“/õùÁsÀßܨDÙÉ[ÓèX¾ 1Ð*µVKª#|R($ 5±ß½8þN“F:†Öë2.¼BgIEND®B`‚ocrad-0.24/ocradcheck.cc000066400000000000000000000066501241541103500151270ustar00rootroot00000000000000/* Ocrcheck - A test program for the ocradlib library Copyright (C) 2009-2014 Antonio Diaz Diaz. This program is free software: you have unlimited permission to copy, distribute and modify it. Usage is: ocradcheck filename.pnm or ocradcheck filename.pnm --utf8 This program reads the specified image file, feeds it to the OCR engine and sends the resulting text to stdout. */ #include #include #include #include #include #include "ocradlib.h" int main( const int argc, const char * const argv[] ) { const bool utf8 = ( argc > 2 ); if( argc < 2 ) { std::fprintf( stderr, "Usage: ocradcheck filename.pnm\n" ); return 1; } if( OCRAD_version()[0] != OCRAD_version_string[0] ) { std::fprintf( stderr, "bad library version" ); return 3; } if( std::strcmp( PROGVERSION, OCRAD_version_string ) != 0 ) { std::fprintf( stderr, "bad library version_string" ); return 3; } OCRAD_Descriptor * const ocrdes = OCRAD_open(); if( !ocrdes || OCRAD_get_errno( ocrdes ) != OCRAD_ok ) { OCRAD_close( ocrdes ); std::fprintf( stderr, "not enough memory.\n" ); return 1; } if( OCRAD_set_image_from_file( ocrdes, argv[1], false ) < 0 ) { const OCRAD_Errno ocr_errno = OCRAD_get_errno( ocrdes ); OCRAD_close( ocrdes ); if( ocr_errno == OCRAD_mem_error ) std::fprintf( stderr, "not enough memory.\n" ); else std::fprintf( stderr, "Can't open file '%s' for reading\n", argv[1] ); return 1; } // std::fprintf( stderr, "ocradcheck: testing file '%s'\n", argv[1] ); if( ( utf8 && OCRAD_set_utf8_format( ocrdes, true ) < 0 ) || OCRAD_set_threshold( ocrdes, -1 ) < 0 || // auto threshold OCRAD_recognize( ocrdes, false ) < 0 ) // no layout { const OCRAD_Errno ocr_errno = OCRAD_get_errno( ocrdes ); OCRAD_close( ocrdes ); if( ocr_errno == OCRAD_mem_error ) { std::fprintf( stderr, "not enough memory.\n" ); return 1; } std::fprintf( stderr, "internal error: invalid argument.\n" ); return 3; } const int blocks = OCRAD_result_blocks( ocrdes ); int chars_total_by_block = 0; int chars_total_by_line = 0; int chars_total_by_count = 0; for( int b = 0; b < blocks; ++b ) { const int lines = OCRAD_result_lines( ocrdes, b ); chars_total_by_block += OCRAD_result_chars_block( ocrdes, b ); for( int l = 0; l < lines; ++l ) { const char * const s = OCRAD_result_line( ocrdes, b, l ); chars_total_by_line += OCRAD_result_chars_line( ocrdes, b, l ); if( s && s[0] ) { std::printf( "%s", s ); const int len = std::strlen( s ) - 1; if( !utf8 ) chars_total_by_count += len; else for( int i = 0; i < len; ++i ) if( (uint8_t)s[i] < 128 || (uint8_t)s[i] >= 0xC0 ) ++chars_total_by_count; } } std::fputs( "\n", stdout ); } const int chars_total = OCRAD_result_chars_total( ocrdes ); if( chars_total_by_block != chars_total || chars_total_by_line != chars_total || chars_total_by_count != chars_total ) { std::fprintf( stderr, "library_error: character counts differ.\n" "%d %d %d %d\n", chars_total, chars_total_by_block, chars_total_by_line, chars_total_by_count ); return 1; } OCRAD_close( ocrdes ); return 0; } ocrad-0.24/ocradlib.cc000066400000000000000000000205411241541103500146130ustar00rootroot00000000000000/* Ocradlib - Optical Character Recognition library Copyright (C) 2009-2014 Antonio Diaz Diaz. This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include "ocradlib.h" #include "common.h" #include "rectangle.h" #include "ucs.h" #include "track.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" #include "textblock.h" #include "textpage.h" struct OCRAD_Descriptor { Page_image * page_image; Textpage * textpage; OCRAD_Errno ocr_errno; Control control; std::string text; OCRAD_Descriptor() : page_image( 0 ), textpage( 0 ), ocr_errno( OCRAD_ok ) { control.outfile = 0; } }; bool verify_descriptor( OCRAD_Descriptor * const ocrdes, const bool result = false ) { if( !ocrdes ) return false; if( !ocrdes->page_image || ( result && !ocrdes->textpage ) ) { ocrdes->ocr_errno = OCRAD_sequence_error; return false; } return true; } const char * OCRAD_version() { return OCRAD_version_string; } OCRAD_Descriptor * OCRAD_open() { verbosity = -1; // keep library silent OCRAD_Descriptor * const ocrdes = new( std::nothrow ) OCRAD_Descriptor; if( !ocrdes ) return 0; return ocrdes; } int OCRAD_close( OCRAD_Descriptor * const ocrdes ) { if( !ocrdes ) return -1; if( ocrdes->textpage ) delete ocrdes->textpage; if( ocrdes->page_image ) delete ocrdes->page_image; delete ocrdes; return 0; } OCRAD_Errno OCRAD_get_errno( OCRAD_Descriptor * const ocrdes ) { if( !ocrdes ) return OCRAD_bad_argument; return ocrdes->ocr_errno; } int OCRAD_set_image( OCRAD_Descriptor * const ocrdes, const OCRAD_Pixmap * const image, const bool invert ) { if( !ocrdes ) return -1; if( !image || image->height < 3 || image->width < 3 || INT_MAX / image->width < image->height || ( image->mode != OCRAD_bitmap && image->mode != OCRAD_greymap && image->mode != OCRAD_colormap ) ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } try { Page_image * const page_image = new Page_image( *image, invert ); if( ocrdes->textpage ) { delete ocrdes->textpage; ocrdes->textpage = 0; } if( ocrdes->page_image ) delete ocrdes->page_image; ocrdes->page_image = page_image; } catch( std::bad_alloc ) { ocrdes->ocr_errno = OCRAD_mem_error; return -1; } return 0; } int OCRAD_set_image_from_file( OCRAD_Descriptor * const ocrdes, const char * const filename, const bool invert ) { if( !ocrdes ) return -1; FILE * infile = 0; if( filename && filename[0] ) { if( std::strcmp( filename, "-" ) == 0 ) infile = stdin; else infile = std::fopen( filename, "rb" ); } if( !infile ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } int retval = 0; try { Page_image * const page_image = new Page_image( infile, invert ); if( ocrdes->textpage ) { delete ocrdes->textpage; ocrdes->textpage = 0; } if( ocrdes->page_image ) delete ocrdes->page_image; ocrdes->page_image = page_image; } catch( std::bad_alloc ) { ocrdes->ocr_errno = OCRAD_mem_error; retval = -1; } catch( ... ) { ocrdes->ocr_errno = OCRAD_bad_argument; retval = -1; } std::fclose( infile ); return retval; } int OCRAD_set_utf8_format( OCRAD_Descriptor * const ocrdes, const bool utf8 ) { if( !verify_descriptor( ocrdes ) ) return -1; ocrdes->control.utf8 = utf8; return 0; } int OCRAD_set_threshold( OCRAD_Descriptor * const ocrdes, const int threshold ) { if( !verify_descriptor( ocrdes ) ) return -1; if( threshold < -1 || threshold > 255 ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } ocrdes->page_image->threshold( threshold ); return 0; } int OCRAD_scale( OCRAD_Descriptor * const ocrdes, const int value ) { if( !verify_descriptor( ocrdes ) ) return -1; int retval = 0; try { if( !ocrdes->page_image->change_scale( value ) ) retval = -1; } catch( ... ) { retval = -1; } if( retval < 0 ) ocrdes->ocr_errno = OCRAD_bad_argument; return retval; } int OCRAD_recognize( OCRAD_Descriptor * const ocrdes, const bool layout ) { if( !verify_descriptor( ocrdes ) ) return -1; Textpage * const textpage = new( std::nothrow ) Textpage( *ocrdes->page_image, "", ocrdes->control, layout ); if( !textpage ) { ocrdes->ocr_errno = OCRAD_mem_error; return -1; } if( ocrdes->textpage ) delete ocrdes->textpage; ocrdes->textpage = textpage; return 0; } int OCRAD_result_blocks( OCRAD_Descriptor * const ocrdes ) { if( !verify_descriptor( ocrdes, true ) ) return -1; return ocrdes->textpage->textblocks(); } int OCRAD_result_lines( OCRAD_Descriptor * const ocrdes, const int blocknum ) { if( !verify_descriptor( ocrdes, true ) ) return -1; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } return ocrdes->textpage->textblock( blocknum ).textlines(); } int OCRAD_result_chars_total( OCRAD_Descriptor * const ocrdes ) { if( !verify_descriptor( ocrdes, true ) ) return -1; int c = 0; for( int b = 0; b < ocrdes->textpage->textblocks(); ++b ) for( int i = 0; i < ocrdes->textpage->textblock( b ).textlines(); ++i ) c += ocrdes->textpage->textblock( b ).textline( i ).characters(); return c; } int OCRAD_result_chars_block( OCRAD_Descriptor * const ocrdes, const int blocknum ) { if( !verify_descriptor( ocrdes, true ) ) return -1; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } int c = 0; for( int i = 0; i < ocrdes->textpage->textblock( blocknum ).textlines(); ++i ) c += ocrdes->textpage->textblock( blocknum ).textline( i ).characters(); return c; } int OCRAD_result_chars_line( OCRAD_Descriptor * const ocrdes, const int blocknum, const int linenum ) { if( !verify_descriptor( ocrdes, true ) ) return -1; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() || linenum < 0 || linenum >= ocrdes->textpage->textblock( blocknum ).textlines() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } return ocrdes->textpage->textblock( blocknum ).textline( linenum ).characters(); } const char * OCRAD_result_line( OCRAD_Descriptor * const ocrdes, const int blocknum, const int linenum ) { if( !verify_descriptor( ocrdes, true ) ) return 0; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() || linenum < 0 || linenum >= ocrdes->textpage->textblock( blocknum ).textlines() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return 0; } const Textline & textline = ocrdes->textpage->textblock( blocknum ).textline( linenum ); ocrdes->text.clear(); if( !ocrdes->control.utf8 ) for( int i = 0; i < textline.characters(); ++i ) ocrdes->text += textline.character( i ).byte_result(); else for( int i = 0; i < textline.characters(); ++i ) ocrdes->text += textline.character( i ).utf8_result(); ocrdes->text += '\n'; return ocrdes->text.c_str(); } int OCRAD_result_first_character( OCRAD_Descriptor * const ocrdes ) { if( !verify_descriptor( ocrdes, true ) ) return -1; int ch = 0; if( ocrdes->textpage->textblocks() > 0 && ocrdes->textpage->textblock( 0 ).textlines() > 0 ) { const Character & character = ocrdes->textpage->textblock( 0 ).textline( 0 ).character( 0 ); if( character.guesses() ) { if( !ocrdes->control.utf8 ) ch = UCS::map_to_byte( character.guess( 0 ).code ); else ch = character.guess( 0 ).code; } } return ch; } ocrad-0.24/ocradlib.h000066400000000000000000000070061241541103500144560ustar00rootroot00000000000000/* Ocradlib - Optical Character Recognition library Copyright (C) 2009-2014 Antonio Diaz Diaz. This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see . */ #ifdef __cplusplus extern "C" { #endif const char * const OCRAD_version_string = "0.24"; /* OCRAD_Pixmap.data is a pointer to image data formed by "height" rows of "width" pixels each. The format for each pixel depends on mode like this: OCRAD_bitmap --> 1 byte per pixel; 0 = white, 1 = black OCRAD_greymap --> 1 byte per pixel; 256 level greymap (0 = black) OCRAD_colormap --> 3 bytes per pixel; 16777216 colors RGB (0,0,0 = black) */ enum OCRAD_Pixmap_Mode { OCRAD_bitmap, OCRAD_greymap, OCRAD_colormap }; struct OCRAD_Pixmap { const unsigned char * data; int height; int width; enum OCRAD_Pixmap_Mode mode; }; enum OCRAD_Errno { OCRAD_ok = 0, OCRAD_bad_argument, OCRAD_mem_error, OCRAD_sequence_error, OCRAD_library_error }; struct OCRAD_Descriptor; const char * OCRAD_version( void ); /*--------------------- Functions ---------------------*/ struct OCRAD_Descriptor * OCRAD_open( void ); int OCRAD_close( struct OCRAD_Descriptor * const ocrdes ); enum OCRAD_Errno OCRAD_get_errno( struct OCRAD_Descriptor * const ocrdes ); int OCRAD_set_image( struct OCRAD_Descriptor * const ocrdes, const struct OCRAD_Pixmap * const image, const bool invert ); int OCRAD_set_image_from_file( struct OCRAD_Descriptor * const ocrdes, const char * const filename, const bool invert ); int OCRAD_set_utf8_format( struct OCRAD_Descriptor * const ocrdes, const bool utf8 ); // 0 = byte, 1 = utf8 int OCRAD_set_threshold( struct OCRAD_Descriptor * const ocrdes, const int threshold ); // 0..255, -1 = auto int OCRAD_scale( struct OCRAD_Descriptor * const ocrdes, const int value ); int OCRAD_recognize( struct OCRAD_Descriptor * const ocrdes, const bool layout ); int OCRAD_result_blocks( struct OCRAD_Descriptor * const ocrdes ); int OCRAD_result_lines( struct OCRAD_Descriptor * const ocrdes, const int blocknum ); // 0..blocks-1 int OCRAD_result_chars_total( struct OCRAD_Descriptor * const ocrdes ); int OCRAD_result_chars_block( struct OCRAD_Descriptor * const ocrdes, const int blocknum ); // 0..blocks-1 int OCRAD_result_chars_line( struct OCRAD_Descriptor * const ocrdes, const int blocknum, // 0..blocks-1 const int linenum ); // 0..lines(block)-1 const char * OCRAD_result_line( struct OCRAD_Descriptor * const ocrdes, const int blocknum, // 0..blocks-1 const int linenum ); // 0..lines(block)-1 int OCRAD_result_first_character( struct OCRAD_Descriptor * const ocrdes ); #ifdef __cplusplus } #endif ocrad-0.24/page_image.cc000066400000000000000000000474151241541103500151230ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "ocradlib.h" #include "common.h" #include "rational.h" #include "rectangle.h" #include "segment.h" #include "mask.h" #include "track.h" #include "page_image.h" namespace { // binarization by Otsu's method based on maximization of inter-class variance // int otsu_th( const std::vector< std::vector< uint8_t > > & data, const Rectangle & re, const int maxval ) { if( maxval == 1 ) return 0; std::vector< int > hist( maxval + 1, 0 ); // histogram of image data for( int row = re.top(); row <= re.bottom(); ++row ) for( int col = re.left(); col <= re.right(); ++col ) ++hist[data[row][col]]; std::vector< int > chist; // cumulative histogram chist.reserve( maxval + 1 ); chist.push_back( hist[0] ); std::vector< long long > cmom; // cumulative moment cmom.reserve( maxval + 1 ); cmom.push_back( 0 ); // 0 times hist[0] equals zero for( int i = 1; i <= maxval; ++i ) { chist.push_back( chist[i-1] + hist[i] ); cmom.push_back( cmom[i-1] + ( i * hist[i] ) ); } const double cmom_max = cmom[maxval]; double bvar_max = 0; int threshold = 0; // threshold for binarization for( int i = 0; i < maxval; ++i ) if( chist[i] > 0 && chist[i] < re.size() ) { double bvar = (double)cmom[i] / chist[i]; bvar -= ( cmom_max - cmom[i] ) / ( re.size() - chist[i] ); bvar *= bvar; bvar *= chist[i]; bvar *= ( re.size() - chist[i] ); if( bvar > bvar_max ) { bvar_max = bvar; threshold = i; } } return threshold; } int absolute_pos( Rational pos, const int left, const int right ) { int a; if( pos >= 0 ) { if( pos <= 1 ) a = left + ( pos * ( right - left ) ).trunc(); else a = left + pos.round(); } else { pos = -pos; if( pos <= 1 ) a = right - ( pos * ( right - left ) ).trunc(); else a = right - pos.round(); } return a; } void convol_23( std::vector< std::vector< uint8_t > > & data, const int scale ) { const int height = data.size(); const int width = data[0].size(); if( height < 3 || width < 3 ) return; std::vector< std::vector< uint8_t > > new_data( height ); new_data[0] = data[0]; // copy first row for( int row = 1; row < height - 1; ++row ) new_data[row].reserve( width ); new_data[height-1] = data[height-1]; // copy last row for( int row = 1; row < height - 1; ++row ) { const std::vector< uint8_t > & datarow1 = data[row-1]; const std::vector< uint8_t > & datarow2 = data[row]; const std::vector< uint8_t > & datarow3 = data[row+1]; std::vector< uint8_t > & new_datarow = new_data[row]; new_datarow.push_back( datarow2[0] ); // copy first col if( scale < 3 ) for( int col = 1; col < width - 1; ++col ) { int sum = datarow1[col-1] + datarow1[col] + datarow1[col+1] + datarow2[col-1] + 2 * datarow2[col] + datarow2[col+1] + datarow3[col-1] + datarow3[col] + datarow3[col+1]; new_datarow.push_back( ( sum + 5 ) / 10 ); } else for( int col = 1; col < width - 1; ++col ) { int sum = datarow1[col-1] + datarow1[col] + datarow1[col+1] + datarow2[col-1] + datarow2[col] + datarow2[col+1] + datarow3[col-1] + datarow3[col] + datarow3[col+1]; new_datarow.push_back( ( 2 * sum + 9 ) / 18 ); } new_datarow.push_back( datarow2[width-1] ); // copy last col } data.swap( new_data ); } void convol_n( std::vector< std::vector< uint8_t > > & data, const int scale ) { const int radius = scale / 2; // this is really radius - 0.5 const int min_size = 2 * radius + 1; const int area = min_size * min_size; const int height = data.size(); const int width = data[0].size(); if( radius < 1 || height < min_size || width < min_size ) return; std::vector< std::vector< uint8_t > > new_data( height ); for( int row = 0; row < radius; ++row ) new_data[row] = data[row]; // copy first rows for( int row = radius; row < height - radius; ++row ) new_data[row].reserve( width ); for( int row = height - radius; row < height; ++row ) new_data[row] = data[row]; // copy last rows for( int row = radius; row < height - radius; ++row ) { const std::vector< uint8_t > & datarow = data[row]; std::vector< uint8_t > & new_datarow = new_data[row]; for( int col = 0; col < radius; ++col ) new_datarow.push_back( datarow[col] ); // copy first cols for( int col = radius; col < width - radius; ++col ) { int sum = 0; for( int r = -radius; r < radius; ++r ) for( int c = -radius; c < radius; ++c ) sum += data[row+r][col+c]; new_datarow.push_back( ( 2 * sum + area ) / ( 2 * area ) ); } for( int col = width - radius; col < width; ++col ) new_datarow.push_back( datarow[col] ); // copy last cols } data.swap( new_data ); } void enlarge_2b( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); const int width = data[0].size(); std::vector< std::vector< uint8_t > > new_data( 2 * height ); for( unsigned row = 0; row < new_data.size(); ++row ) new_data[row].resize( 2 * width, 1 ); for( int row = 0; row < height; ++row ) { const std::vector< uint8_t > & datarow = data[row]; std::vector< uint8_t > & new_datarow0 = new_data[2*row]; std::vector< uint8_t > & new_datarow1 = new_data[2*row+1]; for( int col = 0; col < width; ++col ) { if( datarow[col] == 0 ) { const bool l = col > 0 && datarow[col-1] == 0; const bool t = row > 0 && data[row-1][col] == 0; const bool r = col < width - 1 && datarow[col+1] == 0; const bool b = row < height - 1 && data[row+1][col] == 0; const bool lt = row > 0 && col > 0 && data[row-1][col-1] == 0; const bool rt = row > 0 && col < width - 1 && data[row-1][col+1] == 0; const bool lb = row < height - 1 && col > 0 && data[row+1][col-1] == 0; const bool rb = row < height - 1 && col < width - 1 && data[row+1][col+1] == 0; if( l || t || lt || ( !rt && !lb ) ) new_datarow0[2*col] = 0; if( r || t || rt || ( !lt && !rb ) ) new_datarow0[2*col+1] = 0; if( l || b || lb || ( !lt && !rb ) ) new_datarow1[2*col] = 0; if( r || b || rb || ( !rt && !lb ) ) new_datarow1[2*col+1] = 0; } } } data.swap( new_data ); } void enlarge_3b( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); const int width = data[0].size(); std::vector< std::vector< uint8_t > > new_data( 3 * height ); for( unsigned row = 0; row < new_data.size(); ++row ) new_data[row].resize( 3 * width, 1 ); for( int row = 0; row < height; ++row ) { const int row3 = 3 * row; const std::vector< uint8_t > & datarow = data[row]; std::vector< uint8_t > & new_datarow0 = new_data[row3]; std::vector< uint8_t > & new_datarow1 = new_data[row3+1]; std::vector< uint8_t > & new_datarow2 = new_data[row3+2]; for( int col = 0; col < width; ++col ) { const int col3 = 3 * col; const bool l = col > 0 && datarow[col-1] == 0; const bool t = row > 0 && data[row-1][col] == 0; const bool r = col < width - 1 && datarow[col+1] == 0; const bool b = row < height - 1 && data[row+1][col] == 0; const bool lt = row > 0 && col > 0 && data[row-1][col-1] == 0; const bool rt = row > 0 && col < width - 1 && data[row-1][col+1] == 0; const bool lb = row < height - 1 && col > 0 && data[row+1][col-1] == 0; const bool rb = row < height - 1 && col < width - 1 && data[row+1][col+1] == 0; if( datarow[col] == 0 ) { if( l || t || lt || ( !rt && !lb ) ) new_datarow0[col3] = 0; new_datarow0[col3+1] = 0; if( r || t || rt || ( !lt && !rb ) ) new_datarow0[col3+2] = 0; new_datarow1[col3] = new_datarow1[col3+1] = new_datarow1[col3+2] = 0; if( l || b || lb || ( !lt && !rb ) ) new_datarow2[col3] = 0; new_datarow2[col3+1] = 0; if( r || b || rb || ( !rt && !lb ) ) new_datarow2[col3+2] = 0; } else { if( l && t && lt && ( !rt || !lb ) ) new_datarow0[col3] = 0; if( r && t && rt && ( !lt || !rb ) ) new_datarow0[col3+2] = 0; if( l && b && lb && ( !lt || !rb ) ) new_datarow2[col3] = 0; if( r && b && rb && ( !rt || !lb ) ) new_datarow2[col3+2] = 0; } } } data.swap( new_data ); } void enlarge_n( std::vector< std::vector< uint8_t > > & data, const int n ) { if( n < 2 ) return; const int height = data.size(); const int width = data[0].size(); std::vector< std::vector< uint8_t > > new_data; new_data.reserve( n * height ); for( int row = 0; row < height; ++row ) { const std::vector< uint8_t > & datarow = data[row]; new_data.push_back( std::vector< uint8_t >() ); for( int col = 0; col < width; ++col ) { const uint8_t d = datarow[col]; for( int i = 0; i < n; ++i ) new_data.back().push_back( d ); } for( int i = 1; i < n; ++i ) new_data.push_back( new_data.back() ); } data.swap( new_data ); } void mirror_left_right( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); for( int row = 0; row < height; ++row ) std::reverse( data[row].begin(), data[row].end() ); } void mirror_top_bottom( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); for( int u = 0, d = height - 1; u < d; ++u, --d ) data[u].swap( data[d] ); } void mirror_diagonal( std::vector< std::vector< uint8_t > > & data, Rectangle & re ) { const int size = std::max( re.height(), re.width() ); if( re.height() < size ) { data.resize( size ); for( int row = re.height(); row < size; ++row ) data[row].resize( size ); } else if( re.width() < size ) for( int row = 0; row < re.height(); ++row ) data[row].resize( size ); for( int row = 0; row < size; ++row ) { std::vector< uint8_t > & datarow = data[row]; for( int col = 0; col < row; ++col ) { uint8_t tmp = datarow[col]; datarow[col] = data[col][row]; data[col][row] = tmp; } } const int h = re.height(), w = re.width(); re.height( w ); re.width( h ); if( re.height() < size ) data.resize( re.height() ); else if( re.width() < size ) for( int row = 0; row < re.height(); ++row ) data[row].resize( re.width() ); } } // end namespace // Creates a Page_image from a OCRAD_Pixmap // Page_image::Page_image( const OCRAD_Pixmap & image, const bool invert ) : Rectangle( 0, 0, image.width - 1, image.height - 1 ) { data.resize( height() ); for( unsigned row = 0; row < data.size(); ++row ) data[row].reserve( width() ); const int rows = height(), cols = width(); switch( image.mode ) { case OCRAD_bitmap: { maxval_ = 1; threshold_ = 0; if( !invert ) for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( image.data[i] ? 0 : 1 ); else for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( image.data[i] ? 1 : 0 ); } break; case OCRAD_greymap: { maxval_ = 255; threshold_ = 127; if( !invert ) for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( image.data[i] ); else for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( maxval_ - image.data[i] ); } break; case OCRAD_colormap: { maxval_ = 255; threshold_ = 127; for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, i += 3 ) { const uint8_t r = image.data[i]; // Red value const uint8_t g = image.data[i+1]; // Green value const uint8_t b = image.data[i+2]; // Blue value uint8_t val; if( !invert ) val = std::min( r, std::min( g, b ) ); else val = maxval_ - std::max( r, std::max( g, b ) ); data[row].push_back( val ); } } break; } } // Creates a reduced Page_image // Page_image::Page_image( const Page_image & source, const int scale ) : Rectangle( source ), maxval_( source.maxval_ ), threshold_( source.threshold_ ) { if( scale < 2 || scale > source.width() || scale > source.height() ) Ocrad::internal_error( "bad parameter building a reduced Page_image." ); const int scale2 = scale * scale; Rectangle::height( source.height() / scale ); Rectangle::width( source.width() / scale ); data.resize( height() ); for( int row = 0; row < height(); ++row ) { const int srow = ( row * scale ) + scale; data[row].reserve( width() ); std::vector< uint8_t > & datarow = data[row]; for( int col = 0; col < width(); ++col ) { const int scol = ( col * scale ) + scale; int sum = 0; for( int i = srow - scale; i < srow; ++i ) { const std::vector< uint8_t > & sdatarow = source.data[i]; for( int j = scol - scale; j < scol; ++j ) sum += sdatarow[j]; } datarow.push_back( sum / scale2 ); } } } void Page_image::threshold( const Rational & th ) { if( th >= 0 && th <= 1 ) threshold_ = ( th * maxval_ ).trunc(); else threshold_ = otsu_th( data, *this, maxval_ ); } void Page_image::threshold( const int th ) { if( th >= 0 && th <= 255 ) threshold_ = ( th * maxval_ ) / 255; else threshold_ = otsu_th( data, *this, maxval_ ); } bool Page_image::cut( const Rational ltwh[4] ) { Rectangle re = *this; const int l = absolute_pos( ltwh[0], left(), right() ); if( l > re.left() ) { if( l < re.right() ) re.left( l ); else return false; } const int t = absolute_pos( ltwh[1], top(), bottom() ); if( t > re.top() ) { if( t < re.bottom() ) re.top( t ); else return false; } const int r = l + absolute_pos( ltwh[2], left(), right() ) - 1; if( r < re.right() ) { if( r > re.left() ) re.right( r ); else return false; } const int b = t + absolute_pos( ltwh[3], top(), bottom() ) - 1; if( b < re.bottom() ) { if( b > re.top() ) re.bottom( b ); else return false; } if( re.width() < 3 || re.height() < 3 ) return false; // cutting is performed here if( re.bottom() < bottom() ) data.resize( re.bottom() - top() + 1 ); if( re.right() < right() ) { const int w = re.right() - left() + 1; for( int row = data.size() - 1; row >= 0 ; --row ) data[row].resize( w ); } if( re.top() > top() ) data.erase( data.begin(), data.begin() + ( re.top() - top() ) ); if( re.left() > left() ) { const int d = re.left() - left(); for( int row = data.size() - 1; row >= 0 ; --row ) data[row].erase( data[row].begin(), data[row].begin() + d ); } Rectangle::left( 0 ); Rectangle::top( 0 ); Rectangle::right( data[0].size() - 1 ); Rectangle::bottom( data.size() - 1 ); return true; } void Page_image::draw_mask( const Mask & m ) { const int t = std::max( top(), m.top() ); const int b = std::min( bottom(), m.bottom() ); if( t == m.top() && m.left( t ) >= 0 && m.right( t ) >= 0 ) for( int col = m.left( t ); col <= m.right( t ); ++col ) set_bit( t, col, true ); if( b == m.bottom() && m.left( b ) >= 0 && m.right( b ) >= 0 ) for( int col = m.left( b ); col <= m.right( b ); ++col ) set_bit( b, col, true ); int lprev = m.left( t ); int rprev = m.right( t ); for( int row = t + 1; row <= b; ++row ) { int lnew = m.left( row ), rnew = m.right( row ); if( lnew < 0 ) lnew = lprev; if( rnew < 0 ) rnew = rprev; if( lprev >= 0 && lnew >= 0 ) { int c1 = std::max( left(), std::min( lprev, lnew ) ); int c2 = std::min( right(), std::max( lprev, lnew ) ); for( int col = c1; col <= c2; ++col ) set_bit( row, col, true ); } if( rprev >= 0 && rnew >= 0 ) { int c1 = std::max( left(), std::min( rprev, rnew ) ); int c2 = std::min( right(), std::max( rprev, rnew ) ); for( int col = c1; col <= c2; ++col ) set_bit( row, col, true ); } lprev = lnew; rprev = rnew; } } void Page_image::draw_rectangle( const Rectangle & re ) { const int l = std::max( left(), re.left() ); const int t = std::max( top(), re.top() ); const int r = std::min( right(), re.right() ); const int b = std::min( bottom(), re.bottom() ); if( l == re.left() ) for( int row = t; row <= b; ++row ) set_bit( row, l, true ); if( t == re.top() ) for( int col = l; col <= r; ++col ) set_bit( t, col, true ); if( r == re.right() ) for( int row = t; row <= b; ++row ) set_bit( row, r, true ); if( b == re.bottom() ) for( int col = l; col <= r; ++col ) set_bit( b, col, true ); } void Page_image::draw_track( const Track & tr ) { int l = std::max( left(), tr.left() ); int r = std::min( right(), tr.right() ); if( l == tr.left() ) for( int row = tr.top( l ); row <= tr.bottom( l ); ++row ) if( row >= top() && row <= bottom() ) set_bit( row, l, true ); if( r == tr.right() ) for( int row = tr.top( r ); row <= tr.bottom( r ); ++row ) if( row >= top() && row <= bottom() ) set_bit( row, r, true ); for( int col = l; col <= r; ++col ) { int row = tr.top( col ); if( row >= top() && row <= bottom() ) set_bit( row, col, true ); row = tr.bottom( col ); if( row >= top() && row <= bottom() ) set_bit( row, col, true ); } } bool Page_image::change_scale( int n ) { if( n <= -2 ) { Page_image reduced( *this, -n ); *this = reduced; return true; } if( n >= 2 ) { if( INT_MAX / n < width() * height() ) throw Error( "scale factor too big. 'int' will overflow." ); if( maxval_ == 1 ) { if( n && ( n % 2 ) == 0 ) { enlarge_2b( data ); n /= 2; } else if( n && ( n % 3 ) == 0 ) { enlarge_3b( data ); n /= 3; } } if( n >= 2 ) { enlarge_n( data, n ); if( maxval_ > 1 ) { if( n <= 3 ) convol_23( data, n ); else convol_n( data, n ); } } Rectangle::height( data.size() ); Rectangle::width( data[0].size() ); return true; } return false; } void Page_image::transform( const Transformation & t ) { switch( t.type() ) { case Transformation::none: break; case Transformation::rotate90: mirror_diagonal( data, *this ); mirror_top_bottom( data ); break; case Transformation::rotate180: mirror_left_right( data ); mirror_top_bottom( data ); break; case Transformation::rotate270: mirror_diagonal( data, *this ); mirror_left_right( data ); break; case Transformation::mirror_lr: mirror_left_right( data ); break; case Transformation::mirror_tb: mirror_top_bottom( data ); break; case Transformation::mirror_d1: mirror_diagonal( data, *this ); break; case Transformation::mirror_d2: mirror_diagonal( data, *this ); mirror_left_right( data ); mirror_top_bottom( data ); break; } } ocrad-0.24/page_image.h000066400000000000000000000056671241541103500147700ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ struct OCRAD_Pixmap; class Mask; class Rational; class Track; class Page_image : public Rectangle // left,top is always 0,0 { public: struct Error { const char * const msg; explicit Error( const char * const s ) : msg( s ) {} }; using Rectangle::left; using Rectangle::top; using Rectangle::right; using Rectangle::bottom; using Rectangle::height; using Rectangle::width; private: std::vector< std::vector< uint8_t > > data; // 256 level greymap uint8_t maxval_, threshold_; // x > threshold == white void read_p1( FILE * const f, const bool invert ); void read_p4( FILE * const f, const bool invert ); void read_p2( FILE * const f, const bool invert ); void read_p5( FILE * const f, const bool invert ); void read_p3( FILE * const f, const bool invert ); void read_p6( FILE * const f, const bool invert ); void left ( int ); // resize functions declared as private void top ( int ); void right ( int ); void bottom( int ); void height( int ); void width ( int ); public: // Creates a Page_image from a pbm, pgm or ppm file Page_image( FILE * const f, const bool invert ); // Creates a Page_image from a OCRAD_Pixmap Page_image( const OCRAD_Pixmap & image, const bool invert ); // Creates a reduced Page_image Page_image( const Page_image & source, const int scale ); bool get_bit( const int row, const int col ) const { return data[row-top()][col-left()] <= threshold_; } bool get_bit( const int row, const int col, const uint8_t th ) const { return data[row-top()][col-left()] <= th; } void set_bit( const int row, const int col, const bool bit ) { data[row-top()][col-left()] = ( bit ? 0 : maxval_ ); } uint8_t maxval() const { return maxval_; } uint8_t threshold() const { return threshold_; } void threshold( const Rational & th ); // 0 <= th <= 1, else auto void threshold( const int th ); // 0 <= th <= 255, else auto bool cut( const Rational ltwh[4] ); void draw_mask( const Mask & m ); void draw_rectangle( const Rectangle & re ); void draw_track( const Track & tr ); bool save( FILE * const f, const char filetype ) const; bool change_scale( int n ); void transform( const Transformation & t ); }; ocrad-0.24/page_image_io.cc000066400000000000000000000234621241541103500156060ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "common.h" #include "rational.h" #include "rectangle.h" #include "page_image.h" namespace { uint8_t pnm_getrawbyte( FILE * const f ) { int ch = std::fgetc( f ); if( ch == EOF ) throw Page_image::Error( "end-of-file reading pnm file." ); return ch; } uint8_t pnm_getc( FILE * const f ) { uint8_t ch; bool comment = false; do { ch = pnm_getrawbyte( f ); if( ch == '#' ) comment = true; else if( ch == '\n' ) comment = false; } while( comment ); return ch; } int pnm_getint( FILE * const f ) { uint8_t ch; int i = 0; do ch = pnm_getc( f ); while( std::isspace( ch ) ); if( !std::isdigit( ch ) ) throw Page_image::Error( "junk in pnm file where an integer should be." ); do { if( ( INT_MAX - (ch - '0') ) / 10 < i ) throw Page_image::Error( "number too big in pnm file." ); i = (i * 10) + (ch - '0'); ch = pnm_getc( f ); } while( std::isdigit( ch ) ); return i; } uint8_t pbm_getbit( FILE * const f ) { uint8_t ch; do ch = pnm_getc( f ); while( std::isspace( ch ) ); if( ch == '0' ) return 0; if( ch == '1' ) return 1; throw Page_image::Error( "junk in pbm file where bits should be." ); } } // end namespace void Page_image::read_p1( FILE * const f, const bool invert ) { maxval_ = 1; threshold_ = 0; const int rows = height(), cols = width(); if( !invert ) for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) data[row].push_back( 1 - pbm_getbit( f ) ); else for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) data[row].push_back( pbm_getbit( f ) ); } void Page_image::read_p4( FILE * const f, const bool invert ) { maxval_ = 1; threshold_ = 0; const int rows = height(), cols = width(); if( !invert ) for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ) { uint8_t byte = pnm_getrawbyte( f ); for( uint8_t mask = 0x80; mask > 0 && col < cols; mask >>= 1, ++col ) data[row].push_back( ( byte & mask ) ? 0 : 1 ); } else for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ) { uint8_t byte = pnm_getrawbyte( f ); for( uint8_t mask = 0x80; mask > 0 && col < cols; mask >>= 1, ++col ) data[row].push_back( ( byte & mask ) ? 1 : 0 ); } } void Page_image::read_p2( FILE * const f, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Page_image::Error( "zero maxval in pgm file." ); maxval_ = std::min( maxval, 255 ); threshold_ = maxval_ / 2; const int rows = height(), cols = width(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { int val = pnm_getint( f ); if( val > maxval ) throw Page_image::Error( "value > maxval in pgm file." ); if( invert ) val = maxval - val; if( maxval > 255 ) { val *= 255; val /= maxval; } data[row].push_back( val ); } } void Page_image::read_p5( FILE * const f, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Page_image::Error( "zero maxval in pgm file." ); if( maxval > 255 ) throw Page_image::Error( "maxval > 255 in pgm \"P5\" file." ); maxval_ = maxval; threshold_ = maxval_ / 2; const int rows = height(), cols = width(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { uint8_t val = pnm_getrawbyte( f ); if( val > maxval_ ) throw Page_image::Error( "value > maxval in pgm file." ); if( invert ) val = maxval_ - val; data[row].push_back( val ); } } void Page_image::read_p3( FILE * const f, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Page_image::Error( "zero maxval in ppm file." ); maxval_ = std::min( maxval, 255 ); threshold_ = maxval_ / 2; const int rows = height(), cols = width(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { const int r = pnm_getint( f ); // Red value const int g = pnm_getint( f ); // Green value const int b = pnm_getint( f ); // Blue value if( r > maxval || g > maxval || b > maxval ) throw Page_image::Error( "value > maxval in ppm file." ); int val; if( !invert ) val = std::min( r, std::min( g, b ) ); else val = maxval - std::max( r, std::max( g, b ) ); if( maxval > 255 ) { val *= 255; val /= maxval; } data[row].push_back( val ); } } void Page_image::read_p6( FILE * const f, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Page_image::Error( "zero maxval in ppm file." ); if( maxval > 255 ) throw Page_image::Error( "maxval > 255 in ppm \"P6\" file." ); maxval_ = maxval; threshold_ = maxval_ / 2; const int rows = height(), cols = width(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { const uint8_t r = pnm_getrawbyte( f ); // Red value const uint8_t g = pnm_getrawbyte( f ); // Green value const uint8_t b = pnm_getrawbyte( f ); // Blue value if( r > maxval_ || g > maxval_ || b > maxval_ ) throw Page_image::Error( "value > maxval in ppm file." ); uint8_t val; if( !invert ) val = std::min( r, std::min( g, b ) ); else val = maxval_ - std::max( r, std::max( g, b ) ); data[row].push_back( val ); } } // Creates a Page_image from a pbm, pgm or ppm file // "P1" (pbm), "P4" (pbm RAWBITS), "P2" (pgm), "P5" (pgm RAWBITS), // "P3" (ppm), "P6" (ppm RAWBITS) file formats are recognized. // Page_image::Page_image( FILE * const f, const bool invert ) : Rectangle( 0, 0, 0, 0 ) { unsigned char filetype = 0; if( pnm_getrawbyte( f ) == 'P' ) { unsigned char ch = pnm_getrawbyte( f ); if( ch >= '1' && ch <= '6' ) filetype = ch; } if( filetype == 0 ) throw Error( "bad magic number - not a pbm, pgm or ppm file." ); { int tmp = pnm_getint( f ); if( tmp == 0 ) throw Error( "zero width in pnm file." ); Rectangle::width( tmp ); tmp = pnm_getint( f ); if( tmp == 0 ) throw Error( "zero height in pnm file." ); Rectangle::height( tmp ); if( width() < 3 || height() < 3 ) throw Error( "image too small. Minimum size is 3x3." ); if( INT_MAX / width() < height() ) throw Error( "image too big. 'int' will overflow." ); } data.resize( height() ); for( unsigned row = 0; row < data.size(); ++row ) data[row].reserve( width() ); switch( filetype ) { case '1': read_p1( f, invert ); break; case '4': read_p4( f, invert ); break; case '2': read_p2( f, invert ); break; case '5': read_p5( f, invert ); break; case '3': read_p3( f, invert ); break; case '6': read_p6( f, invert ); break; } if( verbosity >= 1 ) { std::fprintf( stderr, "file type is P%c\n", filetype ); std::fprintf( stderr, "file size is %dw x %dh\n", width(), height() ); } } bool Page_image::save( FILE * const f, const char filetype ) const { if( filetype < '1' || filetype > '6' ) return false; std::fprintf( f, "P%c\n%d %d\n", filetype, width(), height() ); if( filetype != '1' && filetype != '4' ) std::fprintf( f, "%d\n", maxval_ ); if( filetype == '1' ) // pbm for( int row = top(); row <= bottom(); ++row ) { for( int col = left(); col <= right(); ++col ) std::putc( get_bit( row, col ) ? '1' : '0', f ); std::putc( '\n', f ); } else if( filetype == '4' ) // pbm RAWBITS for( int row = top(); row <= bottom(); ++row ) { uint8_t byte = 0, mask = 0x80; for( int col = left(); col <= right(); ++col ) { if( get_bit( row, col ) ) byte |= mask; mask >>= 1; if( mask == 0 ) { std::putc( byte, f ); byte = 0; mask = 0x80; } } if( mask != 0x80 ) std::putc( byte, f ); // incomplete byte at end of row } else if( filetype == '2' ) // pgm for( int row = top(); row <= bottom(); ++row ) { for( int col = left(); col < right(); ++col ) std::fprintf( f, "%d ", data[row][col] ); std::fprintf( f, "%d\n", data[row][right()] ); } else if( filetype == '5' ) // pgm RAWBITS for( int row = top(); row <= bottom(); ++row ) for( int col = left(); col <= right(); ++col ) std::fprintf( f, "%c", data[row][col] ); else if( filetype == '3' ) // ppm for( int row = top(); row <= bottom(); ++row ) { for( int col = left(); col < right(); ++col ) { const uint8_t d = data[row][col]; std::fprintf( f, "%d %d %d ", d, d, d ); } const uint8_t d = data[row][right()]; std::fprintf( f, "%d %d %d\n", d, d, d ); } else if( filetype == '6' ) // ppm RAWBITS for( int row = top(); row <= bottom(); ++row ) for( int col = left(); col <= right(); ++col ) { const uint8_t d = data[row][col]; std::fprintf( f, "%c %c %c ", d, d, d ); } return true; } ocrad-0.24/profile.cc000066400000000000000000000436571241541103500145110ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "bitmap.h" #include "blob.h" #include "profile.h" Profile::Profile( const Bitmap & bm_, const Type t ) : bm( bm_ ), type( t ), limit_( -1 ), max_( -1 ), min_( -1 ), mean_( -1 ), isconcave_( -1 ), isconvex_( -1 ), isflat_( -1 ), isflats_( -1 ), ispit_( -1 ), istpit_( -1 ), isupit_( -1 ), isvpit_( -1 ), istip_( -1 ) {} void Profile::initialize() { switch( type ) { case left : data.resize( bm.height() ); limit_ = bm.width(); for( int row = bm.top(); row <= bm.bottom(); ++row ) { int j = bm.left(); while( j <= bm.right() && !bm.get_bit( row, j ) ) ++j; data[row-bm.top()] = j - bm.left(); } break; case top : data.resize( bm.width() ); limit_ = bm.height(); for( int col = bm.left(); col <= bm.right(); ++col ) { int j = bm.top(); while( j <= bm.bottom() && !bm.get_bit( j, col ) ) ++j; data[col-bm.left()] = j - bm.top(); } break; case right : data.resize( bm.height() ); limit_ = bm.width(); for( int row = bm.top(); row <= bm.bottom(); ++row ) { int j = bm.right(); while( j >= bm.left() && !bm.get_bit( row, j ) ) --j; data[row-bm.top()] = bm.right() - j; } break; case bottom : data.resize( bm.width() ); limit_ = bm.height(); for( int col = bm.left(); col <= bm.right(); ++col ) { int j = bm.bottom(); while( j >= bm.top() && !bm.get_bit( j, col ) ) --j; data[col-bm.left()] = bm.bottom() - j; } break; case height : data.resize( bm.width() ); limit_ = bm.height(); for( int col = bm.left(); col <= bm.right(); ++col ) { int u = bm.top(), d = bm.bottom(); while( u <= d && !bm.get_bit( u, col ) ) ++u; while( u <= d && !bm.get_bit( d, col ) ) --d; data[col-bm.left()] = d - u + 1; } break; case width : data.resize( bm.height() ); limit_ = bm.width(); for( int row = bm.top(); row <= bm.bottom(); ++row ) { int l = bm.left(), r = bm.right(); while( l <= r && !bm.get_bit( row, l ) ) ++l; while( l <= r && !bm.get_bit( row, r ) ) --r; data[row-bm.top()] = r - l + 1; } break; } } int Profile::mean() { if( mean_ < 0 ) { if( limit_ < 0 ) initialize(); mean_ = 0; for( int i = 0; i < samples(); ++i ) mean_ += data[i]; if( samples() > 1 ) mean_ /= samples(); } return mean_; } int Profile::max() { if( max_ < 0 ) { if( limit_ < 0 ) initialize(); max_ = data[0]; for( int i = 1; i < samples(); ++i ) if( data[i] > max_ ) max_ = data[i]; } return max_; } int Profile::max( const int l, int r ) { if( limit_ < 0 ) initialize(); if( r < 0 ) r = samples() - 1; int m = 0; for( int i = l; i <= r; ++i ) if( data[i] > m ) m = data[i]; return m; } int Profile::min() { if( min_ < 0 ) { if( limit_ < 0 ) initialize(); min_ = data[0]; for( int i = 1; i < samples(); ++i ) if( data[i] < min_ ) min_ = data[i]; } return min_; } int Profile::min( const int l, int r ) { if( limit_ < 0 ) initialize(); if( r < 0 ) r = samples() - 1; int m = limit_; for( int i = l; i <= r; ++i ) if( data[i] < m ) m = data[i]; return m; } int Profile::operator[]( int i ) { if( limit_ < 0 ) initialize(); if( i < 0 ) i = 0; else if( i >= samples() ) i = samples() - 1; return data[i]; } int Profile::area( const int l, int r ) { if( limit_ < 0 ) initialize(); if( r < 0 || r >= samples() ) r = samples() - 1; int a = 0; for( int i = l; i <= r; ++i ) a += data[i]; return a; } bool Profile::increasing( int i, const int min_delta ) { if( limit_ < 0 ) initialize(); if( i < 0 || i > samples() - 2 || data[samples()-1] - data[i] < min_delta ) return false; while( ++i < samples() ) if( data[i] < data[i-1] ) return false; return true; } bool Profile::decreasing( int i, int end ) { if( limit_ < 0 ) initialize(); const int dnoise = ( samples() / 20 ) + 1; // domain noise const int rnoise = ( std::min( samples(), limit_ ) / 20 ) + 1; // range noise if( end < 0 || end > samples() - dnoise ) end = samples() - dnoise; if( i < 0 || end - i <= 2 * rnoise || data[i] - data[end-1] <= rnoise ) return false; while( ++i < end ) if( data[i] > data[i-1] ) return false; return true; } bool Profile::isconcave() { if( isconcave_ < 0 ) { isconcave_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return isconcave_; int dmax = -1, l = 0, r = 0; for( int i = pos( 10 ); i <= pos( 90 ); ++i ) { if( data[i] > dmax ) { dmax = data[i]; l = r = i; } else if( data[i] == dmax ) { r = i; } } if( l > r || l < pos( 25 ) || r > pos( 75 ) ) return isconcave_; if( data[pos(10)] >= dmax || data[pos(90)] >= dmax ) return isconcave_; int imax = ( l + r ) / 2; for( int i = pos( 10 ); i < imax; ++i ) if( data[i] > data[i+1] ) return isconcave_; for( int i = pos( 90 ); i > imax; --i ) if( data[i] > data[i-1] ) return isconcave_; isconcave_ = true; } return isconcave_; } bool Profile::isconvex() { if( isconvex_ < 0 ) { isconvex_ = false; if( limit_ < 0 ) initialize(); if( samples() < 9 || limit_ < 5 ) return isconvex_; int min = limit_, min_begin = 0, min_end = 0; int lmin = limit_, rmax = -limit_, l = 0, r = 0; for( int i = 1; i < samples(); ++i ) { int d = data[i] - data[i-1]; if( d < lmin ) { lmin = d; l = i - 1; } if( d >= rmax ) { rmax = d; r = i; } if( data[i] <= min ) { min_end = i; if( data[i] < min ) { min = data[i]; min_begin = i; } } } if( l >= r || l >= pos( 25 ) || r <= pos( 75 ) ) return isconvex_; if( lmin >= 0 || rmax <= 0 || data[l] < 2 || data[r] < 2 || 3 * ( data[l] + data[r] ) <= std::min( samples(), limit_ ) ) return isconvex_; if( 3 * ( min_end - min_begin + 1 ) > 2 * samples() ) return isconvex_; if( 2 * l >= min_begin || 2 * r <= min_end + samples() - 1 ) return isconvex_; if( min_begin < pos( 10 ) || min_end > pos( 90 ) ) return isconvex_; const int noise = ( std::min( samples(), limit_ ) / 30 ) + 1; int dmax = -limit_; for( int i = l + 1; i <= r; ++i ) { if( i >= min_begin && i <= min_end ) { if( data[i] <= noise ) continue; else return isconvex_; } int d = data[i] - data[i-1]; if( d == 0 ) continue; if( d > dmax ) { if( std::abs( d ) <= noise ) ++dmax; else dmax = d; } else if( d < dmax - noise ) return isconvex_; } if( 2 * ( min_end - min_begin + 1 ) < samples() ) { int varea = ( min_begin - l + 1 ) * data[l] / 2; varea += ( r - min_end + 1 ) * data[r] / 2; if( this->area( l, min_begin - 1 ) + this->area( min_end + 1, r ) >= varea ) return isconvex_; } isconvex_ = true; } return isconvex_; } bool Profile::isflat() { if( isflat_ < 0 ) { isflat_ = false; if( limit_ < 0 ) initialize(); if( samples() < 10 ) return isflat_; int mn = data[samples()/2], mx = mn; for( int i = 1; i < samples() - 1; ++i ) { int d = data[i]; if( d < mn ) mn = d; else if( d > mx ) mx = d; } isflat_ = (bool)( mx - mn <= 1 + ( samples() / 30 ) ); } return isflat_; } bool Profile::isflats() { if( isflats_ < 0 ) { isflats_ = false; if( limit_ < 0 ) initialize(); if( samples() < 12 ) return isflats_; const int s1 = std::max( pos( 15 ), 3 ); const int s2 = std::min( pos( 85 ), samples() - 4 ); int mn = -1, mx = 0; for( int i = s1 + 2; i < s2; ++i ) if( data[i-1] == data[i] ) { mn = mx = data[i]; break; } if( mn < 0 ) return isflats_; for( int i = 1; i <= s1; ++i ) if( data[i] > mx ) mx = data[i]; for( int i = s1 + 1; i < s2; ++i ) { int d = data[i]; if( d < mn ) mn = d; else if( d > mx ) mx = d; } for( int i = s2; i < samples() - 1; ++i ) if( data[i] > mx ) mx = data[i]; isflats_ = (bool)( mx - mn <= 1 + ( samples() / 30 ) ); } return isflats_; } bool Profile::ispit() { if( ispit_ < 0 ) { ispit_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return ispit_; const int noise = ( std::min( samples(), limit_ ) / 25 ) + 1; for( int i = 0; i < noise; ++i ) if( data[i] <= noise - i || data[samples()-i-1] <= noise - i ) return ispit_; const int dmin = min(), dmax = limit_ / 2; int begin = 0, end = 0, i, ref; for( i = 0, ref = dmax; i < samples(); ++i ) { int d = data[i]; if( d == dmin ) { begin = i; break; } if( d < ref ) ref = d; else if( d > ref + noise && ref < dmax ) return ispit_; } if( begin < 2 || begin > samples() - 3 ) return ispit_; for( i = samples() - 1, ref = dmax; i >= begin; --i ) { int d = data[i]; if( d == dmin ) { end = i; break; } if( d < ref ) ref = d; else if( d > ref + noise && ref < dmax ) return ispit_; } if( end < begin || end > samples() - 3 ) return ispit_; for( i = begin + 1; i < end; ++i ) if( data[i] > dmin + noise ) return ispit_; ispit_ = true; } return ispit_; } bool Profile::iscpit( const int cpos ) { if( limit_ < 0 ) initialize(); if( samples() < 5 || cpos < 25 || cpos > 75 ) return false; const int mid = ( ( samples() - 1 ) * cpos ) / 100; const int iend = std::min( samples() / 4, std::min( mid, samples() - mid ) ); const int th = ( ( mean() < 2 ) ? 2 : mean() ); int imin = -1; for( int i = 0; i < iend; ++i ) { if( data[mid+i] < th ) { imin = mid + i; break; } if( data[mid-i-1] < th ) { imin = mid - i - 1; break; } } if( imin < 0 ) return false; for( int i = imin + 1; i < samples(); ++i ) if( data[i] > th ) { for( int j = imin - 1; j >= 0; --j ) if( data[j] > th ) return true; break; } return false; } bool Profile::istpit() { if( istpit_ < 0 ) { if( limit_ < 0 ) initialize(); if( limit_ < 5 || samples() < 5 || !ispit() ) { istpit_ = false; return istpit_; } const int noise = ( std::min( samples(), limit_ ) / 30 ) + 1; int l = -1, r = 0; for( int i = 0; i < samples(); ++i ) if( data[i] <= noise ) { r = i; if( l < 0 ) l = i; } istpit_ = (bool)( l > 0 && 4 * ( r - l + 1 ) < samples() ); } return istpit_; } bool Profile::isupit() { if( isupit_ < 0 ) { isupit_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return isupit_; int th = ( mean() < 2 && range() > 2 ) ? 2 : mean(); int status = 0, ucount = 0, lcount = 0, umean =0, lmean = 0; for( int i = 0; i < samples(); ++i ) { int d = data[i]; switch( status ) { case 0: if( d < th ) { if( i < pos( 25 ) || i > pos( 70 ) ) return isupit_; status = 1; break; } if( d > th ) { ++ucount; umean += d; } break; case 1: if( d > th ) { if( i < pos( 30 ) || i > pos( 75 ) ) return isupit_; status = 2; break; } if( d < th ) { ++lcount; lmean += d; } break; case 2: if( d < th ) return isupit_; if( d > th ) { ++ucount; umean += d; } break; } } if( ucount > 1 ) umean /= ucount; if( lcount > 1 ) lmean /= lcount; isupit_ = (bool)( status == 2 && umean - lmean > range() / 2 ); } return isupit_; } bool Profile::isvpit() { if( isvpit_ < 0 ) { if( limit_ < 0 ) initialize(); if( limit_ < 5 || samples() < 5 || !ispit() ) { isvpit_ = false; return isvpit_; } const int noise = limit_ / 20; const int level = ( limit_ / 10 ) + 2; int ll = -1, ln = -1, rl = -1, rn = -1; for( int i = 0; i < samples(); ++i ) if( data[i] <= level ) { rl = i; if( ll < 0 ) ll = i; if( data[i] <= noise ) { rn = i; if( ln < 0 ) ln = i; } } const int wl = rl - ll + 1, wn = rn - ln + 1; isvpit_ = (bool)( ln > 0 && 2 * wl <= samples() + 1 && wl - wn <= 2 * ( level - noise ) ); } return isvpit_; } bool Profile::istip() { if( istip_ < 0 ) { istip_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return istip_; int th = ( mean() < 2 && range() > 2 ) ? 2 : mean(); if( th < 2 ) ++th; int lth = data[0], rth = data[samples()-1]; int begin = 0, end = samples() - 1; const int tries = std::max( 2, ( samples() + 5 ) / 10 ); for( int i = 1; i < tries; ++i ) { if( data[i] < lth ) { lth = data[i]; begin = i; } if( data[samples()-1-i] < rth ) { rth = data[samples()-1-i]; end = samples() - 1 - i; } } if( lth >= th || rth >= th ) return istip_; if( 3 * lth >= 2 * range() || 3 * rth >= 2 * range() ) return istip_; th = std::max( lth, rth ); int status = 0; for( int i = begin + 1; i < end; ++i ) switch( status ) { case 0: if( data[i] > th + 1 ) status = 1; break; case 1: if( data[i] > th + 1 ) status = 2; else status = 0; break; case 2: if( data[i] <= th ) status = 3; break; case 3: if( data[i] > th + 1 ) return istip_; } istip_ = (bool)( status >= 2 ); } return istip_; } bool Profile::isctip( const int cpos ) { if( limit_ < 0 ) initialize(); if( samples() < 5 || cpos < 25 || cpos > 75 ) return false; const int mid = ( ( samples() - 1 ) * cpos ) / 100; const int iend = std::min( samples() / 4, std::min( mid, samples() - mid ) ); int th = std::max( 2, std::min( mean(), limit_ / 3 ) ); int imax = -1; for( int i = 0; i < iend; ++i ) { if( data[mid+i] > th ) { imax = mid + i; break; } if( data[mid-i-1] > th ) { imax = mid - i - 1; break; } } if( imax < 0 && mean() == 0 ) { --th; for( int i = 0; i < iend; ++i ) { if( data[mid+i] > th ) { imax = mid + i; break; } if( data[mid-i-1] > th ) { imax = mid - i - 1; break; } } } if( imax < 0 ) return false; th = std::max( th, data[imax] / 2 ); for( int i = imax + 1; i < samples(); ++i ) if( data[i] < th ) { for( int j = imax - 1; j >= 0; --j ) if( data[j] < th ) return true; break; } return false; } bool Profile::isltip() { if( limit_ < 0 ) initialize(); if( samples() < 5 ) return false; const int noise = ( samples() / 30 ) + 1; if( data[0] < noise + 1 ) return false; const int dmin = min(); int begin = 0, ref = limit_; for( int i = 0; i < samples() - noise; ++i ) { int d = data[i]; if( d == dmin ) { begin = i; break; } if( d < ref ) ref = d; else if( d > ref + 1 ) return false; } if( begin < 2 || 2 * begin > samples() ) return false; return true; } int Profile::imaximum() { if( limit_ < 0 ) initialize(); const int margin = ( samples() / 30 ) + 1; int mbegin = 0, mend, mvalue = 0; for( int i = margin; i < samples() - margin; ++i ) if( data[i] > mvalue ) { mvalue = data[i]; mbegin = i; } for( mend = mbegin + 1; mend < samples(); ++mend ) if( data[mend] < mvalue ) break; return ( mbegin + mend - 1 ) / 2; } int Profile::iminimum( const int m, int th ) { if( limit_ < 0 ) initialize(); const int margin = ( samples() / 30 ) + 1; if( samples() < 2 * margin ) return 0; if( th < 2 ) th = ( mean() < 2 ) ? 2 : mean(); int minima = 0, status = 0; int begin = 0, end, value = limit_ + 1; for( end = margin; end < samples() - margin; ++end ) { if( status == 0 ) { if( data[end] < th ) { status = 1; ++minima; begin = end; } } else if( data[end] > th ) { if( minima == m + 1 ) { --end; break; } else status = 0; } } if( end >= samples() ) --end; if( minima != m + 1 ) return 0; for( int i = begin; i <= end; ++i ) if( data[i] < value ) { value = data[i]; begin = i; } for( ; end >= begin; --end ) if( data[end] == value ) break; return ( begin + end ) / 2; } int Profile::minima( int th ) { if( limit_ < 0 ) initialize(); if( !samples() ) return 0; if( th < 1 ) th = ( mean() < 2 ) ? 2 : mean(); const int noise = limit_ / 40; const int dth = th - ( ( noise + 1 ) / 2 ), uth = th + ( noise / 2 ); if( dth < 1 ) return 1; int minima = ( data[0] < dth ) ? 1 : 0; int status = ( minima ) ? 1 : 0; for( int i = 1; i < samples(); ++i ) switch( status ) { case 0: if( data[i] < dth ) { status = 1; ++minima; } break; case 1: if( data[i] > uth ) status = 0; break; } return minima; } bool Profile::straight( int * const dyp ) { if( limit_ < 0 ) initialize(); if( samples() < 5 ) return false; const int xl = ( samples() / 30 ) + 1, yl = ( data[xl] + data[xl+1] ) / 2 ; const int xr = samples() - xl - 1, yr = ( data[xr-1] + data[xr] ) / 2 ; const int dx = xr - xl, dy = yr - yl; if( dx <= 0 ) return false; const int dmax = dx * ( ( samples() / 20 ) + 2 ); int faults = samples() / 10; for( int i = 0; i < samples(); ++i ) { int y = ( dx * yl ) + ( ( i - xl ) * dy ); int d = std::abs( ( dx * data[i] ) - y ); if( d >= dmax && ( ( dx * data[i] ) < y || ( i >= xl && i <= xr ) ) ) if( d > dmax || ( d == dmax && --faults < 0 ) ) return false; } if( dyp ) *dyp = dy; return true; } ocrad-0.24/profile.h000066400000000000000000000042071241541103500143370ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Profile { public: enum Type { left, top, right, bottom, height, width }; private: const Bitmap & bm; // Bitmap to witch this profile belongs // can be a Blob or a hole Type type; int limit_, max_, min_, mean_; signed char isconcave_, isconvex_, isflat_, isflats_, ispit_, istpit_, isupit_, isvpit_, istip_; std::vector< int > data; void initialize(); int mean(); public: Profile( const Bitmap & bm_, const Type t ); // const Bitmap & bitmap() const { return bm; } int limit() { if( limit_ < 0 ) initialize(); return limit_; } int max(); int max( const int l, int r = -1 ); int min(); int min( const int l, int r = -1 ); int operator[]( int i ); int pos( const int p ) { return ( ( samples() - 1 ) * p ) / 100; } int range() { return max() - min(); } int samples() { if( limit_ < 0 ) initialize(); return data.size(); } int area( const int l = 0, int r = -1 ); bool increasing( int i = 1, const int min_delta = 2 ); bool decreasing( int i = 1, int end = -1 ); bool isconcave(); bool isconvex(); bool isflat(); bool isflats(); bool ispit(); bool iscpit( const int cpos = 50 ); bool istpit(); bool isupit(); bool isvpit(); bool istip(); bool isctip( const int cpos = 50 ); bool isltip(); int imaximum(); int iminimum( const int m = 0, int th = -1 ); int minima( int th = -1 ); bool straight( int * const dyp ); }; ocrad-0.24/rational.cc000066400000000000000000000154271241541103500146540ustar00rootroot00000000000000/* Rational - Rational number class with overflow detection Copyright (C) 2005-2014 Antonio Diaz Diaz. This library is free software: you have unlimited permission to copy, distribute and modify it. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include "rational.h" #ifndef LLONG_MAX #define LLONG_MAX 0x7FFFFFFFFFFFFFFFLL #endif #ifndef LLONG_MIN #define LLONG_MIN (-LLONG_MAX - 1LL) #endif #ifndef ULLONG_MAX #define ULLONG_MAX 0xFFFFFFFFFFFFFFFFULL #endif namespace { int gcd( int n, int m ) // Greatest Common Divisor { if( n < 0 ) n = -n; if( m < 0 ) m = -m; while( true ) { if( m ) n %= m; else return n; if( n ) m %= n; else return m; } } long long llgcd( long long n, long long m ) // Greatest Common Divisor { if( n < 0 ) n = -n; if( m < 0 ) m = -m; while( true ) { if( m ) n %= m; else return n; if( n ) m %= n; else return m; } } const std::string overflow_string( const int n ) { if( n > 0 ) return "+INF"; if( n < 0 ) return "-INF"; return "NAN"; } int overflow_value( const int n ) { if( n > 0 ) return INT_MAX; if( n < 0 ) return -INT_MAX; return 0; } int lloverflow_value( const long long n ) { if( n > 0 ) return INT_MAX; if( n < 0 ) return -INT_MAX; return 0; } } // end namespace void Rational::normalize( long long n, long long d ) { if( d == 0 ) { num = lloverflow_value( n ); den = 0; return; } // set error if( n == 0 ) { num = 0; den = 1; return; } if( d != 1 ) { const long long tmp = llgcd( n, d ); n /= tmp; d /= tmp; } if( n <= INT_MAX && n >= -INT_MAX && d <= INT_MAX && d >= -INT_MAX ) { if( d >= 0 ) { num = n; den = d; } else { num = -n; den = -d; } } else { num = lloverflow_value( (d >= 0) ? n : -n ); den = 0; } } void Rational::normalize() { if( den == 0 ) return; // no op on error if( num == 0 ) { den = 1; return; } if( num < -INT_MAX ) { if( den < -INT_MAX ) den = -INT_MAX; num = overflow_value( -den ); den = 0; return; } if( den < 0 ) { if( den < -INT_MAX ) { num = overflow_value( -num ); den = 0; return; } num = -num; den = -den; } if( den != 1 ) { const int tmp = gcd( num, den ); num /= tmp; den /= tmp; } } Rational Rational::inverse() const { if( den <= 0 ) return *this; // no op on error Rational tmp; if( num > 0 ) { tmp.num = den; tmp.den = num; } else if( num < 0 ) { tmp.num = -den; tmp.den = -num; } else { tmp.num = overflow_value( den ); tmp.den = 0; } // set error return tmp; } Rational & Rational::operator+=( const Rational & r ) { if( den <= 0 ) return *this; // no op on error if( r.den <= 0 ) { num = r.num; den = 0; return *this; } // set error const long long new_den = (long long)den * r.den; const long long new_num = ( (long long)num * r.den ) + ( (long long)r.num * den ); normalize( new_num, new_den ); return *this; } Rational & Rational::operator*=( const Rational & r ) { if( den <= 0 ) return *this; // no op on error if( r.den <= 0 ) { num = r.num; den = 0; return *this; } // set error const long long new_num = (long long)num * r.num; const long long new_den = (long long)den * r.den; normalize( new_num, new_den ); return *this; } int Rational::round() const { if( den <= 0 ) return num; int result = num / den; const int rest = std::abs( num ) % den; if( rest > 0 && rest >= den - rest ) { if( num >= 0 ) ++result; else --result; } return result; } // Recognized formats: 123 123/456 123.456 .123 12% 12/3% 12.3% .12% // Values may be preceded by an optional '+' or '-' sign. // Returns the number of chars read from 's', or 0 if input is invalid. // In case of invalid input, the Rational is not changed. // int Rational::parse( const char * const s ) { if( !s || !s[0] ) return 0; long long n = 0, d = 1; // restrain intermediate overflow int c = 0; bool minus = false; while( std::isspace( s[c] ) ) ++c; if( s[c] == '+' ) ++c; else if( s[c] == '-' ) { ++c; minus = true; } if( !std::isdigit( s[c] ) && s[c] != '.' ) return 0; while( std::isdigit( s[c] ) ) { if( ( LLONG_MAX - (s[c] - '0') ) / 10 < n ) return 0; n = (n * 10) + (s[c] - '0'); ++c; } if( s[c] == '.' ) { ++c; if( !std::isdigit( s[c] ) ) return 0; while( std::isdigit( s[c] ) ) { if( ( LLONG_MAX - (s[c] - '0') ) / 10 < n || LLONG_MAX / 10 < d ) return 0; n = (n * 10) + (s[c] - '0'); d *= 10; ++c; } } else if( s[c] == '/' ) { ++c; d = 0; while( std::isdigit( s[c] ) ) { if( ( LLONG_MAX - (s[c] - '0') ) / 10 < d ) return 0; d = (d * 10) + (s[c] - '0'); ++c; } if( d == 0 ) return 0; } if( s[c] == '%' ) { ++c; if( n % 100 == 0 ) n /= 100; else if( n % 10 == 0 && LLONG_MAX / 10 >= d ) { n /= 10; d *= 10; } else if( LLONG_MAX / 100 >= d ) d *= 100; else return 0; } if( minus ) n = -n; Rational tmp; tmp.normalize( n, d ); if( !tmp.error() ) { *this = tmp; return c; } return 0; } // Returns a string representing the value 'num/den' in decimal point // format with 'prec' decimals. // 'iwidth' is the minimum width of the integer part, prefixed with // spaces if needed. // If 'prec' is negative, only the needed decimals are produced. // const std::string Rational::to_decimal( const unsigned iwidth, int prec ) const { if( den <= 0 ) return overflow_string( num ); std::string s; int ipart = std::abs( num / den ); const bool truncate = ( prec < 0 ); if( prec < 0 ) prec = -prec; do { s += '0' + ( ipart % 10 ); ipart /= 10; } while( ipart > 0 ); if( num < 0 ) s += '-'; if( iwidth > s.size() ) s.append( iwidth - s.size(), ' ' ); std::reverse( s.begin(), s.end() ); long long rest = std::abs( num ) % den; if( prec > 0 && ( rest > 0 || !truncate ) ) { s += '.'; while( prec > 0 && ( rest > 0 || !truncate ) ) { rest *= 10; s += '0' + ( rest / den ); rest %= den; --prec; } } return s; } // Returns a string representing the value 'num/den' in fractional form. // 'width' is the minimum width to be produced, prefixed with spaces if // needed. // const std::string Rational::to_fraction( const unsigned width ) const { if( den <= 0 ) return overflow_string( num ); std::string s; int n = std::abs( num ), d = den; do { s += '0' + ( d % 10 ); d /= 10; } while( d > 0 ); s += '/'; do { s += '0' + ( n % 10 ); n /= 10; } while( n > 0 ); if( num < 0 ) s += '-'; if( width > s.size() ) s.append( width - s.size(), ' ' ); std::reverse( s.begin(), s.end() ); return s; } ocrad-0.24/rational.h000066400000000000000000000141471241541103500145140ustar00rootroot00000000000000/* Rational - Rational number class with overflow detection Copyright (C) 2005-2014 Antonio Diaz Diaz. This library is free software: you have unlimited permission to copy, distribute and modify it. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ // Rationals are kept normalized at all times. // Invariant = ( gcd( num, den ) == 1 && den > 0 ). // Range extends from INT_MAX to -INT_MAX. // Maximum resolution is 1 / INT_MAX. // In case of domain error or overflow, den is set to 0 and num is set // to >0, <0 or 0, meaning +INF, -INF and NAN respectively. This error // condition can be tested with the 'error' function, and can only be // cleared assigning a new value to the Rational. // While in error state, arithmetic operators become no ops and // relational operators return false, except !=, which returns true. // class Rational { int num, den; void normalize( long long n, long long d ); void normalize(); public: Rational( const int n, const int d ) : num( n ), den( d ) // n / d { normalize(); } explicit Rational( const int n ) : num( n ), den( 1 ) // n / 1 { if( num < -INT_MAX ) { num = -INT_MAX; den = 0; } } Rational() : num( 0 ), den( 1 ) {} // zero Rational & assign( const int n, const int d ) { num = n; den = d; normalize(); return *this; } Rational & operator=( const int n ) { num = n; den = 1; if( num < -INT_MAX ) { num = -INT_MAX; den = 0; } return *this; } int numerator() const { return num; } int denominator() const { return den; } int sign() const { if( num > 0 ) return 1; if( num < 0 ) return -1; return 0; } bool error() const { return ( den <= 0 ); } // true if in error state const Rational & operator+() const { return *this; } // unary plus const Rational & operator+() { return *this; } // unary plus Rational operator-() const // unary minus { Rational tmp( *this ); tmp.num = -tmp.num; return tmp; } Rational abs() const { if( num >= 0 ) return *this; else return -*this; } Rational inverse() const; Rational & operator+=( const Rational & r ); Rational & operator-=( const Rational & r ) { return operator+=( -r ); } Rational & operator*=( const Rational & r ); Rational & operator/=( const Rational & r ) { return operator*=( r.inverse() ); } Rational & operator+=( const int n ) { return operator+=( Rational( n ) ); } Rational & operator-=( const int n ) { return operator-=( Rational( n ) ); } Rational & operator*=( const int n ) { return operator*=( Rational( n ) ); } Rational & operator/=( const int n ) { return operator/=( Rational( n ) ); } Rational operator+( const Rational & r ) const { Rational tmp( *this ); return tmp += r; } Rational operator-( const Rational & r ) const { Rational tmp( *this ); return tmp -= r; } Rational operator*( const Rational & r ) const { Rational tmp( *this ); return tmp *= r; } Rational operator/( const Rational & r ) const { Rational tmp( *this ); return tmp /= r; } Rational operator+( const int n ) const { Rational tmp( *this ); return tmp += n; } Rational operator-( const int n ) const { Rational tmp( *this ); return tmp -= n; } Rational operator*( const int n ) const { Rational tmp( *this ); return tmp *= n; } Rational operator/( const int n ) const { Rational tmp( *this ); return tmp /= n; } Rational & operator++() { return operator+=( 1 ); } // prefix Rational operator++( int ) // suffix { Rational tmp( *this ); operator+=( 1 ); return tmp; } Rational & operator--() { return operator-=( 1 ); } // prefix Rational operator--( int ) // suffix { Rational tmp( *this ); operator-=( 1 ); return tmp; } bool operator==( const Rational & r ) const { return ( den > 0 && num == r.num && den == r.den ); } bool operator==( const int n ) const { return ( num == n && den == 1 ); } bool operator!=( const Rational & r ) const { return ( den <= 0 || r.den <= 0 || num != r.num || den != r.den ); } bool operator!=( const int n ) const { return ( num != n || den != 1 ); } bool operator< ( const Rational & r ) const { return ( den > 0 && r.den > 0 && (long long)num * r.den < (long long)r.num * den ); } bool operator<=( const Rational & r ) const { return ( *this < r || *this == r ); } bool operator> ( const Rational & r ) const { return ( den > 0 && r.den > 0 && (long long)num * r.den > (long long)r.num * den ); } bool operator>=( const Rational & r ) const { return ( *this > r || *this == r ); } bool operator< ( const int n ) const { return operator< ( Rational( n ) ); } bool operator<=( const int n ) const { return operator<=( Rational( n ) ); } bool operator> ( const int n ) const { return operator> ( Rational( n ) ); } bool operator>=( const int n ) const { return operator>=( Rational( n ) ); } int round() const; // nearest integer; -1.5 ==> -2, 1.5 ==> 2 int trunc() const // integer part; -x.y ==> -x, x.y ==> x { if( den > 0 ) return ( num / den ); else return num; } int parse( const char * const s ); // returns parsed size const std::string to_decimal( const unsigned iwidth = 1, int prec = -2 ) const; const std::string to_fraction( const unsigned width = 1 ) const; }; inline Rational operator+( const int n, const Rational & r ) { return r + n; } inline Rational operator-( const int n, const Rational & r ) { return -r + n; } inline Rational operator*( const int n, const Rational & r ) { return r * n; } inline Rational operator/( const int n, const Rational & r ) { return Rational( n ) / r; } inline bool operator==( const int n, const Rational & r ) { return r == n; } inline bool operator!=( const int n, const Rational & r ) { return r != n; } inline bool operator< ( const int n, const Rational & r ) { return r > n; } inline bool operator<=( const int n, const Rational & r ) { return r >= n; } inline bool operator> ( const int n, const Rational & r ) { return r < n; } inline bool operator>=( const int n, const Rational & r ) { return r <= n; } ocrad-0.24/rectangle.cc000066400000000000000000000166261241541103500150110ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" namespace { void error( const char * const msg ) { Ocrad::internal_error( msg ); } } // end namespace Rectangle::Rectangle( const int l, const int t, const int r, const int b ) { if( r < l || b < t ) { if( verbosity >= 0 ) std::fprintf( stderr, "l = %d, t = %d, r = %d, b = %d\n", l, t, r, b ); error( "bad parameter building a Rectangle." ); } left_ = l; top_ = t; right_ = r; bottom_ = b; } void Rectangle::left( const int l ) { if( l > right_ ) error( "left, bad parameter resizing a Rectangle." ); left_ = l; } void Rectangle::top( const int t ) { if( t > bottom_ ) error( "top, bad parameter resizing a Rectangle." ); top_ = t; } void Rectangle::right( const int r ) { if( r < left_ ) error( "right, bad parameter resizing a Rectangle." ); right_ = r; } void Rectangle::bottom( const int b ) { if( b < top_ ) error( "bottom, bad parameter resizing a Rectangle." ); bottom_ = b; } void Rectangle::height( const int h ) { if( h <= 0 ) error( "height, bad parameter resizing a Rectangle." ); bottom_ = top_ + h - 1; } void Rectangle::width( const int w ) { if( w <= 0 ) error( "width, bad parameter resizing a Rectangle." ); right_ = left_ + w - 1; } void Rectangle::add_point( const int row, const int col ) { if( row > bottom_ ) bottom_ = row; else if( row < top_ ) top_ = row; if( col > right_ ) right_ = col; else if( col < left_ ) left_ = col; } void Rectangle::add_rectangle( const Rectangle & re ) { if( re.left_ < left_ ) left_ = re.left_; if( re.top_ < top_ ) top_ = re.top_; if( re.right_ > right_ ) right_ = re.right_; if( re.bottom_ > bottom_ ) bottom_ = re.bottom_; } void Rectangle::enlarge( const int scale ) { if( scale > 1 ) { left_ *= scale; top_ *= scale; right_ *= scale; bottom_ *= scale; } } void Rectangle::move( const int row, const int col ) { int d = row - top_; if( d ) { top_ += d; bottom_ += d; } d = col - left_; if( d ) { left_ += d; right_ += d; } } bool Rectangle::includes( const Rectangle & re ) const { return ( left_ <= re.left_ && top_ <= re.top_ && right_ >= re.right_ && bottom_ >= re.bottom_ ); } bool Rectangle::includes( const int row, const int col ) const { return ( left_ <= col && right_ >= col && top_ <= row && bottom_ >= row ); } bool Rectangle::strictly_includes( const Rectangle & re ) const { return ( left_ < re.left_ && top_ < re.top_ && right_ > re.right_ && bottom_ > re.bottom_ ); } bool Rectangle::strictly_includes( const int row, const int col ) const { return ( left_ < col && right_ > col && top_ < row && bottom_ > row ); } bool Rectangle::includes_hcenter( const Rectangle & re ) const { return ( left_ <= re.hcenter() && right_ >= re.hcenter() ); } bool Rectangle::includes_vcenter( const Rectangle & re ) const { return ( top_ <= re.vcenter() && bottom_ >= re.vcenter() ); } bool Rectangle::h_includes( const Rectangle & re ) const { return ( left_ <= re.left_ && right_ >= re.right_ ); } bool Rectangle::v_includes( const Rectangle & re ) const { return ( top_ <= re.top_ && bottom_ >= re.bottom_ ); } bool Rectangle::h_includes( const int col ) const { return ( left_ <= col && right_ >= col ); } bool Rectangle::v_includes( const int row ) const { return ( top_ <= row && bottom_ >= row ); } bool Rectangle::h_overlaps( const Rectangle & re ) const { return ( left_ <= re.right_ && right_ >= re.left_ ); } bool Rectangle::v_overlaps( const Rectangle & re ) const { return ( top_ <= re.bottom_ && bottom_ >= re.top_ ); } int Rectangle::v_overlap_percent( const Rectangle & re ) const { int ov = std::min( bottom_, re.bottom_ ) - std::max( top_, re.top_ ) + 1; if( ov > 0 ) ov = std::max( 1, ( ov * 100 ) / std::min( height(), re.height() ) ); else ov = 0; return ov; } bool Rectangle::is_hcentred_in( const Rectangle & re ) const { if( this->h_includes( re.hcenter() ) ) return true; int w = std::min( re.height(), re.width() ) / 2; if( width() < w ) { int d = ( w + 1 ) / 2; if( hcenter() - d <= re.hcenter() && hcenter() + d >= re.hcenter() ) return true; } return false; } bool Rectangle::is_vcentred_in( const Rectangle & re ) const { if( this->v_includes( re.vcenter() ) ) return true; int h = std::min( re.height(), re.width() ) / 2; if( height() < h ) { int d = ( h + 1 ) / 2; if( vcenter() - d <= re.vcenter() && vcenter() + d >= re.vcenter() ) return true; } return false; } bool Rectangle::precedes( const Rectangle & re ) const { if( right_ < re.left_ ) return true; if( this->h_overlaps( re ) && ( ( top_ < re.top_ ) || ( top_ == re.top_ && left_ < re.left_ ) ) ) return true; return false; } bool Rectangle::h_precedes( const Rectangle & re ) const { return ( hcenter() < re.hcenter() ); } bool Rectangle::v_precedes( const Rectangle & re ) const { if( bottom_ < re.vcenter() || vcenter() < re.top_ ) return true; if( this->includes_vcenter( re ) && re.includes_vcenter( *this ) ) return this->h_precedes( re ); return false; } int Rectangle::distance( const Rectangle & re ) const { return hypoti( h_distance( re ), v_distance( re ) ); } int Rectangle::distance( const int row, const int col ) const { return hypoti( h_distance( col ), v_distance( row ) ); } int Rectangle::h_distance( const Rectangle & re ) const { if( re.right_ <= left_ ) return left_ - re.right_; if( re.left_ >= right_ ) return re.left_ - right_; return 0; } int Rectangle::h_distance( const int col ) const { if( col <= left_ ) return left_ - col; if( col >= right_ ) return col - right_; return 0; } int Rectangle::v_distance( const Rectangle & re ) const { if( re.bottom_ <= top_ ) return top_ - re.bottom_; if( re.top_ >= bottom_ ) return re.top_ - bottom_; return 0; } int Rectangle::v_distance( const int row ) const { if( row <= top_ ) return top_ - row; if( row >= bottom_ ) return row - bottom_; return 0; } int Rectangle::hypoti( const int c1, const int c2 ) { long long temp = c1; temp *= temp; long long target = c2; target *= target; target += temp; int lower = std::max( std::abs( c1 ), std::abs( c2 ) ); int upper = std::abs( c1 ) + std::abs( c2 ); while( upper - lower > 1 ) { int m = ( lower + upper ) / 2; temp = m; temp *= temp; if( temp < target ) lower = m; else upper = m; } temp = lower; temp *= temp; target *= 2; target -= temp; temp = upper; temp *= temp; if( target < temp ) return lower; else return upper; } ocrad-0.24/rectangle.h000066400000000000000000000064411241541103500146450ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Rectangle { int left_, top_, right_, bottom_; public: Rectangle( const int l, const int t, const int r, const int b ); void left ( const int l ); void top ( const int t ); void right ( const int r ); void bottom( const int b ); void height( const int h ); void width ( const int w ); void add_point( const int row, const int col ); void add_rectangle( const Rectangle & re ); void enlarge( const int scale ); void move( const int row, const int col ); int left() const { return left_; } int top() const { return top_; } int right() const { return right_; } int bottom() const { return bottom_; } int height() const { return bottom_ - top_ + 1; } int width() const { return right_ - left_ + 1; } int size() const { return height() * width(); } int hcenter() const { return ( left_ + right_ ) / 2; } int vcenter() const { return ( top_ + bottom_ ) / 2; } int hpos( const int p ) const { return left_ + ( ( ( right_ - left_ ) * p ) / 100 ); } int vpos( const int p ) const { return top_ + ( ( ( bottom_ - top_ ) * p ) / 100 ); } bool operator==( const Rectangle & re ) const { return ( left_ == re.left_ && top_ == re.top_ && right_ == re.right_ && bottom_ == re.bottom_ ); } bool operator!=( const Rectangle & re ) const { return !( *this == re ); } bool includes( const Rectangle & re ) const; bool includes( const int row, const int col ) const; bool strictly_includes( const Rectangle & re ) const; bool strictly_includes( const int row, const int col ) const; bool includes_hcenter( const Rectangle & re ) const; bool includes_vcenter( const Rectangle & re ) const; bool h_includes( const Rectangle & re ) const; bool h_includes( const int col ) const; bool v_includes( const Rectangle & re ) const; bool v_includes( const int row ) const; bool h_overlaps( const Rectangle & re ) const; bool v_overlaps( const Rectangle & re ) const; int v_overlap_percent( const Rectangle & re ) const; bool is_hcentred_in( const Rectangle & re ) const; bool is_vcentred_in( const Rectangle & re ) const; bool precedes( const Rectangle & re ) const; bool h_precedes( const Rectangle & re ) const; bool v_precedes( const Rectangle & re ) const; int distance( const Rectangle & re ) const; int distance( const int row, const int col ) const; int h_distance( const Rectangle & re ) const; int h_distance( const int col ) const; int v_distance( const Rectangle & re ) const; int v_distance( const int row ) const; static int hypoti( const int c1, const int c2 ); }; ocrad-0.24/segment.cc000066400000000000000000000031121241541103500144710ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "segment.h" void Csegment::add_point( const int col ) { if( !valid() ) left = right = col; else if( col < left ) left = col; else if( col > right ) right = col; } void Csegment::add_csegment( const Csegment & seg ) { if( seg.valid() ) { if( !valid() ) *this = seg; else { if( seg.left < left ) left = seg.left; if( seg.right > right ) right = seg.right; } } } int Csegment::distance( const Csegment & seg ) const { if( !valid() || !seg.valid() ) return INT_MAX; if( seg.right < left ) return left - seg.right; if( seg.left > right ) return seg.left - right; return 0; } int Csegment::distance( const int col ) const { if( !valid() ) return INT_MAX; if( col < left ) return left - col; if( col > right ) return col - right; return 0; } ocrad-0.24/segment.h000066400000000000000000000027311241541103500143410ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ struct Csegment // cartesian (one-dimensional) segment { int left, right; // l > r means no segment // in vertical segments, left is top explicit Csegment( const int l = 1, const int r = 0 ) : left( l ), right( r ) {} void add_point( const int col ); void add_csegment( const Csegment & seg ); bool valid() const { return ( left <= right ); } int size() const { return ( left <= right ) ? right - left + 1 : 0; } bool includes( const Csegment & seg ) const { return ( seg.valid() && left <= seg.left && seg.right <= right ); } bool includes( const int col ) const { return ( left <= col && col <= right ); } int distance( const Csegment & seg ) const; int distance( const int col ) const; }; ocrad-0.24/testsuite/000077500000000000000000000000001241541103500145545ustar00rootroot00000000000000ocrad-0.24/testsuite/check.sh000077500000000000000000000104201241541103500161650ustar00rootroot00000000000000#! /bin/sh # check script for GNU Ocrad - Optical Character Recognition program # Copyright (C) 2009-2014 Antonio Diaz Diaz. # # This script is free software: you have unlimited permission # to copy, distribute and modify it. LC_ALL=C export LC_ALL objdir=`pwd` testdir=`cd "$1" ; pwd` OCRAD="${objdir}"/ocrad OCRADCHECK="${objdir}"/ocradcheck framework_failure() { echo "failure in testing framework" ; exit 1 ; } if [ ! -f "${OCRAD}" ] || [ ! -x "${OCRAD}" ] ; then echo "${OCRAD}: cannot execute" exit 1 fi if [ -d tmp ] ; then rm -rf tmp ; fi mkdir tmp cd "${objdir}"/tmp in="${testdir}"/test.pbm txt="${testdir}"/test.txt utxt="${testdir}"/test_utf8.txt fail=0 printf "testing ocrad-%s..." "$2" "${OCRAD}" -q -T-0.1 ${in} > /dev/null if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi "${OCRAD}" -q -T 1.1 ${in} > /dev/null if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi "${OCRAD}" -q -u -2,-1,1,1 ${in} > /dev/null if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi "${OCRAD}" -q -u 1,1,1,1 ${in} > /dev/null if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi "${OCRAD}" -q ${in} > out || fail=1 cmp ${txt} out || fail=1 printf . "${OCRAD}" -q -u 0,0,1,1 ${in} > out cmp ${txt} out || fail=1 printf . "${OCRAD}" -q -u -1,-1,1,1 ${in} > out cmp ${txt} out || fail=1 printf . test_chars() { for coord in ${coords} ; do produced_chars="${produced_chars}`"${OCRAD}" -q -u${coord} ${in}`" || fail=1 done if [ "${produced_chars}" != "${expected_chars}" ] ; then echo echo "expected \"${expected_chars}\"" echo "produced \"${produced_chars}\"" fail=1 fi printf . } coords=' 71,109,17,26 92,109,17,26 114,109,15,26 132,109,17,26 152,109,18,26 172,109,19,26 193,109,17,26 214,109,17,26 234,108,17,27 253,109,18,26 274,109,17,26 68,153,29,27 97,153,24,27 126,153,23,27 153,153,27,27 183,153,24,27 210,153,23,27 237,153,27,27 266,153,30,27 298,153,13,27 313,153,20,27 335,153,29,27 365,153,23,27 391,153,34,27 426,153,30,27 69,189,30,35 102,197,26,27 132,197,24,27 159,197,26,34 188,197,26,27 217,197,20,27 241,197,24,27 266,197,30,27 297,197,28,27 326,197,37,27 364,197,27,27 390,197,28,27 420,197,21,27' expected_chars="0ol23456789ABcDEFGHIJKLMNÑopQRsTuvwxYz" produced_chars= test_chars coords=' 71,250,18,18 90,240,20,28 112,250,15,18 131,240,19,28 152,250,17,18 170,241,16,27 183,249,20,27 204,240,23,28 227,241,11,27 236,241,11,35 251,240,22,28 274,240,11,28 287,250,32,18 321,250,22,18 70,288,22,25 92,295,17,18 111,295,19,26 132,295,20,26 152,295,16,18 169,295,14,18 185,288,13,25 200,295,22,18 221,295,20,18 242,295,27,18 270,295,20,18 289,295,20,26 310,295,16,18' expected_chars="abcdefghijklmnñopqrstuvwxyz" produced_chars= test_chars coords=' 68,366,29,36 97,366,24,36 124,366,13,36 140,366,26,36 168,366,30,36 208,366,29,36 237,366,24,36 265,366,13,36 281,366,26,36 308,366,30,36 349,368,29,34 378,368,24,34 405,368,13,34 421,368,26,34 449,368,30,34 68,410,29,36 97,410,24,36 124,410,13,36 140,410,26,36 167,410,30,36 71,463,18,27 91,463,17,27 109,463,11,27 123,463,17,27 142,463,22,27 177,463,18,27 198,463,17,27 216,463,11,27 229,463,17,27 249,463,22,27 284,466,18,24 305,466,17,24 323,466,12,24 336,466,17,24 356,466,22,24 391,463,18,27 411,463,17,27 431,463,10,27 443,463,17,27 462,463,22,27' expected_chars="ÁÉÍóúÀÈÌòùÄËÏöüÂÊÎôûáéíóúàèìòùäëïöüâêîôû" produced_chars= test_chars coords='137,516,19,19 174,508,15,15 192,508,11,27 245,509,19,26 268,505,17,35 322,508,27,27 353,508,10,31 367,508,9,31 70,558,15,29 86,552,14,27 104,552,9,31 128,552,15,27 158,552,9,31 173,552,17,15 195,552,8,31 215,552,3,27 228,552,9,31 252,560,19,19 275,560,19,19 347,561,15,26 364,552,23,35 391,552,25,27 72,612,18,6 94,613,19,11 114,602,19,22 134,597,12,15 150,597,11,15' expected_chars="+*/#$&()¿?[\\]^{|}<>çÇ@~¬±ªº" produced_chars= test_chars "${OCRADCHECK}" ${in} > out || fail=1 cmp ${txt} out || fail=1 printf . "${OCRADCHECK}" ${in} --utf8 > out || fail=1 cmp ${utxt} out || fail=1 printf . echo if [ ${fail} = 0 ] ; then echo "tests completed successfully." cd "${objdir}" && rm -r tmp else echo "tests failed." fi exit ${fail} ocrad-0.24/testsuite/test.pbm000066400000000000000000001542331241541103500162430ustar00rootroot00000000000000P4 560 792 @€ À>pøà€ðÿàÀøðÿ€àÿÀþàÿüÿà?ðþÀà<€p<€ààø<€Àøð>ÁÀÀð>€ðàx<€Àà>8À<Àà~pàx>€Àà8À<Àà~pà€x€Àà0à<ààïxà€ø€3ÀÀð8<ààïxà€ø3ÀøÀü8<àáÏxàðcÀ?þÇàÿà<àáÏxààüÃÀàãxààüÃÀ8ƒøx?ðàãxàÀƒÀ0ƒð<?øàçxà€À Ãà>8óüýàçxà†ÀÃà8À~ñàîxàÆÀÃÀ8Ààîxà88Ïÿþ8ÃÀx€àüxà`|Ïÿþ|ÃÀx€ÀüpàÀüÀÀ|ÃÀø€ Àø€ðà€øÀÀ|Áàø€Àx€àà8øÀÀxà>ø€>€p€ààÿøà€Àp€ð<øÀ>?ÿŸÿøøüðàðàðpÿðþ0ÿðÿÿü?ÿÿ€þ ?þÿ?þùÿñÿçÿÀÿ€ÿ¿àÿpÿøÿðÿüÿÿü?ÿÿ€ÿð?þÿ?þùÿñÿçÿÀÿÀÿ¿ðÿð€~ð€þüÀ€~ðÀàà€?|Àðððø€8ð€<À€øðÀàà€<xÀðøðø€xx€€À€ððÀàà€pxàðü`ü€ðx€ÀÀððÀàà€`xàðþ`¼€ðx€à ÀƒàpÀàà€Àxàð¾`¾€ð8€à ÀáƒàpÀàà€€xð ðŸ`>€ð8€à Àá‡à0Ààà€xð ð€`€à8€ðÀà‡à0Ààà€xððÀ`€à€ðÁàÀÀàà€xøð‡À`€øà€ðÃàÀÀàà€?xøðƒà`€ÿàà€ðÿÿàÀÿÿàà€xx8ðð` €ÿøà€ðÿÿàÀÿÿàà€ï€x|0ð€ð` €€üà€ðÃàÀÿÀàà€ÇÀx|0ð€ø`À€à€ðÀàÀÿÀàà€‡Àx>pð€|`ÿÀ€à€ðÀàÀÿÀàà€àx>`ð€>`?ÿà€ƒà€ðÀàÀðÀàà€ðx8>`ð€`0à€ð€àÀàÀðÀàà?€øx0àð€`0ð€ð€àÀàðÀàà€øx0Àð€àpð€ð8€ÀÀàðÀàà€|x0Àð€à`ð€€ø0€ÀÀàðÀàà~€~xpÀð€ààø€€xp€€ÀððÀàà`€>xp€ðÀààø€<à€ÀøðÀààpxð<€ðÀàð|À~À€~þÀ~ðÀàà8?€|ð>€ðààþÿ¿ÿüÿ€ÿüÿÿþ?þ?þ0?þÿ?þþÿñÿ÷ÿÿñÿÇÿŸü`þÿ¿ÿðþÿàÿÿü?þø0?þÿ?þøÿñÿ÷ÿÿñÿÃÿŸü`@ø`ÿÀ€üÿàþÿþÿÿüþ0ÿÿ?þÿüûÿŸüïÿûÿáÿÇÿÿ€þÿàÿ€ÿÿ€ÿÀÿÿÿðÿÿ?þÿüûÿŸüïÿûÿáÿÇÿÿ~>>àðÀðð€ð|?àøÀà|àðà~>à?xððà<xðÀðxàxÀÀ|àø€>€>?€ øxðð<<ðà8ðpàpÀ€<ð|<?À ð|ððx>ðà8ppà0à€>ð|€8|7À ð<ððøðà8p`à0à€>ð>€pø3à ð>ððøðà<0`à0ðð À`ð1ð à>ððøðà<0`à0ðø ààð1ø àðñð€ðÀ?0`à0ðø¸àÀà0ø àðáð€ðÀðÀà0øxðñÀÀ0| àðÁð€ð€þà0øƒ|àñ€À0> àðð€ÿþÿÀà0| ‡<8àû€€0 àÿÿð€ÿðÿàà0| †<0ð€0 àÿüð€ð|ÿðà0<†>0ð?0Œàðð€ððà0>Îpø>>0Ìàðð€ð@øà0>8Ìp|>0Ìàðð|ð`xà00Ì`~|€0ìà>ðùþð`8à00ü` >ø€0üð>ðùÇð`8à0pøàø€0üð<ð{ƒžð€p8àpàøÀ8€ð€0|ð|ð{üðŒp8ð`àøÀp€à€8|xxð?üðŒx8ðàÀðÀ`Ààx<|ððøðÌ|pøÀÀð€ààÀü?àðððø€à>þ€Àð€ðð>€?ÿ€ ÿ€ÿ€ÿàÿ‡øwÿÀÿà?ÿ€p€þ?þÿàÿÿÿ€ þÿ€ÿàÿƒðaÿÿàü€`þ?þÿàÿÿðñ€ñ€ù€ÿ€>€àðàÀ?€à?€ðÀ à?À?€àÿÀðàà?À€àãÀðààÀ€àÃÀðÀ àÀ€àÁÀðàÀ€àÀðàÀ€àÀðàÀ€àÀðàÀ€àÀÀðàÀàŸ€ðùàð?üóàñðàçþÀ|€ø¿àüýàø?üþðóüáþçþÁýþ?À?Àð|ñðà<À>>àþ<áþàøÁÿqàãÀà<Àø<>à<À<@øàààÀ>Áðàð<Àxx>àxÀxøàáÀÀ>Àððð<€|x>àxÀxðàã€À>Àððà<€|ðàxÀxðàçÀ<€ððü€<ðàøƒÀxðàïÀ<€ððü€<ðàÿÿƒÀ|ðàÿ€À<€ðð><€<ðàÿÿƒÀ<ðà÷€À<€ððp<€<ðàøÀ|ðàóÀÀ<€ððð<€<ðàøÀððàãàÀ<€ððà<€<øàxÀ<ðàáðÀ<€ððà<ÀxxàxÀ`ðààðÀ<€ððà|Àp| à|À`ðààxÀ<€ðððü‡ñà>‡à>Àþðàà|À<€ððÿ߯ÀøýüüøÿþïüûÿŸùÿ¿÷þçþ ?€àùüðø?ÿ‡þïüûÿŸùÿ¿÷þçþ8?€ð€à€à€à€<ø8þðøàÆü0øøø|ü~ <Ìø>Çü¿Ïóûÿ¿çùÿøùþÿýþÿ˜þ~üÿøþÇü¿Ïóûÿ¿çùÿøÿãÁÿãøþß0|þÁðÀpüáàø|à>ƒ€øŸ`Àð À`|ÁÀð|‡€ð>‡€ø` ÀðÀ`>€Áàx‡€ð>Ç€x p ÀxƒàÀ0ƒàxx<Ïx|ÀxƒàÀ`ÁƒÀxx<ÏxàÀ<0†ðÀÀÀ€xx<Ïx?øÀ<0Æñ€ÀÃxx<ÏxüÀ0Æq€àãxx<ÏxþÀ`ì{€àæxx<Ïx`> À`ì{ðö<x€ø<Ï€ø` ÀÀø? øþxx‡€ð>‡€øp ÀÀø>x|xx‡€à>‡Àøx À€ø0<|ðxƒãÀ?áø~ À€pp?xàxÿ?ñÿ€=þÿxÿÀøðûü€pþÿÀ8ÿøÿ?ð<ü~xÿÀgðàãüp þÿÀ8ÿø<x0<x0<x`<xx`<xxÀ<xy€ÿ€ÿÿ€ÿ>` `0à€à<ð8ðÀà<xx€<xàƒ0`ƒ €pàÀ<ðÇ€pðÇ€88àÀ`8Ç€xðÇ€` `€0€ ƒ0`ƒ €€€@pÿÿÿ€øÿøüÿÿðüÀÿÿÀ8?ÿÿ‡ÿÀüüþpÿÿÿ€?þÿøüÿÿðüÿðÿÿÀ8?ÿÿ‡ÿÀÿüþð€?xø€€àxðÀÀüx>xÀ€<|ÀÀðø€xàÀ€à€xðÀx|À€<ðàÀðø€xàà€À€xpÀx|À€<ððÀàü€xÀð€ÀÀxpÀ€x þÀ€<àøÀ`¼€xÀð€ÀÀx0À>€x ÞÀ€<àxÀ`¾ÃxÀø€À;àx0À>Àx ßÀá€<à|À`>Ãx€ø€À3àx0À|Àx ŸÀá€<À|À`Áx€|€À1àxÀ|àx Àà€<À>À`ƒÀx€|€Àqðx<À|àx €Áà<À>À`‡Àx€|€À`ðx|À|àx €Ãà<À>À`€ÿÀx€|€ÀàøüÀ|àx Àÿà<À>À` €ÿÀx€|€ÀÀøüÀ|àx Àÿà<À>À` €‡Àx€|€ÀÀxx|À|àx ÀÃà<À>À`ÀÀx€|€ÀÀ|xÀ|àx àÀà<À>À`ÿÀÀx€|€ÀÿüxÀ|àx ÿàÀà<À>À`?ÿàÁ€x€|€ÀÿþxÀ|àx ÿðÀàÀ<À>À`0àÁ€x€ø€À>xÀ<Àx ðÀàÀ<À|À`0ð€€xÀø€Àx8À>Àx øÀÀ<à|À`pð€€xÀð€Àx8À>€x8øÀÀ<àxÀà`ð€€xÀðÀ€x8À€|0xÀÀ<àøàÀàø€€xààÀ€€xxÀ|8p|ÀÀ<ððàÀàø€€xðÀà€xxÀ€>pp|ÀÀ<øàð€ð|À?€øü€øÀ|øÀà|?àø>àÀ|~Àüþÿ¿ÿÿÿ€?þÿüÿàûÿÿøüÿðÿÀÿÿßÿÿÇÿÀÿþþÿ¿ÿÿÿ€ø?ðÿàûÿÿðüÀÿÿÿßÿÿ‡ÿÀüøppÀøøà€ü€üðÀŒ9€Ì0à`À 8`À` 0€ @pÿÿÿ€øÿð?øpÿÿÿ€?þÿð?øð€?xø€Àø€xàÀÀø€xàà€ü€xÀð€¼€xÀð€¾ÃxÀø€>Ãx€ø€Áx€|€ƒÀx€|€‡Àx€|€€ÿÀx€|€ €ÿÀx€|€ €‡Àx€|€ÀÀx€|€ÿÀÀx€|€?ÿàÁ€x€|€0àÁ€x€ø€0ð€€xÀø€pð€€xÀð€`ð€€xÀð€àø€€xàà€àø€€xðÀÀð|À?€øü€ð<þÿ¿ÿÿÿ€?þÿøþÿ¿ÿÿÿ€øà€€ @À0€8À€Àpàà€€<<8Àx€€<xððÀÀ|~|àðx€<8xàÃÀ ppàînîpÀ<àÀðãÀ><øð`ÇÃÇ0€8p8Àà8ÃÀpð00ƒ`À`0€ €  ` ƒÂ€ à~xþøøÀ?€àÿøàðàà~<þøøÿøÿøþ?àÿÀàÿÀüàøáüøÿüÿøð|LJøǃø<xðÿñààƒàà<áüð|Çüǃøà<ÀxÀxxð8Àðààà8à<à<À<Àxð<àxàx|à<Àxàà<àxà<ð<à<àxð<àxàx|à<Àxàà<àxà<ð<à<àxà<àxðx8à<€<àà<àðà<à<à<ðxüðxðxà>€<àà|Áàðà<üð<ðxüÿðxðxÿÿþ€<ààÿÁàðà<üÿð<ðx><ÿðxðxÿþ€<àñàÿÁàðà<><ÿð<ðxp<xðxà€<àà|àðà<p<<ðxð<xðx<à€<àà|àðà<ð<<ðxà<xðxxàÀ|àà<àøà<à<<ðxà< xàxxàÀxàà<àxà<à< <àxà|€`xÀxxð Àpàà>àxà|à|€`<Àxðü‡ÁÀxÇ€|?|? ø8ñà‡à‡äà><ðüðü‡ÁÀ<Ç€|?ÿßÃÿƒÿÿ?ïð?÷ððàÿÀýþþþþüøÿ¿ÀÿßÃÿÿƒÿ?ïðþÿþðÃÀÀà?€ñþøxøüð~?Àþÿ€þð`Çp?€a€àÇ€8ÿ€øÀÀÀàÇ8ÿÀžñà€`„8À‡8˜à‡þààpÄxÀ‡8püàà8åð€ƒ8ðàà<?Àƒ8ð>áàƒàƒ8pð< 0áÀp?à‚8pà| póÀ<àpåðàÿÿx `÷€<ð>pÄxÀÿÿØxàþ<ð>p„88pøx0Àøxð>p 8pÿ€xÿÿpÀpàð>xÿðÀÿþø>xÿðÿ€Àÿþx <<xÿðÿ€8Àpàx | <Žxpÿ€0Àpà˜8ø<ÌxppÀ`À˜8ø>ø<ÿÿppÀáÀ˜80ð>ø<ÿÿp`ÀáÀ8pð0?ø<ÿÿpà>pàÀáÀp`ð €ü8øð>pÀÀáÀàÀxÀÃÿðøø>pÀÀáÀßÀÀ?€ÿ?à8øøpÀÀáÀÿ€€üÀ8XXà<pà€`80àÀÀ00ÿüàa€`àÿüàÀ€à€àÀ€ÿ€€þàÀ08øÀÿÿÀÿ€ÀþÀÀ€8<?ÿÀÿÀƒàðÀ?€ÀÀ<8xÀððð`?€ÀÀ8àÀ<8Àððp{€ÀÀààxàðð0sÀÀÀ€Ààpàðð8ñàÀÀÀÀààyÆÀððààÀÀ ÀàÀÿÇ€ððàðÀÀÀáÁçÇ€àð ÀpÀÀ>þ€áÃǃàðÀxÀÀþÀÿ€A‡‡ƒÀð€8ÀÀøðÃÏ€ƒ€ð€<ÀÀàü‡Ï€ƒð€Àà€€Ï€Ÿ‡ð€ÀøþàÏ€ž ðÀ<Àxøø€žðÀÀðÀü€Þ ðàÀà€|€!Þð`ÀÀàüÀ`Ï>8ð0ÀÀøøÀ`çîpð8ÀÀþàÀàcÏÆ8ðÀÀ€€ƒàÀpŽxðÀÀàüÃàÀ8ð>ð ÀÀøðð€8à>ðÀÀþÀÀüðà>ðÀÀ>ÿþÿÀÀpðÀÀþøÿÀøðÀxÀøð€À`Àøÿ€þÀ€|Ààxÿ€þàþàà0àððààüÀ?€ø€þ|‡ÇÃÀ?ƒ€8óƒ€8ƒƒ€8ƒ€8Ç8ÀÆ8ùÀ|88?ÿùÿÿ€?ÿùÿÿ€>?ÿøÃÿÿ€8ÿþÿãÃÿÿ€8ÿþãÿƒÿÿ€8ÿþ€ÿ€8>€8€8€8€¿ÿø¿ÿø¿ÿøocrad-0.24/testsuite/test.txt000066400000000000000000000002511241541103500162720ustar00rootroot0000000000000000123456789 ABCDEFGHIJKLMN ÑOPQRSTUVWXYZ abcdefghijklmn ñopqrstuvwxyz ÁÉÍÓÚ ÀÈÌÒÙ ÄËÏÖÜ ÂÊÎÔÛ áéíóú àèìòù äëïöü âêîôû ,.;:_+-*/¡!"#$%&()= ¿?[ \ ]^{|} <>` ' çÇ@ ~¬±ªº÷ ocrad-0.24/testsuite/test_utf8.txt000066400000000000000000000003341241541103500172420ustar00rootroot0000000000000000123456789 ABCDEFGHIJKLMN ÑOPQRSTUVWXYZ abcdefghijklmn ñopqrstuvwxyz ÃÉÃÓÚ ÀÈÌÒÙ ÄËÃÖÜ ÂÊÎÔÛ áéíóú àèìòù äëïöü âêîôû ,.;:_+-*/¡!"#$%&()= ¿?[ \ ]^{|} <>` ' çÇ@ ~¬±ªº÷ ocrad-0.24/textblock.cc000066400000000000000000000376661241541103500150530ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "rational.h" #include "rectangle.h" #include "track.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" #include "textblock.h" namespace { void insert_line( std::vector< Textline * > & textlinep_vector, int i ) { textlinep_vector.insert( textlinep_vector.begin() + i, new Textline ); } void delete_line( std::vector< Textline * > & textlinep_vector, int i ) { delete textlinep_vector[i]; textlinep_vector.erase( textlinep_vector.begin() + i ); } // Build the vertical composite characters. // void join_characters( std::vector< Textline * > & tlpv ) { for( unsigned current_line = 0; current_line < tlpv.size(); ++current_line ) { Textline & line = *tlpv[current_line]; for( int i = 0 ; i < line.characters() - 1; ) { Character & c1 = line.character( i ); bool joined = false; for( int j = i + 1 ; j < line.characters(); ++j ) { Character & c2 = line.character( j ); if( !c1.h_overlaps( c2 ) ) continue; Character *cup, *cdn; if( c1.vcenter() < c2.vcenter() ) cup = &c1, cdn = &c2; else cup = &c2, cdn = &c1; if( cdn->includes_hcenter( *cup ) || cup->includes_hcenter( *cdn ) || ( cdn->top() > cup->bottom() && cdn->hcenter() < cup->hcenter() ) || ( cdn->blobs() == 2 && 2 * cdn->blob( 0 ).size() < cdn->blob( 1 ).size() && cdn->blob( 0 ).includes_vcenter( *cup ) ) ) { int k; if( 64 * c1.size() < c2.main_blob().size() ) k = i; else if( 64 * c2.size() < c1.main_blob().size() ) k = j; else if( cdn == &c2 ) { c2.join( c1 ); k = i; } else { c1.join( c2 ); k = j; } line.delete_character( k ); joined = true; break; } } if( !joined ) ++i; } } } } // end namespace Textblock::Textblock( const Rectangle & page, const Rectangle & block, std::vector< Blob * > & blobp_vector ) : Rectangle( block ) { std::vector< Blob * > pending; std::vector< Blob * > pending_tall; std::vector< Blob * > pending_short; for( unsigned begin = 0, end = 0; end < blobp_vector.size(); begin = end ) { int botmax = blobp_vector[begin]->bottom(); // make cuts while( ++end < blobp_vector.size() ) { if( blobp_vector[end]->top() > botmax ) break; botmax = std::max( botmax, blobp_vector[end]->bottom() ); } // Classify blobs by height. unsigned samples = 0; std::vector< int > height_distrib; for( unsigned i = begin; i < end; ++i ) { if( blobp_vector[i]->is_abnormal() ) continue; unsigned h = blobp_vector[i]->height(); if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } if( height_distrib.empty() ) // all blobs are abnormal for( unsigned i = begin; i < end; ++i ) { unsigned h = blobp_vector[i]->height(); if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } int mean_height = 0; int valid_samples = 0; for( unsigned i = 0, count = 0; i < height_distrib.size(); ++i ) { int a = height_distrib[i]; if( 10 * ( count + a ) >= samples && 10 * count < 9 * samples ) { mean_height += a * i; valid_samples += a; } count += a; } if( valid_samples ) mean_height /= valid_samples; for( unsigned i = begin; i < end; ++i ) { Blob * const p = blobp_vector[i]; const bool a = p->is_abnormal(); if( p->height() >= 2 * mean_height || ( a && p->height() > mean_height ) ) pending_tall.push_back( p ); else if( 2 * p->height() <= mean_height || p->height() <= 5 || ( a && p->height() < mean_height ) ) pending_short.push_back( p ); else pending.push_back( p ); } } if( pending.empty() ) { for( unsigned i = 0; i < blobp_vector.size(); ++i ) delete blobp_vector[i]; blobp_vector.clear(); return; } blobp_vector.clear(); // Assign normal blobs to characters and create lines. { int min_line = 0; // first line of current cut tlpv.push_back( new Textline ); int current_line = min_line = textlines() - 1; tlpv[current_line]->shift_characterp( new Character( pending[0] ) ); for( unsigned i = 1; i < pending.size(); ++i ) { Blob & b = *pending[i]; current_line = std::max( min_line, current_line - 2 ); while( true ) { const Character *cl = 0, *cr = 0; for( int j = tlpv[current_line]->characters() - 1; j >= 0; --j ) { const Character & cj = tlpv[current_line]->character( j ); if( !b.includes_hcenter( cj ) && !cj.includes_hcenter( b ) ) { if( b.h_precedes( cj ) ) cr = &cj; else { cl = &cj; break; } } } if( ( cl && ( cl->includes_vcenter( b ) || b.includes_vcenter( *cl ) ) ) || ( cr && ( cr->includes_vcenter( b ) || b.includes_vcenter( *cr ) ) ) ) { tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } else if( ( cl && cl->top() > b.bottom() ) || ( cr && cr->top() > b.bottom() ) ) { insert_line( tlpv, current_line ); tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } else if( ( cl && cl->v_overlap_percent( b ) > 5 ) || ( cr && cr->v_overlap_percent( b ) > 5 ) ) { tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } else if( ++current_line >= textlines() ) { tlpv.push_back( new Textline ); current_line = textlines() - 1; tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } } } for( int i = textlines() - 1; i >= 0; --i ) if( !tlpv[i]->characters() ) delete_line( tlpv, i ); join_characters( tlpv ); // Create tracks of lines. for( int i = 0; i < textlines(); ++i ) tlpv[i]->set_track(); // Insert tall blobs. // Seek up, then seek down, needed for slanted or curved lines. current_line = 0; for( unsigned i = 0; i < pending_tall.size(); ++i ) { Blob & b = *pending_tall[i]; while( current_line > 0 && b.bottom() < tlpv[current_line]->vcenter( b.hcenter() ) ) --current_line; while( current_line < textlines() && b.top() > tlpv[current_line]->vcenter( b.hcenter() ) ) ++current_line; if( current_line >= textlines() ) { --current_line; delete &b; continue; } Textline & l = *tlpv[current_line]; const int bi = l.big_initials(); const int mh = l.mean_height(); if( b.height() <= 3 * mh && ( b.height() <= 2 * mh || l.character( bi ).left() < b.left() ) ) l.shift_characterp( new Character( &b ) ); else if( !l.characters() || l.character( std::min( bi+1, l.characters() - 1 ) ).left() > b.hcenter() ) l.shift_characterp( new Character( &b ), true ); // big initial else delete &b; } // for( int i = 0; i < textlines(); ++i ) tlpv[i]->verify_big_initials(); // Insert short blobs. // Seek up, then seek down, needed for slanted or curved lines. current_line = 0; for( unsigned i = 0; i < pending_short.size(); ++i ) { Blob & b = *pending_short[i]; while( current_line > 0 && b.bottom() < tlpv[current_line]->top( b.hcenter() ) ) --current_line; int temp = std::max( 0, current_line - 1 ); while( current_line < textlines() && b.top() > tlpv[current_line]->bottom( b.hcenter() ) ) ++current_line; if( current_line >= textlines() ) { const Textline & l = *tlpv[--current_line]; const Character *p = l.character_at( b.hcenter() ); if( b.top() > l.bottom( b.hcenter() ) + l.height() / 2 && ( !p || b.top() > p->bottom() + l.height() / 2 ) ) { delete &b; continue; } else temp = current_line; } if( current_line - temp > 1 ) temp = current_line - 1; if( current_line != temp && 2 * ( b.top() - tlpv[temp]->bottom( b.hcenter() ) ) < tlpv[current_line]->top( b.hcenter() ) - b.bottom() ) current_line = temp; tlpv[current_line]->shift_characterp( new Character( &b ) ); } // remove clipped lines at top or bottom of page if( textlines() > 2 ) { const Textline * lp = tlpv[textlines()-1]; for( int i = 0, c = 0; i < lp->characters(); ++i ) if( lp->character( i ).bottom() >= page.bottom() && 2 * ++c >= lp->characters() ) { delete_line( tlpv, textlines() - 1 ); break; } lp = tlpv[0]; const int t = std::max( page.top(), 1 ); for( int i = 0, c = 0; i < lp->characters(); ++i ) if( lp->character( i ).top() <= t && 2 * ++c >= lp->characters() ) { delete_line( tlpv, 0 ); break; } } } // Second pass. Join lines of i-dots and tildes. for( int current_line = 0; current_line < textlines() - 1; ) { bool joined = false; Textline & line1 = *tlpv[current_line]; Textline & line2 = *tlpv[current_line+1]; if( line1.characters() <= 2 * line2.characters() && 2 * line1.mean_height() < line2.mean_height() ) for( int i1 = 0; !joined && i1 < line1.characters(); ++i1 ) { Character & c1 = line1.character( i1 ); if( 2 * c1.height() >= line2.mean_height() ) continue; for( int i2 = 0; !joined && i2 < line2.characters(); ++i2 ) { Character & c2 = line2.character( i2 ); if( c2.right() < c1.left() ) continue; if( c2.left() > c1.right() ) break; if( ( c2.includes_hcenter( c1 ) || c1.includes_hcenter( c2 ) ) && c2.top() - c1.bottom() < line2.mean_height() ) { joined = true; line2.join( line1 ); delete_line( tlpv, current_line ); } } } if( !joined ) ++current_line; } join_characters( tlpv ); for( int i = 0; i < textlines(); ++i ) tlpv[i]->verify_big_initials(); // Fourth pass. Remove noise lines. if( textlines() >= 3 ) { for( int i = 0; i + 2 < textlines(); ++i ) { Textline & line1 = *tlpv[i]; Textline & line2 = *tlpv[i+1]; Textline & line3 = *tlpv[i+2]; if( line2.characters() > 2 || line1.characters() < 4 || line3.characters() < 4 ) continue; if( !Ocrad::similar( line1.height(), line3.height(), 10 ) ) continue; if( 8 * line2.height() > line1.height() + line3.height() ) continue; delete_line( tlpv, i + 1 ); } } // Remove leading and trailing noise characters. for( int i = 0; i < textlines(); ++i ) { Textline & l = *tlpv[i]; if( !l.big_initials() && l.characters() > 2 ) { const Character & c0 = l.character( 0 ); const Character & c1 = l.character( 1 ); const Character & c2 = l.character( 2 ); if( c0.blobs() == 1 && 4 * c0.size() < c1.size() && c1.left() - c0.right() > 2 * l.height() && 4 * c0.size() < c2.size() && c2.left() - c1.right() < l.height() ) l.delete_character( 0 ); } if( l.characters() > 2 ) { const Character & c0 = l.character( l.characters() - 1 ); const Character & c1 = l.character( l.characters() - 2 ); const Character & c2 = l.character( l.characters() - 3 ); if( c0.blobs() == 1 && 4 * c0.size() < c1.size() && c0.left() - c1.right() > 2 * l.height() && 4 * c0.size() < c2.size() && c1.left() - c2.right() < l.height() ) l.delete_character( l.characters() - 1 ); } } for( int i = 0; i < textlines(); ++i ) tlpv[i]->insert_spaces(); } Textblock::~Textblock() { for( int i = textlines() - 1; i >= 0; --i ) delete tlpv[i]; } void Textblock::recognize( const Control & control ) { // Recognize characters. for( int i = 0; i < textlines(); ++i ) { // First pass. Recognize the easy characters. tlpv[i]->recognize1( control.charset ); // Second pass. Use context to clear up ambiguities. tlpv[i]->recognize2( control.charset ); } for( unsigned j = 0; j < control.filters.size(); ++j ) for( int i = 0; i < textlines(); ++i ) tlpv[i]->apply_filter( control.filters[j] ); // Remove unrecognized lines. for( int i = textlines() - 1; i >= 0; --i ) { Textline & line1 = *tlpv[i]; bool recognized = false; for( int j = 0 ; j < line1.characters(); ++j ) { if( line1.character( j ).guesses() ) { recognized = true; break; } } if( !recognized ) delete_line( tlpv, i ); } // Add blank lines. if( textlines() >= 3 ) { int min_vdistance = ( tlpv.back()->mean_vcenter() - tlpv.front()->mean_vcenter() ) / ( textlines() - 1 ); for( int i = 0; i + 1 < textlines(); ++i ) { const Textline & line1 = *tlpv[i]; const Textline & line2 = *tlpv[i+1]; if( !Ocrad::similar( line1.characters(), line2.characters(), 50 ) || !Ocrad::similar( line1.width(), line2.width(), 30 ) ) continue; const int vdistance = line2.mean_vcenter() - line1.mean_vcenter(); if( vdistance >= min_vdistance ) continue; const int mh1 = line1.mean_height(), mh2 = line2.mean_height(); if( mh1 < 10 || mh2 < 10 ) continue; if( Ocrad::similar( mh1, mh2, 20 ) && 2 * vdistance > mh1 + mh2 ) min_vdistance = vdistance; } if( min_vdistance > 0 ) for( int i = 0; i + 1 < textlines(); ++i ) { const Textline & line1 = *tlpv[i]; const Textline & line2 = *tlpv[i+1]; int vdistance = line2.mean_vcenter() - line1.mean_vcenter() - min_vdistance; while( 2 * vdistance > min_vdistance ) { insert_line( tlpv, ++i ); vdistance -= min_vdistance; } } } } const Textline & Textblock::textline( const int i ) const { if( i < 0 || i >= textlines() ) Ocrad::internal_error( "line, index out of bounds." ); return *tlpv[i]; } int Textblock::characters() const { int total = 0; for( int i = 0; i < textlines(); ++i ) total += tlpv[i]->characters(); return total; } void Textblock::print( const Control & control ) const { for( int i = 0; i < textlines(); ++i ) tlpv[i]->print( control ); std::fputs( "\n", control.outfile ); } void Textblock::dprint( const Control & control, bool graph, bool recursive ) const { std::fprintf( control.outfile, "%d lines\n\n", textlines() ); for( int i = 0; i < textlines(); ++i ) { std::fprintf( control.outfile, "%d characters in line %d\n", tlpv[i]->characters(), i + 1 ); tlpv[i]->dprint( control, graph, recursive ); } std::fputs( "\n", control.outfile ); } void Textblock::xprint( const Control & control ) const { std::fprintf( control.exportfile, "lines %d\n", textlines() ); for( int i = 0; i < textlines(); ++i ) { std::fprintf( control.exportfile, "line %d chars %d height %d\n", i + 1, tlpv[i]->characters(), tlpv[i]->mean_height() ); tlpv[i]->xprint( control ); } } void Textblock::cmark( Page_image & page_image ) const { for( int i = 0; i < textlines(); ++i ) tlpv[i]->cmark( page_image ); } void Textblock::lmark( Page_image & page_image ) const { for( int i = 0; i < textlines(); ++i ) page_image.draw_track( *tlpv[i] ); } ocrad-0.24/textblock.h000066400000000000000000000030061241541103500146720ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Textblock : public Rectangle { mutable std::vector< Textline * > tlpv; Textblock( const Textblock & ); // declared as private void operator=( const Textblock & ); // declared as private public: Textblock( const Rectangle & page, const Rectangle & block, std::vector< Blob * > & blobp_vector ); ~Textblock(); void recognize( const Control & control ); const Textline & textline( const int i ) const; int textlines() const { return tlpv.size(); } int characters() const; void print( const Control & control ) const; void dprint( const Control & control, bool graph, bool recursive ) const; void xprint( const Control & control ) const; void cmark( Page_image & page_image ) const; void lmark( Page_image & page_image ) const; }; ocrad-0.24/textline.cc000066400000000000000000000235661241541103500147020ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "common.h" #include "histogram.h" #include "rational.h" #include "rectangle.h" #include "track.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" namespace { // Return the character position >= first preceding a big gap or eol. // int find_big_gap( const Textline & line, const int first, const int space_width_limit ) { int i = first; while( i + 1 < line.characters() ) { const Character & c1 = line.character( i ); const Character & c2 = line.character( i + 1 ); const int gap = c2.left() - c1.right() - 1; if( gap > space_width_limit ) break; else ++i; } return i; } } // end namespace Textline::Textline( const Textline & tl ) : Track( tl ), big_initials_( tl.big_initials_ ) { cpv.reserve( tl.cpv.size() ); for( unsigned i = 0; i < tl.cpv.size(); ++i ) cpv.push_back( new Character( *tl.cpv[i] ) ); } Textline & Textline::operator=( const Textline & tl ) { if( this != &tl ) { Track::operator=( tl ); big_initials_ = tl.big_initials_; for( unsigned i = 0; i < cpv.size(); ++i ) delete cpv[i]; cpv.clear(); cpv.reserve( tl.cpv.size() ); for( unsigned i = 0; i < tl.cpv.size(); ++i ) cpv.push_back( new Character( *tl.cpv[i] ) ); } return *this; } Textline::~Textline() { for( unsigned i = 0; i < cpv.size(); ++i ) delete cpv[i]; } void Textline::set_track() { std::vector< Rectangle > rv; for( unsigned i = big_initials_; i < cpv.size(); ++i ) if( !cpv[i]->maybe(' ') ) rv.push_back( *cpv[i] ); Track::set_track( rv ); } void Textline::verify_big_initials() { while( big_initials_ > 0 && cpv[big_initials_-1]->height() <= 2 * mean_height() ) --big_initials_; } Character & Textline::character( const int i ) const { if( i < 0 || i >= characters() ) Ocrad::internal_error( "character, index out of bounds." ); return *cpv[i]; } Character * Textline::character_at( const int col ) const { for( int i = 0; i < characters(); ++i ) if( cpv[i]->h_includes( col ) ) return cpv[i]; return 0; } Rectangle Textline::charbox( const Character & c ) const { return Rectangle( c.left(), top( c.hcenter() ), c.right(), bottom( c.hcenter() ) ); } void Textline::delete_character( const int i ) { if( i < 0 || i >= characters() ) Ocrad::internal_error( "delete_character, index out of bounds." ); if( i < big_initials_ ) --big_initials_; delete cpv[i]; cpv.erase( cpv.begin() + i ); } int Textline::shift_characterp( Character * const p, const bool big ) { int i = characters(); while( i > 0 && p->h_precedes( *cpv[i-1] ) ) --i; cpv.insert( cpv.begin() + i, p ); if( i < big_initials_ ) ++big_initials_; else if( big ) big_initials_ = i + 1; return i; } bool Textline::insert_space( const int i, const bool tab ) { if( i <= 0 || i >= characters() ) Ocrad::internal_error( "insert_space, index out of bounds." ); if( !height() ) Ocrad::internal_error( "insert_space, track not set yet." ); Character & c1 = *cpv[i-1]; Character & c2 = *cpv[i]; int l = c1.right() + 1; int r = c2.left() - 1; if( l > r ) return false; int t = top( ( l + r ) / 2 ); int b = bottom( ( l + r ) / 2 ); Rectangle re( l, t, r, b ); Character * const p = new Character( re, ' ', tab ? 1 : 0 ); if( tab ) p->add_guess( '\t', 0 ); cpv.insert( cpv.begin() + i, p ); return true; } // Insert spaces between characters. // void Textline::insert_spaces() { const Rational mw = mean_width(); if( mw < 2 ) return; const int mwt = mw.trunc(); const int space_width_limit = ( 3 * mw ).trunc(); int first = big_initials_; while( first + 1 < characters() ) { int last = find_big_gap( *this, first, space_width_limit ); const Rational mg = mean_gap_width( first, last ); if( first < last && mg >= 0 ) { int spaces = 0, nospaces = 0, spsum = 0, nospsum = 0; for( int i = first ; i < last; ++i ) { const Character & c1 = character( i ); const Character & c2 = character( i + 1 ); const int gap = c2.left() - c1.right() - 1; if( gap >= mwt || gap > 3 * mg || ( 5 * gap > 2 * mw && gap > 2 * mg ) || ( 3 * c1.width() > 2 * mw && 3 * c2.width() > 2 * mw && 2 * gap > mw && 5 * gap > 8 * mg ) ) { ++spaces; spsum += gap; if( insert_space( i + 1 ) ) { ++i; ++last; } } else { ++nospaces; nospsum += gap; } } if( spaces && nospaces ) { const Rational th = ( Rational( 3 * spsum, spaces ) + Rational( nospsum, nospaces ) ) / 4; for( int i = first ; i < last; ++i ) { const Character & c1 = character( i ); const Character & c2 = character( i + 1 ); const int gap = c2.left() - c1.right() - 1; if( gap > th && insert_space( i + 1 ) ) { ++i; ++last; } } } } if( ++last < characters() && insert_space( last, true ) ) ++last; first = last; } } void Textline::join( Textline & tl ) { for( int i = 0; i < tl.characters(); ++i ) shift_characterp( tl.cpv[i], i < tl.big_initials_ ); tl.big_initials_ = 0; tl.cpv.clear(); } int Textline::mean_height() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) if( !cpv[i]->maybe(' ') ) { ++c; sum += cpv[i]->height(); } if( c ) sum /= c; return sum; } Rational Textline::mean_width() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) if( !cpv[i]->maybe(' ') ) { ++c; sum += cpv[i]->width(); } if( c ) return Rational( sum, c ); return Rational( 0 ); } Rational Textline::mean_gap_width( const int first, int last ) const { if( last < 0 ) last = characters() - 1; int sum = 0; for( int i = first; i < last; ++i ) sum += std::max( 0, cpv[i+1]->left() - cpv[i]->right() - 1 ); if( last > first ) return Rational( sum, last - first ); return Rational( 0 ); } int Textline::mean_hcenter() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) { ++c; sum += cpv[i]->hcenter(); } if( c ) sum /= c; return sum; } int Textline::mean_vcenter() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) { ++c; sum += cpv[i]->vcenter(); } if( c ) sum /= c; return sum; } void Textline::print( const Control & control ) const { for( int i = 0; i < characters(); ++i ) character( i ).print( control ); std::fputs( "\n", control.outfile ); } void Textline::dprint( const Control & control, const bool graph, const bool recursive ) const { if( graph || recursive ) { Histogram hist; for( int i = 0; i < characters(); ++i ) if( !character(i).maybe(' ') ) hist.add_sample( character(i).height() ); std::fprintf( control.outfile, "mean height = %d, median height = %d, track segments = %d\n", mean_height(), hist.median(), segments() ); } for( int i = 0; i < characters(); ++i ) { const Character & c = character( i ); if( i < big_initials_ ) c.dprint( control, c, graph, recursive ); else c.dprint( control, charbox( c ), graph, recursive ); } std::fputs( "\n", control.outfile ); } void Textline::xprint( const Control & control ) const { for( int i = 0; i < characters(); ++i ) character( i ).xprint( control ); } void Textline::cmark( Page_image & page_image ) const { for( int i = 0; i < characters(); ++i ) page_image.draw_rectangle( character( i ) ); } void Textline::recognize1( const Charset & charset ) const { for( int i = 0; i < characters(); ++i ) { Character & c = character( i ); if( i < big_initials_ ) { c.recognize1( charset, c ); if( c.guesses() ) { const int code = c.guess( 0 ).code; if( UCS::islower_ambiguous( code ) ) c.only_guess( UCS::toupper( code ), 0 ); } } else c.recognize1( charset, charbox( c ) ); } } void Textline::apply_filter( const Filter::Type filter ) { bool modified = false; for( int i = characters() - 1; i >= 0; --i ) { Character & c = character( i ); if( !c.guesses() ) continue; c.apply_filter( filter ); if( !c.guesses() ) { delete_character( i ); modified = true; } } if( filter == Filter::same_height ) { Histogram hist; for( int i = 0; i < characters(); ++i ) if( !character(i).maybe(' ') ) hist.add_sample( character(i).height() ); const int median_height = hist.median(); for( int i = characters() - 1; i >= 0; --i ) if( !character(i).maybe(' ') && !Ocrad::similar( character(i).height(), median_height, 13, 2 ) ) { delete_character( i ); modified = true; } } if( modified ) // remove leadind/trailing/duplicate spaces for( int i = characters() - 1; i >= 0; --i ) if( character(i).maybe(' ') && ( i == 0 || i == characters() - 1 || character(i-1).maybe(' ') ) ) delete_character( i ); } ocrad-0.24/textline.h000066400000000000000000000043421241541103500145330ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Page_image; class Rational; class Textline : public Track { int big_initials_; mutable std::vector< Character * > cpv; void check_lower_ambiguous(); public: Textline() : big_initials_( 0 ) {} Textline( const Textline & tl ); Textline & operator=( const Textline & tl ); ~Textline(); void set_track(); void verify_big_initials(); int big_initials() const { return big_initials_; } Character & character( const int i ) const; Character * character_at( const int col ) const; int characters() const { return cpv.size(); } Rectangle charbox( const Character & c ) const; int width() const { return cpv.empty() ? 0 : cpv.back()->right() - cpv.front()->left(); } void delete_character( const int i ); int shift_characterp( Character * const p, const bool big = false ); bool insert_space( const int i, const bool tab = false ); void insert_spaces(); void join( Textline & tl ); int mean_height() const; Rational mean_width() const; Rational mean_gap_width( const int first = 0, int last = -1 ) const; int mean_hcenter() const; int mean_vcenter() const; void print( const Control & control ) const; void dprint( const Control & control, const bool graph, const bool recursive ) const; void xprint( const Control & control ) const; void cmark( Page_image & page_image ) const; void recognize1( const Charset & charset ) const; void recognize2( const Charset & charset ); void apply_filter( const Filter::Type filter ); }; ocrad-0.24/textline_r2.cc000066400000000000000000000707021241541103500152770ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "track.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "textline.h" // All the code in this file is provisional and will be rewritten someday namespace { int find_space_or_hyphen( const std::vector< Character * > & cpv, unsigned i ) { while( i < cpv.size() && !cpv[i]->maybe(' ') && !cpv[i]->maybe('-') ) ++i; return i; } } // end namespace // transform some small letters to capitals void Textline::check_lower_ambiguous() { int begin = big_initials(); bool isolated = false; // isolated letters compare with all line for( int i = big_initials(); i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { if( i + 2 < characters() && character( i + 2 ).maybe(' ') ) { begin = big_initials(); isolated = true; } else { begin = i + 1; isolated = false; } continue; } if( c1.guesses() == 1 ) { const int code = c1.guess( 0 ).code; if( !UCS::islower_small_ambiguous( code ) ) continue; if( 5 * c1.height() < 4 * mean_height() ) continue; bool capital = ( 4 * c1.height() > 5 * mean_height() ); bool small = false; for( int j = begin; j < characters(); ++j ) if( j != i ) { const Character & c2 = character( j ); if( !c2.guesses() ) continue; if( c2.maybe(' ') ) { if( isolated ) continue; else break; } const int code2 = c2.guess( 0 ).code; if( code2 >= 128 || !std::isalpha( code2 ) ) continue; if( !capital ) { if( 4 * c1.height() > 5 * c2.height() ) capital = true; else if( std::isupper( code2 ) && code2 != 'B' && code2 != 'Q' && ( c1.height() >= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) capital = true; else if( code2 == 't' && c1.height() >= c2.height() ) capital = true; } if( !small && std::islower( code2 ) && code2 != 'l' && code2 != 'j' ) { if( 5 * c1.height() < 4 * c2.height() ) small = true; else if( UCS::islower_small( code2 ) && code2 != 'r' && !c2.maybe('Q') && ( j < i || !UCS::islower_small_ambiguous( code2 ) ) && Ocrad::similar( c1.height(), c2.height(), 10 ) ) small = true; } } if( capital && !small ) c1.insert_guess( 0, std::toupper( code ), 1 ); } } } void Textline::recognize2( const Charset & charset ) { if( big_initials() >= characters() ) return; // try to recognize separately the 3 overlapped blobs of an // unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 3 ) { const Blob & b1 = c.blob( 0 ); const Blob & b2 = c.blob( 1 ); const Blob & b3 = c.blob( 2 ); // lower blob if( Ocrad::similar( b2.height(), b3.height(), 20 ) && !b2.h_overlaps( b3 ) && b2.v_includes( b3.vcenter() ) && b3.v_includes( b2.vcenter() ) && b1.bottom() < b2.top() && b1.bottom() < b3.top() ) { if( b1.height() > b2.height() && b1.height() > b3.height() ) { Character c1( new Blob( b1 ) ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) c = c1; } else { Character c2( new Blob( b2 ) ); Character c3( new Blob( b3 ) ); if( b2.h_includes( b1.hcenter() ) ) c2.shift_blobp( new Blob( b1 ) ); else if( b3.h_includes( b1.hcenter() ) ) c3.shift_blobp( new Blob( b1 ) ); c2.recognize1( charset, charbox( c2 ) ); c3.recognize1( charset, charbox( c3 ) ); if( c2.guesses() && c3.guesses() ) { c = c2; shift_characterp( new Character( c3 ) ); ++i; } } } } } // try to recognize separately the 2 overlapped blobs of an // unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 2 && c.blob( 0 ).v_overlaps( c.blob( 1 ) ) ) { Character c1( new Blob( c.blob( 0 ) ) ); c1.recognize1( charset, charbox( c1 ) ); Character c2( new Blob( c.blob( 1 ) ) ); c2.recognize1( charset, charbox( c2 ) ); if( ( c1.guesses() && c2.guesses() ) || Ocrad::similar( c1.height(), c2.height(), 20 ) ) { if( c1.height() > c2.height() ) c = c1; else { c = c2; c2 = c1; } // discards spurious dots if( !c2.maybe('.') || c2.top() > c.vcenter() ) { shift_characterp( new Character( c2 ) ); ++i; } } } } // remove speckles under the charbox' bottom of an unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 2 && c.blob( 0 ).size() > 10 * c.blob( 1 ).size() && c.blob( 1 ).top() > charbox( c ).bottom() ) { Character c1( new Blob( c.blob( 0 ) ) ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) c = c1; } } // remove speckles above the charbox' top of an unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 2 && c.blob( 1 ).size() > 5 * c.blob( 0 ).size() && c.blob( 0 ).bottom() + 2 * c.blob( 0 ).height() < charbox( c ).top() ) { Character c1( new Blob( c.blob( 1 ) ) ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) c = c1; } } // try to separate lightly merged characters // FIXME try relative minima (small pixel count surrounded by larger counts) // FIXME try all possible separation points // FIXME try other separation paths (an irregular line, not a column) // FIXME sometimes leaves unconnected blobs for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.width() > 20 && 5 * c.width() >= 3 * c.height() && 5 * c.height() >= 3 * mean_height() ) { int ib = c.blobs() - 1; for( int k = ib - 1; k >= 0; --k ) if( c.blob( k ).width() > c.blob( ib ).width() ) ib = k; if( ib < 0 || 10 * c.blob( ib ).width() < 9 * c.width() || c.blob( ib ).bottom() < c.bottom() ) continue; const Blob & b = c.blob( ib ); // widest blob int colmin = 0, cmin = b.height() + 1; for( int col = b.hpos( 30 ); col <= b.hpos( 70 ); ++col ) { int c = 0; for( int row = b.top(); row <= b.bottom(); ++row ) if( b.id( row, col ) ) ++c; if( c < cmin || ( c == cmin && col <= b.hcenter() ) ) { cmin = c; colmin = col; } } if( 4 * cmin > b.height() || ( 5 * cmin > b.height() && ( colmin <= b.hpos( 40 ) || colmin >= b.hpos( 60 ) ) ) ) continue; if( colmin <= b.left() || colmin >= b.right() ) continue; Rectangle r1( b.left(), b.top(), colmin - 1, b.bottom() ); Rectangle r2( colmin + 1, b.top(), b.right(), b.bottom() ); Blob b1( b, r1 ); b1.adjust_height(); if( 2 * b1.height() < b.height() ) continue; Blob b2( b, r2 ); b2.adjust_height(); if( 2 * b2.height() < b.height() ) continue; b1.find_holes(); b2.find_holes(); Character c1( new Blob( b1 ) ); Character c2( new Blob( b2 ) ); for( int j = 0; j < c.blobs(); ++j ) if( j != ib ) { const Blob & bj = c.blob( j ); if( c1.includes_hcenter( bj ) ) c1.shift_blobp( new Blob( bj ) ); else if( c2.includes_hcenter( bj ) ) c2.shift_blobp( new Blob( bj ) ); } c1.recognize1( charset, charbox( c1 ) ); c2.recognize1( charset, charbox( c2 ) ); if( ( c1.guesses() && c2.guesses() ) || ( ( c1.guesses() || c2.guesses() ) && c.width() > c.height() ) ) { c = c1; shift_characterp( new Character( c2 ) ); if( !c1.guesses() ) --i; else if( c2.guesses() ) ++i; } } } // try to recognize 1 blob unrecognized characters with holes by // removing small holes (noise) for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 1 && c.blob( 0 ).holes() ) { Character c1( c ); Blob & b = c1.blob( 0 ); for( int j = b.holes() - 1; j >= 0; --j ) if( 64 * b.hole( j ).size() <= b.size() || 16 * b.hole( j ).height() <= b.height() ) b.fill_hole( j ); if( b.holes() < c.blob( 0 ).holes() ) { c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) { c = c1; continue; } } /* if( b.holes() == 1 && 25 * b.hole( 0 ).size() < b.size() && Ocrad::similar( b.height(), b.width(), 40 ) ) { b.fill_hole( 0 ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) { c = c1; continue; } }*/ } } // separate merged characters recognized by recognize1 for( int i = big_initials(); i < characters(); ) { if( !cpv[i]->guesses() || cpv[i]->guess( 0 ).code >= 0 ) { ++i; continue; } const Character c( *cpv[i] ); const int blob_index = -(c.guess( 0 ).code + 1); delete_character( i ); if( c.guesses() >= 3 && c.blobs() >= 1 && blob_index < c.blobs() ) { int left = c.guess( 0 ).value; for( int g = 1; g < c.guesses(); ++g ) { Blob b( c.blob( blob_index ) ); Rectangle re( left, b.top(), c.guess( g ).value, b.bottom() ); b.add_rectangle( re ); Blob b1( b, re ); b1.adjust_height(); b1.adjust_width(); b1.find_holes(); Character c1( new Blob( b1 ) ); for( int k = 0; k < c.blobs(); ++k ) if( k != blob_index && !c.blob( k ).includes( re ) && re.includes_hcenter( c.blob( k ) ) ) c1.shift_blobp( new Blob( c.blob( k ) ) ); c1.add_guess( c.guess( g ).code, 0 ); shift_characterp( new Character( c1 ) ); left = re.right() + 1; } } } // choose between 'B' and 'a' for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() ) { int code = c1.guess( 0 ).code; if( c1.guesses() != 2 || code != 'B' || c1.guess( 1 ).code != 'a' ) continue; if( 4 * c1.height() > 5 * mean_height() ) continue; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( c2.guesses() >= 1 ) { int code2 = c2.guess( 0 ).code; if( code2 >= 128 ) continue; if( ( std::isupper( code2 ) && code2 != 'B' && code2 != 'Q' && 5 * c1.height() < 4 * c2.height() ) || ( UCS::islower_small( code2 ) && code2 != 'r' && !UCS::islower_small_ambiguous( code2 ) && ( c1.height() <= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) ) { c1.swap_guesses( 0, 1 ); break; } } } } } // choose between '8' and 'a' or 'e' for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() == 2 && c1.guess( 1 ).code == '8' ) { int code = c1.guess( 0 ).code; if( ( code != 'a' && code != 'e' ) || 5 * c1.height() < 4 * mean_height() ) continue; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( c2.guesses() >= 1 ) { int code2 = c2.guess( 0 ).code; if( code2 >= 128 ) continue; if( ( ( std::isalpha( code2 ) || code2 == ':' ) && 4 * c1.height() > 5 * c2.height() ) || ( ( std::isdigit( code2 ) || std::isupper( code2 ) || code2 == 'l' ) && ( c1.height() >= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) ) { c1.swap_guesses( 0, 1 ); break; } } } } } check_lower_ambiguous(); // transform 'i' into 'j' for( int i = big_initials(); i < characters(); ++i ) { Character & c1 = character( i ); if( c1.guesses() == 1 && c1.guess( 0 ).code == 'i' ) { int j = i + 1; if( j >= characters() || !character( j ).guesses() ) { j = i - 1; if( j < big_initials() || !character( j ).guesses() ) continue; } Character & c2 = character( j ); if( UCS::isvowel( c2.guess( 0 ).code ) && c1.bottom() >= c2.bottom() + ( c2.height() / 4 ) ) c1.insert_guess( 0, 'j', 1 ); } } // transform small o or u with accent or diaeresis to capital { int begin = big_initials(); bool isolated = false; // isolated letters compare with all line for( int i = big_initials(); i < characters(); ++i ) { Character & c1 = character( i ); if( c1.guesses() >= 1 ) { if( c1.maybe(' ') ) { if( i + 2 < characters() && character( i + 2 ).maybe(' ') ) { begin = big_initials(); isolated = true; } else { begin = i + 1; isolated = false; } continue; } int code = c1.guess( 0 ).code; if( code < 128 || c1.blobs() < 2 ) continue; int codeb = UCS::base_letter( code ); if( codeb != 'o' && codeb != 'u' ) continue; const Blob & b1 = c1.blob( c1.blobs() - 1 ); // lower blob for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.guesses() >= 1 ) { if( c2.maybe(' ') ) { if( isolated ) continue; else break; } int code2 = c2.guess( 0 ).code; int code2b = UCS::base_letter( code2 ); if( !code2b && code2 >= 128 ) continue; if( ( std::isalpha( code2 ) && 4 * b1.height() > 5 * c2.height() ) || ( std::isupper( code2 ) && Ocrad::similar( b1.height(), c2.height(), 10 ) ) || ( std::isalpha( code2b ) && 4 * c1.height() > 5 * c2.height() ) || ( std::isupper( code2b ) && Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) { c1.insert_guess( 0, UCS::toupper( code ), 1 ); break; } } } } } } // transform 'O' or 'l' into '0' or '1' for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() >= 1 ) { int code = c1.guess( 0 ).code; if( code != 'o' && code != 'O' && code != 'l' ) continue; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( c2.guesses() >= 1 ) { int code2 = c2.guess( 0 ).code; if( UCS::isdigit( code2 ) ) { if( Ocrad::similar( c1.height(), c2.height(), 10 ) ) c1.insert_guess( 0, (code == 'l') ? '1' : '0', c1.guess( 0 ).value + 1 ); break; } if( UCS::isalpha( code2 ) && code2 != 'o' && code2 != 'O' && code2 != 'l' ) break; } } } } // transform a small 'p' to a capital 'P' for( int i = characters() - 1; i >= big_initials(); --i ) { Character & c1 = character( i ); if( c1.guesses() == 1 && c1.guess( 0 ).code == 'p' ) { const int noise = std::max( 2, c1.height() / 20 ); bool cap = false, valid_c2 = false; if( i < characters() - 1 && character(i+1).guesses() ) { Character & c2 = character( i + 1 ); int code = c2.guess( 0 ).code; if( UCS::isalnum( code ) || code == '.' || code == '|' ) { valid_c2 = true; switch( code ) { case 'g': case 'j': case 'p': case 'q': case 'y': cap = ( c1.bottom() + noise <= c2.bottom() ); break; case 'Q': cap = ( std::abs( c1.top() - c2.top() ) <= noise ); break; default : cap = ( std::abs( c1.bottom() - c2.bottom() ) <= noise ); } } } if( !valid_c2 && i > big_initials() && !character(i-1).maybe(' ') ) cap = ( std::abs( c1.bottom() - charbox(c1).bottom() ) <= noise ); if( cap ) c1.only_guess( 'P', 0 ); } } // transform a capital 'Y' to a small 'y' for( int i = characters() - 1; i > big_initials(); --i ) { Character & c1 = character( i - 1 ); if( c1.guesses() == 1 && c1.guess( 0 ).code == 'Y' ) { Character & c2 = character( i ); if( !c2.guesses() ) continue; int code = c2.guess( 0 ).code; if( UCS::isalnum( code ) || code == '.' || code == '|' ) { switch( code ) { case 'g': case 'j': case 'p': case 'q': case 'y': if( c1.bottom() < c2.bottom() - 2 ) continue; break; case 'Q': if( c1.top() < c2.top() + 2 ) continue; break; default : if( c1.bottom() < c2.bottom() + 2 ) continue; } c1.only_guess( 'y', 0 ); } } } // transform a SSCEDI to a CSCEDI if( charset.enabled( Charset::iso_8859_9 ) ) for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( c.guesses() == 1 && c.guess( 0 ).code == UCS::SSCEDI ) { if( i > big_initials() && character( i - 1 ).guesses() ) { Character & c1 = character( i - 1 ); int code = c1.guess( 0 ).code; if( ( UCS::islower( code ) && c.top() < c1.top() - 2 ) || ( UCS::base_letter( code ) && code != UCS::SINODOT && Ocrad::similar( c.top(), c1.top(), 10 ) ) ) { c.insert_guess( 0, UCS::CSCEDI, 1 ); continue; } } if( i < characters() - 1 && character( i + 1 ).guesses() ) { Character & c1 = character( i + 1 ); int code = c1.guess( 0 ).code; if( ( UCS::islower( code ) && c.top() < c1.top() - 2 ) || ( UCS::base_letter( code ) && code != UCS::SINODOT && Ocrad::similar( c.top(), c1.top(), 10 ) ) ) { c.insert_guess( 0, UCS::CSCEDI, 1 ); continue; } } } } // transform words like 'lO.OOO' into numbers like '10.000' for( int begin = big_initials(), end = begin; begin < characters(); begin = end + 1 ) { end = find_space_or_hyphen( cpv, begin ); if( end - begin < 2 ) continue; Character & c1 = character( begin ); if( !c1.guesses() ) continue; const int height = c1.height(); const int code1 = c1.guess( 0 ).code; if( UCS::isdigit( code1 ) || code1 == 'l' || code1 == 'O' || code1 == 'o' ) { int digits = 1; int i = begin + 1; for( ; i < end; ++i ) { Character & c = character( i ); if( !c.guesses() ) break; bool valid = false; int code = c.guess( 0 ).code; if( ( UCS::isdigit( code ) || code == 'l' || code == 'O' || code == 'o' ) && Ocrad::similar( c.height(), height, 10 ) ) { valid = true; ++digits; } if( code == '.' || code == ',' || code == ':' || code == '+' || code == '-' ) valid = true; if( !valid ) break; } if( i >= end && digits >= 2 ) for( i = begin; i < end; ++i ) { Character & c = character( i ); int code = c.guess( 0 ).code; if( code == 'l' ) code = '1'; else if( code == 'O' || code == 'o' ) code = '0'; else code = 0; if( code ) c.insert_guess( 0, code, c.guess( 0 ).value + 1 ); } } } // detects Roman numerals 'II', 'III' and 'IIII' for( int begin = big_initials(), end = begin; begin < characters(); begin = end + 1 ) { end = find_space_or_hyphen( cpv, begin ); if( end - begin < 2 || end - begin > 4 ) continue; const int height = character( begin ).height(); int i; for( i = begin; i < end; ++i ) { Character & c = character( i ); if( !c.maybe('|') || !Ocrad::similar( c.height(), height, 10 ) ) break; } if( i >= end ) for( i = begin; i < end; ++i ) { Character & c = character( i ); if( !character(i).maybe('I') ) c.insert_guess( 0, 'I', c.guess( 0 ).value + 1 ); } } // choose between 'a' and 'Q' for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( c.guesses() == 2 && c.guess( 0 ).code == 'a' && c.guess( 1 ).code == 'Q' ) { if( 4 * c.height() > 5 * mean_height() ) { c.swap_guesses( 0, 1 ); check_lower_ambiguous(); continue; } if( i < characters() - 1 && character( i + 1 ).guesses() ) { const int code = character( i + 1 ).guess( 0 ).code; if( ( UCS::ishigh( code ) ) && 10 * c.height() > 9 * character( i + 1 ).height() ) { c.swap_guesses( 0, 1 ); check_lower_ambiguous(); continue; } } if( i > big_initials() && character( i - 1 ).guesses() ) { const int code = character( i - 1 ).guess( 0 ).code; if( ( UCS::ishigh( code ) ) && 10 * c.height() > 9 * character( i - 1 ).height() ) { c.swap_guesses( 0, 1 ); check_lower_ambiguous(); } } } } // transform a vertical bar into 'l' or 'I' (or a 'l' into an 'I') for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( c.guesses() != 1 ) continue; int code = c.guess( 0 ).code; if( code == '|' || code == 'l' ) { int lcode = 0, rcode = 0; if( i > 0 && character( i - 1 ).guesses() ) lcode = character( i - 1 ).guess( 0 ).code; if( i < characters() - 1 && character( i + 1 ).guesses() ) rcode = character( i + 1 ).guess( 0 ).code; if( ( UCS::isupper( rcode ) || UCS::isdigit( rcode ) ) && ( !lcode || UCS::isupper( lcode ) || !UCS::isalnum( lcode ) ) ) { c.insert_guess( 0, 'I', 1 ); continue; } if( code == 'l' ) continue; if( UCS::isalpha( lcode ) || UCS::isalpha( rcode ) ) { c.insert_guess( 0, 'l', 1 ); continue; } if( rcode == '|' && ( !lcode || !UCS::isalnum( lcode ) ) ) { if( i < characters() - 2 && character( i + 2 ).guesses() && UCS::isalpha( character( i + 2 ).guess( 0 ).code ) ) { c.insert_guess( 0, 'l', 1 ); continue; } if( i >= 2 && character( i - 2 ).guesses() && UCS::isalpha( character( i - 2 ).guess( 0 ).code ) ) { c.insert_guess( 0, 'l', 1 ); continue; } } } } // transform a vertical bar into 'I' at end of word for( int begin = big_initials(), end = begin; begin < characters(); begin = end + 1 ) { end = find_space_or_hyphen( cpv, begin ); if( end - begin < 3 ) continue; Character & ce = character( end - 1 ); if( !ce.maybe('|') || ce.maybe('I') ) continue; const int height = ce.height(); int i; for( i = begin; i < end - 1; ++i ) { const Character & c = character( i ); if( !c.guesses() ) break; const int code = c.guess( 0 ).code; if( ( !UCS::isupper( code ) && !UCS::isdigit( code ) ) || !Ocrad::similar( c.height(), height, 10 ) ) break; } if( i >= end - 1 ) ce.insert_guess( 0, 'I', ce.guess( 0 ).value + 1 ); } // transform 'l' or '|' into UCS::SINODOT if( charset.enabled( Charset::iso_8859_9 ) ) for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() ) { int code = c1.guess( 0 ).code; if( code != 'l' && code != '|' ) continue; if( 4 * c1.height() > 5 * mean_height() ) continue; if( 5 * c1.height() < 4 * mean_height() ) { c1.only_guess( UCS::SINODOT, 0 ); continue; } bool capital = false, small = false; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( !c2.guesses() ) continue; int code2 = c2.guess( 0 ).code; if( code2 >= 128 || !std::isalpha( code2 ) ) continue; if( !capital ) { if( 4 * c1.height() > 5 * c2.height() ) capital = true; else if( std::isupper( code2 ) && code2 != 'B' && code2 != 'Q' && ( c1.height() >= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) capital = true; } if( !small && std::islower( code2 ) && code2 != 'l' ) { if( 5 * c1.height() < 4 * c2.height() ) small = true; else if( UCS::islower_small( code2 ) && ( j < i || !UCS::islower_small_ambiguous( code2 ) ) && Ocrad::similar( c1.height(), c2.height(), 10 ) ) small = true; } } if( !capital && small ) c1.insert_guess( 0, UCS::SINODOT, 1 ); } } // join two adjacent single quotes into a double quote for( int i = big_initials(); i < characters() - 1; ++i ) { Character & c1 = character( i ); Character & c2 = character( i + 1 ); if( c1.guesses() == 1 && c2.guesses() == 1 ) { int code1 = c1.guess( 0 ).code; int code2 = c2.guess( 0 ).code; if( ( code1 == '\'' || code1 == '`' ) && code1 == code2 && 2 * ( c2.left() - c1.right() ) < 3 * c1.width() ) { c1.join( c2 ); c1.only_guess( '"', 0 ); delete_character( i + 1 ); } } } // join a comma followed by a period into a semicolon for( int i = big_initials(); i < characters() - 1; ++i ) { Character & c1 = character( i ); Character & c2 = character( i + 1 ); if( c1.guesses() == 1 && c2.guesses() == 1 ) { int code1 = c1.guess( 0 ).code; int code2 = c2.guess( 0 ).code; if( code1 == ',' && code2 == '.' && c1.top() > c2.bottom() && c2.left() - c1.right() < c2.width() ) { c1.join( c2 ); c1.only_guess( ';', 0 ); delete_character( i + 1 ); } } } // choose between '.' and '-' if( characters() >= 2 ) { Character & c = character( characters() - 1 ); if( c.guesses() >= 2 && c.guess( 0 ).code == '.' && c.guess( 1 ).code == '-' ) { const Character & lc = character( characters() - 2 ); if( lc.guesses() && UCS::isalpha( lc.guess( 0 ).code ) ) c.swap_guesses( 0, 1 ); } } // join a 'n' followed by a 'I' into a 'm' for( int i = big_initials(); i < characters() - 1; ++i ) { Character & c1 = character( i ); Character & c2 = character( i + 1 ); if( c1.guesses() == 1 && c2.guesses() == 1 ) { int code1 = c1.guess( 0 ).code; int code2 = c2.guess( 0 ).code; if( code1 == 'n' && ( code2 == 'I' || code2 == 'l' ) && Ocrad::similar( c1.height(), c2.height(), 10 ) && c2.left() - c1.right() < c2.width() ) { c1.join( c2 ); c1.only_guess( 'm', 0 ); delete_character( i + 1 ); } } } // join the secuence '°', '/', 'o', ' ' into a '%' for( int i = big_initials(); i + 2 < characters(); ++i ) { Character & c1 = character( i ); if( c1.guesses() == 1 && c1.guess( 0 ).code == UCS::DEG ) { if( character( i + 1 ).maybe('/') && character( i + 2 ).maybe('o') && ( i + 3 >= characters() || character( i + 3 ).maybe(' ') ) ) { c1.join( character( i + 1 ) ); c1.join( character( i + 2 ) ); delete_character( i + 2 ); delete_character( i + 1 ); c1.only_guess( '%', 0 ); } } } } ocrad-0.24/textpage.cc000066400000000000000000000410621241541103500146560ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "mask.h" #include "track.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" #include "textblock.h" #include "textpage.h" namespace { struct Zone { Mask mask; std::vector< Blob * > blobp_vector; Zone( const Rectangle & re ) : mask( re ) {} void join( Zone & z ); }; void Zone::join( Zone & z ) { mask.add_mask( z.mask ); blobp_vector.insert( blobp_vector.end(), z.blobp_vector.begin(), z.blobp_vector.end() ); z.blobp_vector.clear(); } int blobs_in_page( const std::vector< Zone > & zone_vector ) { int sum = 0; for( unsigned i = 0; i < zone_vector.size(); ++i ) sum += zone_vector[i].blobp_vector.size(); return sum; } void bprint( const std::vector< Zone > & zone_vector, FILE * const outfile ) { // std::fprintf( outfile, "page size %dw x %dh\n", width(), height() ); std::fprintf( outfile, "total zones in page %d\n", (int)zone_vector.size() ); std::fprintf( outfile, "total blobs in page %d\n\n", blobs_in_page( zone_vector ) ); for( unsigned zindex = 0; zindex < zone_vector.size(); ++zindex ) { const Rectangle & r = zone_vector[zindex].mask; const std::vector< Blob * > & blobp_vector = zone_vector[zindex].blobp_vector; std::fprintf( outfile, "zone %d of %d\n", zindex + 1, (int)zone_vector.size() ); std::fprintf( outfile, "zone size %dw x %dh\n", r.width(), r.height() ); std::fprintf( outfile, "total blobs in zone %u\n\n", (unsigned)zone_vector[zindex].blobp_vector.size() ); for( unsigned i = 0; i < blobp_vector.size(); ++i ) blobp_vector[i]->print( outfile ); } } inline void join_blobs( std::vector< Blob * > & blobp_vector, std::vector< Blob * > & v1, std::vector< Blob * > & v2, Blob * p1, Blob * p2, int i ) { if( p1->top() > p2->top() ) { Blob * const temp = p1; p1 = p2; p2 = temp; std::replace( v2.begin(), v2.begin() + ( i + 1 ), p2, p1 ); } else std::replace( v1.begin() + i, v1.end(), p2, p1 ); i = blobp_vector.size(); while( --i >= 0 && blobp_vector[i] != p2 ) ; if( i < 0 ) Ocrad::internal_error( "join_blobs, lost blob." ); blobp_vector.erase( blobp_vector.begin() + i ); p1->add_bitmap( *p2 ); delete p2; } void ignore_abnormal_blobs( std::vector< Blob * > & blobp_vector ) { for( unsigned i = blobp_vector.size(); i > 0; ) { Blob & b = *blobp_vector[--i]; if( b.height() > 35 * b.width() || b.width() > 25 * b.height() ) { delete blobp_vector[i]; blobp_vector.erase( blobp_vector.begin() + i ); } } } void ignore_small_blobs( std::vector< Blob * > & blobp_vector ) { int to = 0, blobs = blobp_vector.size(); for( int from = 0; from < blobs; ++from ) { Blob * const p = blobp_vector[from]; if( p->height() > 4 || p->width() > 4 || ( ( p->height() > 2 || p->width() > 2 ) && p->area() > 5 ) ) { blobp_vector[from] = blobp_vector[to]; blobp_vector[to] = p; ++to; } } if( to < blobs ) { for( int i = to; i < blobs; ++i ) delete blobp_vector[i]; blobp_vector.erase( blobp_vector.begin() + to, blobp_vector.end() ); } } void remove_top_bottom_noise( std::vector< Blob * > & blobp_vector ) { int blobs = blobp_vector.size(); for( int i = 0; i < blobs; ++i ) { Blob & b = *blobp_vector[i]; if( b.height() < 11 ) continue; int c = 0; for( int col = b.left(); col <= b.right(); ++col ) if( b.get_bit( b.top(), col ) && ++c > 1 ) break; if( c <= 1 ) b.top( b.top() + 1 ); c = 0; for( int col = b.left(); col <= b.right(); ++col ) if( b.get_bit( b.bottom(), col ) && ++c > 1 ) break; if( c <= 1 ) b.bottom( b.bottom() - 1 ); } } void remove_left_right_noise( std::vector< Blob * > & blobp_vector ) { int blobs = blobp_vector.size(); for( int i = 0; i < blobs; ++i ) { Blob & b = *blobp_vector[i]; if( b.width() < 6 ) continue; int c = 0; for( int row = b.top(); row <= b.bottom(); ++row ) if( b.get_bit( row, b.left() ) && ++c > 1 ) break; if( c <= 1 ) b.left( b.left() + 1 ); c = 0; for( int row = b.top(); row <= b.bottom(); ++row ) if( b.get_bit( row, b.right() ) && ++c > 1 ) break; if( c <= 1 ) b.right( b.right() - 1 ); } } void find_holes( std::vector< Zone > & zone_vector ) { for( unsigned zi = 0; zi < zone_vector.size(); ++zi ) { std::vector< Blob * > & blobp_vector = zone_vector[zi].blobp_vector; for( unsigned bvi = 0; bvi < blobp_vector.size(); ++bvi ) blobp_vector[bvi]->find_holes(); } } void ignore_wide_blobs( const Rectangle & re, std::vector< Blob * > & blobp_vector ) { for( unsigned i = 0; i < blobp_vector.size(); ) { Blob & b = *blobp_vector[i]; if( 2 * b.width() < re.width() ) { ++i; continue; } blobp_vector.erase( blobp_vector.begin() + i ); if( 4 * b.area() <= 3 * b.size() ) { int blobs = 0; for( unsigned j = i; j < blobp_vector.size(); ++j ) { if( blobp_vector[j]->top() > b.bottom() ) break; if( blobp_vector[j]->size() >= 16 ) ++blobs; } if( blobs <= b.size() / 400 ) { if( 4 * b.area() <= b.size() ) // thin grid or frame { delete &b; continue; } b.find_holes(); bool frame = false; if( b.holes() < std::min( b.height(), b.width() ) ) for( int j = 0; j < b.holes(); ++j ) { if( 4 * b.hole( j ).size() >= b.size() && 4 * b.hole( j ).area() >= b.size() ) { frame = true; break; } } if( frame ) { delete &b; continue; } } } // picture, not frame if( 5 * b.width() > 4 * re.width() && 5 * b.height() > 4 * re.height() ) { for( unsigned j = 0; j < blobp_vector.size(); ++j ) delete blobp_vector[j]; blobp_vector.clear(); delete &b; break; } for( unsigned j = blobp_vector.size(); j > i; ) { const Blob & b2 = *blobp_vector[--j]; if( b.includes( b2 ) ) { delete &b2; blobp_vector.erase( blobp_vector.begin() + j ); } } delete &b; } } int mean_blob_height( const std::vector< Blob * > & blobp_vector ) { int mean_height = 0; unsigned samples = 0; std::vector< int > height_distrib; for( unsigned i = 0; i < blobp_vector.size(); ++i ) { const unsigned h = blobp_vector[i]->height(); const unsigned w = blobp_vector[i]->width(); if( h < 10 || w >= 3 * h ) continue; if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } if( height_distrib.empty() ) for( unsigned i = 0; i < blobp_vector.size(); ++i ) { const unsigned h = blobp_vector[i]->height(); if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } int valid_samples = 0; for( unsigned i = 0, count = 0; i < height_distrib.size(); ++i ) { const int a = height_distrib[i]; if( 10 * ( count + a ) >= samples && 10 * count < 9 * samples ) { mean_height += a * i; valid_samples += a; } count += a; } if( valid_samples ) mean_height /= valid_samples; return mean_height; } int analyse_layout( std::vector< Blob * > & blobp_vector, std::vector< Zone > & zone_vector ) { if( blobp_vector.empty() ) return 0; const int mean_height = mean_blob_height( blobp_vector ); zone_vector.push_back( Zone( *blobp_vector[0] ) ); zone_vector.back().blobp_vector.push_back( blobp_vector[0] ); for( unsigned i = 1; i < blobp_vector.size(); ++i ) { Blob & b = *blobp_vector[i]; if( b.height() > 10 * mean_height ) { delete &b; continue; } int first = -1; for( unsigned j = 0; j < zone_vector.size(); ++j ) { if( zone_vector[j].mask.distance( b ) < 2 * mean_height ) { if( first < 0 ) first = j; else { zone_vector[first].join( zone_vector[j] ); zone_vector.erase( zone_vector.begin() + j ); --j; } } } if( first >= 0 ) { zone_vector[first].mask.add_rectangle( b ); zone_vector[first].blobp_vector.push_back( &b ); } else { zone_vector.push_back( Zone( b ) ); zone_vector.back().blobp_vector.push_back( &b ); } } blobp_vector.clear(); // sort zone_vector int botmax = zone_vector.empty() ? 0 : zone_vector[0].mask.bottom(); std::vector< int > cut_index_vector; for( unsigned i = 1; i < zone_vector.size(); ++i ) { if( zone_vector[i].mask.top() > botmax ) cut_index_vector.push_back( i ); botmax = std::max( botmax, zone_vector[i].mask.bottom() ); } cut_index_vector.push_back( zone_vector.size() ); for( unsigned begin = 0, cut = 0; cut < cut_index_vector.size(); ++cut ) { const unsigned end = cut_index_vector[cut]; for( unsigned i = begin; i + 1 < end; ++i ) { unsigned first = i; for( unsigned j = i + 1; j < end; ++j ) if( zone_vector[j].mask.precedes( zone_vector[first].mask ) ) first = j; if( first != i ) std::swap( zone_vector[i], zone_vector[first] ); } bool join = ( end - begin > 1 ); for( unsigned i = begin; join && i < end; ++i ) if( zone_vector[i].blobp_vector.size() > 80 || zone_vector[i].mask.v_distance( zone_vector[begin].mask ) > zone_vector[i].mask.height() + zone_vector[begin].mask.height() ) join = false; for( unsigned i = begin; join && i < end; ++i ) if( zone_vector[i].mask.height() > 4 * mean_blob_height( zone_vector[i].blobp_vector ) ) join = false; if( join ) { for( unsigned i = begin + 1; i < end; ++i ) zone_vector[begin].join( zone_vector[i] ); zone_vector.erase( zone_vector.begin() + ( begin + 1 ), zone_vector.begin() + end ); for( unsigned i = cut; i < cut_index_vector.size(); ++i ) cut_index_vector[i] -= ( end - begin - 1 ); ++begin; } else begin = end; } return zone_vector.size(); } void scan_page( const Page_image & page_image, std::vector< Zone > & zone_vector, const int debug_level, const bool layout ) { const Rectangle & re = page_image; const int zthreshold = page_image.threshold(); std::vector< Blob * > blobp_vector; std::vector< Blob * > old_data( re.width(), (Blob *) 0 ); std::vector< Blob * > new_data( re.width(), (Blob *) 0 ); for( int row = re.top(); row <= re.bottom(); ++row ) { old_data.swap( new_data ); for( int col = re.left(); col <= re.right(); ++col ) { const int dcol = col - re.left(); if( !page_image.get_bit( row, col, zthreshold ) ) new_data[dcol] = 0; // white pixel else // black pixel { Blob *p; Blob *lp = ( (dcol > 0) ? new_data[dcol-1] : 0 ); Blob *ltp = ( (dcol > 0) ? old_data[dcol-1] : 0 ); Blob *tp = old_data[dcol]; Blob *rtp = ( (col < re.right()) ? old_data[dcol+1] : 0 ); if( lp ) { p = lp; p->add_point( row, col ); } else if( ltp ) { p = ltp; p->add_point( row, col ); } else if( tp ) { p = tp; p->add_point( row, col ); } else if( rtp ) { p = rtp; p->add_point( row, col ); } else { p = new Blob( col, row, col, row ); p->set_bit( row, col, true ); blobp_vector.push_back( p ); } new_data[dcol] = p; if( rtp && p != rtp ) join_blobs( blobp_vector, old_data, new_data, p, rtp, dcol ); } } } if( debug_level <= 99 && blobp_vector.size() > 3 ) { ignore_wide_blobs( re, blobp_vector ); ignore_small_blobs( blobp_vector ); ignore_abnormal_blobs( blobp_vector ); remove_top_bottom_noise( blobp_vector ); remove_left_right_noise( blobp_vector ); } if( layout && re.width() > 200 && re.height() > 200 && blobp_vector.size() > 3 ) { analyse_layout( blobp_vector, zone_vector ); if( debug_level <= 99 && zone_vector.size() > 1 ) for( unsigned i = 0; i < zone_vector.size(); ++i ) ignore_wide_blobs( zone_vector[i].mask, zone_vector[i].blobp_vector ); } else { zone_vector.push_back( Zone( re ) ); zone_vector.back().blobp_vector.swap( blobp_vector ); } find_holes( zone_vector ); } } // end namespace Textpage::Textpage( const Page_image & page_image, const char * const filename, const Control & control, const bool layout ) : Rectangle( page_image ), name( filename ) { const int debug_level = control.debug_level; if( debug_level < 0 || debug_level > 100 ) return; std::vector< Zone > zone_vector; // layout zones scan_page( page_image, zone_vector, debug_level, layout ); if( verbosity >= 1 ) std::fprintf( stderr, "number of text blocks = %d\n", (int)zone_vector.size() ); if( debug_level >= 98 ) { if( control.outfile ) bprint( zone_vector, control.outfile ); return; } if( debug_level > 95 || ( debug_level > 89 && debug_level < 94 ) ) return; // build a Textblock for every zone with text for( unsigned i = 0; i < zone_vector.size(); ++i ) { Textblock * const tbp = new Textblock( page_image, zone_vector[i].mask, zone_vector[i].blobp_vector ); if( tbp->textlines() && debug_level < 90 ) tbp->recognize( control ); if( tbp->textlines() ) tbpv.push_back( tbp ); else delete tbp; } if( debug_level == 0 ) return; if( !control.outfile ) return; if( debug_level >= 86 ) { bool graph = ( debug_level >= 88 ); bool recursive = ( debug_level & 1 ); for( int i = 0; i < textblocks(); ++i ) tbpv[i]->dprint( control, graph, recursive ); return; } if( debug_level > 77 ) return; if( debug_level >= 70 ) { Page_image tmp( page_image ); if( ( debug_level - 70 ) & 1 ) // mark zones for( unsigned i = 0; i < zone_vector.size(); ++i ) { if( debug_level == 71 ) tmp.draw_mask( zone_vector[i].mask ); else tmp.draw_rectangle( zone_vector[i].mask ); } if( ( debug_level - 70 ) & 2 ) // mark lines { for( int i = 0; i < textblocks(); ++i ) tbpv[i]->lmark( tmp ); } if( ( debug_level - 70 ) & 4 ) // mark characters { for( int i = 0; i < textblocks(); ++i ) tbpv[i]->cmark( tmp ); } tmp.save( control.outfile, control.filetype ); return; } } Textpage::~Textpage() { for( int i = textblocks() - 1; i >= 0; --i ) delete tbpv[i]; } const Textblock & Textpage::textblock( const int i ) const { if( i < 0 || i >= textblocks() ) Ocrad::internal_error( "Textpage::textblock, index out of bounds." ); return *(tbpv[i]); } int Textpage::textlines() const { int total = 0; for( int i = 0; i < textblocks(); ++i ) total += tbpv[i]->textlines(); return total; } int Textpage::characters() const { int total = 0; for( int i = 0; i < textblocks(); ++i ) total += tbpv[i]->characters(); return total; } void Textpage::print( const Control & control ) const { if( control.outfile ) for( int i = 0; i < textblocks(); ++i ) tbpv[i]->print( control ); } void Textpage::xprint( const Control & control ) const { if( !control.exportfile ) return; std::fprintf( control.exportfile, "source file %s\n", name.c_str() ); std::fprintf( control.exportfile, "total text blocks %d\n", textblocks() ); for( int i = 0; i < textblocks(); ++i ) { const Textblock & tb = *(tbpv[i]); std::fprintf( control.exportfile, "text block %d %d %d %d %d\n", i + 1, tb.left(), tb.top(), tb.width(), tb.height() ); tb.xprint( control ); } } ocrad-0.24/textpage.h000066400000000000000000000025731241541103500145240ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Textblock; class Textpage : public Rectangle { const std::string name; std::vector< Textblock * > tbpv; Textpage( const Textpage & ); // declared as private void operator=( const Textpage & ); // declared as private public: Textpage( const Page_image & page_image, const char * const filename, const Control & control, const bool layout ); ~Textpage(); const Textblock & textblock( const int i ) const; int textblocks() const { return tbpv.size(); } int textlines() const; int characters() const; void print( const Control & control ) const; void xprint( const Control & control ) const; }; ocrad-0.24/track.cc000066400000000000000000000230131241541103500141350ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "common.h" #include "rectangle.h" #include "track.h" namespace { void error( const char * const msg ) { Ocrad::internal_error( msg ); } int good_reference( const Rectangle & r1, const Rectangle & r2, int & val, const int mean_height, const int mean_width ) { if( 4 * r1.height() >= 3 * mean_height && 4 * r2.height() >= 3 * mean_height && ( r1.width() >= mean_width || r2.width() >= mean_width ) && val > 0 ) { if( 4 * r1.height() <= 5 * mean_height && 4 * r2.height() <= 5 * mean_height ) { if( 9 * r1.height() <= 10 * mean_height && 9 * r2.height() <= 10 * mean_height && 10 * std::abs( r1.bottom() - r2.bottom() ) <= mean_height ) { val = 0; return ( r1.height() <= r2.height() ) ? 0 : 1; } if( val > 1 && 10 * std::abs( r1.vcenter() - r2.vcenter() ) <= mean_height ) { val = 1; return ( r1.bottom() <= r2.bottom() ) ? 0 : 1; } } if( val > 2 && 10 * std::abs( r1.vcenter() - r2.vcenter() ) <= mean_height ) { val = 2; return ( r1.bottom() <= r2.bottom() ) ? 0 : 1; } } return -1; } int set_l( const std::vector< Rectangle > & rectangle_vector, const int mean_height, const int mean_width ) { const int rectangles = rectangle_vector.size(); const int imax = rectangles / 4; int ibest = -1, val = 3; for( int i1 = 0; i1 < imax && val > 0; ++i1 ) for( int i2 = i1 + 1; i2 <= imax && i2 <= i1 + 2; ++i2 ) { int i = good_reference( rectangle_vector[i1], rectangle_vector[i2], val, mean_height, mean_width ); if( i >= 0 ) { ibest = (i == 0) ? i1 : i2; if( val == 0 ) break; } } return ibest; } int set_r( const std::vector< Rectangle > & rectangle_vector, const int mean_height, const int mean_width ) { const int rectangles = rectangle_vector.size(); const int imin = rectangles - 1 - ( rectangles / 4 ); int ibest = -1, val = 3; for( int i1 = rectangles - 1; i1 > imin && val > 0; --i1 ) for( int i2 = i1 - 1; i2 >= imin && i2 >= i1 - 2; --i2 ) { int i = good_reference( rectangle_vector[i1], rectangle_vector[i2], val, mean_height, mean_width ); if( i >= 0 ) { ibest = (i == 0) ? i1 : i2; if( val == 0 ) break; } } return ibest; } Vrhomboid set_partial_track( const std::vector< Rectangle > & rectangle_vector ) { const int rectangles = rectangle_vector.size(); int mean_vcenter = 0, mean_height = 0, mean_width = 0; for( int i = 0; i < rectangles; ++i ) { mean_vcenter += rectangle_vector[i].vcenter(); mean_height += rectangle_vector[i].height(); mean_width += rectangle_vector[i].width(); } if( rectangles ) { mean_vcenter /= rectangles; mean_height /= rectangles; mean_width /= rectangles; } // short line if( rectangles < 8 ) return Vrhomboid( rectangle_vector.front().left(), mean_vcenter, rectangle_vector.back().right(), mean_vcenter, mean_height ); // look for reference rectangles (characters) int l = set_l( rectangle_vector, mean_height, mean_width ); int r = set_r( rectangle_vector, mean_height, mean_width ); int lcol, lvc, rcol, rvc; if( l >= 0 ) { lcol = rectangle_vector[l].hcenter(); lvc = rectangle_vector[l].bottom() - ( mean_height / 2 ); } else { lcol = rectangle_vector.front().hcenter(); lvc = mean_vcenter; } if( r >= 0 ) { rcol = rectangle_vector[r].hcenter(); rvc = rectangle_vector[r].bottom() - ( mean_height / 2 ); } else { rcol = rectangle_vector.back().hcenter(); rvc = mean_vcenter; } Vrhomboid tmp( lcol, lvc, rcol, rvc, mean_height ); tmp.extend_left( rectangle_vector.front().left() ); tmp.extend_right( rectangle_vector.back().right() ); return tmp; } } // end namespace Vrhomboid::Vrhomboid( const int l, const int lc, const int r, const int rc, const int h ) { if( r < l || h <= 0 ) { if( verbosity >= 0 ) std::fprintf( stderr, "l = %d, lc = %d, r = %d, rc = %d, h = %d\n", l, lc, r, rc, h ); error( "bad parameter building a Vrhomboid." ); } left_ = l; lvcenter_ = lc; right_ = r; rvcenter_ = rc; height_ = h; } void Vrhomboid::left( const int l ) { if( l > right_ ) error( "left, bad parameter resizing a Vrhomboid." ); left_ = l; } void Vrhomboid::right( const int r ) { if( r < left_ ) error( "right, bad parameter resizing a Vrhomboid." ); right_ = r; } void Vrhomboid::height( const int h ) { if( h <= 0 ) error( "height, bad parameter resizing a Vrhomboid." ); height_ = h; } void Vrhomboid::extend_left( const int l ) { if( l > right_ ) error( "extend_left, bad parameter resizing a Vrhomboid." ); lvcenter_ = vcenter( l ); left_ = l; } void Vrhomboid::extend_right( const int r ) { if( r < left_ ) error( "extend_right, bad parameter resizing a Vrhomboid." ); rvcenter_ = vcenter( r ); right_ = r; } int Vrhomboid::vcenter( const int col ) const { const int dx = right_ - left_, dy = rvcenter_ - lvcenter_; int vc = lvcenter_; if( dx && dy ) vc += ( dy * ( col - left_ ) ) / dx; return vc; } bool Vrhomboid::includes( const Rectangle & r ) const { if( r.left() < left_ || r.right() > right_ ) return false; const int tl = top( r.left() ), bl = bottom( r.left() ); const int tr = top( r.right() ), br = bottom( r.left() ); const int t = std::max( tl, tr ), b = std::min( bl, br ); return ( t <= r.top() && b >= r.bottom() ); } bool Vrhomboid::includes( const int row, const int col ) const { if( col < left_ || col > right_ ) return false; const int t = top( col ), b = bottom( col ); return ( t <= row && b >= row ); } // rectangle_vector must be ordered by increasing hcenter(). // void Track::set_track( const std::vector< Rectangle > & rectangle_vector ) { data.clear(); if( rectangle_vector.empty() ) return; std::vector< Rectangle > tmp; int max_gap = 0; bool last = false; { int s1 = rectangle_vector[0].width(), s2 = 0; for( unsigned i = 1; i < rectangle_vector.size(); ++i ) { s1 += rectangle_vector[i].width(); s2 += ( rectangle_vector[i].left() - rectangle_vector[i-1].right() ); } max_gap = ( 5 * std::max( s1, s2 ) ) / rectangle_vector.size(); } for( unsigned i = 0; i < rectangle_vector.size(); ++i ) { const Rectangle & r1 = rectangle_vector[i]; tmp.push_back( r1 ); if( i + 1 >= rectangle_vector.size() ) last = true; else { const Rectangle & r2 = rectangle_vector[i+1]; if( r2.left() - r1.right() >= max_gap ) last = true; } if( last ) { last = false; data.push_back( set_partial_track( tmp ) ); tmp.clear(); } } for( unsigned i = 0; i + 1 < data.size(); ++i ) { const Vrhomboid & v1 = data[i]; const Vrhomboid & v2 = data[i+1]; if( v1.right() + 1 < v2.left() ) { Vrhomboid v( v1.right() + 1, v1.rvcenter(), v2.left() - 1, v2.lvcenter(), ( v1.height() + v2.height() ) / 2 ); ++i; data.insert( data.begin() + i, v ); } } } int Track::bottom( const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) { const Vrhomboid & vr = data[i]; if( col <= vr.right() || i + 1 >= data.size() ) return vr.bottom( col ); } return 0; } int Track::top( const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) { const Vrhomboid & vr = data[i]; if( col <= vr.right() || i + 1 >= data.size() ) return vr.top( col ); } return 0; } int Track::vcenter( const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) { const Vrhomboid & vr = data[i]; if( col <= vr.right() || i + 1 >= data.size() ) return vr.vcenter( col ); } return 0; } bool Track::includes( const Rectangle & r ) const { for( unsigned i = 0; i < data.size(); ++i ) if( data[i].includes( r ) ) return true; if( data.empty() ) return false; if( r.right() > data.back().right() ) { Vrhomboid tmp = data.back(); tmp.extend_right( r.right() ); return tmp.includes( r ); } if( r.left() < data.front().left() ) { Vrhomboid tmp = data.front(); tmp.extend_left( r.left() ); return tmp.includes( r ); } return false; } bool Track::includes( const int row, const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) if( data[i].includes( row, col ) ) return true; if( data.empty() ) return false; if( col > data.back().right() ) { Vrhomboid tmp = data.back(); tmp.extend_right( col ); return tmp.includes( row, col ); } if( col < data.front().left() ) { Vrhomboid tmp = data.front(); tmp.extend_left( col ); return tmp.includes( row, col ); } return false; } ocrad-0.24/track.h000066400000000000000000000051501241541103500140010ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Vrhomboid // Rhomboid with two vertical sides. { int left_, lvcenter_, right_, rvcenter_, height_; public: Vrhomboid( const int l, const int lc, const int r, const int rc, const int h ); int left() const { return left_; } int lvcenter() const { return lvcenter_; } int right() const { return right_; } int rvcenter() const { return rvcenter_; } int height() const { return height_; } int width() const { return right_ - left_ + 1; } int size() const { return height_ * width(); } void left( const int l ); void lvcenter( const int lc ) { lvcenter_ = lc; } void right( const int r ); void rvcenter( const int rc ) { rvcenter_ = rc; } void height( const int h ); void extend_left( const int l ); void extend_right( const int r ); int bottom( const int col ) const { return vcenter( col ) + ( height_ / 2 ); } int top( const int col ) const { return bottom( col ) - height_ + 1; } int vcenter( const int col ) const; bool includes( const Rectangle & r ) const; bool includes( const int row, const int col ) const; }; class Track // vector of Vrhomboids tracking a Textline. { std::vector< Vrhomboid > data; public: Track() {} Track( const Track & t ) : data( t.data ) {} Track & operator=( const Track & t ) { if( this != &t ) { data = t.data; } return *this; } void set_track( const std::vector< Rectangle > & rectangle_vector ); int segments() const { return data.size(); } int height() const { return data.empty() ? 0 : data[0].height(); } int left() const { return data.empty() ? 0 : data[0].left(); } int right() const { return data.empty() ? 0 : data.back().right(); } int bottom( const int col ) const; int top( const int col ) const; int vcenter( const int col ) const; bool includes( const Rectangle & r ) const; bool includes( const int row, const int col ) const; }; ocrad-0.24/ucs.cc000066400000000000000000000255101241541103500136270ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "ucs.h" int UCS::base_letter( const int code ) { switch( code ) { case CAGRAVE: case CAACUTE: case CACIRCU: case CATILDE: case CADIAER: case CARING : return 'A'; case CCCEDI : return 'C'; case CEGRAVE: case CEACUTE: case CECIRCU: case CEDIAER: return 'E'; case CGBREVE: return 'G'; case CIGRAVE: case CIACUTE: case CICIRCU: case CIDIAER: case CIDOT : return 'I'; case CNTILDE: return 'N'; case COGRAVE: case COACUTE: case COCIRCU: case COTILDE: case CODIAER: return 'O'; case CSCEDI : return 'S'; case CUGRAVE: case CUACUTE: case CUCIRCU: case CUDIAER: return 'U'; case CYACUTE: return 'Y'; case SAGRAVE: case SAACUTE: case SACIRCU: case SATILDE: case SADIAER: case SARING : return 'a'; case SCCEDI : return 'c'; case SEGRAVE: case SEACUTE: case SECIRCU: case SEDIAER: return 'e'; case SGBREVE: return 'g'; case SIGRAVE: case SIACUTE: case SICIRCU: case SIDIAER: case SINODOT: return 'i'; case SNTILDE: return 'n'; case SOGRAVE: case SOACUTE: case SOCIRCU: case SOTILDE: case SODIAER: return 'o'; case SSCEDI : return 's'; case SUGRAVE: case SUACUTE: case SUCIRCU: case SUDIAER: return 'u'; case SYACUTE: case SYDIAER: return 'y'; default: return 0; } } int UCS::compose( const int letter, const int accent ) { switch( letter ) { case 'A': if( accent == '\'') return CAACUTE; if( accent == '`' ) return CAGRAVE; if( accent == '^' ) return CACIRCU; if( accent == ':' ) return CADIAER; break; case 'E': if( accent == '\'') return CEACUTE; if( accent == '`' ) return CEGRAVE; if( accent == '^' ) return CECIRCU; if( accent == ':' ) return CEDIAER; break; case 'G': return CGBREVE; case '[': case 'I': if( accent == '\'') return CIACUTE; if( accent == '`' ) return CIGRAVE; if( accent == '^' ) return CICIRCU; if( accent == ':' ) return CIDIAER; break; case 'N': if( accent != ':' ) return CNTILDE; break; case 'O': if( accent == '\'') return COACUTE; if( accent == '`' ) return COGRAVE; if( accent == '^' ) return COCIRCU; if( accent == ':' ) return CODIAER; break; case 'S': return CSCARON; case 'U': case 'V': if( accent == '\'') return CUACUTE; if( accent == '`' ) return CUGRAVE; if( accent == '^' ) return CUCIRCU; if( accent == ':' ) return CUDIAER; break; case 'Z': return CZCARON; case 'a': if( accent == '\'') return SAACUTE; if( accent == '`' ) return SAGRAVE; if( accent == '^' ) return SACIRCU; if( accent == ':' ) return SADIAER; break; case 'e': if( accent == '\'') return SEACUTE; if( accent == '`' ) return SEGRAVE; if( accent == '^' ) return SECIRCU; if( accent == ':' ) return SEDIAER; break; case '9': case 'g': return SGBREVE; case '|': case ']': case 'i': case 'l': if( accent == '\'') return SIACUTE; if( accent == '`' ) return SIGRAVE; if( accent == '^' ) return SICIRCU; if( accent == ':' ) return SIDIAER; break; case 'n': if( accent != ':' ) return SNTILDE; break; case 'o': if( accent == '\'') return SOACUTE; if( accent == '`' ) return SOGRAVE; if( accent == '^' ) return SOCIRCU; if( accent == ':' ) return SODIAER; break; case 's': return SSCARON; case 'u': case 'v': if( accent == '\'') return SUACUTE; if( accent == '`' ) return SUGRAVE; if( accent == '^' ) return SUCIRCU; if( accent == ':' ) return SUDIAER; break; case 'y': if( accent == '\'') return SYACUTE; if( accent == ':' ) return SYDIAER; break; case 'z': return SZCARON; } return 0; } bool UCS::isalnum( const int code ) { return ( UCS::isalpha( code ) || UCS::isdigit( code ) ); } bool UCS::isalpha( const int code ) { return ( ( code < 128 && std::isalpha( code ) ) || base_letter( code ) ); } bool UCS::isdigit( const int code ) { return ( code <= '9' && code >= '0' ); } bool UCS::ishigh( const int code ) { if( isupper( code ) || isdigit( code ) ) return true; switch( code ) { case 'b': case 'd': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'p': case 'q': case 't': case 'y': case '|': return true; default : return false; } } bool UCS::islower( const int code ) { if( code < 128 && std::islower( code ) ) return true; const int base = base_letter( code ); return ( base && std::islower( base ) ); } bool UCS::islower_ambiguous( const int code ) { if( islower_small_ambiguous( code ) ) return true; switch( code ) { case 'k': case 'p': case SCCEDI: case SIGRAVE: case SIACUTE: case SICIRCU: case SIDIAER: case SOGRAVE: case SOACUTE: case SOCIRCU: case SOTILDE: case SODIAER: case SUGRAVE: case SUACUTE: case SUCIRCU: case SUDIAER: case SSCEDI: case SSCARON: case SZCARON: return true; default : return false; } } bool UCS::islower_small( const int code ) { if( code >= 128 || !std::islower( code ) ) return false; switch( code ) { case 'a': case 'c': case 'e': case 'm': case 'n': case 'o': case 'r': case 's': case 'u': case 'v': case 'w': case 'x': case 'z': return true; default : return false; } } bool UCS::islower_small_ambiguous( const int code ) { if( code >= 128 || !std::islower( code ) ) return false; switch( code ) { case 'c': case 'o': case 's': case 'u': case 'v': case 'w': case 'x': case 'z': return true; default : return false; } } bool UCS::isspace( const int code ) { return ( code < 128 && std::isspace( code ) ); } bool UCS::isupper( const int code ) { if( code < 128 && std::isupper( code ) ) return true; const int base = base_letter( code ); return ( base && std::isupper( base ) ); } bool UCS::isvowel( int code ) { if( code >= 128 ) code = base_letter( code ); if( !code || !std::isalpha( code ) ) return false; code = std::tolower( code ); return ( code == 'a' || code == 'e' || code == 'i' || code == 'o' || code == 'u' ); } unsigned char UCS::map_to_byte( const int code ) { if( code < 0 ) return 0; if( code < 256 ) return code; switch( code ) { case CGBREVE: return 0xD0; case SGBREVE: return 0xF0; case CIDOT : return 0xDD; case SINODOT: return 0xFD; case CSCEDI : return 0xDE; case SSCEDI : return 0xFE; case CSCARON: return 0xA6; case SSCARON: return 0xA8; case CZCARON: return 0xB4; case SZCARON: return 0xB8; case EURO : return 0xA4; default : return 0; } } const char * UCS::ucs_to_utf8( const int code ) { static char s[7]; if( code < 0 || code > 0x7FFFFFFF ) { s[0] = 0; return s; } // invalid code if( code < 128 ) { s[0] = code; s[1] = 0; return s; } // plain ascii int i, mask; if( code < 0x800 ) { i = 2; mask = 0xC0; } // 110X XXXX else if( code < 0x10000 ) { i = 3; mask = 0xE0; } // 1110 XXXX else if( code < 0x200000 ) { i = 4; mask = 0xF0; } // 1111 0XXX else if( code < 0x4000000 ) { i = 5; mask = 0xF8; } // 1111 10XX else { i = 6; mask = 0xFC; } // 1111 110X s[i] = 0; --i; int d = 0; for( ; i > 0; --i, d+=6 ) s[i] = 0x80 | ( ( code >> d ) & 0x3F ); // 10XX XXXX s[0] = mask | ( code >> d ); return s; } int UCS::to_nearest_digit( const int code ) { switch( code ) { case 'O': case 'Q': case 'o': return '0'; case '|': case 'I': case 'L': case 'l': case SINODOT: return '1'; case 'Z': case 'z': return '2'; case 'A': case 'q': return '4'; case 'S': case 's': return '5'; case 'G': case 'b': case SOACUTE: return '6'; case 'J': case 'T': return '7'; case '&': case 'B': return '8'; case 'g': return '9'; default: return code; } } int UCS::to_nearest_letter( const int code ) { switch( code ) { case '0': return 'O'; case '1': return 'l'; case '2': return 'Z'; case '4': return 'q'; case '5': return 'S'; case '6': return SOACUTE; case '7': return 'I'; case '8': return 'B'; case '9': return 'g'; default: return code; } } int UCS::to_nearest_upper_num( const int code ) { switch( code ) { case 'l': case '|': return 'I'; case DEG: return 'O'; case MICRO: return 'U'; case POW1: case SINODOT: return '1'; case POW2: return '2'; case POW3: return '3'; case 'q': return '4'; case 'b': case SOACUTE: return '6'; case '&': return '8'; case 'g': case MASCORD: return '9'; } if( islower_ambiguous( code ) ) return toupper( code ); return code; } int UCS::toupper( const int code ) { if( code < 128 ) return std::toupper( code ); switch( code ) { case SAGRAVE: return CAGRAVE; case SAACUTE: return CAACUTE; case SACIRCU: return CACIRCU; case SATILDE: return CATILDE; case SADIAER: return CADIAER; case SARING : return CARING; case SCCEDI : return CCCEDI; case SEGRAVE: return CEGRAVE; case SEACUTE: return CEACUTE; case SECIRCU: return CECIRCU; case SEDIAER: return CEDIAER; case SGBREVE: return CGBREVE; case SIGRAVE: return CIGRAVE; case SIACUTE: return CIACUTE; case SICIRCU: return CICIRCU; case SIDIAER: return CIDIAER; case SNTILDE: return CNTILDE; case SOGRAVE: return COGRAVE; case SOACUTE: return COACUTE; case SOCIRCU: return COCIRCU; case SOTILDE: return COTILDE; case SODIAER: return CODIAER; case SSCEDI : return CSCEDI; case SUGRAVE: return CUGRAVE; case SUACUTE: return CUACUTE; case SUCIRCU: return CUCIRCU; case SUDIAER: return CUDIAER; case SYACUTE: return CYACUTE; default: return code; } } ocrad-0.24/ucs.h000066400000000000000000000145031241541103500134710ustar00rootroot00000000000000/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2014 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ namespace UCS { enum { IEXCLAM = 0x00A1, // inverted exclamation mark COPY = 0x00A9, // copyright sign FEMIORD = 0x00AA, // feminine ordinal indicator LDANGLE = 0x00AB, // left-pointing double angle quotation mark NOT = 0x00AC, // not sign REG = 0x00AE, // registered sign DEG = 0x00B0, // degree sign PLUSMIN = 0x00B1, // plus-minus sign POW2 = 0x00B2, // superscript two POW3 = 0x00B3, // superscript three MICRO = 0x00B5, // micro sign PILCROW = 0x00B6, // pilcrow sign MIDDOT = 0x00B7, // middle dot POW1 = 0x00B9, // superscript one MASCORD = 0x00BA, // masculine ordinal indicator RDANGLE = 0x00BB, // right-pointing double angle quotation mark IQUEST = 0x00BF, // inverted question mark CAGRAVE = 0x00C0, // latin capital letter a with grave CAACUTE = 0x00C1, // latin capital letter a with acute CACIRCU = 0x00C2, // latin capital letter a with circumflex CATILDE = 0x00C3, // latin capital letter a with tilde CADIAER = 0x00C4, // latin capital letter a with diaeresis CARING = 0x00C5, // latin capital letter a with ring above CCCEDI = 0x00C7, // latin capital letter c with cedilla CEGRAVE = 0x00C8, // latin capital letter e with grave CEACUTE = 0x00C9, // latin capital letter e with acute CECIRCU = 0x00CA, // latin capital letter e with circumflex CEDIAER = 0x00CB, // latin capital letter e with diaeresis CIGRAVE = 0x00CC, // latin capital letter i with grave CIACUTE = 0x00CD, // latin capital letter i with acute CICIRCU = 0x00CE, // latin capital letter i with circumflex CIDIAER = 0x00CF, // latin capital letter i with diaeresis CNTILDE = 0x00D1, // latin capital letter n with tilde COGRAVE = 0x00D2, // latin capital letter o with grave COACUTE = 0x00D3, // latin capital letter o with acute COCIRCU = 0x00D4, // latin capital letter o with circumflex COTILDE = 0x00D5, // latin capital letter o with tilde CODIAER = 0x00D6, // latin capital letter o with diaeresis CUGRAVE = 0x00D9, // latin capital letter u with grave CUACUTE = 0x00DA, // latin capital letter u with acute CUCIRCU = 0x00DB, // latin capital letter u with circumflex CUDIAER = 0x00DC, // latin capital letter u with diaeresis CYACUTE = 0x00DD, // latin capital letter y with acute SSSHARP = 0x00DF, // latin small letter sharp s (german) SAGRAVE = 0x00E0, // latin small letter a with grave SAACUTE = 0x00E1, // latin small letter a with acute SACIRCU = 0x00E2, // latin small letter a with circumflex SATILDE = 0x00E3, // latin small letter a with tilde SADIAER = 0x00E4, // latin small letter a with diaeresis SARING = 0x00E5, // latin small letter a with ring above SCCEDI = 0x00E7, // latin small letter c with cedilla SEGRAVE = 0x00E8, // latin small letter e with grave SEACUTE = 0x00E9, // latin small letter e with acute SECIRCU = 0x00EA, // latin small letter e with circumflex SEDIAER = 0x00EB, // latin small letter e with diaeresis SIGRAVE = 0x00EC, // latin small letter i with grave SIACUTE = 0x00ED, // latin small letter i with acute SICIRCU = 0x00EE, // latin small letter i with circumflex SIDIAER = 0x00EF, // latin small letter i with diaeresis SNTILDE = 0x00F1, // latin small letter n with tilde SOGRAVE = 0x00F2, // latin small letter o with grave SOACUTE = 0x00F3, // latin small letter o with acute SOCIRCU = 0x00F4, // latin small letter o with circumflex SOTILDE = 0x00F5, // latin small letter o with tilde SODIAER = 0x00F6, // latin small letter o with diaeresis DIV = 0x00F7, // division sign SUGRAVE = 0x00F9, // latin small letter u with grave SUACUTE = 0x00FA, // latin small letter u with acute SUCIRCU = 0x00FB, // latin small letter u with circumflex SUDIAER = 0x00FC, // latin small letter u with diaeresis SYACUTE = 0x00FD, // latin small letter y with acute SYDIAER = 0x00FF, // latin small letter y with diaeresis CGBREVE = 0X011E, // latin capital letter g with breve SGBREVE = 0x011F, // latin small letter g with breve CIDOT = 0x0130, // latin capital letter i with dot above SINODOT = 0x0131, // latin small letter i dotless CSCEDI = 0x015E, // latin capital letter s with cedilla SSCEDI = 0x015F, // latin small letter s with cedilla CSCARON = 0x0160, // latin capital letter s with caron SSCARON = 0x0161, // latin small letter s with caron CZCARON = 0x017D, // latin capital letter z with caron SZCARON = 0x017E, // latin small letter z with caron EURO = 0x20AC // symbole euro }; int base_letter( const int code ); int compose( const int letter, const int accent ); bool isalnum( const int code ); bool isalpha( const int code ); bool isdigit( const int code ); bool ishigh( const int code ); // high chars like "A1bp|" bool islower( const int code ); bool islower_ambiguous( const int code ); bool islower_small( const int code ); bool islower_small_ambiguous( const int code ); bool isspace( const int code ); bool isupper( const int code ); bool isvowel( int code ); unsigned char map_to_byte( const int code ); const char * ucs_to_utf8( const int code ); int to_nearest_digit( const int code ); int to_nearest_letter( const int code ); int to_nearest_upper_num( const int code ); int toupper( const int code ); } // end namespace UCS