code4nagoya / covid19-aichi-tools

MIT License
6 stars 5 forks source link

「検査陽性者の状況(main_summary_history)」の取得を自動化する #44

Open amay077 opened 4 years ago

amay077 commented 4 years ago

https://github.com/code4nagoya/covid19/issues/429#issuecomment-627992667

で、 @imabari さんが OCR で自動取得するスクリプトを提示されたので、この issue に引き継ぎます。

ozo360 commented 4 years ago

scrapingOccurrenceStatus.pyと比較しての話になりますが 各値を個別にピクセル指定していない点や、OCR結果を検算している点など とても良さように見えます。 ただし、画像サイズや縮小率など過去いろいろとあったのに加え、 そもそも合計値が合わない結果が公表されたりしたことも考慮すると OCRに任せきりにすることは難しいのではないかと考えますがいかがでしょうか? プルリクエストをマージする時点で正しい値かどうかを検証することを運用ルールにするればいいかな?

amay077 commented 4 years ago

OCRに任せきりにすることは難しいのではないかと考えます

これは同意です。

理想は以下の形です。

  1. OCR で項目を取得する
  2. 期待した項目が全て取得できたら、Google スプレッドシートに新規行を追加する(既に同日の行がある場合は追加しない)
  3. Google スプレッドシート → main_summary_history.csv → data.json の流れは現状ママとする
  4. data.json と main_summary_history.csv 含むその他 CSVs を PR で投げる(現状ママ)

OCR が期待通り動作していない事が確認できたら、これまで通り手動でスプレッドシートに記入して、運用が回せます。

ただ、「2. Google スプレッドシートに新規行を追加する」は、難易度が高い(面倒な)ので、スプレッドシートの代わりに covid19 repo の main_summary_history.csv を手動メンテの対象とするのが良いと考えます。

以下、その流れです。

  1. covid19 repo の main_summary_history.csv を取得する
  2. OCR で項目を取得する
  3. 期待した項目が全て取得できたら、取得した main_summary_history.csv に新規行を追加する(既に同日の行がある場合は追加しない)
  4. main_summary_history.csv → data.json を行う
  5. data.json と main_summary_history.csv 含むその他 CSVs を PR で投げる(現状ママ)

運用面で代わるのは、

です。

Google スプレッドシートも生かすのもアリですが、スプレッドシートからの差分更新を行う必要も出てくるので使用や仕組みが煩雑になるかなと思います。

imabari commented 4 years ago

今日のデータは失敗しているようなのでBGR調整をループ回して実験しているのでもう少しお待ちください。

画像調整しながら同じデータがn回以上でれば採用という形でもいいかもしれません。

ozo360 commented 4 years ago

(既に同日の行がある場合は追加しない)

同日同時刻時点として修正データが発表されます。 追加はしないけど更新はするという意味で大丈夫でしょうか?

ozo360 commented 4 years ago

あとは現時点で取得できていない備考についてです。 特に再感染者数は変更されていくでしょうから取得が必要だと考えますがいかがでしょうか? 前回値を引き継いで変更があったら手動でCSVを編集するって方法もありますけど。

amay077 commented 4 years ago

(既に同日の行がある場合は追加しない)

同日同時刻時点として修正データが発表されます。 追加はしないけど更新はするという意味で大丈夫でしょうか?

既に同日の行がある場合、追加も更新もしない、の意でした。 理由は、「OCRによる自動取得よりも、CSV の手動追記を優先としたい」ためです。

が、「同日同時刻時点として修正データが発表される」は考慮できてなかったので、再考が必要ですね。

再考案としては、

  1. CSV に「上書き禁止フラグ(1=禁止)」を持って、OCR ではフラグが 0 なら同日更新を行う。手動更新した場合はフラグを 1 にする(手動更新した後で修正データが更新された場合、それは OCR で自動更新できない制約がある)
  2. 「OCRによる自動取得よりも、CSV の手動追記を優先としたい」を諦めて、同日の行があったらそれを更新する。

あたりでしょうか。 いずれも一長一短あって悩ましいので、実装が楽な 2 ですかねえ。

amay077 commented 4 years ago

特に再感染者数は変更されていくでしょうから取得が必要だと考えますがいかがでしょうか? 前回値を引き継いで変更があったら手動でCSVを編集するって方法もありますけど。

備考も取りたいところですが、実装難易度と精度次第ですかね。 現実的には、「前回値を引き継いで…」な気もします。

