LoginSignup
7
3

More than 3 years have passed since last update.

続 Google Cloud PlatformのOCRサービスを使ってサイマの課題に挑戦する

Last updated at Posted at 2019-12-20

Ateam cyma Advent Calendar 2019、21日目です!
株式会社エイチーム EC事業本部サイマのエンジニア @shimura_atsushi が2回目の登場です。

1回目でGoogle Could PlatformのOCRサービスを使ってサイマの課題に挑戦するとしてサイマの抱える課題に立ち向かう1手目の挑戦をしました。
2回目である今回は更に納品書チェックに対して踏み込んだ取り組みをしていきます。

前回をおさらい

前回の投稿でGCPのサービスを利用して単純なOCRを試しました。しかし、今サイマで運用されている納品書類は文面が複雑なものが多くただOCRにかけただけでは文字おこしの精度は低く例えうまく文字おこしされたとしてもデータのラベリングがされておらず文字データの再利用性が乏しい状態でした。

今回は・・・

前回の反省を基に今回は「OCRをしやすい画像が用意する」にフォーカスしてOCRにかける画像の前処理を作成していきます。
内容は前回からの継続ですのでタイトルはそのままですが今回はPythonでの実装がメインでGoogle Could Platformに関しては薄めですあしからずご容赦ください。

今回はサイマのエンジニアである@NamedPythonさんの勧めもあって画像処理ライブラリが充実しているPythonを利用して行きます。

環境を準備する

今回用いた開発端末

  • 開発端末 MacBook Pro 15-inch
  • OS macOS Mojave

もろもろインストールする

ここらへんはサクッと行きます。

インストール

  • Pythonをインストールする
    • pyenv
      • pythonのインストールするバージョンを管理できる
    • python 3.8.0
      • 執筆時点の最新を利用
    • pip
      • pythonにおけるパッケージ管理ツール
      • pythonをインストールすると一緒に入ると思います
  • pdf2imageインストール

    • PDFからPNGやJPEGへ変換するのに利用
  • popplerインストール

    • pdf2imageでPDF変換に利用
  • pillowインストール

    • 画像の加工に利用、主に切り抜きに利用
  • opencvインストール

    • 画像の加工に利用、主に2値化に利用
brew install pyenv 
pyenv install --list #インストール可能なバージョンを確認
pyenv install 3.8.0
pip3 install pdf2image
brew install poppler 
pip install pillow
pip install opencv

大まかな流れ

  1. 書類を複合機にてスキャン
  2. スキャンデータ(PDF)を画像データに変換する
  3. 変換した画像データを切り抜く
  4. 切り抜いたデータの2値化する
  5. OCRにかける

複合機にてスキャン(PDF)

本社にある複合機を利用します、スキャンすると登録されているメールアドレスにPDFが添付されてきます。
※最終的な運用ではサイマの各工場でスキャンをする予定

PDFから画像データへ変換する

スキャンされたデータはPDF形式なので画像データに変換します。
ディレクトリを指定すると格納されたPDFファイルを画像データに変換してくれるというものです。
pdf2imageをインポートしてconvert_from_pathというメソッドに変換したいファイルパスを渡せば
変換してくれます、簡単ですね。

pdf2png.py
from pdf2image import convert_from_path
from pathlib import Path
import os

p = Path('./img/pdf')
pdf_list = os.listdir(p)
print(pdf_list)

for i, pdf_file_path in enumerate(pdf_list):
  images = convert_from_path('./img/pdf/{}'.format(pdf_file_path))
  for image in images:
    image.save('./img/png/{}.png'.format(i), 'png')

変換したデータを切り抜く

今回のOCRの肝はこの工程です。
前回の反省を基に複雑な納品書のデータから必要な箇所を切り取りラベリングするという処理を本工程で実装します。

設定ファイルをJSONで用意する

納品書のフォーマットは仕入先ごとに基本的には一定(自転車とパーツで別パターンはある)なので切り抜くために必要な座標をJSON形式の設定ファイルを納品書フォーマットごとに用意します。

納品書上の内容で必要な情報は

  • 仕入先名
  • 納品日
  • 商品番号
  • 個数
  • 単価

ですのでこれらが記載される箇所の座標を設定ファイルに持っておきます。

各仕入先ごとのJSON設定ファイル

shiiresaki_setting.json
{
  "wholesaler_id": 2,
  "warehouse": {
    "x":10,
    "y":10,
    "height":50,
    "width":100
  },
  "date": {
    "x":20,
    "y":20,
    "height":50,
    "width":100
  },
  "product": {
    "x":30,
    "y":30,
    "height":150,
    "width":200
  },
  "figure": {
    "x":40,
    "y":40,
    "height":200,
    "width":250
  },
  "price": {
    "x":50,
    "y":50,
    "height":200,
    "width":250
  }
}

