2017年10月16日月曜日

Raspberry PiとPython3とTwitterでOCRボットを作る

Raspberry PiでOCRを使うでRaspberry PiにTesseract-OCRをインストールしてOCRできるようにしたが、これとTwitterを利用して、Python3でOCRボットを作る。


環境


OSはRaspbian Jessie。



事前準備


以下の事項を済ませておく。


PythonラッパーPython-tesseractのインストール


PythonでTesseract-OCRを使うためにPythonラッパーのPython-tesseractをインストールする。ただその前に、libjpeg8-devと画像処理ライブラリのPillowをインストールしておく。libjpeg8-devはjpg画像を扱うのに必要。



続いてPython-tesseractのインストール。


Pillowとpytesseractのバージョンは以下の通り。



Python-tesseractを使ってみる


はじめにTesseract-OCRのパスを確認しておく。


使い方は簡単。tesseractのパスを設定して、OCRする画像を読み込むだけ。日本語OCRの精度に不安はあるが(Raspberry PiでOCRを使う)、とりあえずこれで進める。

from PIL import Image
import pytesseract

# tesseractのパスを設定
pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'

# text.pngという画像を日本語OCRして結果を出力
print(pytesseract.image_to_string(Image.open('test.png'), lang='jpn'))

Requests-OAuthlibによるStreaming APIの利用


PythonでOCRできるようになったので、次はTwitter APIを利用できるようにする。Python3でTwitterのデータを取得するではTweepyというTwitter APIのPythonラッパーを使ったが、ここではRequests-OAuthlibというHTTPライブラリのRequestsをサポートするOAuth認証ライブラリを使用する。Requests-OAuthlibはpipでインストール。


以下のバージョンがインストールされた。


Twitterが提供するAPIには、過去データを取得できるTwitter APIだけでなく、リアルタイムにデータ取得できるStreaming APIもある。Streaming APIを使ってリアルタイムに特定キーワードを含むツイートを検索してみる。Streaming APIを利用するサンプルはRequestsのページにあるので、これを参考に以下のようなサンプルコードを作成。

import json
from requests_oauthlib import OAuth1Session, OAuth1

# Twitter APIの認証情報
# Twitterの開発者向けのページで取得したキーとトークンを使う
CONSUMER_KEY = 'Consumer Key'
CONSUMER_SECRET = 'Consumer Secret'
ACCESS_TOKEN = 'Access Token'
ACCESS_TOKEN_SECRET = 'Access Token Secret'

# 認証情報の設定
tw = OAuth1Session(CONSUMER_KEY,
                client_secret=CONSUMER_SECRET,
                resource_owner_key=ACCESS_TOKEN,
                resource_owner_secret=ACCESS_TOKEN_SECRET)

# Twitterデータのリアルタイム検索
# https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter.html
url = 'https://stream.twitter.com/1.1/statuses/filter.json'
# 検索キーワードの指定
data={'track':'ラーメン'}
# Streaming APIを使うときはオプションstream=Trueを指定
r = tw.post(url, data=data, stream=True)

for line in r.iter_lines():
    if line:
        status = json.loads(line.decode('utf-8'))

        # ツイートを表示
        print(status['text'] + '\n')

上記コードをPython3で実行すると、以下のように「ラーメン」を含むツイートが表示される。



OCRボットの作成


要領としては、TwitterとPythonでRaspberry Piを再起動するでやったように、Twitterのダイレクトメッセージを使う。スマホから自分宛にダイレクトメッセージで画像を送信し、Raspberry Piで画像のURLを含むダイレクトメッセージを受信する。画像をダウンロードしてからOCRして、結果を自分宛に返信すると、スマホでOCRの結果を受け取れる。以下のコードを作成。

"""
Twitterのダイレクトメッセージで送信した画像をOCRして返信する
"""

import os
import json
import uuid
from io import BytesIO

from requests_oauthlib import OAuth1Session, OAuth1

from PIL import Image
import pytesseract

# Twitter APIの認証情報
# Twitterの開発者向けのページで取得したキーとトークンを使う
CONSUMER_KEY = 'Consumer Key'
CONSUMER_SECRET = 'Consumer Secret'
ACCESS_TOKEN = 'Access Token'
ACCESS_TOKEN_SECRET = 'Access Token Secret'

# Twitterの認証情報でOAuth1Sessionオブジェクト作成
tw = OAuth1Session(CONSUMER_KEY,
                    client_secret=CONSUMER_SECRET,
                    resource_owner_key=ACCESS_TOKEN,
                    resource_owner_secret=ACCESS_TOKEN_SECRET)

# pytesseractのパスを設定
pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'

def send_dm(sn, text):
    """
    ダイレクトメッセージの送信
    """
    data = {'screen_name':sn, 'text':text}
    s = tw.post('https://api.twitter.com/1.1/direct_messages/new.json', data=data)

def dl(url):
    """
    ダイレクトメッセージに添付された画像ファイルのダウンロード
    """
    # ダイレクトメッセージに添付された画像のURLにHTTPリクエスト
    r = tw.get(url)

    # HTTPリクエストのレスポンスから画像データ作成
    img = Image.open(BytesIO(r.content))

    # 画像ファイルの拡張子を取得
    root, ext = os.path.splitext(url)

    # UUIDでユニークなファイル名を作成して画像を保存
    uname = str(uuid.uuid4())
    img.save(uname+ext)

    return uname+ext

def get_text(img_path):
    """
    pytesseractを使ってOCRした結果を返す
    """
    return pytesseract.image_to_string(Image.open(img_path), lang='jpn+eng')

def streaming():
    """
    Streaming APIでダイレクトメッセージを受信して、画像があればOCRして
    結果をダイレクトメッセージで送信する
    """
    # Stream APIを使うときはオプションstream=Trueを指定
    r = tw.get('https://userstream.twitter.com/1.1/user.json', stream=True)

    print('Ready to OCR')
    for line in r.iter_lines():
        if line:
            line = line.decode('utf-8')
        else:
            continue

        if 'direct_message' in line:
            # JSONから辞書型に変換
            dm = json.loads(line)['direct_message']

            # 送信者と受信者の一致をチェック
            # 他人からのDirect Messageに反応しないようにするため
            if dm['sender_screen_name'] == dm['recipient_screen_name']:

                if 'entities' in dm:
                    if 'media' in dm['entities']:
                        print('DM: {} from @{}'.format(dm['text'], dm['sender_screen_name']))
                        for media in dm['entities']['media']:
                            # 画像ファイルのURL
                            img_url = media['media_url']

                            # 画像をダウンロード
                            fname = dl(img_url)

                            # ダウンロードした画像をOCR
                            ocr_text = get_text(fname)
                            print(ocr_text)

                            # ダイレクトメッセージでOCRの結果を送信
                            send_dm(dm['sender_screen_name'], ocr_text)

                            # 画像ファイルの削除
                            os.remove(fname)

if __name__ == '__main__':
    streaming()

上記コードをPython3で実行し、スマホで撮った画像をTwitterのダイレクトメッセージで自分宛に送る。送ったのは以下2つの画像。解像度はそれぞれ4050 X 719と3898 X 850。



Twitterの遅延なのか数分遅れることがあるものの、ちゃんと動いている。ただ、OCRの精度はイマイチ。実用は難しいか・・・


0 件のコメント:

コメントを投稿