/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003, 2004, 2005, 2006, 2007 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 3 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 "common.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" #include "textpage.h" namespace { struct Zone { Rectangle rectangle; std::vector< std::vector< Blob * > > blobp_matrix; Zone( const Rectangle & r ) : rectangle( r ) {} }; int blobs_in_zone( const std::vector< Zone > & zone_vector, int i ) throw() { const std::vector< std::vector< Blob * > > & blobp_matrix = zone_vector[i].blobp_matrix; int sum = 0; for( unsigned int cut = 0; cut < blobp_matrix.size(); ++cut ) sum += blobp_matrix[cut].size(); return sum; } int blobs_in_page( const std::vector< Zone > & zone_vector ) throw() { int sum = 0; for( unsigned int i = 0; i < zone_vector.size(); ++i ) sum += blobs_in_zone( zone_vector, i ); return sum; } void bprint( const std::vector< Zone > & zone_vector, FILE * outfile ) throw() { // 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 int zindex = 0; zindex < zone_vector.size(); ++zindex ) { const Rectangle & r = zone_vector[zindex].rectangle; const std::vector< std::vector< Blob * > > & blobp_matrix = zone_vector[zindex].blobp_matrix; 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 cuts in zone %d\n", (int)blobp_matrix.size() ); std::fprintf( outfile, "total blobs in zone %d\n\n", blobs_in_zone( zone_vector, zindex ) ); for( unsigned int cut = 0; cut < blobp_matrix.size(); ++cut ) { std::fprintf( outfile, "cut %d blobs %d\n", cut + 1, (int)blobp_matrix[cut].size() ); for( unsigned int i = 0; i < blobp_matrix[cut].size(); ++i ) blobp_matrix[cut][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 ) throw() { if( p1->top() > p2->top() ) { Blob * 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 ) throw() { for( int i = blobp_vector.size() - 1; i >= 0; --i ) { 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 ) throw() { int to = 0, blobs = blobp_vector.size(); for( int from = 0; from < blobs; ++from ) { Blob * 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 ignore_wide_blobs( Zone & zone ) throw() { const Rectangle & r = zone.rectangle; std::vector< std::vector< Blob * > > & blobp_matrix = zone.blobp_matrix; for( unsigned int cut = 0; cut < blobp_matrix.size(); ++cut ) { bool frame_found = false; for( unsigned int i = 0; i < blobp_matrix[cut].size(); ) { std::vector< Blob * > & blobp_vector = blobp_matrix[cut]; Blob & b = *blobp_vector[i]; if( 2 * b.width() < r.width() ) { ++i; continue; } blobp_vector.erase( blobp_vector.begin() + i ); if( 4 * b.area() <= 3 * b.size() ) { if( 4 * b.area() <= b.size() ) { frame_found = true; delete &b; continue; } b.find_holes(); int j = -1; if( b.holes() < std::min( b.height(), b.width() ) ) for( j = 0; j < b.holes(); ++j ) if( 4 * b.hole( j ).size() >= b.size() && 4 * b.hole( j ).area() >= b.size() ) break; if( j >= 0 && j < b.holes() ) { frame_found = true; delete &b; continue; } } // image, not frame if( 5 * b.width() > 4 * r.width() && 5 * b.height() > 4 * r.height() ) { for( unsigned int j = 0; j < blobp_vector.size(); ++j ) delete blobp_vector[j]; blobp_vector.clear(); delete &b; break; } for( int j = blobp_vector.size() - 1; j >= 0; --j ) { const Blob & b2 = *blobp_vector[j]; if( b.includes( b2 ) ) { delete &b2; blobp_vector.erase( blobp_vector.begin() + j ); } else if( b2.top() < b.top() ) break; } delete &b; } if( frame_found ) // Make cuts from blobs inside deleted frame(s) { int bottom = 0; for( unsigned int i = 0; i < blobp_matrix[cut].size(); ++i ) { std::vector< Blob * > & blobp_vector = blobp_matrix[cut]; const Blob & b = *blobp_vector[i]; if( b.bottom() > bottom ) { int old_bottom = bottom; bottom = b.bottom(); if( b.top() > old_bottom && i > 0 ) { std::vector< Blob * > new_blobp_vector( blobp_vector.begin() + i, blobp_vector.end() ); blobp_vector.erase( blobp_vector.begin() + i, blobp_vector.end() ); blobp_matrix.insert( blobp_matrix.begin() + cut + 1, new_blobp_vector ); ++cut; i = 0; } } } } } } void remove_top_bottom_noise( std::vector< Blob * > & blobp_vector ) throw() { 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 ) throw() { 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 ) throw() { for( unsigned int zi = 0; zi < zone_vector.size(); ++zi ) { std::vector< std::vector< Blob * > > & blobp_matrix = zone_vector[zi].blobp_matrix; for( unsigned int bmi = 0; bmi < blobp_matrix.size(); ++bmi ) { std::vector< Blob * > & blobp_vector = blobp_matrix[bmi]; for( unsigned int bvi = 0; bvi < blobp_vector.size(); ++bvi ) blobp_vector[bvi]->find_holes(); } } } void scan_page( const Page_image & page_image, std::vector< Zone > & zone_vector, const int debug_level ) throw() { for( int zindex = 0; zindex < page_image.zones(); ++zindex ) { zone_vector.push_back( Zone( page_image.rectangle( zindex ) ) ); const Rectangle & re = zone_vector[zindex].rectangle; const int zthreshold = page_image.threshold( zindex ); std::vector< std::vector< Blob * > > & blobp_matrix = zone_vector[zindex].blobp_matrix; 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() + 1; row <= re.bottom(); ++row ) { bool blank_row = true; old_data.swap( new_data ); for( int col = re.left() + 1; col < re.right(); ++col ) { const int dcol = col - re.left(); if( !page_image.get_bit( row, col, zthreshold ) ) new_data[dcol] = 0; // white point else // black point { blank_row = false; Blob *p; Blob *lp = new_data[dcol-1]; Blob *ltp = old_data[dcol-1]; Blob *tp = old_data[dcol]; Blob *rtp = old_data[dcol+1]; 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( blank_row && blobp_vector.size() ) { blobp_matrix.push_back( std::vector< Blob * >() ); blobp_vector.swap( blobp_matrix.back() ); } } if( blobp_vector.size() ) { blobp_matrix.push_back( std::vector< Blob * >() ); blobp_vector.swap( blobp_matrix.back() ); } } if( debug_level <= 99 ) for( unsigned int zindex = 0; zindex < zone_vector.size(); ++zindex ) { ignore_wide_blobs( zone_vector[zindex] ); std::vector< std::vector< Blob * > > & blobp_matrix = zone_vector[zindex].blobp_matrix; for( unsigned int cut = 0; cut < blobp_matrix.size(); ++cut ) { std::vector< Blob * > & blobp_vector = blobp_matrix[cut]; ignore_small_blobs( blobp_vector ); ignore_abnormal_blobs( blobp_vector ); remove_top_bottom_noise( blobp_vector ); remove_left_right_noise( blobp_vector ); } } for( int zindex = zone_vector.size() - 1; zindex >= 0; --zindex ) { std::vector< std::vector< Blob * > > & blobp_matrix = zone_vector[zindex].blobp_matrix; for( int cut = blobp_matrix.size() - 1; cut >= 0; --cut ) if( !blobp_matrix[cut].size() ) blobp_matrix.erase( blobp_matrix.begin() + cut ); if( !blobp_matrix.size() ) zone_vector.erase( zone_vector.begin() + zindex ); } find_holes( zone_vector ); } } // end namespace Textpage::Textpage( const Page_image & page_image, const char * filename, const Control & control ) throw() : 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; scan_page( page_image, zone_vector, debug_level ); 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 int i = 0; i < zone_vector.size(); ++i ) { Textblock * tbp = new Textblock( zone_vector[i].rectangle, zone_vector[i].blobp_matrix ); if( debug_level < 90 ) tbp->recognize( control.charset, control.filter ); if( tbp->textlines() ) tbpv.push_back( tbp ); else delete tbp; } if( debug_level == 0 ) { if( control.outfile ) print( control ); if( control.exportfile ) xprint( control ); 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 int i = 0; i < zone_vector.size(); ++i ) tmp.draw_rectangle( zone_vector[i].rectangle ); } 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() throw() { for( int i = textblocks() - 1; i >= 0; --i ) delete tbpv[i]; } /* const Textblock & Textpage::textblock( int i ) const throw() { if( i < 0 || i >= textblocks() ) Ocrad::internal_error( "Textpage::textblock, index out of bounds" ); return *(tbpv[i]); } */ void Textpage::print( const Control & control ) const throw() { if( control.outfile ) for( int i = 0; i < textblocks(); ++i ) tbpv[i]->print( control ); } void Textpage::xprint( const Control & control ) const throw() { 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 ); } }