※数字は仮のものを記載してます。

pillowを用いた画像切り抜き処理

crop4image.py
from PIL import Image
import sys
import json
import productsetting

args = sys.argv
p = productsetting.product.ProductSetting(args[1])
image = Image.open('img/png/{wholesaler_id}.png'.format(wholesaler_id=p.wholesaler_id))

rect = (
  p.warehouse['x'],
  p.warehouse['y'],
  p.warehouse['x'] + p.warehouse['width'], 
  p.warehouse['y'] + p.warehouse['height']
)
print(rect)
cropped_image = image.crop(rect)
cropped_image.save('{wholesaler_id}.png'.format(wholesaler_id=p.wholesaler_id))

JSONによる設定ファイルを読み込むクラス

productsetting.py
import sys
import json

class ProductSetting:
  CONFIG_SETTING_FILE_BASE_FORMAT = './settings/product/{wholesaler_id}.json'

  def __init__(self, wholesaler):
    config_file_path = open(self.CONFIG_SETTING_FILE_BASE_FORMAT.format(wholesaler_id=wholesaler), 'r')
    config = json.load(config_file_path)
    self.wholesaler_id = config['wholesaler_id']
    self.warehouse = {
      'x': config['warehouse']['x'],
      'y': config['warehouse']['y'],
      'height': config['warehouse']['height'],
      'width': config['warehouse']['width']
    }    
    self.product = {
      'x': config['product']['x'],
      'y': config['product']['y'],
      'height': config['product']['height'],
      'width': config['product']['width']
    }    
    self.date = {
      'x': config['date']['x'],
      'y': config['date']['y'],
      'height': config['date']['height'],
      'width': config['date']['width']
    }    
    self.figure = {
      'x': config['figure']['x'],
      'y': config['figure']['y'],
      'height': config['figure']['height'],
      'width': config['figure']['width']
    }    

このスクリプトを実行するとこのような画像から
スクリーンショット 2019-12-19 22.11.02.png
※画像は加工してあります。

このように指定した座標で切り抜くことができました。
buyoption_1013.png

切り抜いたデータを2値化する

次は切り取った画像のOCR精度を高めるために文字を2値化して読み取り精度の向上を図ります。

opencvを利用して作成
2値化のプログラムはシンプルにこんな感じで

deeply_character.py
import cv2
img = cv2.imread('./result/png/1013/buyoption_1013.png', 0)
threshold = 100 #しきい値
ret, img_thresh = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
cv2.imwrite('./result/deeply/test/buyoption_1013.png', img_thresh)

切り取ったこんな画像が
buyoption_1013.png

こんな感じに2値化されました
buyoption_1013.png

サンプルが良くないのかあまり恩恵を感じられないです。

手強そうなこの画像で試してみようと思います。
sample.png

しきい値を調整して2値化すると・・・
buyoption_1013.png

なんと!よりはっきりとした画像になりました。

この画像を前回作成したGCPのOCRにかけてみます。
その結果・・・
スクリーンショット 2019-12-19 20.24.52.png

このように文字起こしされました、よく考えると「配達日」という箇所もノイズになるので省いても良かったですね。
ただこの精度ならチェックに関する再利用性が保てそうです。

まとめ

今回はOCRに掛ける前処理として、

  • 必要な箇所だけの切り抜き
  • 切り抜いた画像の2値化による鮮明化

をすることで如何にOCRに対して有利な状況を作れるかに挑戦してみました。

今回の事務処理効率化の取り組みですが事業部内で「やってみます!」と豪語したは良かったですが、納品書の現物を見ると本当に自動化できるのかと不安でした。
結果、画像の切り抜きによるノイズの除去と2値化による鮮明化の後でOCRにかけることで、精度が高めることでき、自動化が現実的になったかなと実感してます。

アドベントカレンダー2回にわたりサイマにおける納品書チェックという課題にOCRを中心とした技術で自動化へ挑戦をはじめました。今後はシステムの実装を進めつつ工場を巻き込んた運用の実現に取り組みたいです。

最後に

Ateam cyma Advent Calendar 2019の21日目いかがでしたか。
22日目はサイマのデザイナである@ryo_cyさんがBEMを用いたCSS設計について話をしてくれますのでお楽しみに!

株式会社エイチームでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。

エンジニアで興味を持った方はcymaのQiita Jobsをご覧ください。

そのほかの職種は、エイチームグループ採用サイトをご覧ください。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3