yrarchi / household_accounts

レシートのデータ化
19 stars 8 forks source link

レシートの検知能力を上げる #21

Closed yrarchi closed 3 years ago

yrarchi commented 3 years ago

課題

一部レシートの検知がうまく行えていないことがそれなりの頻度で発生している。 (人間の目で見る分には背景との差異がある程度あるように思える場合にも)

目標

検知できない要因を調べ、検知能力を上げることを目標とする。

試した手法

原案 大津の2値化 改善案1 減色する 改善案2 HSV色空間にする 改善案3 エッジ検出を使う 改善案4 適応的しきい値処理を使う 改善案5 ハフ変換を使う

yrarchi commented 3 years ago

事例

こんな感じで、一部レシートの検知ができていない(赤枠で囲っているのが検知できたレシート)。

改めて見ると、この画像では机の右下が光を反射して色が薄くなっているので、それが要因かもしれない。

yrarchi commented 3 years ago

現在の検知手順

1 読み込んだ画像をグレースケールに変換

2 白と黒の2値化(大津の2値化)

3 矩形の検知

※ カラー画像になっているが、2の2値化した画像に対して矩形検知している

4 検知した矩形を条件で取捨(閉じていないものは切り捨て + 面積が画像全体の2〜90%の矩形のみにする)

yrarchi commented 3 years ago

https://github.com/yrarchi/household_accounts/issues/21#issuecomment-821744974 の失敗要因

yrarchi commented 3 years ago

改善案

単純に考えると、2値化する際に背景の机が黒と判定されれば(ヒストグラムで背景の机が白の山に入らず、黒の山に入るように調整できれば)うまくいくはず

案1 グレースケールに変換する前に、減色しておけばうまいこといくのでは

【案の内容】 単純化すれば扱う画像は以下の3色に分けられるはず

この3色に画像を減色できている状態でグレースケールに変換すれば、ヒストグラムの山が3つになる。その状態で2値化すれば、うまくいくのでは

【実践】 減色はK-meansでできる(!) 考えてみれば色は3つの数字で表されているので、3次元空間でクラスタリングするのと同じことなのだった

案1の改善案 減色した後、2値化の閾値を自分で決める

「自動でいい感じの位置で閾値決めてください」が厳しいのかもしれない。 何色に減色したかに依るが、レシートは減色したX色のうち白側上位1色か2色になることが多いだろう。そこで、2値化の閾値を白側から2色と3色の間の位置とするルールにしてみる。 7色の場合(5色だとうまく検知できなかった…)          減色           →      グレースケール化 →       2値化                  ヒストグラム

案1の課題 「レシートは減色したX色のうち白側上位1色か2色になる」という仮定の元、閾値を任意で定めている。そのため、レシートが白側上位3色になっている場合、あるいは白側上位1、2位に背景も含まれてしまっている場合、この案は役に立たなくなる。特に背景が白っぽい場合にうまく背景とレシートを分離できないだろう。

yrarchi commented 3 years ago

案2 HSV色空間に変換して2値化する

【案の内容】 色を条件として検出するには,RGB 色空間よりHSV色空間を使ったほうが検出しやすいらしい。

HSVのパラメータ ① 色相(H):色合い 0 ≤ H ≤ 179(環状、0や179が赤) ② 彩度(S):鮮やかさ 0 ≤ S ≤ 255(大きいほど鮮やか) ③ 明度(V):明るさ 0 ≤ V ≤ 255(大きいほど明るい)

【実践】 手順としては

        2値化                   検知

2値化が期待した形でできているので、ばっちり検出できている

案2の課題 レシートの白色の範囲をHSVで指定する部分が、どの程度の幅にすれば適切なのかがわかっていない(これは背景が白に近づくほど厳しくなる) 今回の画像ではうまくいったけど、場合によりうまくいかない時もあるだろう。

yrarchi commented 3 years ago

案3 エッジ検出

