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.
L'environnement utilise Google Colaboratory. La version Python est ci-dessous.
import platform
print("python " + platform.python_version())
# python 3.6.9
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])
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
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")
Il a été confirmé que l'image était divisée en plusieurs unités telles que bloc, paragraphe, mot et symbole.
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.
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.
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