2017-10-19

【Python】Google Vision APIで、スクショした表形式の画像データから文字を抽出してみたよん

目的とやりたいこと


PDFとかならいいんですけど、画像データのテーブルを手動で写すのは尋常もなく面倒くさいのでなんとかできないかと考えていました。ほら、例えば雑誌でしか手に入らないテーブルって、切り抜きを手動でエクセルにまとめるぐらいしか方法がないじゃないですか。


だから最終的には、スクショした画像がそのままCSVになるってのがいいんですけど、それがどうやったらできそうかなぁと考えたときに


・画像ファイルから文字を抽出してくれるようなアルゴリズムを書く
・抽出した文字をCSV形式に変換して、保存できるアルゴリズムを書く


という流れが必要だと考えたわけです。そこで、画像ファイルを文字に抽出してくれるのでいい方法がないかなと考えたときに、機械学習で精度を極限まで高めたGoogleのVision APIなんてよさそうだ!と思い、試してみました。ちなみに画像から文字を検知するのはTEXT DETECTIONというらしいです。


今回やりたいこととしては、Pythonを利用して、Google Vision APIで、スクショしたテーブルイメージのテキストを取得するところまでです。校舎の抽出した文字をCSV形式に変換して、保存できるアルゴリズムは今回のスコープ対象外とします。今回はできなかったけど、Python コーディングでCSVにいい感じに変換ってところまでをやりたいです。


Google Vision APIを取得と、実装



とりあえず、下記サイトで、APIの登録方法に従い、無料体験プランに登録してください。そして下記サイトのコードを参考にコードをコピペしました

凄すぎ!Google Cloud Vision APIをつかって簡単高精度にOCR

今回は、Google Cloud Vision APIのOCRを試してみたら精度が良すぎてビビったので、やり方と結果を紹介してみます。 上のリンクにアクセスし、 プロジェクトを作成した上で、


しかし上記サイトのコードがPython2に対応だったので、Python3の場合は以下のページを参考に、適当に修正してください。下記のソースコードを利用して、いい感じにコピペしてください。

[Google Cloud Vision] python 3.5 でリクエストした時のエラー対処 (is not JSON serializable)

以前こちら↓の記事でGoogle Cloud Visionの記事を紹介したのですが、掲載したコードはpython2.7での挙動を確認したものでした。 今回ちょっとした用でpython3.5で動かしてみたところエラーがでて動かなくなってい



実行結果


今回は、こういう短い数字の画像をスクショしてみたところ、いい感じにデータをJSON形式で取得できました。




