$30 off During Our Annual Pro Sale. View Details »

CloudVisionAPIでOCRする

 CloudVisionAPIでOCRする

KosukeShimizu

October 03, 2023
Tweet

More Decks by KosukeShimizu

Other Decks in Programming

Transcript

  1. © WED, Inc.
    WED株式会社 | Kosuke Shimizu
    CloudVisionAPIで
    OCRする

    View Slide

  2. 2
    スライドのQRコード

    View Slide

  3. CloudVisionAPIでOCRする
    自己紹介
    ● Name: 清水 幸佑(Kosuke Shimizu)
    ○ thimi0412
    ● Language
    ○ Python
    ○ Go
    ● Job: 2022-02 WEDにJOIN
    ○ データエンジニア
    ○ OCR周りの開発
    ○ データ基盤やデータパイプラインの作成
    ○ ML周りのインフラ基盤の作成
    3

    View Slide

  4. CloudVisionAPIと
    ONEのOCRについて
    4

    View Slide

  5. CloudVisionAPIでOCRする
    5
    VisionAPIの説明
    ● 顔検出
    ● 物体検出
    ● OCR(文字認識)
    ● ランドマーク検出
    ● etc…
    https://cloud.google.com/vision/docs/drag-and-drop

    View Slide

  6. 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
    を取得する

    View Slide

  7. CloudVisionAPIでOCRする
    7
    ONEのOCRについて
    Kubernetes Cluster
    GKE
    Multiple Instances Cloud
    Vision API
    OCRサーバー 画像を送る
    OCR結果をもらう

    View Slide

  8. CloudVisionAPIでOCRする
    ● レシートをそれぞれの領域に分ける
    ○ header
    ■ 店名, 住所, TEL
    ○ item
    ■ 商品名, 値段, 個数
    ○ total
    ■ 合計金額, 小計
    ○ footer
    ■ sumより下部の領域
    ● おそらくheaderとtotalの部分に商品がある
    ○ 商品名の右 or 右下に数字があるものを
    商品名として抽出している
    ○ 変なものも商品名として取得してしまう
    ことがある
    8
    商品名を取得する

    View Slide

  9. CloudVisionAPIでOCRする
    9
    転生の時
    OCRサーバーちょっと
    根本的に書き換えたくない?
    商品名にノイズがある
    機械学習とかで
    いい感じにできないの?
    レシート以外もOCRすること
    あるし汎用的に作りたい
    O S S

    View Slide

  10. CloudVisionAPIでOCRする
    10
    清水の思考(土日)
    S
    ● 機械学習のモデルとか組み込む
    からPythonで書くかぁ
    ● PythonならFastAPIで書くかぁ
    ● どうせGKEで動かすから
    Dockerも必要かぁ
    ● 多分商品名の抽出とかはML
    チームがやってくれるはず
    ● まずは基盤となる
    OCR部分だけ作るかぁ

    View Slide

  11. ここからは実装部分の話
    11

    View Slide

  12. 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

    View Slide

  13. 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にして
    餃子という文字が鮫子と読んでしまう問題が解決した!

    View Slide

  14. CloudVisionAPIでOCRする
    14
    レスポンスの中身を見てみる
    ● response.text_annotations[0]には全てのOCR結果が入っている
    ● index1以降はOCR結果が分割されたものが入っている
    ● 基本的にはresponse.text_annotations[1:]を使用していく
    raw_text, texts = response.text_annotations[0], response.text_annotations[1:]
    raw_lines = raw_text.description.split("\n")

    View Slide

  15. 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座標が格納されてい
    ● 座標の順番は左下から反時計回り

    View Slide

  16. 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がない

    View Slide

  17. 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以内

    View Slide

  18. そして諸々作成したのが
    こちら(実演)
    18

    View Slide

  19. CloudVisionAPIでOCRする
    ● 傾いた画像だとOCRしずらいので画像の傾きを補正する(済)
    ○ MLチームの方が作ってくれた(あんまり理解してない)
    ● 固有表現抽出等を使用して商品名などを抽出する
    ○ T5(Text-to-Text Transfer Transformer) で今いい感じに取れるらしい
    ● レシート画像以外への対応
    ○ 新幹線の切符
    ○ そのほかチケットとか
    19
    今後やっていきたいこと

    View Slide

  20. Thank You
    20
    今日使ったコード
    https://gist.github.com/thimi0412/d548cec8c132a0f299d1096cc4e908e0

    View Slide