Named Log

とあるエンジニアの歩み

スマートフォンで撮った書類から影を取り除いてみた

はじめに

久しぶりに記事を書いています、NamedPythonです。

卒業が近づき、卒業研究も大詰めになってまいりました。テーマは「陰影を含む文書画像からの陰影除去」と題して画像処理をちまちまやっていました。

ぶっちゃけほぼ既存だし、目新しいこともしてないので論文書くついでに研究?のまとめとして記事を書きたいと思います。

なんならGitHub公開しています、ハハハ。もはやこうやって記事のネタにすること、公開することを想定してなんとDockerにのっけてあります。すぐ試せるね。

プロジェクトのコードネームはdeshade-doc(でしぇーど どっく)shadeで挟むことによる謎のそれっぽさを演出しています。スタバで10分考えました。

github.com

愚かにも入力画像をignoreしていないですが、見逃してください。

2020/07/11 追記

嬉しいことにStarが一個ついたので、README書いたり、OpenCVのバージョンを固定、不要な画像を削除するなどしました。

jjanzic/docker-python3-opencv というDockerイメージをFROMに指定しているんですが、今でもメンテされているみたいで、結構感動しました。

OpenCVのバージョンを固定、と書きましたが、

FROM jjanzic/docker-python3-opencv
...
FROM jjanzic/docker-python3-opencv:contrib-opencv-3.4.2
...

こうしただけです。

追記おわり!

きっかけ

授業ノートとか、書類とかを紙でとっておくのは面倒なので、Scanbotというアプリ でスキャンして管理していました。

そこそこ気に入っていて、ある程度のノイズは取り除いてくれていました。しかし、にはどうも弱いらしく黒つぶれが多発していました。

表面化していた問題はそこではなく、OCRが効かないというところだったので、当初はOCRの精度改善をテーマにしようと考えていましたが、なにせ郵便局あたりが頑張ってしまって研究の余地がない。ないというか、僕の技術では追いつけない

じゃあOCRかける前段階、二値化とか画像への前処理を研究すればいいんじゃないの、っていうことで陰影の除去をテーマにしました。

一応重複とか類似の研究は調べましたが、ここまでピンポイントなのはなかった気がします。あったらこっそり教えてください。といっても時すでに遅しですが。

環境

めちゃんこわかりやすい。

あとはこの上でドッカーン。コンテナの中身は

です。もともと FROM ですでにあるイメージを引っ張ってきてPillowを足してるだけなのでシンプル。他の授業でも使うので流用ができている、良きかな。

理論みたいな

そんな大層なものではないですが、考えなしにやっているわけでもないのでそれっぽく。

二値化の手法としてそこそこ有名?な判別分析法ではうまく二値化できないことを利用して、「陰影を含んでいる文書は判別分析法でうまく二値化できない。問題がある。」といちゃもんをつけて問題を解決する形をとっています。

しかし、判別分析法のいいところ(と、勝手に思っているの)は、曖昧な影の輪郭(領域)を、ヒストグラムをもとにはっきり示してくれるところです。しかしこの判断ができるのは、文書画像のみであることに注意されたい。風景写真ではこんなガバガバな判断基準で陰影の領域は断定できないです。

では、「判別分析法によってはっきりした領域を切り出して、個別に処理してあげればいいではないか」と考えて、手っ取り早くガンマ補正をしました。

一回ではそうはうまくいかないので、ちまちま二値化、切り出し、ガンマ補正、元の位置に貼り付け、二値化、...という風に繰り返す処理にしました。

切り出しの条件はというと、面積です。そこそこ大きい面積の暗い領域を影と決めつけて、切り出しています。

そして、このが見つからなくなったら繰り返し脱出、有限回で終了、アルゴリズムとしても破綻していない(はず)です。

以下はこれらに結果をつけながらつらつらと。

各ステップと結果

1.

