読者です 読者をやめる 読者になる 読者になる

Python3でPyOCRを用いてPacerのシェア画像から歩数抽出

Python

Pacer(iOS, Android)という歩数管理のアプリではTwitterFacebookに歩数などの記録を画像としてシェアできます。今回はその画像から文字部分を抽出して文字として認識可能なように挑戦したもので、環境は Windows 7, Python 3.5.1 です。

Pacerでシェアした抽出対象の画像

  • 当初種類1に分類している画像を利用していましたが、日付が入っていないので種類2に分類している画像を利用するようにしました。

iPhone SEでキャプチャした画像

種類1 種類2
f:id:nfnoface:20161101140102j:plain:w2501.jpg f:id:nfnoface:20161101140111j:plain:w2502.jpg
f:id:nfnoface:20161102185632j:plain:w2503.jpg f:id:nfnoface:20161102185647j:plain:w2504.jpg

Windowsなので付属のペイントでルーラーグリッド線を有効にして座標を確認

  • 赤枠の部分が抽出対象です。

f:id:nfnoface:20161101141009j:plain f:id:nfnoface:20161101141019j:plain

必要なアプリケーション及びPythonモジュールを用意

Tesseract-OCR

PyOCR, PIL, numpy

  • Pythonから上記OCRを利用するモジュールpyocrを利用しています。
  • 画像操作でPIL(Pillow)が必要になります。
  • 画像を2値化する際numpyを利用しています。
  • インストール時点のバージョンはそれぞれ pyocr (0.4.2), Pillow (3.4.2), numpy (1.11.2) です。

Windows

python -m pip install pyocr
python -m pip install Pillow
python -m pip install numpy

LinuxmacOS

pip install pyocr
pip install Pillow
pip install numpy

コード

  • 当初全体から抽出を試みたものの、どこからどこまでが何の情報かわからないので対象部分を切り抜いてから文字抽出を試みるのが良さそうです。
  • カラー画像だと抽出精度が低くなるようなので2値化しています。
# -*- encoding: utf-8 -*-
from PIL import Image, ImageDraw
import numpy, os, pyocr, pyocr.builders, re, sys

tools = pyocr.get_available_tools()
if len(tools) == 0:
    sys.exit('Not Found OCR tools')

MIN_COLOR = 0
MAX_COLOR = 255

def retrieveStr(image_path):
    result = {}

    # 画像読み込み
    img = Image.open(image_path)

    # 文字を認識しやすいよう閾値200で白か黒の2値に変換
    array = numpy.asarray(img.convert('L'))
    array.flags.writeable = True
    w, h = img.size
    for y in range(h):
        for x in range(w):
            array[y, x] = MAX_COLOR if (array[y, x] > 200) else MIN_COLOR
    img = Image.fromarray(numpy.uint8(array))

    positions = {}

    # 1ピクセル目のRGBで画像種類を判断
    r, g, b = img.crop((0, 0, 1, 1)).convert('RGB').getpixel((0, 0))
    if r == MAX_COLOR:
        # 活動時間
        positions['active time'] = (190, 70, 440, 140)
        # カロリー
        positions['calories'] = (10, 70, 170, 140)
        # Km
        positions['km'] = (450, 70, 630, 140)
        # 歩数
        positions['steps'] = (170, 280, 470, 390)
    else:
        # 活動時間
        positions['active time'] = (220, 410, 420, 460)
        # カロリー
        positions['calories'] = (80, 410, 210, 460)
        # 日付
        positions['date'] = (190, 130, 450, 165)
        # Km
        positions['km'] = (430, 410, 560, 460)
        # 歩数
        positions['steps'] = (220, 220, 440, 300)
        # 日本語辞書を使わないので`年月日`を塗りつぶす
        dr = ImageDraw.Draw(img)
        # [(始点x,始点y),(終点x,終点y)],fill=塗つぶし色
        dr.rectangle([(275, 130), (305, 160)], fill=MAX_COLOR) # 年
        dr.rectangle([(340, 130), (365, 160)], fill=MAX_COLOR) # 月
        dr.rectangle([(405, 130), (430, 160)], fill=MAX_COLOR) # 日

    for label, pos in positions.items():
        # 欲しい部分だけ画像を切り抜く
        im = img.crop(pos)

        # 拡大縮小
        # im.thumbnail((im.size[0] / 3, im.size[1] / 3), Image.ANTIALIAS)
        # im = im.resize((im.size[0] * 3, im.size[1] * 3))

        # 画像表示
        # im.show()

        # 画像から文字抽出
        text = tools[0].image_to_string(
            im,
            builder=pyocr.builders.TextBuilder(tesseract_layout=6)
        )
        # バイト列をUTF-8でエンコードしてUTF-8の文字列にデコード
        text = text.encode('utf_8').decode('utf_8')

        # 時間を取り出して秒に変える
        if label == 'active time':
            at = re.split('[^\d]+', text)[:-1]
            hours = 0  if len(at) == 1 else int(at[0])
            minutes = int(at[len(at) - 1])
            text = (hours * 60 * 60) + (minutes * 60)
        # str -> int
        elif label == 'calories':
            text = int(text)
        # 日付整理
        elif label == 'date':
            text = '-'.join(re.split('[^\d]+', text))
        # str -> float
        elif label == 'km':
            text = float(text)
        # 数字以外の文字を詰める
        elif label == 'steps':
            text = int(''.join(re.split('[^\d]+', text)))

        result[label] = text

    return result

if __name__ == '__main__':
    base_dir = os.path.dirname(__file__)
    if not base_dir:
        base_dir = '.'

    img_dir = base_dir + os.path.sep + 'img' + os.path.sep

    for i in range(4):
        res = retrieveStr(img_dir + str(i + 1) + '.jpg')
        for val in res.items():
            print('{}: {}'.format(*val))
        print()

結果

c:\>python C:\py\ocr\img.py
steps: 17589
active time: 9300
km: 13.9
calories: 640

date: 2016-10-31
steps: 18709
active time: 10140
km: 15.0
calories: 685

steps: 42177
active time: 22080
km: 33.4
calories: 1449

date: 2016-11-01
steps: 11488
active time: 6720
km: 9.0
calories: 390


c:\>

参考