ozo360 commented 4 years ago

@amay077 さんはご存じかとは思いますが scrapingOccurrenceStatus.pyを作っていただいていた時の落ちとしては main_summaryの項目数などを考慮すると 目視で精査したり修正が必要な程度のOCRをブラッシュアップするよりは 愛知県がオープンデータを公開してくれるまで手動で入力したほうがいいですね ということになったんですよね。

amay077 commented 4 years ago

そうですね、当時の結論を翻意する理由としては、

ですかね。

もちろん「愛知県がオープンデータを公開してくれる」「スクレイピングしやすい形式で公開してくれる」ことが望ましいので、 @imabari さんも、その点ご留意いただきつつ、可能な限りでご協力いただけたら幸いです(特に急がないです)。

imabari commented 4 years ago

調整を行いましたあくまでも補助だと思っています使えるようだったらご利用ください https://github.com/imabari/covid19-data/blob/master/aichi/aichi_ocr.ipynb

目視後GitHub Actions で手動トリガーで取り込むようにするのはどうなのでしょうか? https://qiita.com/proudust/items/51599abd2b107b708e1e

amay077 commented 4 years ago

https://github.com/code4nagoya/covid19-aichi-tools/issues/44 で imabari さんの OCR スクリプトを組み込んでみました。

OCRに任せきりにすることは難しいのではないかと考えますがいかがでしょうか?

については改めて、以下のような戦略でいかがでしょうか?

  1. 前提として、1日1行のデータとする(1日に複数行は無いとする)
  2. 前回変換した main_summary_history.csv を 現在公開されているサイト から取得する → A とする
  3. 画像から OCR する → B とする
  4. A と B をマージする → C とする(B が A に既存ならマージしない)
  5. Google スプレッドシートからデータを取得する → D とする
  6. C と D をマージする → E とする(D が C に既存の場合、その日の行を上書きする)
  7. E を最終結果とする

こうしておけば、OCR が正しくないとしても、Google スプレッドシートにその日のデータを追加することで、それが採用されます。

https://github.com/code4nagoya/covid19-aichi-tools/issues/44 は、上記の「3. 」までを実装したものです。 この流れで問題なさそうなら、4~ も実装しようと思います。

takainou commented 4 years ago

ご検討ありがとうございます。

C と D をマージする → E とする(D が C に既存の場合、その日の行を上書きする)

D(スプレッドシート)は、B(OCR)やA(前回csv)を上書きしたい際に、上書きしたい日の行を追加して記載するのですよね。例えば8/1-19はBの値で対応していて、8/20の備考が変更になった場合、Dに8/1-19の行は追加せず、8/20の行のみ追加して記載(備考だけでなく、全項目)するのですね。

翌8/21はDに記載せず、最新8/21のBと、備考のみはAの前日値(8/20)をCの8/21に採用し、以後その備考が引き継がれていく事になるのですね。

amay077 commented 4 years ago

例えば8/1-19はBの値で対応していて、8/20の備考が変更になった場合、Dに8/1-19の行は追加せず、8/20の行のみ追加して記載(備考だけでなく、全項目)するのですね。 翌8/21はDに記載せず、最新8/21のBと、備考のみはAの前日値(8/20)をCの8/21に採用し、以後その備考が引き継がれていく事になるのですね。

はい、そのとおりです。 D(スプレッドシート)は、手動で上書きしたい日の行のみ(列は全項目)を用意します。

takainou commented 4 years ago

理解しました。全体的な流れについて賛同します。

OCR精度にもよるのでしょうが、Bに取り込む際に、チェック機能(行単位の和チェック[累計陽性者数と入院])を設けてはいかがでしょうか。チェック結果が不整合ならエラーか警告を出して、D(スプレッドシート)での修正を促します。プレビューの確認は残るようですので、その手助けにもなると考えます。

amay077 commented 4 years ago

PR をマージして GitHub Actions で動作させてみました。

https://github.com/code4nagoya/covid19/pull/874/files

https://github.com/code4nagoya/covid19-aichi-tools/issues/44#issuecomment-674996873

です。 現在の画像が「(注) 検査実施人数には…について掲」で切れているので、そこだけ文として不完全になってます。

takainou commented 4 years ago

ありがとうございます。数値は問題なく取り込めていますね。素晴らしいです。注記は「また、再感7人については、含めていない。」で再感 "染" が抜けていますが問題無いレベルと考えます。