エッジ = 画素値の急激に変化している箇所 と捉える すると、微分した値が大きい箇所をエッジとみなせる

エッジ検出

Sobel法

(X方向)          エッジ検出             輪郭の検知(精査前) ノイズが多く、輪郭線を多く検知している割に、その後レシートの輪郭線だけにする処理のところがうまくいっていない

Laplacian法

         エッジ検出             輪郭の検知(精査前) ↑ 見づらいが、わずかにレシート矩形や印字の一部が検出されている

ノイズが多く、輪郭の検知がされすぎている

Canny法

         エッジ検出             輪郭の検知(精査前) 上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の値でかなり検出結果が変わることがわかる。本来画像ごとに適切な値が変わるが、今回は都度変えるのも難しいので、この点はネックになりそうな気がする

yrarchi commented 3 years ago

案3の改善案 ノイズ処理を追加する

エッジ検出→矩形検出をそのままやると、うまくいかなかったので、ノイズ処理を追加してみる

canny→モルフォロジー変換(クロージング)

検知したエッジをいったん膨張させて他の細かいエッジと結合させた後、膨張させたのを戻す

kernel 10 結果は中央の1枚だけ検知となった

右のレシートは一部外枠切れている状態なので、もっと膨張させればつながるのでは?と考えてkernelを大きくしてみる

kernel 30 中央の1枚しか検知できない 左右は背景のノイズ検知と繋がってしまい、矩形と判断できないため

(参考)kernel 120 レシート同士がつながってしまい、1枚も検知できない結果となった

canny→モルフォロジー変換(膨張)

kernel 10 1枚だけ検知できた

こちらも同様に、右のレシートは一部外枠切れている状態なので、もっと膨張させればつながるのでは?と考えてkernelを大きくしてみる

kernel 30 これだと、3枚とも検知できた

検知結果を見ると、レシートを囲む2重線のうち、内側のみうまく生き残っている。 これは、外側のレシートを囲む線は背景のノイズを拾った線とつながるなどしていて、矩形と判定されていないため。今回kernelの大きさが割とぎりぎりで、もう少し大きくすると今度はレシートの印字の検知の範囲とレシートを囲む内側の線がくっついてしまい、うまく検知されなくなることが予想される。 ※ そのため、cv2.findContoursの引数はcv2.RETR_EXTERNALにしてしまうとうまくいかなくなる(レシートを囲む内側の線がなくなってしまうため)

※ クロージング + cv2.RETR_EXTERNALにして、さらに検知した外形を絞る際の条件をゆるくする(頂点が4つ以上あってもよしとする)とうまくいくのでは…としてみたが、以下の結果だった

yrarchi commented 3 years ago

案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枚ともうまく拾えている

課題 閾値を結果を見ながら調整したので、画像によってはうまく拾えないだろうな…(もとより、全ての画像に対応できる手法はないだろうが)

yrarchi commented 3 years ago

案5 ハフ変換を使う

もしハフ変換でレシートと背景の境界線をうまく検出できれば、そこから矩形検出につなげられそう

Canny法でエッジ検出した状態でハフ変換で直線を検出してみる 上記3つのパラメータを変化させて検出結果の違いを確認する image image 思っていたより検出の精度は低かった。パラメータの調整等で精度は上げられるだろうが、任意の画像に対応するのは難しそうなので、この案は採用しない。

yrarchi commented 3 years ago

改善案まとめ

IMG_7455_白飛び

他の画像でも試す IMG_7483_端に白飛び IMG_7448_影かかり

yrarchi commented 3 years ago

矩形検出の改善を本番反映

https://github.com/yrarchi/household_accounts/issues/21#issuecomment-835751560 で一番検出率高かった適応的閾値処理でアプリ修正した https://github.com/yrarchi/household_accounts/pull/22

yrarchi commented 3 years ago

このイシューの目的だった、検知できない要因を調べ、検知能力を上げる が完全にないにせよ一定達成されたため、このイシューは完了とする