Es gibt eine Technologie namens "OCR (Optical Character Recognition)", die gedruckte oder handgeschriebene Zeichen liest und in Zeichendaten umwandelt.
OCR-Dienste werden für verschiedene Dokumente wie Rechnungen, Quittungen, Visitenkarten und Lizenzen bereitgestellt. Durch die Verwendung von OCR können Sie die Eingabe von Daten reduzieren. Durch die Verknüpfung mit anderen Systemen ist es außerdem möglich, Daten effektiv zu nutzen.
Die von jedem Unternehmen bereitgestellte OCR umfasst Dienstleistungen für Unternehmen und Einzelpersonen. Als OCR, die von Einzelpersonen verwendet werden kann, gibt es "Google Vision API (im Folgenden als Vision API bezeichnet)". Vision API ist ein sehr leistungsstarker Bildanalysedienst von Google. (Klicken Sie hier, um die kostenlose Testversion aufzurufen (https://cloud.google.com/vision?hl=ja).)
Dieses Mal habe ich eine einfache OCR für Quittungen mithilfe der Vision-API ausprobiert.
Die Umgebung verwendet Google Colaboratory. Die Python-Version ist unten.
import platform
print("python " + platform.python_version())
# python 3.6.9
Jetzt schreiben wir den Code. Importieren Sie zunächst die Bibliothek, die zum Anzeigen des Bildes erforderlich ist.
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib
Bereiten Sie auch ein Beispielbild der Quittung vor. Lassen Sie uns das Bild anzeigen.
img = cv2.imread(input_file) # input_Datei ist der Pfad des Bildes
plt.figure(figsize=[10,10])
plt.axis('off')
plt.imshow(img[:,:,::-1])
Lassen Sie uns nun dieses Quittungsbild mithilfe der Google Vision-API OCR.
Im Folgenden sind die erforderlichen Vorbereitungen für die Verwendung der Vision-API aufgeführt. Bitte fahren Sie gemäß [hier] fort (https://cloud.google.com/vision/docs/quickstart-client-libraries?hl=JA). Sie müssen die Clientbibliothek installieren und einen Dienstkontoschlüssel ausgeben.
Die Installation der Client-Bibliothek ist wie folgt.
pip install google-cloud-vision
Verwenden Sie den ausgegebenen Dienstkontoschlüssel, um Umgebungsvariablen festzulegen.
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = json_path # json_Pfad ist der Pfad des Dienstkontoschlüssels
Senden wir nun eine Anfrage an die Vision-API und erhalten eine Antwort.
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)
Wenn es fehlerfrei ausgeführt werden kann, kann die Anforderung an die API gesendet und die Antwort abgerufen werden.
Diese Antwort enthält das OCR-Ergebnis der Vision-API. Es enthält verschiedene Informationen wie gelesene Textinformationen, Koordinateninformationen, Sicherheit und Sprache. Hier überprüfen wir die Textinformationen des gelesenen Volltexttextes.
print(response.text_annotations[0].description)
SAVERSONICS Sieben-Elf Chiyoda-Laden 8-8 Nibancho, Chiyoda-ku, Tokio Telefon: 03-1234-5678 Registrierkasse Nr. 31 Dienstag, 01. Oktober 2019 08:45 Verantwortung 012 Quittung Handgerollter Reisbällchen würziger Kabeljaurogen Coca-Cola 500ml Paradu Mini Nail PK03 Mobius One 50 Yen Briefmarke *130 *140 300 490、 50 Jahre Zwischensumme (8% ohne Steuern) ¥270 Verbrauchssteuer usw. (8%) ¥21 Zwischensumme (10% ohne Steuern) ¥300 Verbrauchssteuer usw. (10%) ¥30 Zwischensumme (10% einschließlich Steuern) ¥490 Zwischensumme (steuerfrei) ¥50 Gesamt ¥ 1.161 (Steuersatz 8% Ziel ¥291) (Steuersatz 10% Ziel ¥820) (Innenverbrauchsteuer usw. 8% ¥21) (Interne Verbrauchsteuer usw. 10% ¥74) Bargeldloser Rückzahlungsbetrag -22 Nanaco Zahlung ¥1,139 Die Kaufdetails sind wie oben. Nanaco-Nummer *******9999 Dieser Zeitpunkt 2P Die Marke [*] unterliegt dem ermäßigten Steuersatz. |
Sie können sehen, dass es mit sehr hoher Genauigkeit gelesen wird.
Die Vision-API unterteilt das Bild in Blöcke, Absätze usw., je nachdem, wie die Zeichen gesammelt werden. Lassen Sie uns jeden unterteilten Bereich überprüfen. Definieren Sie zunächst die Funktion (Einzelheiten finden Sie unter Code hier).
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
Schreiben wir nun jeden Bereich auf das Bild und zeigen ihn an.
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")
Es wurde bestätigt, dass der Bereich des Bildes in verschiedene Einheiten wie Block, Absatz, Wort und Symbol unterteilt war.
Wie Sie sehen können, teilt die Vision-API den Bereich gut auf, in einigen Fällen kann dies jedoch ein Nachteil sein. In diesem Fall werden beispielsweise "handgerollter würziger Onigiri-Kabeljaurogen" und seine Menge "* 130" getrennt. Aufgrund der Art der Quittung werden die Informationen häufig zeilenweise organisiert. Teilen Sie sie daher zeilenweise auf.
Wie kann ich jede Zeile trennen? Die Vision-API enthält zeichenweise Koordinateninformationen (das Symbol bounding_box oben). Das Sortieren von links nach rechts und von oben nach unten nach Koordinatenwerten scheint zu funktionieren. Im Folgenden erstellen wir einen Prozess zum Gruppieren nach Zeilen gemäß den Koordinaten der Zeichen.
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
Lass es uns überprüfen.
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")
Sieben-Elf SAVERSONICS Chiyoda-Laden 8-8 Nibancho, Chiyoda-ku, Tokio Telefon: 03-1234-5678 Registrierkasse Nr. 31 Dienstag, 01. Oktober 2019 08:45 Verantwortung 012 Quittung Handgerollter Onigiri würziger Kabeljaurogen * 130 Coca-Cola 500 ml * 140 Paradu Mini Nail PK03300 Mobius One 490, 50 Yen Briefmarke 50 Jahre Zwischensumme (8% ohne Steuern) ¥ 270 Verbrauchssteuer usw. (8%) ¥21 Zwischensumme (10% ohne Steuern) ¥ 300 Verbrauchssteuer usw. (10%) ¥ 30 Zwischensumme (10% einschließlich Steuern) ¥ 490 Zwischensumme (steuerfrei) ¥ 50 Gesamt ¥ 1.161 (Steuersatz 8% Ziel ¥291) (Steuersatz 10% Ziel ¥ 820) (Einschließlich Verbrauchssteuer usw. 8% ¥ 21) (Interne Verbrauchssteuer usw. 10% ¥ 74) Bargeldlose Rückgabe-22 Nanaco Zahlung ¥ 1.139 Die Kaufdetails sind wie oben. Nanaco-Nummer ******* 9999 Diesmal 2P Die Marke [*] unterliegt dem ermäßigten Steuersatz. |
Ich konnte es Zeile für Zeile organisieren.
Durch Textformatierung konnte ich die Zeichenketten organisieren. Dies erleichtert das Abrufen der benötigten Informationen. Um die erforderlichen Informationen abzurufen, kann die Textverarbeitung mit regulären Ausdrücken oder die Verarbeitung in natürlicher Sprache in Betracht gezogen werden.
Dieses Mal extrahieren und strukturieren wir Informationen wie "Datum", "Telefonnummer" und "Gesamtbetrag" mithilfe regulärer Ausdrücke. Siehe auch hier für reguläre Ausdrücke.
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}[/\-Jahr](0?[1-9]|1[0-2])[/\-Mond](0?[1-9]|[12][0-9]|3[01])Tag?'
pattern_dict['time'] = r'((0?|1)[0-9]|2[0-3])[:Zeit][0-5][0-9]Protokoll?'
pattern_dict['tel'] = '0\d{1,3}-\d{1,4}-\d{4}'
pattern_dict['total_price'] = r'Insgesamt ¥(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
#Datum 01. Oktober 2019
# time 08:45
# total_Preis Gesamt ¥ 1,161
Ich konnte die Telefonnummer, das Datum, die Uhrzeit und den Gesamtbetrag extrahieren.
Dieses Mal habe ich die Vision-API verwendet, um eine Empfangs-OCR durchzuführen.
Die Vision-API verfügt über eine sehr hochpräzise OCR-Funktion. Es ist auch ein OCR-Dienst, den auch Einzelpersonen problemlos nutzen können. Es ist auch möglich, die gewünschten Informationen zu extrahieren, indem reguläre Ausdrücke und die Verarbeitung natürlicher Sprache auf den Text des OCR-Ergebnisses angewendet werden.
Warum versuchen Sie es nicht mit OCR mit verschiedenen Dokumenten?
Recommended Posts