まず当面はこのままOCR結果のテストを続けるのでしょうか。8/14から画像の解像度が良くなっており、担当者が代わって画質が落ちる可能性はあります。実際、検査陽性者の状況がこの形となった8/12と8/13は低解像度でした。過去には改ページプレビュー状態の画像もありました。

あと注記は「OCRに任せる」方針ですか?それとも「A(前回csv)の前日値を引き継ぐ」方針ですか?検査実施件数は現在掲載していませんし、中国人渡航者はもう出ないでしょうし、となると修正する必要があるのは再感染者数だけで更新頻度は少なくD(スプレッドシート)での修正で十分で「前回csvの前日値を引き継ぐ」でも良いと考えています。

これはアプリ側に記載する内容かもしれませんが「現在陽性者数」の定義の注記も必要です。他アプリ含め「注記はスプレッドシートから」というのは便利なのかもしれませんね。

amay077 commented 4 years ago

チェック機能(行単位の和チェック[累計陽性者数と入院])を設けてはいかがでしょうか。チェック結果が不整合ならエラーか警告を出して

チェック機能は入れましょう。 ただ、現状、警告をわかりやすく通知しつつ処理を継続させる方法が未確立なため、当面は以下のようにしようと思います。

  1. 前提として、1日1行のデータとする(1日に複数行は無いとする)
  2. 前回変換した main_summary_history.csv を 現在公開されているサイト から取得する → A とする
  3. 画像から OCR する → B とする
    • OCR結果のチェックで不整合 → B は出力しない。また、ログにはエラーを出力する。処理は続行する。
  4. A と B をマージする → C とする(B が A に既存ならマージしない)
    • B が存在しない場合、A を C として処理を続行する
  5. Google スプレッドシートからデータを取得する → D とする
  6. C と D をマージする → E とする(D が C に既存の場合、その日の行を上書きする)
  7. E を最終結果とする

つまり、OCR がスキップされてそれ以外の処理は正常終了します。 「OCRが正常に機能したか?」は、UPDATE DATA のプルリクの内容に B(main_summary_recognized.csv ) が存在するかで判断します。 B が存在しなければ、Google スプレッドシート に当日のデータを記入し、再度定時処理を手動で re-run させます。

まず当面はこのままOCR結果のテストを続けるのでしょうか。

上記の 1~7 までの流れは、とりあえず本番の運用に乗せて、 OCR結果のテストは運用しながら行おうかと思います。 前述の通り OCR が失敗しても処理は続行され、代替としてこれまで通りの Google スプレッドシート 入力が使えるので、支障は無いと思います。

amay077 commented 4 years ago

あと注記は「OCRに任せる」方針ですか?それとも「A(前回csv)の前日値を引き継ぐ」方針ですか?検査実施件数は現在掲載していませんし、中国人渡航者はもう出ないでしょうし、となると修正する必要があるのは再感染者数だけで更新頻度は少なくD(スプレッドシート)での修正で十分で「前回csvの前日値を引き継ぐ」でも良いと考えています。

注記も、上記の 1~7 の流れに従います。 なので、通常は「OCRに任せる」で、OCRの問題に気づいた時のみ、スプレッドシートの備考列に記述をします。 記述内容は画像に描かれている事を忠実に再現しなくてもよいと思います。

takainou commented 4 years ago

「スプレッドシート入力停止」=「OCR本番移行」なのですね。現時点で未対応のアプリが有り、当方のスプレッドシート入力が間に合わなかった場合(県HPの更新が10時直前で当方のスプレッドシート入力は間に合わず、OCRには間に合うケースもありえます)も本番移行となってしまうのが怖いですが、反映前に確認いただいているので、了解です。

県HPの注記は「OCRに任せる」で了解しました。当サイトとしての注記はアプリ側に記載ですね。

認識一致しているかと思いますが、チェック機能(行単位の和チェック)は以下2点の認識です。 1.陽性患者数=入院+入院調整+施設入所+自宅療養+調整+退院+死亡 2.入院=軽症無症状+中等症+重症 検査実施人数だけ和チェックに入れられません。

8/18 20:00は「入院」セルがOCR時に読み飛ばされたのか、以降の右側が全て1セルずつ左にズレて、右端の「死亡」3320は次の行先頭の「陽性者数」が入ってますね。「入院」セルの左上にExcelでのエラー表示が有るのがOCRに影響しているのでしょうか。

amay077 commented 4 years ago

エラー表示が有るのがOCRに影響しているのでしょうか