$ python OCRProject.py
begin request
end request
{
  "responses": [
    {
      "textAnnotations": [
        {
          "locale": "en",
          "description": "o 1 4 6 7 9\n",
          "boundingPoly": {
            "vertices": [
              {
                "x": 12,
                "y": 2
              },
              {
                "x": 160,
                "y": 2
              },
              {
                "x": 160,
                "y": 34
              },
              {
                "x": 12,
                "y": 34
              }
            ]
          }
        },
        {
          "description": "o",
          "boundingPoly": {
            "vertices": [
              {
                "x": 12,
                "y": 2
              },
              {
                "x": 21,
                "y": 2
              },
              {
                "x": 21,
                "y": 34
              },
              {
                "x": 12,
                "y": 34
              }
            ]
          }
        },
        {
          "description": "1",
          "boundingPoly": {
            "vertices": [
              {
                "x": 34,
                "y": 2
              },
              {
                "x": 43,
                "y": 2
              },
              {
                "x": 43,
                "y": 34
              },
              {
                "x": 34,
                "y": 34
              }
            ]
          }
        },

以下、取得したJSONデータの公式リファレンスです。

Text detection samples | Google Cloud Vision API Documentation | Google Cloud Platform

For your convenience, the Cloud Vision API can perform Text detection directly on an image file located in Google Cloud Storage or on the Web without the need to send the contents of the image file in the body of your request.


し、しかし・・・・文字の座標とかいらねんだよ!!
ほしいのはJSONのresponses > textAnnotations > descriptionsの文字列データのみをなんじゃ!さて、困ったものだ!そこで、PythonでDictionary型に変換して、必要なとこだけ取得できるのがよさそうだなぁと考えました。


ということで、


・JSON形式のAPIレスポンスを、Python用に処理しやすいようDictionary型に変換
・Dictionary型から、responses > textAnnotations > descriptionsの文字列データをプリントする


ってことを、適当にググって修正したコードがこちらです。(べたうちビンビン丸)
(image.jpgは、下記の画像です。pythonファイルが存在する同一階層上に存在する適当なファイルでございます。)

http://tmaita77.blogspot.jp/2017/10/blog-post_9.html


#!/usr/bin/python
#coding:utf-8
import base64
import json
from requests import Request, Session
from bs4 import BeautifulSoup

path = "image.JPG"

def recognize_captcha(str_image_path):
        bin_captcha = open(str_image_path, 'rb').read()

        #str_encode_file = base64.b64encode(bin_captcha)
        str_encode_file = base64.b64encode(bin_captcha).decode("utf-8")
       

        str_url = "https://vision.googleapis.com/v1/images:annotate?key="

        str_api_key = "ここにAPI KEYをいれてね!"

        str_headers = {'Content-Type': 'application/json'}

        str_json_data = {
            'requests': [
                {
                    'image': {
                        'content': str_encode_file
                    },
                    'features': [
                        {
                            'type': "TEXT_DETECTION",
                            'maxResults': 10
                        }
                    ]
                }
            ]
        }
       
        print("begin request")
        obj_session = Session()
        obj_request = Request("POST",
                              str_url + str_api_key,
                              data=json.dumps(str_json_data),
                              headers=str_headers
                              )
        obj_prepped = obj_session.prepare_request(obj_request)
        obj_response = obj_session.send(obj_prepped,
                                        verify=True,
                                        timeout=60
                                        )
        print("end request")

        if obj_response.status_code == 200:
            #print (obj_response.text)
            with open('data.json', 'w') as outfile:
                json.dump(obj_response.text, outfile)
            return obj_response.text
        else:
            return "error"

if __name__ == '__main__':
    data = json.loads(recognize_captcha(path))
    data = data["responses"]
    print(data)
    for i in data:
        print(i["fullTextAnnotation"]["text"])


ちょこっと頭を使ったところに関しては、ここの下り。


data = json.loads(recognize_captcha(path))
    data = data["responses"]
    print(data)
    for i in data:
        print(i["fullTextAnnotation"]["text"])


json形式をdictionary形式に変換してdateにぶち込みます。そんで、なんか外側に余計なかっこが存在するので、for分で取っ払います。あとは、先ほどのresponses > fullTextAnnotation>textにアクセスできるように実装しましたんご


で、出力結果がこちら

$python OCRproject.py
>
首都圏の公務員就職率の上位20位
設置




















大学·学部名
日本文化大学·法学部
創価大学·看護学部
東京大学法学部
千葉大学法経学部
千葉大学·園芸学部
首都大学東京·都市環境学部
北里大学·獣医学部生物環境科学科
千葉科学大学危機管理学部
慶應義塾大学·理工学部
お茶の水女子大学·生活科学部
千葉大学·文学部
埼玉大学·工学部
中央大学·法学部
埼玉大学·経済学部(昼間)
日本社会事業大学·社会福祉学部
お茶の水女子大学文教育学部
国際武道大学·体育学部
東京大学·教養学部
東京大学·農学部(獣医学課程以外)
専修大学·法学部(一部)
母数
229
75
294
352
110
95
82
210
573
98
172
160
1188
297
193
170
460
129
74
697
48.9
41.3
31.0
28.1
26.4
26.3
25.6
23.8
23.6
23.5
23.3
22.5
21.5
20.9
19.7
18.2
18.0
17.8
17.6
17.5



いいねぇ、画像にテキストが埋め込まれているわけじゃないのに、きれいに出ました!
文字の出力の精度高すぎ!!さっすが独裁グローバル企業!

課題


出たは出たんだけど、たぶんOCRは縦に列を見て、今度は横の列を見てって処理を行っているみたいなので、へんなふうに改行されてしまいます。これだとちょっとなぁ、確かにいちいちマニュアルでデータを入力する作業コピーしなくても済むけど、エクセルでコピペして列ずらすとかの作業は必要になってしまうなぁと思いました。


解決された領域はこんな感じです。

・データを手動で入力 →〇
・ファイルの整理 → △
・CSV出力 → ×


今度は、この取得したデータをもとに、いい感じにデータを整理して、CSVファイルに落とし込む方法を模索してみたいと思います。おそらくやり方は二つあって、


・Google Vision APIで行で処理してくれる方法を探す
・全要素数と列数から、一列あたりの要素数を特定し、その値をどんどん列に追加していく


前者は、ここの要素に座標が付随されているので、同一列のものをグルーピングしていく方法が考えられます。


後者は、全要素数を3でわって、その列の最後の数まで列に値を追加、次にその次の列に値を追加、というのをfor文回してあげればできるんじゃないかなぁと考えていますが、それだと引数に列数を指定したり、ユーザビリティが落ちそうですね。


とりあえず、いろいろ考えてみます。うんこ!

注目の投稿

 PythonのTweepyを利用して、Twitter APIを利用している。 その中で、ハマったポイントをメモしておく。 まず、Searchに関して。 Twitter検索は、クライアントアプリ側では、全期間の検索が可能になっている。 一方で、APIを利用する際は、過去1週間しか...