[PYTHON] J'ai essayé "Receipt OCR" avec l'API Google Vision

introduction

Il existe une technologie appelée «OCR (reconnaissance optique de caractères)» qui lit les caractères imprimés ou manuscrits et les convertit en données de caractères.

Les services OCR sont fournis pour divers documents tels que factures, reçus, cartes de visite et licences. En utilisant l'OCR, vous pouvez réduire les problèmes de saisie de données. De plus, en établissant des liens avec d'autres systèmes, il est possible d'utiliser efficacement les données.

L'OCR fourni par chaque entreprise comprend des services pour les entreprises et les particuliers. En tant qu'OCR pouvant être utilisé par des particuliers, il existe "l'API Google Vision (ci-après dénommée API Vision)". Vision API est un service d'analyse d'images très performant fourni par Google. (Cliquez ici pour consulter la page d'essai gratuite (https://cloud.google.com/vision?hl=ja))

Cette fois, j'ai essayé un simple OCR de reçu en utilisant l'API Vision.

Réception OCR

environnement

L'environnement utilise Google Colaboratory. La version Python est ci-dessous.

import platform
print("python " + platform.python_version())
# python 3.6.9

Montrons l'image

Maintenant écrivons le code. Tout d'abord, importez la bibliothèque requise pour afficher l'image.

import cv2
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib

Préparez également un exemple d'image du reçu. Montrons l'image.

img = cv2.imread(input_file) # input_file est le chemin de l'image
plt.figure(figsize=[10,10])
plt.axis('off')
plt.imshow(img[:,:,::-1])

image.png

Configuration de l'API Vision

Maintenant, OCR cette image de reçu à l'aide de l'API Google Vision.

Voici les préparatifs nécessaires pour utiliser l'API Vision. Veuillez procéder selon ici. Vous devrez installer la bibliothèque cliente et émettre une clé de compte de service.

L'installation de la bibliothèque cliente est la suivante.

pip install google-cloud-vision

Utilisez la clé de compte de service émise pour définir les variables d'environnement.

import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = json_path # json_path est le chemin de la clé du compte de service

Envoyer une demande à l'API / Obtenir une réponse

Maintenant, envoyons une demande à l'API Vision et obtenons une réponse.

import io

from google.cloud import vision
from google.cloud.vision import types

client = vision.ImageAnnotatorClient()
with io.open(input_file, 'rb') as image_file:
    content = image_file.read()
image = types.Image(content=content)
response = client.document_text_detection(image=image)

Si elle peut être exécutée sans aucune erreur, la requête peut être envoyée à l'API et la réponse peut être obtenue.

Cette réponse contient le résultat OCR de l'API Vision. Il contient diverses informations telles que les informations de texte lues, les informations de coordonnées, la certitude et la langue. Ici, vérifions les informations textuelles du texte intégral lu.

print(response.text_annotations[0].description)
SAVERSONICS
Seven-Eleven
Boutique Chiyoda
8-8 Nibancho, Chiyoda-ku, Tokyo
Téléphone: 03-1234-5678
Caisse enregistreuse # 31
Mardi 01 octobre 2019 08:45 Responsabilité 012
Reçu
Boule de riz roulée à la main œufs de morue épicés Coca-Cola 500ml
Paradu Mini Nail PK03
Mobius One
Timbre de 50 yens
*130
*140
300
490、
50 ans
Sous-total (8% hors taxes)
¥270
Taxe à la consommation, etc. (8%)
¥21
Sous-total (10% hors taxes)
¥300
Taxe à la consommation, etc. (10%)
¥30
Sous-total (10% TTC)
¥490
Sous-total (exonéré d'impôt)
¥50
Total ¥ 1,161
(Taux d'imposition cible de 8%
¥291)
(Taux d'imposition cible de 10%
¥820)
(Taxe de consommation intérieure, etc. 8%
¥21)
(Taxe de consommation intérieure, etc. 10%
¥74)
Montant du retour sans numéraire
-22
paiement nanaco
¥1,139
Les détails d'achat sont comme ci-dessus.
numéro nanaco
*******9999
Ce point dans le temps
2P
La marque [*] est soumise au taux d'imposition réduit.

Vous pouvez voir qu'il est lu avec une très grande précision.

L'API Vision divise l'image en blocs, paragraphes, etc., en fonction de la manière dont les caractères sont collectés. Vérifions chaque zone divisée. Tout d'abord, définissez la fonction (voir Code ici pour plus de détails).

from enum import Enum

class FeatureType(Enum):
    PAGE = 1
    BLOCK = 2
    PARA = 3
    WORD = 4
    SYMBOL = 5

def draw_boxes(input_file, bounds):
    img = cv2.imread(input_file, cv2.IMREAD_COLOR)
    for bound in bounds:
      p1 = (bound.vertices[0].x, bound.vertices[0].y) # top left
      p2 = (bound.vertices[1].x, bound.vertices[1].y) # top right
      p3 = (bound.vertices[2].x, bound.vertices[2].y) # bottom right
      p4 = (bound.vertices[3].x, bound.vertices[3].y) # bottom left
      cv2.line(img, p1, p2, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
      cv2.line(img, p2, p3, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
      cv2.line(img, p3, p4, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
      cv2.line(img, p4, p1, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
    return img

def get_document_bounds(response, feature):
    document = response.full_text_annotation
    bounds = []
    for page in document.pages:
        for block in page.blocks:
            for paragraph in block.paragraphs:
                for word in paragraph.words:
                    for symbol in word.symbols:
                        if (feature == FeatureType.SYMBOL):
                          bounds.append(symbol.bounding_box)
                    if (feature == FeatureType.WORD):
                        bounds.append(word.bounding_box)
                if (feature == FeatureType.PARA):
                    bounds.append(paragraph.bounding_box)
            if (feature == FeatureType.BLOCK):
                bounds.append(block.bounding_box)
    return bounds

Maintenant, écrivons chaque zone sur l'image et affichons-la.

bounds = get_document_bounds(response, FeatureType.BLOCK)
img_block = draw_boxes(input_file, bounds)

bounds = get_document_bounds(response, FeatureType.PARA)
img_para = draw_boxes(input_file, bounds)

bounds = get_document_bounds(response, FeatureType.WORD)
img_word = draw_boxes(input_file, bounds)

bounds = get_document_bounds(response, FeatureType.SYMBOL)
img_symbol = draw_boxes(input_file, bounds)

plt.figure(figsize=[20,20])
plt.subplot(141);plt.imshow(img_block[:,:,::-1]);plt.title("img_block")
plt.subplot(142);plt.imshow(img_para[:,:,::-1]);plt.title("img_para")
plt.subplot(143);plt.imshow(img_word[:,:,::-1]);plt.title("img_word")
plt.subplot(144);plt.imshow(img_symbol[:,:,::-1]);plt.title("img_symbol")

image.png

Il a été confirmé que l'image était divisée en plusieurs unités telles que bloc, paragraphe, mot et symbole.

Formatage du texte

Comme vous pouvez le voir, l'API Vision divise bien la zone, mais dans certains cas, cela peut être un inconvénient. Par exemple, dans ce cas, les «œufs de morue épicés onigiri roulés à la main» et leur quantité «* 130» sont séparés. En raison de la nature des reçus, les informations sont souvent organisées ligne par ligne, pensez donc à la diviser ligne par ligne.

Comment puis-je séparer chaque ligne? L'API Vision a des informations de coordonnées caractère par caractère (symbole bounding_box ci-dessus). Le tri de gauche à droite et de haut en bas par valeurs de coordonnées semble fonctionner. Ci-dessous, nous allons créer un processus de regroupement par ligne en fonction des coordonnées des caractères.

def get_sorted_lines(response):
    document = response.full_text_annotation
    bounds = []
    for page in document.pages:
      for block in page.blocks:
        for paragraph in block.paragraphs:
          for word in paragraph.words:
            for symbol in word.symbols:
              x = symbol.bounding_box.vertices[0].x
              y = symbol.bounding_box.vertices[0].y
              text = symbol.text
              bounds.append([x, y, text, symbol.bounding_box])
    bounds.sort(key=lambda x: x[1])
    old_y = -1
    line = []
    lines = []
    threshold = 1
    for bound in bounds:
      x = bound[0]
      y = bound[1]
      if old_y == -1:
        old_y = y
      elif old_y-threshold <= y <= old_y+threshold:
        old_y = y
      else:
        old_y = -1
        line.sort(key=lambda x: x[0])
        lines.append(line)
        line = []
      line.append(bound)
    line.sort(key=lambda x: x[0])
    lines.append(line)
    return lines

Vérifions-le.

img = cv2.imread(input_file, cv2.IMREAD_COLOR)

lines = get_sorted_lines(response)
for line in lines:
  texts = [i[2] for i in line]
  texts = ''.join(texts)
  bounds = [i[3] for i in line]
  print(texts)
  for bound in bounds:
    p1 = (bounds[0].vertices[0].x, bounds[0].vertices[0].y)   # top left
    p2 = (bounds[-1].vertices[1].x, bounds[-1].vertices[1].y) # top right
    p3 = (bounds[-1].vertices[2].x, bounds[-1].vertices[2].y) # bottom right
    p4 = (bounds[0].vertices[3].x, bounds[0].vertices[3].y)   # bottom left
    cv2.line(img, p1, p2, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
    cv2.line(img, p2, p3, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
    cv2.line(img, p3, p4, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
    cv2.line(img, p4, p1, (0, 255, 0), thickness=1, lineType=cv2.LINE_AA)

plt.figure(figsize=[10,10])
plt.axis('off')
plt.imshow(img[:,:,::-1]);plt.title("img_by_line")
Seven-Eleven
SAVERSONICS
Boutique Chiyoda
8-8 Nibancho, Chiyoda-ku, Tokyo
Téléphone: 03-1234-5678 Caisse enregistreuse # 31
Mardi 01 octobre 2019 08:45 Responsabilité 012
Reçu
Œufs de morue épicés onigiri roulés à la main * 130
Coca-Cola 500 ml * 140
Paradu Mini Nail PK03300
Mobius One 490,
Timbre de 50 yens 50 ans
Sous-total (8% hors taxes) ¥ 270
Taxe à la consommation, etc. (8%)
¥21
Sous-total (10% hors taxes) 300 ¥
Taxe à la consommation, etc. (10%) ¥ 30
Sous-total (10% TTC) 490 ¥
Sous-total (exonéré d'impôt) ¥ 50
Total ¥ 1,161
(Taux d'imposition cible de 8%
¥291)
(Taux d'imposition cible de 10% 820 ¥)
(Taxe de consommation incluse, etc. 8% ¥ 21)
(Taxe de consommation intérieure, etc. 10% ¥ 74)
Montant du retour sans numéraire-22
paiement nanaco ¥ 1,139
Les détails d'achat sont comme ci-dessus.
numéro nanaco ******* 9999
Ce point de temps 2P
La marque [*] est soumise au taux d'imposition réduit.

J'ai pu l'organiser ligne par ligne.

Structuration de texte

Grâce à la mise en forme du texte, j'ai pu organiser les chaînes de caractères. Cela facilite la récupération des informations dont vous avez besoin. Lors de la récupération des informations nécessaires, un traitement de texte à l'aide d'expressions régulières ou un traitement en langage naturel peut être envisagé.

Cette fois, extrayons et structurons des informations telles que «date», «numéro de téléphone» et «montant total» à l'aide d'expressions régulières. Voir aussi ici pour les expressions régulières.

import re

def get_matched_string(pattern, string):
    prog = re.compile(pattern)
    result = prog.search(string)
    if result:
        return result.group()
    else:
        return False

pattern_dict = {}
pattern_dict['date'] = r'[12]\d{3}[/\-Année](0?[1-9]|1[0-2])[/\-Mois](0?[1-9]|[12][0-9]|3[01])journée?'
pattern_dict['time'] = r'((0?|1)[0-9]|2[0-3])[:Temps][0-5][0-9]Minutes?'
pattern_dict['tel'] = '0\d{1,3}-\d{1,4}-\d{4}'
pattern_dict['total_price'] = r'Total ¥(0|[1-9]\d*|[1-9]\d{0,2}(,\d{3})+)$'

for line in lines:
  texts = [i[2] for i in line]
  texts = ''.join(texts)
  for key, pattern in pattern_dict.items():
    matched_string = get_matched_string(pattern, texts)
    if matched_string:
      print(key, matched_string)

# tel 03-1234-5678
#date 01 octobre 2019
# time 08:45
# total_prix Total ¥ 1,161

J'ai pu extraire le numéro de téléphone, la date, l'heure et le montant total.

Résumé

Cette fois, j'ai utilisé l'API Vision pour effectuer l'OCR de réception.

L'API Vision dispose d'une fonction OCR de très haute précision. C'est également un service OCR que même les particuliers peuvent facilement utiliser. Il est également possible d'extraire les informations souhaitées en appliquant des expressions régulières et un traitement en langage naturel au texte du résultat OCR.

Pourquoi n'essayez-vous pas l'OCR avec divers documents?

Recommended Posts

J'ai essayé "Receipt OCR" avec l'API Google Vision
J'ai essayé "License OCR" avec l'API Google Vision
J'ai essayé d'utiliser l'API Google Cloud Vision
J'ai essayé Google Sign-In avec Spring Boot + Spring Security REST API
J'ai essayé l'API Google Cloud Vision pour la première fois
J'ai essayé d'extraire des caractères des sous-titres (OpenCV: API Google Cloud Vision)
J'ai essayé de découvrir notre obscurité avec l'API Chatwork
J'ai essayé de créer une application OCR avec PySimpleGUI
J'ai essayé de frapper l'API avec le client python d'echonest
J'ai essayé fp-growth avec python
J'ai essayé de gratter avec Python
J'ai essayé Learning-to-Rank avec Elasticsearch!
J'ai essayé le clustering avec PyCaret
J'ai essayé gRPC avec Python
J'ai essayé de gratter avec du python
J'ai essayé de connecter Raspeye et conect + avec l'API Web
J'ai essayé la gestion du suivi avec l'API Twitter et Python (facile)
J'ai essayé de sauvegarder l'historique des demandes d'API DRF avec django-request
J'ai essayé de créer l'API Quip
J'ai essayé le roman Naro API 2
J'ai essayé de résumer des phrases avec summpy
J'ai essayé l'apprentissage automatique avec liblinear
J'ai essayé webScraping avec python.
J'ai essayé de déplacer de la nourriture avec SinGAN
J'ai essayé d'implémenter DeepPose avec PyTorch
J'ai touché l'API de Tesla
J'ai essayé l'API du roman Naruro
J'ai essayé d'exécuter prolog avec python 3.8.2.
J'ai essayé la communication SMTP avec Python
J'ai essayé la génération de phrases avec GPT-2
J'ai essayé d'apprendre LightGBM avec Yellowbrick
Présentation de l'API Google Map avec rails
J'ai essayé la reconnaissance faciale avec OpenCV
J'ai essayé d'utiliser l'API checkio
J'ai essayé d'automatiser tout, y compris l'authentification en deux étapes de Google OAuth
J'ai essayé ChatOps avec Slack x API Gateway x Lambda (Python) x RDS
J'ai essayé d'utiliser l'API de reconnaissance vocale docomo et l'API Google Speech en Java
J'ai essayé d'utiliser l'API Google avec Ruby et Python - Faites de la base de données une feuille de calcul et gérez-la avec Google Drive
Exemple d'API Google Cloud Vision pour python
J'ai essayé d'envoyer un SMS avec Twilio
J'ai essayé de supprimer régulièrement les mauvais tweets avec l'API AWS Lambda + Twitter
J'ai essayé linebot avec flacon (anaconda) + heroku
J'ai essayé de visualiser AutoEncoder avec TensorFlow
J'ai essayé de commencer avec Hy
J'ai essayé d'utiliser l'API de données YOUTUBE V3
J'ai essayé l'analyse factorielle avec des données Titanic!
J'ai essayé d'apprendre avec le Titanic de Kaggle (kaggle②)
J'ai essayé le rendu non réaliste avec Python + opencv
J'ai essayé de créer un LINE BOT "Sakurai-san" avec API Gateway + Lambda
J'ai essayé d'obtenir le code d'authentification de l'API Qiita avec Python.
Utiliser l'API Google Cloud Vision de Python
J'ai essayé d'utiliser l'API UnityCloudBuild de Python
Transcription d'images avec l'API Vision de GCP
J'ai essayé un langage fonctionnel avec Python
Lors de l'introduction de l'API Google Cloud Vision sur les rails, j'ai suivi la documentation.