質問するログイン新規登録

回答編集履歴

2

実装例追記

2018/11/14 12:28

投稿

fana
fana

スコア12285

answer CHANGED
@@ -5,4 +5,97 @@
5
5
  →領域のx幅の最貧値あたりから,行間の幅を推定(行間の幅は行の幅よりも小さいとか何とか条件を付けたりして)
6
6
 
7
7
  2. これらの情報をもとに,各行(文字のある箇所)のx座標を推定
8
- 3. 各行位置を下から上へ見ていき,黒範囲を決定
8
+ 3. 各行位置を下から上へ見ていき,黒範囲を決定
9
+
10
+ ---
11
+
12
+ 上記のような話を実装.
13
+ * 当方,pythonがダメなのでC++です.
14
+ * 各処理のクオリティが色々と雑です.
15
+ * 質問の画像とは白黒が逆になってます(背景が白で,文字が黒)
16
+ * 画像左右の文字が無い領域が,余白なのか空行なのか判断できません.
17
+ ```ここに言語を入力
18
+ int main(void)
19
+ {
20
+ //(0)画像読込
21
+ cv::Mat Src = cv::imread( "TestLetter.png", CV_LOAD_IMAGE_GRAYSCALE );
22
+ if( Src.empty() )return 0;
23
+ cv::imshow( "Src", Src );
24
+
25
+ //(1)各x座標における,黒画素の下端y座標を求めておく
26
+ std::vector<int> YMax( Src.cols, 0 );
27
+ for( int y=0; y<Src.rows; ++y )
28
+ {
29
+ const unsigned char *pI = Src.ptr<unsigned char>(y);
30
+ for( int x=0; x<Src.cols; ++x, ++pI )
31
+ {
32
+ if( *pI < 250 ){ YMax[x] = y; }
33
+ }
34
+ }
35
+ //(2)文字が無いx区間を収集
36
+ std::vector< std::pair<int,int> > NonLetterReg;
37
+ int RegLeft = -1;
38
+ for( int x=0; x<YMax.size(); ++x )
39
+ {
40
+ if( RegLeft < 0 )
41
+ {
42
+ if( YMax[x]<=0 )
43
+ { RegLeft=x; }
44
+ }
45
+ else
46
+ {
47
+ if( YMax[x]>0 )
48
+ {
49
+ NonLetterReg.emplace_back( RegLeft, x-1 );
50
+ RegLeft = -1;
51
+ }
52
+ }
53
+ }
54
+ if( RegLeft >= 0 )
55
+ { NonLetterReg.emplace_back( RegLeft, YMax.size()-1 ); }
56
+
57
+ //(3)行と行間の幅を推定
58
+ int LetterWidth = 0; //行の幅
59
+ {//行の幅をNonLetterRegの隣接要素間の間隔の平均として推定
60
+ int SumDx = 0;
61
+ for( int i=0; i+1<NonLetterReg.size(); ++i )
62
+ { SumDx += NonLetterReg[i+1].first - NonLetterReg[i].second; }
63
+ LetterWidth = SumDx / (NonLetterReg.size()-1);
64
+ }
65
+ int BLWidth = Src.cols; //行間の幅
66
+ {//行間の幅の推定… 面倒なのでここでは最小値にしちゃう^^
67
+ for( auto &Reg : NonLetterReg )
68
+ { BLWidth = std::min( BLWidth, Reg.second-Reg.first+1 ); }
69
+ }
70
+
71
+ //(4)文字の下端位置を各行に関して調べる
72
+ //※面倒なので結果はデータ化せずに直接画像に描画している
73
+ cv::Mat Result; //結果描画用の絵
74
+ cv::cvtColor( Src, Result, CV_GRAY2BGR );
75
+ {
76
+ int LetterLeft = NonLetterReg.front().second + 1;
77
+ while( LetterLeft+LetterWidth-1 < YMax.size() )
78
+ {
79
+ //着目行範囲x座標(の推定):LetterLeft~LetterRight
80
+ int LetterRight = LetterLeft + LetterWidth-1;
81
+ int Bottom = *std::max_element( YMax.begin()+LetterLeft, YMax.begin()+LetterRight );
82
+
83
+ //x範囲全域でYMax[x]が0だったら空行.
84
+ //そうでない場合…LetterLeftの位置決めがあまりにも雑なのでYMax[LetterLeft]>0になる位置に微調整する
85
+ if( Bottom>0 && YMax[LetterLeft]<=0 )
86
+ { ++LetterLeft; continue; } //※座標調整の実装が雑すぎるが
87
+
88
+ //結果描画
89
+ cv::rectangle( Result, cv::Point(LetterLeft,Bottom), cv::Point(LetterRight,Result.rows), cv::Scalar( 0,0,255 ), -1 );
90
+
91
+ //次の行(の推定位置)へ
92
+ LetterLeft = LetterRight + 1 + BLWidth;
93
+ }
94
+ }
95
+
96
+ cv::imshow( "Result", Result );
97
+ cv::waitKey();
98
+ return 0;
99
+ }
100
+ ```
101
+ ![イメージ説明](1b132163d7dd6be33dbc4fb417cf144a.png)

1

追記

2018/11/14 12:28

投稿

fana
fana

スコア12285

answer CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  1. 上から下まで全て黒である(=文字がない)箇所のx座標範囲を求める.→複数の領域が見つかり,その多くは「行間」であるハズ.
4
4
  →隣接する領域間の距離の最頻値あたりから,行(文字のある箇所)の幅を推定
5
- →領域のx幅の最貧値あたりから,行間の幅を推定
5
+ →領域のx幅の最貧値あたりから,行間の幅を推定(行間の幅は行の幅よりも小さいとか何とか条件を付けたりして)
6
6
 
7
7
  2. これらの情報をもとに,各行(文字のある箇所)のx座標を推定
8
8
  3. 各行位置を下から上へ見ていき,黒範囲を決定