その可能性はあると思います。

https://github.com/code4nagoya/covid19-aichi-tools/runs/1002200014?check_suite_focus=true#step:3:594

で、OCR直後のテキストが見られますが、入院 の枠内だけ飛ばされてますね。 これは推測ですが、画像内の矩形を認識して、その中のテキストを抽出しているのではないか、 そのためエラーの緑三角があると、矩形認識に失敗して、テキスト抽出できていないのではないかと思われます。

takainou commented 4 years ago

担当によりExcelの設定が異なるのか、作業手順が異なるのか分かりませんが、Excelのエラー表示は結構な頻度で表示されますし、同じファイルを使いまわしているならエラーを無視とかしない限りずっと出ますね。

少し遡ってみましたが、8/5のクラスター情報の入院等でエラー表示が付いてました。

このパターン(読み込めずに飛ばされる)はチェック機能でひっかかるのでOCR結果は接続はされませんが、本問題が解消出来ないとスプレッドシートへの入力回数が増えてしまいますね。

amay077 commented 4 years ago

認識一致しているかと思いますが、チェック機能(行単位の和チェック)は以下2点の認識です。 1.陽性患者数=入院+入院調整+施設入所+自宅療養+調整+退院+死亡 2.入院=軽症無症状+中等症+重症 検査実施人数だけ和チェックに入れられません。

はい、認識あっています。 https://github.com/code4nagoya/covid19-aichi-tools/pull/63/files#diff-0360629f98b5c76df7fa16b2108e6d8aR69-R74 のあたりで実装してます。

今回の OCR 認識不良も、このチェックで引っかかって OCR結果CSV は出力されず、前回CSV+スプレッドシート が採用されたでしょう。

このパターン(読み込めずに飛ばされる)はチェック機能でひっかかるのでOCR結果は接続はされませんが、本問題が解消出来ないとスプレッドシートへの入力回数が増えてしまいますね。

そうですね、県職員さん頼むよ…という気持ちですが、 頻発するようなら、OCR 側で何らかの対応ができるか検討したいと思います。

amay077 commented 4 years ago

「スプレッドシート入力停止」=「OCR本番移行」なのですね。現時点で未対応のアプリが有り、当方のスプレッドシート入力が間に合わなかった場合(県HPの更新が10時直前で当方のスプレッドシート入力は間に合わず、OCRには間に合うケースもありえます)も本番移行となってしまうのが怖いですが、反映前に確認いただいているので、了解です。

OCR結果の CSV は、現状と互換性を維持するようにしますので、「現時点で未対応のアプリ」は無くなります。 残りの不安は、

等でしょうか、クリティカルなサイトではないのでエイヤで恐る恐る開始してみようかと思います。

amay077 commented 4 years ago

解析モード(PSM)を変更してみた ら、EXCELエラーセルもテキスト抽出できるようになりました。 「xx人」の文字列を収集しているので、ゴミ文字列が混入しても問題ないです。

image

検査実施|陽性者数| 入院 | 軽症・                   入院    施設    自宅
人数※1 | ※2            無症状 | 中等症| 重症    調整    入所    療状    調整 |退院等| 死亡
41111人| 3896人 「 335人 | 240人 | 79人    16人    30人    53人 | 712人 | 57人 |2661人| 48人
imabari commented 4 years ago

img = cv2.inRange(src, (150, 120, 130), (255, 255, 255)) txt = pytesseract.image_to_string(img_crop, lang="jpn", config="--psm 3").replace(".", "").replace(",", "")

こちらの方が認識がよさそうです

amay077 commented 4 years ago

psm 11 Sparse text: 不特定の順序でできるだけ多くのテキストを探す

とのことで、psm11 は確かに文字の認識精度は高かったですが、「不特定の順序で」とあるので、表1行の順序が保証されるのか不安ですかね。

psm 3 でも EXCEL セルエラーの問題は解消できましたので https://github.com/code4nagoya/covid19-aichi-tools/issues/44#issuecomment-676895717 を採用させていただきました。

amay077 commented 4 years ago

画像の認識精度は必要十分を確保できそうです。 OCR の結果は、現状の出力と同じ仕様であり Webアプリに影響はあたえない(つもり)なので、 ここらあたりで https://github.com/code4nagoya/covid19-aichi-tools/pull/63 をマージして、運用しながら確認してみます。

imabari commented 4 years ago

こちらのOCR SPACEのFREEのAPIを利用はできないでしょうか? 昨日から試していますがかなりきれいに取り込めます https://ocr.space/

