思いつく単純な手順:
- 上から下まで全て黒である(=文字がない)箇所のx座標範囲を求める.→複数の領域が見つかり,その多くは「行間」であるハズ.
→隣接する領域間の距離の最頻値あたりから,行(文字のある箇所)の幅を推定
→領域のx幅の最貧値あたりから,行間の幅を推定(行間の幅は行の幅よりも小さいとか何とか条件を付けたりして)
- これらの情報をもとに,各行(文字のある箇所)のx座標を推定
- 各行位置を下から上へ見ていき,黒範囲を決定
上記のような話を実装.
- 当方,pythonがダメなのでC++です.
- 各処理のクオリティが色々と雑です.
- 質問の画像とは白黒が逆になってます(背景が白で,文字が黒)
- 画像左右の文字が無い領域が,余白なのか空行なのか判断できません.
int main(void)
{
//(0)画像読込
cv::Mat Src = cv::imread( "TestLetter.png", CV_LOAD_IMAGE_GRAYSCALE );
if( Src.empty() )return 0;
cv::imshow( "Src", Src );
//(1)各x座標における,黒画素の下端y座標を求めておく
std::vector<int> YMax( Src.cols, 0 );
for( int y=0; y<Src.rows; ++y )
{
const unsigned char *pI = Src.ptr<unsigned char>(y);
for( int x=0; x<Src.cols; ++x, ++pI )
{
if( *pI < 250 ){ YMax[x] = y; }
}
}
//(2)文字が無いx区間を収集
std::vector< std::pair<int,int> > NonLetterReg;
int RegLeft = -1;
for( int x=0; x<YMax.size(); ++x )
{
if( RegLeft < 0 )
{
if( YMax[x]<=0 )
{ RegLeft=x; }
}
else
{
if( YMax[x]>0 )
{
NonLetterReg.emplace_back( RegLeft, x-1 );
RegLeft = -1;
}
}
}
if( RegLeft >= 0 )
{ NonLetterReg.emplace_back( RegLeft, YMax.size()-1 ); }
//(3)行と行間の幅を推定
int LetterWidth = 0; //行の幅
{//行の幅をNonLetterRegの隣接要素間の間隔の平均として推定
int SumDx = 0;
for( int i=0; i+1<NonLetterReg.size(); ++i )
{ SumDx += NonLetterReg[i+1].first - NonLetterReg[i].second; }
LetterWidth = SumDx / (NonLetterReg.size()-1);
}
int BLWidth = Src.cols; //行間の幅
{//行間の幅の推定… 面倒なのでここでは最小値にしちゃう^^
for( auto &Reg : NonLetterReg )
{ BLWidth = std::min( BLWidth, Reg.second-Reg.first+1 ); }
}
//(4)文字の下端位置を各行に関して調べる
//※面倒なので結果はデータ化せずに直接画像に描画している
cv::Mat Result; //結果描画用の絵
cv::cvtColor( Src, Result, CV_GRAY2BGR );
{
int LetterLeft = NonLetterReg.front().second + 1;
while( LetterLeft+LetterWidth-1 < YMax.size() )
{
//着目行範囲x座標(の推定):LetterLeft~LetterRight
int LetterRight = LetterLeft + LetterWidth-1;
int Bottom = *std::max_element( YMax.begin()+LetterLeft, YMax.begin()+LetterRight );
//x範囲全域でYMax[x]が0だったら空行.
//そうでない場合…LetterLeftの位置決めがあまりにも雑なのでYMax[LetterLeft]>0になる位置に微調整する
if( Bottom>0 && YMax[LetterLeft]<=0 )
{ ++LetterLeft; continue; } //※座標調整の実装が雑すぎるが
//結果描画
cv::rectangle( Result, cv::Point(LetterLeft,Bottom), cv::Point(LetterRight,Result.rows), cv::Scalar( 0,0,255 ), -1 );
//次の行(の推定位置)へ
LetterLeft = LetterRight + 1 + BLWidth;
}
}
cv::imshow( "Result", Result );
cv::waitKey();
return 0;
}