Closed yrarchi closed 3 years ago
こんな感じで、一部レシートの検知ができていない(赤枠で囲っているのが検知できたレシート)。
改めて見ると、この画像では机の右下が光を反射して色が薄くなっているので、それが要因かもしれない。
1 読み込んだ画像をグレースケールに変換
2 白と黒の2値化(大津の2値化)
3 矩形の検知
※ カラー画像になっているが、2の2値化した画像に対して矩形検知している
4 検知した矩形を条件で取捨(閉じていないものは切り捨て + 面積が画像全体の2〜90%の矩形のみにする)
2 の2値化の段階でレシートのみ白になるイメージだったが、背景の一部も白になってしまっているため、3 で矩形として認識できていない。
大津の2値化は2値の閾値を自動で決めてくれるけど、その閾値が都合の良いところで引かれていない状態 1 の段階(グレースケール)画像のヒストグラムを見てみると、山がいくつもあるのでうまくいかないのだろうなと思った 閾値は133となっていたので、画像右下の反射して光っている机の部分は133より値が大きいのだろう
単純に考えると、2値化する際に背景の机が黒と判定されれば(ヒストグラムで背景の机が白の山に入らず、黒の山に入るように調整できれば)うまくいくはず
【案の内容】 単純化すれば扱う画像は以下の3色に分けられるはず
この3色に画像を減色できている状態でグレースケールに変換すれば、ヒストグラムの山が3つになる。その状態で2値化すれば、うまくいくのでは
【実践】 減色はK-meansでできる(!) 考えてみれば色は3つの数字で表されているので、3次元空間でクラスタリングするのと同じことなのだった
3色に減色を試してみるとこんな感じで、背景が1色にはならず、かつ背景の一部はレシートと同色になっている(なるほどそうなるのだなあ) 減色 → グレースケール化 → 2値化 ヒストグラム
そこで、色数(クラスタリング数)を少し増やして、レシートと背景が同化しないようにする 4色の場合 減色 → グレースケール化 → 2値化 ヒストグラム 5色の場合 減色 → グレースケール化 → 2値化 ヒストグラム
案1の改善案 減色した後、2値化の閾値を自分で決める
「自動でいい感じの位置で閾値決めてください」が厳しいのかもしれない。 何色に減色したかに依るが、レシートは減色したX色のうち白側上位1色か2色になることが多いだろう。そこで、2値化の閾値を白側から2色と3色の間の位置とするルールにしてみる。 7色の場合(5色だとうまく検知できなかった…) 減色 → グレースケール化 → 2値化 ヒストグラム
案1の課題 「レシートは減色したX色のうち白側上位1色か2色になる」という仮定の元、閾値を任意で定めている。そのため、レシートが白側上位3色になっている場合、あるいは白側上位1、2位に背景も含まれてしまっている場合、この案は役に立たなくなる。特に背景が白っぽい場合にうまく背景とレシートを分離できないだろう。
【案の内容】 色を条件として検出するには,RGB 色空間よりHSV色空間を使ったほうが検出しやすいらしい。
HSVのパラメータ ① 色相(H):色合い 0 ≤ H ≤ 179(環状、0や179が赤) ② 彩度(S):鮮やかさ 0 ≤ S ≤ 255(大きいほど鮮やか) ③ 明度(V):明るさ 0 ≤ V ≤ 255(大きいほど明るい)
【実践】 手順としては
2値化 検知
2値化が期待した形でできているので、ばっちり検出できている
案2の課題 レシートの白色の範囲をHSVで指定する部分が、どの程度の幅にすれば適切なのかがわかっていない(これは背景が白に近づくほど厳しくなる) 今回の画像ではうまくいったけど、場合によりうまくいかない時もあるだろう。
エッジ = 画素値の急激に変化している箇所 と捉える すると、微分した値が大きい箇所をエッジとみなせる
エッジ検出
(X方向) エッジ検出 輪郭の検知(精査前) ノイズが多く、輪郭線を多く検知している割に、その後レシートの輪郭線だけにする処理のところがうまくいっていない
エッジ検出 輪郭の検知(精査前) ↑ 見づらいが、わずかにレシート矩形や印字の一部が検出されている
ノイズが多く、輪郭の検知がされすぎている
エッジ検出 輪郭の検知(精査前) 上2つに比べるとうまくいっているように見えるが、実際にはその後レシートの輪郭線だけにする処理のところがうまくいかず、1枚も検知できていない。これは、レシートの外形を囲っているように見えてそれらはひとつながりの外形になっておらず、別々の矩形と捉えられているからと思われる。
検出したエッジのうち、どれを残すかを第2,3引数の値(minVal と maxVal)で指定する
maxValを変えて変化を見る(minVal固定) maxVal= 100 maxVal= 200 maxVal= 300 maxVal= 400
minValを変えて変化を見る(maxVal固定) minVal= 10 minVal= 100 minVal= 200 minVal= 300
このサイズだとわかりづらいが、minVal と maxValの値でかなり検出結果が変わることがわかる。本来画像ごとに適切な値が変わるが、今回は都度変えるのも難しいので、この点はネックになりそうな気がする
エッジ検出→矩形検出をそのままやると、うまくいかなかったので、ノイズ処理を追加してみる
検知したエッジをいったん膨張させて他の細かいエッジと結合させた後、膨張させたのを戻す
kernel 10 結果は中央の1枚だけ検知となった
右のレシートは一部外枠切れている状態なので、もっと膨張させればつながるのでは?と考えてkernelを大きくしてみる
kernel 30 中央の1枚しか検知できない 左右は背景のノイズ検知と繋がってしまい、矩形と判断できないため
(参考)kernel 120 レシート同士がつながってしまい、1枚も検知できない結果となった
kernel 10 1枚だけ検知できた
こちらも同様に、右のレシートは一部外枠切れている状態なので、もっと膨張させればつながるのでは?と考えてkernelを大きくしてみる
kernel 30 これだと、3枚とも検知できた
検知結果を見ると、レシートを囲む2重線のうち、内側のみうまく生き残っている。 これは、外側のレシートを囲む線は背景のノイズを拾った線とつながるなどしていて、矩形と判定されていないため。今回kernelの大きさが割とぎりぎりで、もう少し大きくすると今度はレシートの印字の検知の範囲とレシートを囲む内側の線がくっついてしまい、うまく検知されなくなることが予想される。 ※ そのため、cv2.findContoursの引数はcv2.RETR_EXTERNALにしてしまうとうまくいかなくなる(レシートを囲む内側の線がなくなってしまうため)
※ クロージング + cv2.RETR_EXTERNALにして、さらに検知した外形を絞る際の条件をゆるくする(頂点が4つ以上あってもよしとする)とうまくいくのでは…としてみたが、以下の結果だった
全体ではなく、画像中の小領域ごとにしきい値の値を計算する方法 → 領域ごとに光源環境が変わってしまうような画像に対して,単純なしきい値処理より良い結果が得られる
引数の一部を変えて結果を確認する
Block Size(C固定2) Block Size = 11 Block Size = 101 Block Size = 1001
C(Block Size固定101) C = 1 C = 3 C = 5
Block Sizeをある程度大きくした方がfindContoursがうまく働く気がする
Bloce Size=255、C=2でやってみる
レシート3枚拾えているが、余計な背景も拾ってしまっている かなりノイズが多いので、それがよくないかも
ノイズ処理を追加してみる(中央値フィルタ)
今度はレシートのみ、3枚ともうまく拾えている
課題 閾値を結果を見ながら調整したので、画像によってはうまく拾えないだろうな…(もとより、全ての画像に対応できる手法はないだろうが)
もしハフ変換でレシートと背景の境界線をうまく検出できれば、そこから矩形検出につなげられそう
Canny法でエッジ検出した状態でハフ変換で直線を検出してみる 上記3つのパラメータを変化させて検出結果の違いを確認する 思っていたより検出の精度は低かった。パラメータの調整等で精度は上げられるだろうが、任意の画像に対応するのは難しそうなので、この案は採用しない。
他の画像でも試す
https://github.com/yrarchi/household_accounts/issues/21#issuecomment-835751560 で一番検出率高かった適応的閾値処理でアプリ修正した https://github.com/yrarchi/household_accounts/pull/22
このイシューの目的だった、検知できない要因を調べ、検知能力を上げる
が完全にないにせよ一定達成されたため、このイシューは完了とする
課題
一部レシートの検知がうまく行えていないことがそれなりの頻度で発生している。 (人間の目で見る分には背景との差異がある程度あるように思える場合にも)
目標
検知できない要因を調べ、検知能力を上げることを目標とする。
試した手法
原案 大津の2値化 改善案1 減色する 改善案2 HSV色空間にする 改善案3 エッジ検出を使う 改善案4 適応的しきい値処理を使う 改善案5 ハフ変換を使う