def ocr_space_url(url, overlay=False, api_key="xxxxxxxxxx"", language="eng"):

    payload = {
        "url": url,
        "isOverlayRequired": overlay,
        "apikey": api_key,
        "language": language,
    }
    r = requests.post("https://api.ocr.space/parse/image", data=payload,)
    return r.json()

test_url = ocr_space_url(url="https://www.pref.aichi.jp/uploaded/image/242724.jpg", language="jpn")

print(test_url['ParsedResults'][0]['ParsedText'])

結果

0検査陽性者の状況 2020年8月19日20時現在 検査実施陽性者数 入院 軽症・ 入院 施設 自宅 人数※1※2 無症状 中等症 重症 調整 入所 療養 調整 退院等 死亡 41,111人3,896人 335人 240人 79人 16人 30人 53人 712人 57人 2,661人 48人

imabari commented 4 years ago

スクレイピングからOCRまでのサンプルです https://imabari.hateblo.jp/entry/2020/08/21/091155

amay077 commented 4 years ago

情報ありがとうございます。 確かに精度が高いですね。 https://ocr.space/OCRAPI を見ると Free プランでもこのサイトでは必要十分な利用制限のようです。

現状は、Tesseract で必要な情報抽出ができているので、当面はこれで運用してみたいと思います。 何か課題が生じたら OCR SPACE も検討したいとおもいます。

amay077 commented 4 years ago

私も https://ocr.hblab.ai/ というのを試していましたが、Tesseract や OCR SPACE の方が期待した結果が得られました。

imabari commented 4 years ago

そこまでコード数が増えないので両方実行してチェックするのはどうですか?

imabari commented 4 years ago

データ追加について https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.update.html unionで追加分のindexを追加してから上書き

def df_update(df1, df2):

    df = df1.reindex(df1.index.union(df2.index))
    df.update(df2)

    return df
amay077 commented 4 years ago

8/22 の画像は OCR 失敗しました。 https://github.com/code4nagoya/covid19-aichi-tools/runs/1017879032?check_suite_focus=true

左:8/21、右:8/22 の画像とその情報ですが、

image image

あたりが主な違いと見られます。

terrasect psm 3

結構悲惨な認識結果になってます(汗

講和数| 入院 | 科-       入院 | 放設 | 邊
数1 天2   押症状 | 中等症| 重定 | 油尺| 入所 | 矢務| 避束|骨院幸| 到選
[95669人 197和| 356人 | 212人 | 118A | 26A | z3A | 59A | 549A | 69A |2ge6A| 54和人

OCR SPACE

こちらの方が精度は高いですが、「35石人」の誤認など、自動で成功といえるまでではないようです。

****** Result for Image/Page 1 ******
0検査陽性者の状況
2020年8月22日20物現在
検査実施陽性者数入院
軽症・
入院
施設
自宅
無症状
中等症
重症
調整
入所
療養
調整
退院等
死亡
人数※1※2
45,669人4,137人35石人
212人
11g人
26人
23人
59人
510人
69人
2966人
54人
7月~
性者入院
軽症
無症状
中等症
重症
調整
入所
療養
調整
退院等
死亡
3,618人356人
212人
118人
26人
23人
59人
610人
69人
2481人
20人
※1検査実施人数については、発表時点での把振数。なお、検査件数は、5み2件。
2陽性者数については、中国人航者2人を除くらまた、再感染7人については、含めていない
(注)検査実施人数には県内において疑い例または患者の澱厚接触者として検査を行ったものについて掲
imabari commented 4 years ago

昨日の画像がないので確かめれないのですが今日の分は とりあえず画像拡大して以下の設定で数値部分は取得できました

src = cv2.imread(str(jpg_path))

height = src.shape[0]
width = src.shape[1]

tmp = cv2.resize(src, (int(width * 2), int(height * 2)))

img = cv2.inRange(tmp, (145, 125, 110), (255, 255, 255))
imabari commented 4 years ago

こちらの線画抽出を利用 https://qiita.com/pashango2/items/145d858eff3c505c100a#%E7%B7%9A%E7%94%BB%E6%8A%BD%E5%87%BA

8/21の画像だとかなり鮮明に取り込めます index

from PIL import Image, ImageChops, ImageOps, ImageFilter

import pytesseract

jpg_path = get_file(link)

img = Image.open(jpg_path)

gray = img.convert("L")
gray2 = gray.filter(ImageFilter.MaxFilter(5))
senga_inv = ImageChops.difference(gray, gray2)
senga = ImageOps.invert(senga_inv)

img_width, img_height = senga.size

img_crop = senga.crop((0, 0, img_width, img_height / 2.5))

img_2x = img_crop.resize((int(img_crop.width * 2), int(img_crop.height * 2)))

img_2xb = img_2x.point(lambda x: 0 if x < 150 else x)

img_2xb

txt = pytesseract.image_to_string(img_2xb, lang="jpn", config="--psm 6").replace(".", "").replace(",", "")

print(txt)
amay077 commented 4 years ago

@imabari さん、すごい、ありがとうございます。 試してみます。

ちなみに、

昨日の画像がないので

は、GitHub Actions のログ↓ から

https://github.com/code4nagoya/covid19-aichi-tools/runs/1017879032?check_suite_focus=true

Artifacts の zip から取り出せます。

image

imabari commented 4 years ago

両方確認しましたがpsmは6より3の方が結果がよさそうです

txt = pytesseract.image_to_string(img_2xb, lang="jpn", config="--psm 3").replace(".", "").replace(",", "").replace(" ", "")

amay077 commented 4 years ago

いくつかのパターンで実行して、最初に妥当性チェックが OK になった結果を採用するようにした方が良さそうですね。

8/24付けの画像も OCR は NG でした。

https://github.com/code4nagoya/covid19-aichi-tools/runs/1024328186?check_suite_focus=true

imabari commented 4 years ago

検査実施人数以外はあっていると思うのですが 147113, 4230, 361, 224, 115, 22, 24, 63, 532, 43, 3150, 57 検査実施人数を別のところで確認できないのでしょうか?

縦線を誤認識しないように調整するのは難しそう 縦線だけでも削除できたらいいのですが

amay077 commented 4 years ago

検査実施人数を別のところで確認できないのでしょうか?

https://www.pref.aichi.jp/site/covid19-aichi/kansensya-kensa.html に 「※【参考】疑い例または患者の濃厚接触者として検査実施した人数は計47,890人。」 と載るのですが、画像と公開日がズレるので、できたら画像から取得したいところです。

amay077 commented 4 years ago
  1. 画像を、上から25%の高さで切り取り
  2. https://stackoverflow.com/a/60068297 の方法で画像内の矩形を抽出
  3. 面積の一番大きな矩形を抽出(検査実施人数~ の矩形が取れる)
    • 8/22 の用に画像全体に矩形が描かれている場合は、threshold でそれを除外
  4. さらに下から25%を切り取り(46,351人~ の行が取れる)
  5. https://github.com/code4nagoya/covid19-aichi-tools/issues/44#issuecomment-679072285 の加工を実施
  6. lang="eng", config="--psm 3" で OCR 実行

結果

image

46351A 4187 | 362A | 215A | 121A | 26A | 28A | 644A | 605A | SOA 3021A 57TA

「50人」 が 「SOA」になってる以外は OK.

さらにこの画像を https://ocr.space/ で language=English, Use OCR Engine2 で実行。

****** Result for Image/Page 1 ******
46,351A 4,187A 362A 215A 121A 26A 28A 64A 605A 50A 3.021A 57A

これは良さそう。 加工を python で行って API に投げるパターンも良さそうです。

imabari commented 4 years ago

昨日こちらの枠線を消すのを試していたのですがあまりいい結果にはなりませんでした https://qiita.com/tifa2chan/items/d2b6c476d9f527785414

line clear

47i13人14230人|361人|224人|115人|22人。24人3人532人43人3450人|57人|

gray3 = copy.deepcopy(contour)

edges = cv2.Canny(contour, 100, 200, apertureSize=3) minLineLength = 30 maxLineGap = 25 lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 60, minLineLength, maxLineGap) for line in lines: x1, y1, x2, y2 = line[0] cv2.line(gray3, (x1, y1), (x2, y2), (255,255, 255), 3)

cv2_imshow(gray3)

imabari commented 4 years ago

毎日今日ぐらいの解像度なら大丈夫なんだけどな 画像サイズ確認してはじいた方がはやいのかも

2020年8月25日18時現在

〇検査陽性者の状況 検査実施|陽性者数|入院|軽症・入院施設自宅 人数※1|※2無症状|中等症|重症調整入所|療養調整|退院等|死亡 47890人|4273人|369人|232人|116人|21人22人57人|395人|43人|3329人|58人

amay077 commented 4 years ago

画像を作る人によって解像度が変わるみたいなので、、、高解像度を維持してほしいですね。

https://github.com/code4nagoya/covid19-aichi-tools/pull/65

で、複数の認識処理を、成功するまで繰り返すようにしました。

  1. 事前加工なし
  2. https://github.com/code4nagoya/covid19-aichi-tools/issues/44#issuecomment-680003456 の事前加工を行う

です。 また、更新日付、数値群、注記 の認識を分けたので、それぞれで複数パターンの認識処理をはめ込むことができます。

精度の低い画像では顕著ですが、 数値群は 検査実施人数 以外は Validation できるのでまだ成否の判定がしやすいが、 注記はまともな文章とならず諦めたほうが良いかもしれません。

OCR SPACE などの API に投げるパターンも追加して、更に精度をあげることは可能とは思います。

imabari commented 4 years ago

下のプログラムで白黒化した画像がいまのところ一番きれいなのでこちらの画像ファイルを利用に変更してみてもらえますか?

from PIL import Image, ImageChops, ImageOps, ImageFilter

img = Image.open(jpg_path)

gray = img.convert("L")
gray2 = gray.filter(ImageFilter.MaxFilter(5))
senga_inv = ImageChops.difference(gray, gray2)
senga = ImageOps.invert(senga_inv)

img_2x = senga.resize((int(senga.width * 2), int(senga.height * 2)))

# 濃くする
img_2xb = img_2x.point(lambda x: 0 if x < 150 else x)
img_2xb.save("main.png")
imabari commented 4 years ago

cv2だとこちらで

neiborhood24 = np.array(
    [
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
    ],
    np.uint8,
)

gray = cv2.imread(str(jpg_path), 0)

height = gray.shape[0]
width = gray.shape[1]

dilated = cv2.dilate(gray, neiborhood24, iterations=1)
diff = cv2.absdiff(dilated, gray)
contour = 255 - diff

gray2 = cv2.resize(contour, (int(width * 2), int(height * 2)))
th, im_th = cv2.threshold(gray2, 180, 255, cv2.THRESH_BINARY)

cv2.imwrite("main.png", im_th)
amay077 commented 4 years ago

https://github.com/code4nagoya/covid19-aichi-tools/issues/44#issuecomment-680930973 を組み込みました。

imabari commented 4 years ago

OpenCVでの表のセルの認識方法 https://teratail.com/questions/151317

親の1番最初の表を抽出して下部23%を切り出しでリサイズしなくても大丈夫そう

img = cv2.imread(str(jpg_path))

# BGR -> グレースケール
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# エッジ抽出 (Canny)
edges = cv2.Canny(gray, 1, 100, apertureSize=3)
cv2.imwrite("edges.png", edges)

# 膨張処理
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
edges = cv2.dilate(edges, kernel)

# 輪郭抽出
contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 面積でフィルタリング
rects = []
for cnt, hrchy in zip(contours, hierarchy[0]):
    if cv2.contourArea(cnt) < 3000:
        continue  # 面積が小さいものは除く
    if hrchy[3] == -1:
        # 輪郭を囲む長方形を計算する。
        rect = cv2.minAreaRect(cnt)
        rect_points = cv2.boxPoints(rect).astype(int)
        rects.append(rect_points)

# x-y 順でソート
rects = sorted(rects, key=lambda x: (x[0][1], x[0][0]))

# senga
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
dilated = cv2.dilate(gray, kernel, iterations=1)
diff = cv2.absdiff(dilated, gray)

contour = cv2.bitwise_not(diff)

rect = rects[0]

# 座標
x_max, y_max = np.amax(rect.T, axis=1)
x_min, y_min = np.amin(rect.T, axis=1)
# print(i, y_min, y_max, x_min, x_max)

y_crop = int((y_max - y_min) * 0.23)

# 切り出し
dst = contour[y_max - y_crop : y_max - 3, x_min + 3 : x_max - 3]
cv2.imwrite(f"yousei.png", dst)

# 縦線消去
edges = cv2.Canny(dst, 100, 200, apertureSize=3)

lines = cv2.HoughLines(edges, 1, np.pi / 2, 15)

for line in lines:
    for rho, theta in line:

        if theta == 0:

            a = np.cos(theta)
            # b = np.sin(theta)

            x0 = int(a * rho)

            x1, x2 = x0, x0
            y1, y2 = 100, -100

            cv2.line(dst, (x1, y1), (x2, y2), (255, 255, 255), 3)

txt = pytesseract.image_to_string(dst, lang="eng", config="--psm 3").replace(".", "").replace(",", "")
print(txt)

data = list(map(int, re.findall("\d+", txt)))
print(data)
imabari commented 4 years ago

別の方式試してたらこちらの方がよかったので

文字抽出を利用して日付と表を抽出 https://stackoverflow.com/questions/23506105/extracting-text-opencv

index

import cv2
import numpy as np

# 画像枠対策に周囲2ドット削除
gray = cv2.imread(str(jpg_path), cv2.IMREAD_GRAYSCALE)[2:-2, 2:-2]

# 線画抽出
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dilated = cv2.dilate(gray, kernel, iterations=1)
diff = cv2.absdiff(dilated, gray)
contour = cv2.bitwise_not(diff).copy()

# ノイズのグレー除去
contour[contour > 200] = 255

# 画像を2倍に拡大
img = cv2.resize(contour, None, fx=2, fy=2)

# 楕円形カーネル
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))

