スキャンしたレシートをGoogle Vision APIを使って自前でOCRしてcsv形式に変換する(Python)
はじめに
概要
レシートをScanSnapでスキャンした画像を
Google Vision APIを使ってOCRして
Pythonを使ってzaimに渡せるようなcsv形式に変換してみた
問題提起
家計管理のため、レシートをzaimという家計管理アプリでスキャンして管理しているのですが自分は定期的に撮影するということができず、200枚近くのレシートをためてしまいました。
これらの写真を撮影して...という作業に嫌気が指したのでScanSnapでまとめてスキャンしてOCRも自前でできたらいいなということで作業をはじめました。
Google Vision APIについて
Pythonで軽く試せるOCRにはいくつか選択肢があります。
前回Tesseraactを使って見た感想として、チューニングなどしないときちんと精度が出ず結構面倒だったというのがあったので
すでにある程度完成しているGoogle Vision APIというものを使ってみました。
レシートOCRに関しては下記の記事がちょうど該当したのでこちらを流用してOCRしていこうと思います。
実際の処理
本来は下記のようなデータフローを想定しています。
画像 -> OCR結果 -> 辞書型などのデータ -> csv形式
しかし、Google Vision APIが従量課金制である都合上OCRをかける回数を最小にしたいため、JSONファイルを介して結果を保存して再利用します。
(最初だけ行う) 画像 -> OCR結果 -> JSONファイル (試行錯誤する処理) JSONファイル -> 辞書型などのデータ -> csv形式
画像のOCRと保存
別記事に書いたのでそちらを参照してください。
について書いてあります。
OCR結果を使いやすい形式に変換
下記を流用します。
- 概要:行ごとにOCR結果のテキストをまとめる
- 入力:OCR結果のオブジェクト
- 出力:データのリスト
- 処理
- テキストと位置に関する記述を抽出
- テキストのBoundingBoxの左上の縦方向位置(Y座標)によってテキストをクラスタリング
以降ではこの関数を通して作成したlines
というリストを前提とします。
def get_sorted_lines(response,threshold = 5): """Boundingboxの左上の位置を参考に行ごとの文章にParseする Args: response (_type_): VisionのOCR結果のObject threshold (int, optional): 同じ列だと判定するしきい値 Returns: line: list of [x,y,text,symbol.boundingbox] """ # 1. テキスト抽出とソート document = response.full_text_annotation bounds = [] for page in document.pages: for block in page.blocks: for paragraph in block.paragraphs: for word in paragraph.words: for symbol in word.symbols: #左上のBBOXの情報をx,yに集約 x = symbol.bounding_box.vertices[0].x y = symbol.bounding_box.vertices[0].y text = symbol.text bounds.append([x, y, text, symbol.bounding_box]) bounds.sort(key=lambda x: x[1]) # 2. 同じ高さのものをまとめる old_y = -1 line = [] lines = [] for bound in bounds: x = bound[0] y = bound[1] if old_y == -1: old_y = y elif old_y-threshold <= y <= old_y+threshold: old_y = y else: old_y = -1 line.sort(key=lambda x: x[0]) lines.append(line) line = [] line.append(bound) line.sort(key=lambda x: x[0]) lines.append(line) return lines
必要なテキストの抽出
最低限必要なテキストとして重要な順に下記が挙げられます。
- 日付
- 店名
- 金額
- 品目名(Optional)
これらについてそれぞれOCR結果を用いて抽出をしていきます。 思ったよりも手順が莫大になってしまったので詳細は個別記事に書きました。
日付の抽出
日付は正規表現を使うことで文字列から抽出できると考えて実装しました。
大まかにはそれで良かったのですが、OCRの都合で年や月といった文字が認識されていなかったりしたのでそういう意味では苦労しました。
店名の抽出
店名は基本的に最初の方にデカデカと載っているため下記の手順を考えました。
- 上から5行分の文字列を抽出
- その中から最も縦方向に大きなBoundingBoxを持つものを店名とする
上記は正直必ずしも正しくない方針ですが、ある程度店目に検討が付けばよいと思い強行しました。
金額の抽出
金額は基本的に「小計」または「合計」というキーワードと対応している...と思いましたが思ったよりも様々な表記がなされていて、フローがとても複雑になってしまいました。
OCR結果のcsv形式への変換
さて、最後はZaimにOCR結果をCSVでアップする段ですが、下記のように項目ごとに列を指定できるので普通のcsv形式に変換しちゃって良さそうです。
Pandasでちょちょっと書けば終わりなのと後述の理由もあってやる気をなくしたので省略させてください。
オチ
散々いろいろやって気づいたのですがZaimは有料会員にならないとカテゴリをcsvから読んでくれません。
ということでこれではカテゴリの管理ができません…。アホかと…。
一応、それっぽくまとめたものを下記においておきます。