Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CloudVisionAPIでOCRする

 CloudVisionAPIでOCRする

KosukeShimizu

October 03, 2023
Tweet

More Decks by KosukeShimizu

Other Decks in Programming

Transcript

  1. CloudVisionAPIでOCRする 自己紹介 • Name: 清水 幸佑(Kosuke Shimizu) ◦ thimi0412 •

    Language ◦ Python ◦ Go • Job: 2022-02 WEDにJOIN ◦ データエンジニア ◦ OCR周りの開発 ◦ データ基盤やデータパイプラインの作成 ◦ ML周りのインフラ基盤の作成 3
  2. CloudVisionAPIでOCRする 5 VisionAPIの説明 • 顔検出 • 物体検出 • OCR(文字認識) •

    ランドマーク検出 • etc… https://cloud.google.com/vision/docs/drag-and-drop
  3. CloudVisionAPIでOCRする 6 ONEのOCRについて { "purchased_at": "2020-06-12", "address": null, "items": [

    { "name": "食パン", "quantity": 1, "price": 340 }, { "name": "塩レモン", "quantity": 1, "price": 350 } ], … } APIに画像を POSTしてJSON を取得する
  4. CloudVisionAPIでOCRする • レシートをそれぞれの領域に分ける ◦ header ▪ 店名, 住所, TEL ◦

    item ▪ 商品名, 値段, 個数 ◦ total ▪ 合計金額, 小計 ◦ footer ▪ sumより下部の領域 • おそらくheaderとtotalの部分に商品がある ◦ 商品名の右 or 右下に数字があるものを 商品名として抽出している ◦ 変なものも商品名として取得してしまう ことがある 8 商品名を取得する
  5. CloudVisionAPIでOCRする 10 清水の思考(土日) S • 機械学習のモデルとか組み込む からPythonで書くかぁ • PythonならFastAPIで書くかぁ •

    どうせGKEで動かすから Dockerも必要かぁ • 多分商品名の抽出とかはML チームがやってくれるはず • まずは基盤となる OCR部分だけ作るかぁ
  6. CloudVisionAPIでOCRする 12 API呼び出し部分 from google.cloud import vision def text_detection( content:

    str, vision_api_model: str ) -> vision.AnnotateImageResponse: image = vision.Image(content=content) client = vision.ImageAnnotatorClient(credentials=env.google_credentials) req = vision.AnnotateImageRequest( image=image, features=[ vision.Feature( type=vision.Feature.Type.TEXT_DETECTION, model=vision_api_model ), ], image_context={"language_hints": ["ja", "en"]}, ) response: vision.AnnotateImageResponse = client.annotate_image(req) return response
  7. CloudVisionAPIでOCRする 13 API呼び出し部分 content: str = base64.b64encode(data).decode() response: vision.AnnotateImageResponse =

    text_detection( content, "builtin/latest" ) • open関数でfile.read()したbytesをbase64で変換してデコードすれば使える • builtin/latestはVisionAPIで使用するmodelの名前 ◦ builtin/latest: 最新版 ◦ builtin/stable: 安定版 ◦ builtin/legacy: 古い版 ◦ 以前はbuiltin/stableを使っていたがbuiltin/latestにして 餃子という文字が鮫子と読んでしまう問題が解決した!
  8. CloudVisionAPIでOCRする 15 レスポンスの中身を見てみる { "description": "セブン", "bounding_poly": { "vertices" {

    "x": 920, "y": 131 } "vertices" { "x": 1446, "y": 137 } "vertices" { "x": 1444, "y": 260 } "vertices" { "x": 919, "y": 254 } } } • bounding_poly.verticesに OCRされた文字を囲うx, y座標が格納されてい • 座標の順番は左下から反時計回り
  9. CloudVisionAPIでOCRする 16 レスポンスの中身を見てみる [ "7 セブン-イレブン", "新宿余丁町店", "東京都新宿区余丁町10-10", "電話:03-3351-2122", "2023年09月10日

    (日) 14:48", "領収書", "ダイソーキッチンペーパ-168枚", "ピルクル ミラクルケア", "195ml", "@135x 2", "炭火焼き豚丼", "小 計 (税抜 8%)", "消費税等 (8%)", "小計 (税抜10%)", "消費税等 (10%)", "合計", ... ] レスポンスはBlockという単位で分かれていて y座標でソートされていないので並び替えが必要 後ろのあるはずの 100がない
  10. CloudVisionAPIでOCRする 1. 空のリストを作成 2. 各行の4点のX, Y座標の平均を計算 3. X, Y座標の平均と文字をリストに格納 4.

    Y座標が近いもので1行を作成する a. Y座標の差が15以内だったら 同じ行と判定(勘) (p.s. ChatGPTに教えてもらいました) 17 Y座標で並び替える docs = [] for text in texts: vertices = [ {"x": vertex.x, "y": vertex.y} for vertex in text.bounding_poly.vertices ] # 頂点の座標の平均を取る average_x = sum([vertex["x"] for vertex in vertices]) / len(vertices) average_y = sum([vertex["y"] for vertex in vertices]) / len(vertices) docs.append( {"text": text.description, "vertex": {"x": average_x, "y": average_y}} ) # y座標でデータをソート sorted_data = sorted(docs, key=lambda x: x["vertex"]["y"]) # グループ分け groups = [] current_group = [] for i in range(len(sorted_data) - 1): current_group.append(sorted_data[i]) if ( sorted_data[i + 1]["vertex"]["y"] - sorted_data[i]["vertex"]["y"] > threshold ): groups.append(current_group) current_group = [] # 最後のグループを追加 current_group.append(sorted_data[-1]) groups.append(current_group) セブン ー イレブン 差が15以内
  11. CloudVisionAPIでOCRする • 傾いた画像だとOCRしずらいので画像の傾きを補正する(済) ◦ MLチームの方が作ってくれた(あんまり理解してない) • 固有表現抽出等を使用して商品名などを抽出する ◦ T5(Text-to-Text Transfer

    Transformer) で今いい感じに取れるらしい • レシート画像以外への対応 ◦ 新幹線の切符 ◦ そのほかチケットとか 19 今後やっていきたいこと