# モルフォロジー勾配(物体の境界線)
grad = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

# 二値化
_, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

# 矩形カーネル
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))

# ノイズ除去
connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)

# cv2.RETR_EXTERNAL 輪郭
# cv2.CHAIN_APPROX_NONE 輪郭全点の情報を保持

contours, hierarchy = cv2.findContours(
    connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
)

mask = np.zeros(bw.shape, dtype=np.uint8)

rects = []

for idx in range(len(contours)):
    # 外接矩形
    x, y, w, h = cv2.boundingRect(contours[idx])

    mask[y : y + h, x : x + w] = 0

    # 輪郭を描画
    cv2.drawContours(mask, contours, idx, (255, 255, 255), -1)

    # 面積割合
    r = float(cv2.countNonZero(mask[y : y + h, x : x + w])) / (w * h)

    # 抽出条件 面積、縦・横の長さ
    if r > 0.45 and w > 20 and h > 8:
        # cv2.rectangle(large, (x, y), (x + w - 1, y + h - 1), (0, 255, 0), 2)
        # color = np.random.randint(0, 255, 3).tolist()
        # cv2.putText(large, str(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 3)

        rects.append((x, x + w, y, y + h))

# 矩形の左下でソート
rects = sorted(rects, key=lambda x: (x[3], x[0]))

