GCP OCR & Java: Rechnungsautomatisierung
Die manuelle Rechnungsverarbeitung kann mühsam, fehleranfällig und zeitaufwendig sein. Optical Character Recognition (OCR) bietet eine leistungsstarke Lösung, indem sie die Textextraktion aus Rechnungen automatisiert und so Genauigkeit und Effizienz in Finanzabläufen deutlich verbessert.
Voraussetzungen
Bevor Sie mit GCP OCR und Java beginnen, stellen Sie sicher, dass Sie über Folgendes verfügen:
- Java 11 oder höher installiert
- Maven 3.8+ für die Verwaltung von Abhängigkeiten
- Ein Google Cloud-Konto mit aktivierter Abrechnung
- Die Cloud Vision API in Ihrem Google Cloud-Projekt aktiviert
- Die Google Cloud CLI installiert
Warum OCR bei der Rechnungsverarbeitung wichtig ist
OCR-Technologie wandelt Bilder von Text in maschinenlesbare Daten um. Die Integration von OCR in Arbeitsabläufe der Rechnungsverarbeitung kann:
- Fehler bei der manuellen Dateneingabe reduzieren
- Die Bearbeitungszeiten von Rechnungen beschleunigen
- Skalierbare und automatisierte Finanzprozesse ermöglichen
Google Cloud Vision API mit Java einrichten
Authentifizierung einrichten
Bevor Sie die Cloud Vision API verwenden, müssen Sie die Authentifizierung einrichten:
-
Installieren Sie die Google Cloud CLI, falls noch nicht geschehen.
-
Initialisieren Sie die CLI mit folgendem Befehl:
gcloud init -
Richten Sie die Standard-Zugangsdaten für Anwendungen ein:
gcloud auth application-default login -
Stellen Sie sicher, dass die Cloud Vision API in Ihrem Google Cloud-Projekt aktiviert ist:
gcloud services enable vision.googleapis.com
Abhängigkeiten hinzufügen
Fügen Sie Ihrem Java-Projekt die Cloud Vision-Abhängigkeit mit Maven hinzu. Empfohlen wird die Verwendung der Google Cloud BOM (Bill of Materials):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.56.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
Die BOM verwaltet beide Versionen. Setzen Sie das Maven Compiler Release auf 11 oder höher. Speichern Sie jede der folgenden Java-Klassen in einer eigenen Datei, die nach der Klasse benannt ist; das letzte Beispiel verwendet Gson zum Serialisieren von JSON.
Text aus Rechnungen extrahieren
Dieses Beispiel extrahiert Text aus einem JPEG- oder PNG-Rechnungsbild. Für PDF-/TIFF-Dokumente
benötigen Sie die API zur Dateiannotation von Vision mit einem
InputConfig, anstatt Dokument-Bytes an Image zu übergeben.
Jede verarbeitete PDF-Seite ist ein separates abrechenbares Bild.
import com.google.cloud.vision.v1.AnnotateImageRequest;
import com.google.cloud.vision.v1.AnnotateImageResponse;
import com.google.cloud.vision.v1.BatchAnnotateImagesResponse;
import com.google.cloud.vision.v1.Feature;
import com.google.cloud.vision.v1.Image;
import com.google.cloud.vision.v1.ImageAnnotatorClient;
import com.google.cloud.vision.v1.TextAnnotation;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class InvoiceOCR {
public static void main(String[] args) {
// The path to your invoice image
String filePath = "invoice.jpg";
try {
// Load the image
ByteString imgBytes = ByteString.copyFrom(Files.readAllBytes(Paths.get(filePath)));
Image img = Image.newBuilder().setContent(imgBytes).build();
// Create feature for text detection
Feature feature = Feature.newBuilder()
.setType(Feature.Type.DOCUMENT_TEXT_DETECTION)
.build();
// Build the request
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(feature)
.setImage(img)
.build();
List<AnnotateImageRequest> requests = new ArrayList<>();
requests.add(request);
// Process the request
try (ImageAnnotatorClient client = ImageAnnotatorClient.create()) {
BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = response.getResponsesList();
for (AnnotateImageResponse res : responses) {
if (res.hasError()) {
throw new IOException("Vision OCR failed (code " + res.getError().getCode() + ")");
}
if (!res.hasFullTextAnnotation() || res.getFullTextAnnotation().getText().isBlank()) {
System.out.println("No text found in image");
return;
}
TextAnnotation annotation = res.getFullTextAnnotation();
System.out.println("Extracted Text:\n" + annotation.getText());
}
}
} catch (Exception e) {
System.err.println("Invoice OCR failed (" + e.getClass().getSimpleName() + ")");
System.exit(1);
}
}
}
Dokumenttexterkennung vs. Texterkennung
Google Cloud Vision bietet zwei zentrale OCR-Modi:
-
DOCUMENT_TEXT_DETECTION: Optimiert für dichten Text in strukturierten Dokumenten wie Rechnungen. Layout und Struktur des Textes bleiben erhalten, was den Modus ideal für die Rechnungsverarbeitung macht.
-
TEXT_DETECTION: Besser geeignet für Text in Szenen oder für Bilder mit wenig Text. Der Modus ist weniger strukturiert, eignet sich aber gut, um Text in natürlichen Szenen zu erfassen.
Für die Rechnungsverarbeitung ist DOCUMENT_TEXT_DETECTION in der Regel die bessere Wahl, da
dabei die Struktur des Dokuments erhalten bleibt.
Rechnungsdaten parsen und strukturieren
Nach dem Extrahieren des Rohtexts müssen Sie ihn in strukturierte Daten parsen. Reguläre Ausdrücke oder NLP-Bibliotheken helfen dabei, Schlüsselfelder wie Rechnungsnummer, Datum, Gesamtbetrag und Lieferantendaten zu identifizieren.
Der folgende Parser ist bewusst auf beschriftete Zeilen beschränkt: Invoice No: INV-123,
Date: 03/19/2025, Total: $1,234.56 und Vendor: Example Ltd. Er
akzeptiert US-Datumsangaben im Format Monat/Tag/Jahr sowie Beträge mit Dezimalpunkt und weist
fehlende Felder oder ungültige Datumsangaben zurück. OCR-Text mit anderen Layouts oder
Gebietsschemata erfordert einen anderen Parser sowie eine Prüfung durch Menschen, bevor Daten an ein
Finanzsystem übermittelt werden.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class InvoiceParser {
public static Map<String, String> parseInvoiceData(String extractedText) {
Map<String, String> invoiceData = new HashMap<>();
// Match whole labeled lines so "Subtotal" and adjacent fields cannot be captured.
Pattern invoiceNumberPattern = Pattern.compile("(?im)^Invoice(?:[ \\t]+(?:No\\.?|Number)|[ \\t]*#)?[ \\t]*:[ \\t]*([A-Z0-9][A-Z0-9/-]*)[ \\t]*$");
Pattern datePattern = Pattern.compile("(?im)^(?:Invoice Date|Date)[ \\t]*:[ \\t]*(\\d{1,2}/\\d{1,2}/\\d{4})[ \\t]*$");
Pattern totalPattern = Pattern.compile("(?im)^(?:Total|Amount Due|Balance Due)[ \\t]*:[ \\t]*[$€£]?[ \\t]*((?:\\d{1,3}(?:,\\d{3})+|\\d+)\\.\\d{2})[ \\t]*$");
Pattern vendorPattern = Pattern.compile("(?im)^(?:From|Vendor|Supplier|Company)[ \\t]*:[ \\t]*([^\\r\\n]+)$");
// Extract invoice number
Matcher invoiceMatcher = invoiceNumberPattern.matcher(extractedText);
if (invoiceMatcher.find()) {
invoiceData.put("invoiceNumber", invoiceMatcher.group(1).trim());
}
// Extract date
Matcher dateMatcher = datePattern.matcher(extractedText);
if (dateMatcher.find()) {
invoiceData.put("date", dateMatcher.group(1).trim());
}
// Extract total amount
Matcher totalMatcher = totalPattern.matcher(extractedText);
if (totalMatcher.find()) {
invoiceData.put("totalAmount", totalMatcher.group(1).replace(",", ""));
}
// Extract vendor
Matcher vendorMatcher = vendorPattern.matcher(extractedText);
if (vendorMatcher.find()) {
invoiceData.put("vendor", vendorMatcher.group(1).trim());
}
validateInvoiceData(invoiceData);
return invoiceData;
}
private static void validateInvoiceData(Map<String, String> data) {
for (String field : new String[] {"invoiceNumber", "date", "totalAmount", "vendor"}) {
if (!data.containsKey(field) || data.get(field).isBlank()) {
throw new IllegalArgumentException("Missing invoice field: " + field);
}
}
DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("M/d/uuuu")
.withResolverStyle(ResolverStyle.STRICT);
LocalDate date = LocalDate.parse(data.get("date"), inputFormatter);
data.put("date", DateTimeFormatter.ISO_LOCAL_DATE.format(date));
}
}
Dateneingabe in Finanzsysteme automatisieren
Nach Validierung und Prüfung können strukturierte Rechnungsdaten über APIs oder Datenbankintegrationen in Finanzsysteme eingetragen werden. Allein das Vorhandensein von Feldern belegt nicht, dass OCR die richtigen Werte gelesen hat.
Hier ein konzeptionelles Beispiel für die Integration in ein Finanzsystem:
import com.google.gson.Gson;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
public class FinancialSystemIntegration {
private static final String API_ENDPOINT = "https://financial-system.example.invalid/invoices";
public static void submitInvoiceData(Map<String, String> invoiceData)
throws IOException, InterruptedException {
String apiKey = System.getenv("FINANCIAL_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("Set FINANCIAL_API_KEY before submitting invoices");
}
String jsonData = convertToJson(invoiceData);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_ENDPOINT))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
if (response.statusCode() >= 200 && response.statusCode() < 300) {
System.out.println("Invoice successfully submitted to financial system");
} else {
throw new IOException("Invoice submission failed (HTTP " + response.statusCode() + ")");
}
}
private static String convertToJson(Map<String, String> data) {
return new Gson().toJson(data);
}
}
Ersetzen Sie den reservierten Beispiel-Endpunkt durch den dokumentierten HTTPS-Endpunkt Ihres Systems und übergeben Sie die Zugangsdaten über das Secret Management Ihres Deployments. Die aufrufende Seite muss Fehler und Unterbrechungen behandeln. Bevor Sie eine Übermittlung erneut versuchen, nutzen Sie den Idempotenz-Mechanismus des Ziels oder gleichen Sie dessen Status ab, um doppelte Rechnungen zu vermeiden.