文書を携帯端末で撮影、クロップします。なので入力画像はクロップされた3チャンネル画像です。画像は実際に僕がとったノート。

f:id:namedpython:20181225155543j:plain
入力画像

2.

判別分析法を用いて二値化。 すると、影がある場合はヒストグラム(および割り出される閾値)が偏って影の部分は黒く潰れます。以下は大津の二値化をかけたもの。すごく黒く潰れる。

f:id:namedpython:20181225160131p:plain
判別分析法で二値化をかけた入力画像

3.

影の部分を切り出してガンマ補正γ=1.2くらい(これらは1.18)でゆっくり明るくしていく。以下は切り出した影の部分(左)とガンマ補正をかけたあと(右)。

f:id:namedpython:20181225162331p:plainf:id:namedpython:20181225162326p:plain
ガンマ補正前、ガンマ補正後

4.

元の位置に貼り付けます。実はMatで貼り付けるのがめんどくさくてImage.paste()を使うためにここだけPILを使っていたり。貼り付けると明るくしたのがわかりやすい。

f:id:namedpython:20181225163243p:plain
元の位置に貼り付け

5.

2の手順に戻って判別分析法で二値化します。以下は2との比較。

f:id:namedpython:20181225160131p:plainf:id:namedpython:20181225163716p:plain
さっきの二値化、今回の二値化

あれ、なんか領域広くなってますね。いいんです。これはまだ途中経過。こうやって階層的に明るくする範囲を拡大していくんです。

6.

以降は、切り出し、ガンマ補正、元の位置に貼り付けまで一気にやったものを、前後比較しながら見ていきましょう。

f:id:namedpython:20181225163243p:plainf:id:namedpython:20181225164217p:plain
貼り付け1回目、貼り付け2回目

少し段ができましたね。次も見ていきましょう。

7.

プログラム内のループで言えば3ループ目になります。

f:id:namedpython:20181225164217p:plainf:id:namedpython:20181225164704p:plain
貼り付け2回目、貼り付け3回目

8.

そうこうして15ループします。このループ数は影の分散具合とγをどれくらい細かく(>1.00)するかによります。これが最終結果。元画像と比べてみましょう。

f:id:namedpython:20181225155543j:plainf:id:namedpython:20181225165104p:plain
最初、最後

...できてはいるけど汚い。っていうのは心にしまっておいてください。階層的に明るくしていった感じがわかると思います。思いの外、この階層が影の濃度ごとに分かれていて面白い。

9.

最後のものを判別分析法によって二値化します。プログラム的には、ここである一定の面積の黒い領域がなければbreakとなっています。以下は最初の二値化と最後の二値化。

f:id:namedpython:20181225160131p:plainf:id:namedpython:20181225165445p:plain
最初の二値化、最後の二値化

こう見ると、だいぶ綺麗になっています(言い聞かせるように)。 かすれている部分は、僕の筆圧が足りていなかったり過度に照明が当たっていたりする部分です。

まとめと所感

「やってみた感」と思ったあなた、正解です。研究としての体裁を最低限保ったやってみたです。でも研究の動機はそんくらいがいいだろうと思っているので、僕は僕を許します。

ちなみに実行時間はまあまあ現実的。入力画像はリサイズしていたりします。 上の結果は15ステップですが、実行時間は約20[sec]timeコマンドをつけて計測したrealタイム。リアルタイム処理は想定していないのでこんくらいが妥当だと愚考。

もちろん、伸び代はあります。γは可変にできますし、現在の処理に加えて明るすぎる領域は明度を下げて白飛び黒つぶれ両方に対応したらもっと綺麗になると思います。

ですが、今回はここまで。あとは論文を書くだけです。

さいごに

読んでいただきありがとうございました。まとめかた、およびアウトプットが下手なので、とりあえず数を打って改善を図ります。

GitHubに上がってはいますが、なんというかバックアップに近いので、READMEは需要があれば書きます。

ではまた。