# 日付

# 座標 タイトルと日付のY座標が変わるため右側にあるのを採用
x1, x2, y1, y2 = rects[0] if rects[0][0] > rects[1][0] else rects[1]

# 切り出し
dst = img[y1:y2, x1:x2]
cv2_imshow(dst)

txt = (
    pytesseract.image_to_string(dst, lang="jpn", config="--psm 6")
    .strip()
    .replace(".", "")
    .replace(",", "")
    .replace(" ", "")
)
txt

# 座標
x1, x2, y1, y2 = rects[2]

y_crop = int((y2 - y1) * 0.23)

# 切り出し
dst = img[y1:y2, x1:x2][-y_crop:-5, 5:-5]

cv2_imshow(dst)

txt = pytesseract.image_to_string(dst, lang="jpn", config="--psm 6").strip()
txt

cv2.imwrite(f"yousei.png", dst)
cv2_imshow(dst)

edges = cv2.Canny(dst, 100, 200, apertureSize=3)

lines = cv2.HoughLines(edges, 1, np.pi / 2, 15)

# 縦線除去

for line in lines:
    for rho, theta in line:

        if theta == 0:

            a = np.cos(theta)
            # b = np.sin(theta)

            x0 = int(a * rho)

            x1, x2 = x0, x0
            y1, y2 = 100, -100

            cv2.line(dst, (x1, y1), (x2, y2), (255, 255, 255), 3)

cv2_imshow(dst)

txt = (
    pytesseract.image_to_string(dst, lang="jpn", config="--psm 6")
    .strip()
    .replace(".", "")
    .replace(",", "")
)
print(txt)

data = list(map(int, re.findall("\d+", txt)))
print(data)

# 検査実施人数
# 座標
x1, x2, y1, y2 = rects[8]

# 切り出し
dst = img[y1:y2, x1:x2]

cv2_imshow(dst)

txt = (
    pytesseract.image_to_string(dst, lang="jpn", config="--psm 6")
    .strip()
    .replace(".", "")
    .replace(",", "")
)
txt