Wie man Async und Multithreading in C# verwendet
Die Migration von Mindeezu IronOCR verlagert die Dokumentenverarbeitung von externen Cloud-Servern auf Ihre eigene Infrastruktur – wodurch die Abrechnung pro Seite, die Datenübertragung durch Dritte und die Netzwerkverfügbarkeit als Laufzeitabhängigkeit entfallen. Die folgenden Abschnitte behandeln den Paketaustausch, Namespace-Aktualisierungen und vier praktische Vorher-Nachher-Code-Szenarien: Extraktion von Rechnungszonen, Verarbeitung mehrseitiger Beleg-PDFs, Vorverarbeitung von Finanzdokumenten und Archivierung durchsuchbarer PDFs.
Warum von Mindeeabwandern?
Mindee löst ein reales Problem auf elegante Weise: Man sendet ein Dokument ein und erhält strukturierte JSON-Felder. Für Teams, bei denen die Übermittlung von Finanzdokumenten an eine externe API akzeptabel ist und das Volumen moderat bleibt, liefert es schnelle Ergebnisse. Der Auslöser für die Migration tritt üblicherweise ein, wenn sich eine von mehreren Bedingungen ändert.
Finanzdaten überschreiten eine Compliance-Grenze. Mindee's ParseAsync<InvoiceV4> lädt das vollständige Dokument auf externe Server hoch. IBAN-Codes, Bankleitzahlen, Lieferanten-EINs und detaillierte Positionsnummern werden über das öffentliche Internet übertragen und auf Hardware verarbeitet, die sich außerhalb Ihrer Kontrolle befindet. Die SOC 2 Typ II Zertifizierung bestätigt, dass Mindeedie Sicherheitsstandards einhält; Es sorgt nicht dafür, dass Ihre Dokumente innerhalb Ihres Sicherheitsbereichs bleiben. Wenn eine Compliance-Prüfung, ein neuer Kundenvertrag oder eine Datenresidenzvorschrift die Grenze für die Cloud-Verarbeitung durch Dritte zieht, wird die Architektur von Mindeeunabhängig von ihrer Extraktionsgenauigkeit disqualifiziert.
Die Kosten pro Seite steigen mit Ihrem Verbrauch. Das Starter-Paket umfasst 1.000 Seiten pro Monat für 49 $/Monat. Der Pro-Tarif bietet 5.000 Seiten pro Monat zum Preis von 499 US-Dollar pro Monat. Bei mehrseitigen PDFs wird jede Seite gezählt. Entwicklungsläufe werden auf das Kontingent angerechnet. Ein Team, das 4.000 Rechnungen pro Monat bearbeitet, zahlt 499 US-Dollar pro Monat – 5.988 US-Dollar pro Jahr –, wobei sich dieser Betrag jedes Jahr neu festlegt und mit jedem Mengenwachstum steigt. Bei 24.000 Seiten pro Monat verhandeln Sie über Enterprise Preise. Die unbefristete Lizenz von IronOCR für 1.499 US-Dollar (Professional -Version) deckt ein unbegrenztes Dokumentenvolumen ab und amortisiert sich im Vergleich zu MindeePro in weniger als vier Monaten.
Asynchrones Polling breitet sich durch Ihren gesamten Stack aus. Jede Mindee-Operation ist asynchron, da jede Operation eine Netzwerkanfrage darstellt. Diese obligatorische asynchrone Kette setzt sich kaskadierend über Controller, Service-Schichten und Worker-Klassen fort. Wenn das Netzwerk nicht verfügbar ist oder der Dienst von Mindeeausfällt, führt die Kaskade zu einem Ausfall ohne lokale Ausweichmöglichkeit.IronOCR verarbeitet synchron — Arbeit mit lokaler CPU — und umhüllt in Task.Run, wo asynchrone Schnittstellen für architektonische Konsistenz erforderlich sind.
Der vorgefertigte API-Katalog hat eine feste Grenze. Mindeedeckt InvoiceV4, ReceiptV5, PassportV1 und einen kleinen Katalog anderer Dokumenttypen ab. Jeder Dokumenttyp außerhalb dieser Liste erfordert ein Enterprise-Plan-Modelltraining: Sammlung von Beispielen, Beschriftung, Trainingszeit und Vorlaufzeit, bevor die API verfügbar ist. Für jeden neuen Dokumententyp, den Ihr Unternehmen benötigt, ist ein separater Auftrag an einen externen Dienstleister erforderlich.IronOCR verarbeitet jeden Dokumenttyp mit dem gleichen IronTesseract().Read() Aufruf; Das Hinzufügen eines neuen Dokumenttyps bedeutet das Schreiben von Extraktionsmustern, nicht das Aushandeln eines Schulungsvertrags.
Die Verwaltung von API-Schlüsseln erhöht die operative Flexibilität. Mindee-Zugangsdaten müssen sicher serverseitig gespeichert, regelmäßig ausgetauscht und vor unbefugtem Zugriff geschützt werden. Die Netzwerkausgangsregeln müssen ausgehende HTTPS-Verbindungen zu Mindee-Endpunkten zulassen. Die Wiederholungslogik muss HttpRequestException und MindeeException von den unvermeidlichen Netzwerkfehlern handhaben. Mit dem Entfernen von Mindeeverschwindet die gesamte operative Oberfläche: keine Anmeldeinformationen, keine Ausgangsregeln, keine Wiederholungsmechanismen für die Netzwerk-E/A.
Ausgeschlossen sind abgeschottete und eingeschränkte Umgebungen. Regierungsauftragnehmer, Unterauftragnehmer der Verteidigungsindustrie, Gesundheitsnetzwerke mit strengen ausgehenden Internetkontrollen und industrielle Systeme, die in Einrichtungen ohne zuverlässige Internetverbindung eingesetzt werden, können Mindeenicht wie vorgesehen nutzen.IronOCR läuft identisch in Docker-Containern ohne ausgehenden Zugriff, in Azure Functions mit Austrittsbeschränkungen und in vollständig abgeschotteten Umgebungen. Es besteht keine Cloud-Abhängigkeit zur Laufzeit. Details zu den Bereitstellungsstufen finden Sie auf der IronOCR -Lizenzseite .
Das grundsätzliche Problem
Jedes von Mindeeverarbeitete Finanzdokument überschreitet eine Netzwerkgrenze, die Sie nicht kontrollieren:
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
Imports System.Threading.Tasks
' Mindee: invoice with bank details leaves your infrastructure on this line
Dim response = Await _client.ParseAsync(Of InvoiceV4)(New LocalInputSource("invoice.pdf"))
' IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
' IronOCR: same invoice, same result, zero data transmission
Dim result = New IronTesseract().Read("invoice.pdf")
' All processing local — bank details never leave your machine
IronOCR vs. Mindee: Funktionsvergleich
Die folgende Tabelle erfasst die architektonischen und leistungsbezogenen Unterschiede, die den Migrationsentscheidungen zugrunde liegen.
| Feature | Mindee | IronOCR |
|---|---|---|
| Verarbeitungsort | MindeeCloud-Server | Lokaler Rechner / Ihre Infrastruktur |
| Internetverbindung zur Laufzeit erforderlich | Stets | Niemals |
| Datenübertragung | Vollständiges Dokument wird pro Anruf hochgeladen | None |
| Kosten pro Dokument | Ja (pro Seite, abhängig vom Tarif) | Nein (unbefristete Lizenz) |
| Startpreis | 49 $/Monat (1.000 Seiten) | $999 einmalig (Lite) |
| Offline-/Air-Gap-Unterstützung | Nicht unterstützt | Volle Unterstützung |
| Lokale Bereitstellung | Nicht verfügbar | Einzige Option |
| Rechnungsanalyse | Vorgefertigte strukturierte API (InvoiceV4) |
OCR + regionenbasierte oder reguläre Ausdrucksextraktion |
| Beleganalyse | Vorgefertigte strukturierte API (ReceiptV5) |
OCR + Extraktionsmuster |
| Beliebige Dokumenttypen | Ohne individuelle Schulung nicht verfügbar. | Vollständige Unterstützung, jedes Dokument |
| Kundenspezifisches Modelltraining | Enterprise + Vorlaufzeit | Nicht erforderlich |
| API-Aufrufmuster | Asynchron obligatorisch (Netzwerk-E/A) | Synchron (asynchron optional) |
| Ratenbegrenzung | Planabhängig | None |
| PDF-Eingabe | Ja | Ja (nativ, keine Konvertierung) |
| Verarbeitung mehrseitiger PDFs | Ja | Ja |
| Durchsuchbare PDF-Ausgabe | Nein | Ja (result.SaveAsSearchablePdf()) |
| Bildvorverarbeitung | Keine exponiert | Entzerren, Rauschen entfernen, Kontrast erhöhen, Binärisierung, Schärfen, Skalieren |
| regionenbasierte Extraktion | Feldkoordinaten als Antwort | CropRectangle bei Eingabe |
| Unterstützung für mehr als 125 Sprachen | Beschränkt | Ja (im Paket enthalten, keine Tessdata-Verwaltung) |
| Barcode-Lesung | Nein | Ja (gleichzeitig mit OCR) |
| Strukturierte Wortkoordinaten | Nein | Ja (Seiten, Absätze, Zeilen, Wörter) |
| Thread-Sicherheit | Nicht verfügbar (Netzwerk-E/A pro Anruf) | Eingebaut |
| Plattformübergreifend | Cloud (plattformunabhängig) | Windows, Linux, macOS, Docker, Azure, AWS |
| .NET -Kompatibilität | .NET Standard 2.0+ | .NET Framework 4.6.2+, .NET 5/6/7/8/9 |
| Kommerzielle Unterstützung | Ja | Ja |
Schnellstart: Migration von Mindeezu IronOCR
Schritt 1: Ersetzen des NuGet-Pakets
Entfernen Sie das Mindee-Paket:
dotnet remove package Mindee
dotnet remove package Mindee
Installieren Sie IronOCR über NuGet :
dotnet add package IronOcr
Schritt 2: Namespaces aktualisieren
Ersetzen Sie den Mindee-Namensraumblock durch den IronOCR Namensraum:
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;
// After (IronOCR)
using IronOcr;
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;
// After (IronOCR)
using IronOcr;
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports Mindee.Product.Receipt
Imports Mindee.Product.FinancialDocument
Imports IronOcr
Schritt 3: Lizenz initialisieren
Fügen Sie den Lizenzschlüssel beim Start der Anwendung hinzu (vor dem ersten OCR-Aufruf):
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Beispiele für die Code-Migration
Extraktion von Rechnungsfeldern mithilfe von Dokumentzonen
Mindee gibt vorausgelesene Felder an bekannten Stellen zurück, da das serverseitige Modell weiß, wo Rechnungsnummern, Daten und Summen üblicherweise erscheinen. Das IronOCR-Äquivalent verwendet CropRectangle, um spezifische Zonen des Dokuments zu lesen, was der räumlichen Wahrnehmung von Mindee's Extraktion entspricht, ohne das Dokument zu übertragen.
Mindee-Ansatz:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeZoneExtractor
{
private readonly MindeeClient _client;
public MindeeZoneExtractor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<(string invoiceNumber, string supplierName, decimal? total)>
ExtractKeyFieldsAsync(string filePath)
{
// Document transmitted to Mindee— spatial field detection happens server-side
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Fields arrive pre-parsed; no zone configuration required on your end
return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
);
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeZoneExtractor
{
private readonly MindeeClient _client;
public MindeeZoneExtractor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<(string invoiceNumber, string supplierName, decimal? total)>
ExtractKeyFieldsAsync(string filePath)
{
// Document transmitted to Mindee— spatial field detection happens server-side
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Fields arrive pre-parsed; no zone configuration required on your end
return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
);
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeZoneExtractor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ExtractKeyFieldsAsync(filePath As String) As Task(Of (invoiceNumber As String, supplierName As String, total As Decimal?))
' Document transmitted to Mindee— spatial field detection happens server-side
Dim inputSource = New LocalInputSource(filePath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
' Fields arrive pre-parsed; no zone configuration required on your end
Return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
)
End Function
End Class
IronOCR Ansatz:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrZoneExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string invoiceNumber, string supplierName, string total)
ExtractKeyFields(string filePath)
{
// Zone 1: supplier name — top 12% of document, full width
var headerRegion = new CropRectangle(0, 0, 850, 120);
using var headerInput = new OcrInput();
headerInput.LoadImage(filePath, headerRegion);
var supplierResult = _ocr.Read(headerInput);
// Zone 2: invoice number and date — upper-right quadrant
var metaRegion = new CropRectangle(450, 80, 400, 100);
using var metaInput = new OcrInput();
metaInput.LoadImage(filePath, metaRegion);
var metaResult = _ocr.Read(metaInput);
// Zone 3: totals block — bottom-right 20%
var totalsRegion = new CropRectangle(500, 800, 350, 200);
using var totalsInput = new OcrInput();
totalsInput.LoadImage(filePath, totalsRegion);
var totalsResult = _ocr.Read(totalsInput);
var invoiceNumber = Regex.Match(
metaResult.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
totalsResult.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
RegexOptions.IgnoreCase).Groups[1].Value;
// Supplier name is the first substantial text line in the header zone
var supplierName = supplierResult.Text
.Split('\n')
.FirstOrDefault(l => l.Trim().Length > 4)
?.Trim();
return (invoiceNumber, supplierName, total);
}
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrZoneExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string invoiceNumber, string supplierName, string total)
ExtractKeyFields(string filePath)
{
// Zone 1: supplier name — top 12% of document, full width
var headerRegion = new CropRectangle(0, 0, 850, 120);
using var headerInput = new OcrInput();
headerInput.LoadImage(filePath, headerRegion);
var supplierResult = _ocr.Read(headerInput);
// Zone 2: invoice number and date — upper-right quadrant
var metaRegion = new CropRectangle(450, 80, 400, 100);
using var metaInput = new OcrInput();
metaInput.LoadImage(filePath, metaRegion);
var metaResult = _ocr.Read(metaInput);
// Zone 3: totals block — bottom-right 20%
var totalsRegion = new CropRectangle(500, 800, 350, 200);
using var totalsInput = new OcrInput();
totalsInput.LoadImage(filePath, totalsRegion);
var totalsResult = _ocr.Read(totalsInput);
var invoiceNumber = Regex.Match(
metaResult.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
totalsResult.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
RegexOptions.IgnoreCase).Groups[1].Value;
// Supplier name is the first substantial text line in the header zone
var supplierName = supplierResult.Text
.Split('\n')
.FirstOrDefault(l => l.Trim().Length > 4)
?.Trim();
return (invoiceNumber, supplierName, total);
}
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrZoneExtractor
Private ReadOnly _ocr As New IronTesseract()
Public Function ExtractKeyFields(filePath As String) As (invoiceNumber As String, supplierName As String, total As String)
' Zone 1: supplier name — top 12% of document, full width
Dim headerRegion As New CropRectangle(0, 0, 850, 120)
Using headerInput As New OcrInput()
headerInput.LoadImage(filePath, headerRegion)
Dim supplierResult = _ocr.Read(headerInput)
' Zone 2: invoice number and date — upper-right quadrant
Dim metaRegion As New CropRectangle(450, 80, 400, 100)
Using metaInput As New OcrInput()
metaInput.LoadImage(filePath, metaRegion)
Dim metaResult = _ocr.Read(metaInput)
' Zone 3: totals block — bottom-right 20%
Dim totalsRegion As New CropRectangle(500, 800, 350, 200)
Using totalsInput As New OcrInput()
totalsInput.LoadImage(filePath, totalsRegion)
Dim totalsResult = _ocr.Read(totalsInput)
Dim invoiceNumber = Regex.Match(metaResult.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
Dim total = Regex.Match(totalsResult.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)", RegexOptions.IgnoreCase).Groups(1).Value
' Supplier name is the first substantial text line in the header zone
Dim supplierName = supplierResult.Text.Split(vbLf).FirstOrDefault(Function(l) l.Trim().Length > 4)?.Trim()
Return (invoiceNumber, supplierName, total)
End Using
End Using
End Using
End Function
End Class
Zonenbasierte OCR entspricht der Intention der räumlichen Felderkennung von Mindee: bestimmte Bereiche des Dokuments lesen, anstatt reguläre Ausdrücke auf die gesamte Seite anzuwenden. CropRectangle(x, y, width, height) nimmt Pixel-Koordinaten an, daher erfordert das Tuning das Abmessen an einer repräsentativen Beispielrechnung. Der regionenbasierte OCR-Leitfaden behandelt Koordinatenmessung und Strategien zur Überlappung von Zonen. Für Rechnungen mit variablen Layouts liefert die Kombination von Zonenauswertung mit Ganzseiten-Regex auf result.Text die zuverlässigsten Ergebnisse.
Verarbeitung mehrseitiger Spesenabrechnungen im PDF-Format
Mindee akzeptiert eine PDF-Datei und verarbeitet jede Seite als separate Dokumenteinheit, wobei pro Seite ein strukturiertes Ergebnis zurückgegeben wird.IronOCR liest mehrseitige PDFs nativ durch OcrInput.LoadPdf(), verarbeitet alle Seiten in einem einzigen Aufruf und liefert result.Pages mit seitenweisem Inhalt und Wortkoordinaten.
Mindee-Ansatz:
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;
public class MindeeExpenseReportProcessor
{
private readonly MindeeClient _client;
public MindeeExpenseReportProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
{
var summaries = new List<ExpenseSummary>();
// Mindeeprocesses per-document; each page of a PDF is a separate upload
// For a 10-page expense report: 10 API calls, 10 pages billed
var inputSource = new LocalInputSource(pdfPath);
var response = await _client.ParseAsync<ReceiptV5>(inputSource);
var prediction = response.Document.Inference.Prediction;
summaries.Add(new ExpenseSummary
{
MerchantName = prediction.SupplierName?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value ?? 0m,
Category = prediction.Category?.Value
});
return summaries;
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;
public class MindeeExpenseReportProcessor
{
private readonly MindeeClient _client;
public MindeeExpenseReportProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
{
var summaries = new List<ExpenseSummary>();
// Mindeeprocesses per-document; each page of a PDF is a separate upload
// For a 10-page expense report: 10 API calls, 10 pages billed
var inputSource = new LocalInputSource(pdfPath);
var response = await _client.ParseAsync<ReceiptV5>(inputSource);
var prediction = response.Document.Inference.Prediction;
summaries.Add(new ExpenseSummary
{
MerchantName = prediction.SupplierName?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value ?? 0m,
Category = prediction.Category?.Value
});
return summaries;
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Receipt
Imports System.Threading.Tasks
Public Class MindeeExpenseReportProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessExpenseReportAsync(pdfPath As String) As Task(Of List(Of ExpenseSummary))
Dim summaries As New List(Of ExpenseSummary)()
' Mindee processes per-document; each page of a PDF is a separate upload
' For a 10-page expense report: 10 API calls, 10 pages billed
Dim inputSource As New LocalInputSource(pdfPath)
Dim response = Await _client.ParseAsync(Of ReceiptV5)(inputSource)
Dim prediction = response.Document.Inference.Prediction
summaries.Add(New ExpenseSummary With {
.MerchantName = prediction.SupplierName?.Value,
.Date = prediction.Date?.Value?.ToString(),
.TotalAmount = If(prediction.TotalAmount?.Value, 0D),
.Category = prediction.Category?.Value
})
Return summaries
End Function
End Class
IronOCR Ansatz:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrExpenseReportProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
{
// Load entire multi-page PDF in one call — no per-page upload
using var input = new OcrInput();
input.LoadPdf(pdfPath); // all pages loaded locally
var result = _ocr.Read(input);
var summaries = new List<ExpenseSummary>();
// Each page maps to one receipt in the expense report
foreach (var page in result.Pages)
{
var pageText = page.Text;
// Skip pages without receipt content
if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
continue;
var merchantName = page.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
pageText,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var dateMatch = Regex.Match(
pageText,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");
var categoryMatch = Regex.Match(
pageText,
@"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase);
summaries.Add(new ExpenseSummary
{
PageNumber = page.PageNumber,
MerchantName = merchantName,
Date = dateMatch.Success ? dateMatch.Value : null,
TotalAmount = totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: 0m,
Category = categoryMatch.Success
? categoryMatch.Groups[1].Value.Trim()
: null,
Confidence = page.Words.Any()
? page.Words.Average(w => (double)w.Confidence)
: 0
});
}
return summaries;
}
}
public class ExpenseSummary
{
public int PageNumber { get; set; }
public string MerchantName { get; set; }
public string Date { get; set; }
public decimal TotalAmount { get; set; }
public string Category { get; set; }
public double Confidence { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrExpenseReportProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
{
// Load entire multi-page PDF in one call — no per-page upload
using var input = new OcrInput();
input.LoadPdf(pdfPath); // all pages loaded locally
var result = _ocr.Read(input);
var summaries = new List<ExpenseSummary>();
// Each page maps to one receipt in the expense report
foreach (var page in result.Pages)
{
var pageText = page.Text;
// Skip pages without receipt content
if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
continue;
var merchantName = page.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
pageText,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var dateMatch = Regex.Match(
pageText,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");
var categoryMatch = Regex.Match(
pageText,
@"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase);
summaries.Add(new ExpenseSummary
{
PageNumber = page.PageNumber,
MerchantName = merchantName,
Date = dateMatch.Success ? dateMatch.Value : null,
TotalAmount = totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: 0m,
Category = categoryMatch.Success
? categoryMatch.Groups[1].Value.Trim()
: null,
Confidence = page.Words.Any()
? page.Words.Average(w => (double)w.Confidence)
: 0
});
}
return summaries;
}
}
public class ExpenseSummary
{
public int PageNumber { get; set; }
public string MerchantName { get; set; }
public string Date { get; set; }
public decimal TotalAmount { get; set; }
public string Category { get; set; }
public double Confidence { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrExpenseReportProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessExpenseReport(pdfPath As String) As List(Of ExpenseSummary)
' Load entire multi-page PDF in one call — no per-page upload
Using input As New OcrInput()
input.LoadPdf(pdfPath) ' all pages loaded locally
Dim result = _ocr.Read(input)
Dim summaries As New List(Of ExpenseSummary)()
' Each page maps to one receipt in the expense report
For Each page In result.Pages
Dim pageText = page.Text
' Skip pages without receipt content
If Not pageText.Contains("Total", StringComparison.OrdinalIgnoreCase) Then
Continue For
End If
Dim merchantName = page.Lines _
.FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
?.Text.Trim()
Dim totalMatch = Regex.Match(
pageText,
"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase)
Dim dateMatch = Regex.Match(
pageText,
"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b")
Dim categoryMatch = Regex.Match(
pageText,
"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase)
summaries.Add(New ExpenseSummary With {
.PageNumber = page.PageNumber,
.MerchantName = merchantName,
.Date = If(dateMatch.Success, dateMatch.Value, Nothing),
.TotalAmount = If(totalMatch.Success, Decimal.Parse(totalMatch.Groups(1).Value.Replace(",", "")), 0D),
.Category = If(categoryMatch.Success, categoryMatch.Groups(1).Value.Trim(), Nothing),
.Confidence = If(page.Words.Any(), page.Words.Average(Function(w) CDbl(w.Confidence)), 0)
})
Next
Return summaries
End Using
End Function
End Class
Public Class ExpenseSummary
Public Property PageNumber As Integer
Public Property MerchantName As String
Public Property Date As String
Public Property TotalAmount As Decimal
Public Property Category As String
Public Property Confidence As Double
End Class
Die Verarbeitung eines 10-seitigen Spesenabrechnungs-PDFs mit Mindeeerzeugt 10 API-Aufrufe und 10 abgerechnete Seiten.IronOCR verarbeitet dieselbe Datei in einem einzigen lokalen Aufruf ohne Kosten pro Seite und ohne Netzwerk-Roundtrip pro Seite. Die result.Pages Sammlung bietet seitenweises Text-, wortweise Begrenzungsrahmen- und Linienpositionen für layoutbewusste Extraktion. Informationen zum passwortgeschützten Laden von PDFs und zur Auswahl von Seitenbereichen finden Sie im PDF-Eingabeleitfaden , sowie im Leitfaden für strukturierte Leseergebnisse Informationen zur Arbeit mit Wortkoordinaten.
Pipeline zur Vorverarbeitung gescannter Rechnungen
Mindees Cloud-Verarbeitung wendet vor der Feldextraktion eine eigene Bildnormalisierung an. Die Qualität dieser Normalisierung ist undurchsichtig – man hat keine Kontrolle darüber und keinen Einblick in die durchgeführte Vorverarbeitung. Wenn eine gescannte Rechnung Verzerrungen, Schatten oder einen geringen Kontrast aufweist, gibt Mindeeniedrigere Konfidenzwerte zurück, ohne dass es eine Möglichkeit gibt, den Scan vor der Einreichung zu verbessern.IronOCR legt die Vorverarbeitungspipeline explizit offen, sodass Sie genau die Korrekturen anwenden können, die ein bestimmtes Dokument benötigt, bevor die Erkennung ausgeführt wird.
Mindee-Ansatz:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeScanProcessor
{
private readonly MindeeClient _client;
public MindeeScanProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
{
// Mindeeapplies internal normalization — no developer control over preprocessing
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new ScanResult
{
InvoiceNumber = prediction.InvoiceNumber?.Value,
Total = prediction.TotalAmount?.Value,
// Per-field confidence: only proxy for scan quality
InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
TotalConfidence = prediction.TotalAmount?.Confidence
};
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeScanProcessor
{
private readonly MindeeClient _client;
public MindeeScanProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
{
// Mindeeapplies internal normalization — no developer control over preprocessing
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new ScanResult
{
InvoiceNumber = prediction.InvoiceNumber?.Value,
Total = prediction.TotalAmount?.Value,
// Per-field confidence: only proxy for scan quality
InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
TotalConfidence = prediction.TotalAmount?.Confidence
};
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeScanProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessScannedInvoiceAsync(scanPath As String) As Task(Of ScanResult)
' Mindee applies internal normalization — no developer control over preprocessing
Dim inputSource = New LocalInputSource(scanPath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
Return New ScanResult With {
.InvoiceNumber = prediction.InvoiceNumber?.Value,
.Total = prediction.TotalAmount?.Value,
' Per-field confidence: only proxy for scan quality
.InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
.TotalConfidence = prediction.TotalAmount?.Confidence
}
End Function
End Class
IronOCR Ansatz:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrScanProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ScanResult ProcessScannedInvoice(string scanPath)
{
// First pass: read without preprocessing
var quickResult = _ocr.Read(scanPath);
OcrResult result;
if (quickResult.Confidence < 75)
{
// Low confidence — apply full preprocessing pipeline
using var input = new OcrInput();
input.LoadImage(scanPath);
input.Deskew(); // correct page rotation up to ±45 degrees
input.DeNoise(); // remove scanner artifacts and speckle
input.Contrast(); // normalize contrast for faded or overexposed scans
input.Sharpen(); // sharpen blurred text edges
result = _ocr.Read(input);
}
else
{
result = quickResult;
}
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
result.Text,
@"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups[1].Value;
return new ScanResult
{
InvoiceNumber = invoiceNumber,
Total = total,
DocumentConfidence = result.Confidence,
PreprocessingApplied = quickResult.Confidence < 75
};
}
}
public class ScanResult
{
public string InvoiceNumber { get; set; }
public string Total { get; set; }
public double DocumentConfidence { get; set; }
public bool PreprocessingApplied { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrScanProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ScanResult ProcessScannedInvoice(string scanPath)
{
// First pass: read without preprocessing
var quickResult = _ocr.Read(scanPath);
OcrResult result;
if (quickResult.Confidence < 75)
{
// Low confidence — apply full preprocessing pipeline
using var input = new OcrInput();
input.LoadImage(scanPath);
input.Deskew(); // correct page rotation up to ±45 degrees
input.DeNoise(); // remove scanner artifacts and speckle
input.Contrast(); // normalize contrast for faded or overexposed scans
input.Sharpen(); // sharpen blurred text edges
result = _ocr.Read(input);
}
else
{
result = quickResult;
}
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
result.Text,
@"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups[1].Value;
return new ScanResult
{
InvoiceNumber = invoiceNumber,
Total = total,
DocumentConfidence = result.Confidence,
PreprocessingApplied = quickResult.Confidence < 75
};
}
}
public class ScanResult
{
public string InvoiceNumber { get; set; }
public string Total { get; set; }
public double DocumentConfidence { get; set; }
public bool PreprocessingApplied { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrScanProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessScannedInvoice(scanPath As String) As ScanResult
' First pass: read without preprocessing
Dim quickResult = _ocr.Read(scanPath)
Dim result As OcrResult
If quickResult.Confidence < 75 Then
' Low confidence — apply full preprocessing pipeline
Using input As New OcrInput()
input.LoadImage(scanPath)
input.Deskew() ' correct page rotation up to ±45 degrees
input.DeNoise() ' remove scanner artifacts and speckle
input.Contrast() ' normalize contrast for faded or overexposed scans
input.Sharpen() ' sharpen blurred text edges
result = _ocr.Read(input)
End Using
Else
result = quickResult
End If
Dim invoiceNumber = Regex.Match(
result.Text,
"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value
Dim total = Regex.Match(
result.Text,
"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups(1).Value
Return New ScanResult With {
.InvoiceNumber = invoiceNumber,
.Total = total,
.DocumentConfidence = result.Confidence,
.PreprocessingApplied = quickResult.Confidence < 75
}
End Function
End Class
Public Class ScanResult
Public Property InvoiceNumber As String
Public Property Total As String
Public Property DocumentConfidence As Double
Public Property PreprocessingApplied As Boolean
End Class
Das zweistufige Verfahren – zuerst schnelles Lesen, dann Vorverarbeitung bei geringer Konfidenz – vermeidet den Aufwand einer vollständigen Vorverarbeitung bei sauberen Scans. Für die häufigsten Szenarien bei Kreditoren (leicht geneigte Flachbettscans, Faxartefakte, fotokopierte Rechnungen) stellen Deskew() und DeNoise() gemeinsam den größten Teil der Erkennungsgenauigkeit wieder her. Der Leitfaden zur Bildqualitätskorrektur dokumentiert alle verfügbaren Filter und gibt Hinweise darauf, welche Kombinationen bei verschiedenen Scanfehlern am besten geeignet sind. Der Leitfaden zur Bildausrichtungskorrektur behandelt die automatische Drehung von Dokumenten, die auf dem Kopf gescannt wurden.
Durchsuchbare PDF-Archivierung für Finanzdokumente
Mindee erzeugt keine PDF-Ausgabe. Die Architektur ist rein eingabebasiert: Man sendet ein Dokument und empfängt JSON-Felder. Teams, die gescannte Rechnungen in einem durchsuchbaren Format archivieren, müssen diese Datenverarbeitungskette getrennt von der Extraktion durch Mindeeverwalten.IronOCR generiert durchsuchbare PDFs direkt aus demselben Erkennungsdurchlauf, der für die Feldextraktion verwendet wird – keine zusätzliche Bibliothek, kein zweiter Verarbeitungsschritt.
Mindee-Ansatz:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeArchiveProcessor
{
private readonly MindeeClient _client;
public MindeeArchiveProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
{
// Extract fields via Mindee(document transmitted to cloud)
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Mindeereturns no PDF output — searchable PDF requires a separate library
// (e.g., iTextSharp, PdfSharp, or a separate OCR library)
// The scan at scanPath is the only PDF available for archiving;
// it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeArchiveProcessor
{
private readonly MindeeClient _client;
public MindeeArchiveProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
{
// Extract fields via Mindee(document transmitted to cloud)
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Mindeereturns no PDF output — searchable PDF requires a separate library
// (e.g., iTextSharp, PdfSharp, or a separate OCR library)
// The scan at scanPath is the only PDF available for archiving;
// it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeArchiveProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ArchiveInvoiceAsync(scanPath As String, archivePath As String) As Task
' Extract fields via Mindee(document transmitted to cloud)
Dim inputSource = New LocalInputSource(scanPath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
' Mindeereturns no PDF output — searchable PDF requires a separate library
' (e.g., iTextSharp, PdfSharp, or a separate OCR library)
' The scan at scanPath is the only PDF available for archiving;
' it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.")
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}")
End Function
End Class
IronOCR Ansatz:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrArchiveProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
{
// Apply preprocessing before recognition and archiving
using var input = new OcrInput();
input.LoadPdf(scanPath); // works with both PDF scans and image files
input.Deskew();
input.DeNoise();
var result = _ocr.Read(input);
// Extract fields from the same recognition pass
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var vendor = result.Pages.FirstOrDefault()?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
result.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
// Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath);
return new ArchiveResult
{
InvoiceNumber = invoiceNumber,
VendorName = vendor,
Total = totalMatch.Success ? totalMatch.Groups[1].Value : null,
ArchivePath = archiveOutputPath,
DocumentConfidence = result.Confidence,
PageCount = result.Pages.Count()
};
}
}
public class ArchiveResult
{
public string InvoiceNumber { get; set; }
public string VendorName { get; set; }
public string Total { get; set; }
public string ArchivePath { get; set; }
public double DocumentConfidence { get; set; }
public int PageCount { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrArchiveProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
{
// Apply preprocessing before recognition and archiving
using var input = new OcrInput();
input.LoadPdf(scanPath); // works with both PDF scans and image files
input.Deskew();
input.DeNoise();
var result = _ocr.Read(input);
// Extract fields from the same recognition pass
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var vendor = result.Pages.FirstOrDefault()?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
result.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
// Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath);
return new ArchiveResult
{
InvoiceNumber = invoiceNumber,
VendorName = vendor,
Total = totalMatch.Success ? totalMatch.Groups[1].Value : null,
ArchivePath = archiveOutputPath,
DocumentConfidence = result.Confidence,
PageCount = result.Pages.Count()
};
}
}
public class ArchiveResult
{
public string InvoiceNumber { get; set; }
public string VendorName { get; set; }
public string Total { get; set; }
public string ArchivePath { get; set; }
public double DocumentConfidence { get; set; }
public int PageCount { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Imports System.Linq
Public Class IronOcrArchiveProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ArchiveInvoice(scanPath As String, archiveOutputPath As String) As ArchiveResult
' Apply preprocessing before recognition and archiving
Using input As New OcrInput()
input.LoadPdf(scanPath) ' works with both PDF scans and image files
input.Deskew()
input.DeNoise()
Dim result = _ocr.Read(input)
' Extract fields from the same recognition pass
Dim invoiceNumber = Regex.Match(result.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
Dim vendor = result.Pages.FirstOrDefault()?.Lines.FirstOrDefault(Function(l) l.Text.Trim().Length > 4)?.Text.Trim()
Dim totalMatch = Regex.Match(result.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})", RegexOptions.IgnoreCase)
' Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath)
Return New ArchiveResult With {
.InvoiceNumber = invoiceNumber,
.VendorName = vendor,
.Total = If(totalMatch.Success, totalMatch.Groups(1).Value, Nothing),
.ArchivePath = archiveOutputPath,
.DocumentConfidence = result.Confidence,
.PageCount = result.Pages.Count()
}
End Using
End Function
End Class
Public Class ArchiveResult
Public Property InvoiceNumber As String
Public Property VendorName As String
Public Property Total As String
Public Property ArchivePath As String
Public Property DocumentConfidence As Double
Public Property PageCount As Integer
End Class
result.SaveAsSearchablePdf() bettet die erkannte Textebene direkt in das Ausgabe-PDF ein und erzeugt eine Datei, bei der jedes Wort in jedem PDF-Viewer oder Unternehmensdokumentenverwaltungssystem auswählbar und durchsuchbar ist. Feldextraktion und Archivierung laufen im selben Durchgang — ein _ocr.Read(input) Aufruf liefert sowohl result.Text zum Mustermatching als auch result.SaveAsSearchablePdf() für das Archiv. Der durchsuchbare PDF-Leitfaden behandelt Ausgabeoptionen einschließlich HOCR-Export und die PDF-OCR-Anwendungsseitenseite deckt Produktionsbereitstellungsmuster für Dokumentenarchivierungspipelines ab.
Entfernen des benutzerdefinierten Endpunkts FinancialDocumentV1
Mindee's FinancialDocumentV1 API behandelt sowohl Rechnungen als auch Quittungen durch automatische Erkennung des Dokumenttyps. Teams, die es nutzen, eliminieren die Verzweigungslogik, allerdings auf Kosten einer höheren Cloud-Abhängigkeit – jedes Dokument wird unabhängig vom Typ hochgeladen. Durch den Einsatz von IronOCR werden Positionsdaten auf Wortebene verwendet, um die Dokumentstruktur zu erkennen und die Extraktionslogik ohne Cloud-Aufruf zu steuern.
Mindee-Ansatz:
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;
public class MindeeFinancialRouter
{
private readonly MindeeClient _client;
public MindeeFinancialRouter(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
{
// All financial documents uploaded for auto-detection and parsing
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new FinancialSummary
{
DocumentType = prediction.DocumentType?.Value, // "INVOICE" or "RECEIPT"
InvoiceNumber = prediction.InvoiceNumber?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value,
SupplierName = prediction.SupplierName?.Value,
CustomerName = prediction.CustomerName?.Value
};
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;
public class MindeeFinancialRouter
{
private readonly MindeeClient _client;
public MindeeFinancialRouter(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
{
// All financial documents uploaded for auto-detection and parsing
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new FinancialSummary
{
DocumentType = prediction.DocumentType?.Value, // "INVOICE" or "RECEIPT"
InvoiceNumber = prediction.InvoiceNumber?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value,
SupplierName = prediction.SupplierName?.Value,
CustomerName = prediction.CustomerName?.Value
};
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.FinancialDocument
Imports System.Threading.Tasks
Public Class MindeeFinancialRouter
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessFinancialDocumentAsync(filePath As String) As Task(Of FinancialSummary)
' All financial documents uploaded for auto-detection and parsing
Dim inputSource = New LocalInputSource(filePath)
Dim response = Await _client.ParseAsync(Of FinancialDocumentV1)(inputSource)
Dim prediction = response.Document.Inference.Prediction
Return New FinancialSummary With {
.DocumentType = prediction.DocumentType?.Value, ' "INVOICE" or "RECEIPT"
.InvoiceNumber = prediction.InvoiceNumber?.Value,
.Date = prediction.Date?.Value?.ToString(),
.TotalAmount = prediction.TotalAmount?.Value,
.SupplierName = prediction.SupplierName?.Value,
.CustomerName = prediction.CustomerName?.Value
}
End Function
End Class
IronOCR Ansatz:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrFinancialRouter
{
private readonly IronTesseract _ocr = new IronTesseract();
public FinancialSummary ProcessFinancialDocument(string filePath)
{
var result = _ocr.Read(filePath);
var text = result.Text;
// Detect document type using structural keywords from word data
var isInvoice = DetectInvoice(result);
if (isInvoice)
{
return new FinancialSummary
{
DocumentType = "INVOICE",
InvoiceNumber = Regex.Match(text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value,
Date = Regex.Match(text,
@"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups[1].Value,
TotalAmount = ParseAmount(text,
@"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result),
CustomerName = Regex.Match(text,
@"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups[1].Value.Trim()
};
}
else
{
// Receipt structure: merchant at top, no "Bill To" section
return new FinancialSummary
{
DocumentType = "RECEIPT",
Date = Regex.Match(text,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
TotalAmount = ParseAmount(text,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result)
};
}
}
private bool DetectInvoice(OcrResult result)
{
// Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
var text = result.Text;
var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
return invoiceSignals.Any(s =>
text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
}
private string ExtractTopLine(OcrResult result)
{
return result.Pages
.FirstOrDefault()
?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
}
private decimal? ParseAmount(string text, string pattern)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success &&
decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
return val;
return null;
}
}
public class FinancialSummary
{
public string DocumentType { get; set; }
public string InvoiceNumber { get; set; }
public string Date { get; set; }
public decimal? TotalAmount { get; set; }
public string SupplierName { get; set; }
public string CustomerName { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrFinancialRouter
{
private readonly IronTesseract _ocr = new IronTesseract();
public FinancialSummary ProcessFinancialDocument(string filePath)
{
var result = _ocr.Read(filePath);
var text = result.Text;
// Detect document type using structural keywords from word data
var isInvoice = DetectInvoice(result);
if (isInvoice)
{
return new FinancialSummary
{
DocumentType = "INVOICE",
InvoiceNumber = Regex.Match(text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value,
Date = Regex.Match(text,
@"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups[1].Value,
TotalAmount = ParseAmount(text,
@"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result),
CustomerName = Regex.Match(text,
@"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups[1].Value.Trim()
};
}
else
{
// Receipt structure: merchant at top, no "Bill To" section
return new FinancialSummary
{
DocumentType = "RECEIPT",
Date = Regex.Match(text,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
TotalAmount = ParseAmount(text,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result)
};
}
}
private bool DetectInvoice(OcrResult result)
{
// Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
var text = result.Text;
var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
return invoiceSignals.Any(s =>
text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
}
private string ExtractTopLine(OcrResult result)
{
return result.Pages
.FirstOrDefault()
?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
}
private decimal? ParseAmount(string text, string pattern)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success &&
decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
return val;
return null;
}
}
public class FinancialSummary
{
public string DocumentType { get; set; }
public string InvoiceNumber { get; set; }
public string Date { get; set; }
public decimal? TotalAmount { get; set; }
public string SupplierName { get; set; }
public string CustomerName { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrFinancialRouter
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessFinancialDocument(filePath As String) As FinancialSummary
Dim result = _ocr.Read(filePath)
Dim text = result.Text
' Detect document type using structural keywords from word data
Dim isInvoice = DetectInvoice(result)
If isInvoice Then
Return New FinancialSummary With {
.DocumentType = "INVOICE",
.InvoiceNumber = Regex.Match(text,
"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value,
.Date = Regex.Match(text,
"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups(1).Value,
.TotalAmount = ParseAmount(text,
"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
.SupplierName = ExtractTopLine(result),
.CustomerName = Regex.Match(text,
"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups(1).Value.Trim()
}
Else
' Receipt structure: merchant at top, no "Bill To" section
Return New FinancialSummary With {
.DocumentType = "RECEIPT",
.Date = Regex.Match(text,
"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
.TotalAmount = ParseAmount(text,
"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
.SupplierName = ExtractTopLine(result)
}
End If
End Function
Private Function DetectInvoice(result As OcrResult) As Boolean
' Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
Dim text = result.Text
Dim invoiceSignals = {"Invoice", "Bill To", "Due Date", "Purchase Order"}
Return invoiceSignals.Any(Function(s) text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)
End Function
Private Function ExtractTopLine(result As OcrResult) As String
Return result.Pages _
.FirstOrDefault() _
?.Lines _
.FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
?.Text.Trim()
End Function
Private Function ParseAmount(text As String, pattern As String) As Decimal?
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
If match.Success AndAlso
Decimal.TryParse(match.Groups(1).Value.Replace(",", ""), val) Then
Return val
End If
Return Nothing
End Function
End Class
Public Class FinancialSummary
Public Property DocumentType As String
Public Property InvoiceNumber As String
Public Property Date As String
Public Property TotalAmount As Decimal?
Public Property SupplierName As String
Public Property CustomerName As String
End Class
Die Dokumenttyperkennungslogik ersetzt Mindee's serverseitiges DocumentType Feld durch einen einfachen Schlüsselwortscan gegen result.Text. Bei der überwiegenden Mehrheit der Finanzdokumente kennzeichnet das Vorhandensein von "Rechnung" oder "Rechnungsempfänger" Rechnungen eindeutig; das Fehlen identifiziert Quittungen. Wortpositionsdaten in result.Pages ermöglichen eine ausgefeiltere, layoutbasierte Erkennung, wenn Ihr Dokumentsatz Randfälle umfasst. Der Ergebnisse lesen Leitfaden erklärt das vollständige Objektmodell einschließlich Words, Lines und Paragraphs mit ihren Begrenzungskoordinaten.
MindeeAPI zu IronOCR Mapping-Referenz
| Mindee | IronOCR-Äquivalent |
|---|---|
new MindeeClient(apiKey) |
new IronTesseract() — kein API-Schlüssel erforderlich |
new LocalInputSource(filePath) |
new OcrInput() + input.LoadImage(filePath) oder ocr.Read(filePath) |
_client.ParseAsync<InvoiceV4>(input) |
_ocr.Read(filePath) + Regex-Extraktion auf result.Text |
_client.ParseAsync<ReceiptV5>(input) |
_ocr.Read(filePath) + Quittungsextraktionsmuster |
_client.ParseAsync<PassportV1>(input) |
_ocr.Read(filePath) + MRZ-Zonenauswertung über CropRectangle |
_client.ParseAsync<FinancialDocumentV1>(input) |
_ocr.Read(filePath) + Dokumenttyperkennung auf result.Text |
response.Document.Inference.Prediction |
result.Text, result.Pages, result.Lines, result.Words |
prediction.InvoiceNumber?.Value |
Regex.Match(result.Text, @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)") |
prediction.SupplierName?.Value |
Top-Zeile der ersten Seite über result.Pages[0].Lines.First() |
prediction.TotalAmount?.Value |
Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})") |
prediction.LineItems |
result.Lines gefiltert nach Zeilenartikelregexmustern |
prediction.SupplierPaymentDetails[].Iban |
Regex.Match(result.Text, @"IBAN\s*:?\s*([\w\s]+)") |
prediction.InvoiceDate?.Value |
Regex.Match(result.Text, @"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{4})") |
field.Confidence |
result.Confidence (Dokumentenebene) oder word.Confidence (pro Wort) |
catch (MindeeException ex) |
Kein Äquivalent – keine Netzwerkfehler bei der Erkennungszeit |
await Task.Delay(100) (Ratenbegrenzung) |
Nicht erforderlich – keine Ratenbegrenzungen |
input.LoadPdf(path) |
ocrInput.LoadPdf(path) — gleiche Operation, vollständig lokal |
| API-Schlüssel in der Konfiguration | Lizenzschlüssel über IronOcr.License.LicenseKey = "..." — keine Rotation erforderlich |
Gängige Migrationsprobleme und Lösungen
Problem 1: Die Koordinaten des Zuschneiderechtecks stimmen nicht mit dem Dokumentlayout überein
Mindee: Räumliche Feldkoordinaten werden serverseitig verarbeitet. Das Mindee-Modell passt sich ohne Koordinatenkonfiguration durch den Entwickler an unterschiedliche Rechnungslayouts an.
Lösung: Messen Sie die Zonenkoordinaten anhand einer repräsentativen Stichprobe von Dokumenten Ihres Lieferanten. Öffnen Sie eine Beispielrechnung in einem beliebigen Bildeditor, lesen Sie die Pixeldimensionen ab und definieren Sie CropRectangle(x, y, width, height) entsprechend. Bei Rechnungen mit variablem Layout lesen Sie bitte zuerst das gesamte Dokument und verwenden Sie die Wortpositionsdaten, um die Zonen dynamisch zu lokalisieren:
var result = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;
// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));
if (totalLabel != null)
{
var valueRegion = new CropRectangle(
totalLabel.X + totalLabel.Width + 5,
totalLabel.Y - 5,
200,
totalLabel.Height + 10);
using var valueInput = new OcrInput();
valueInput.LoadImage("invoice.jpg", valueRegion);
var valueResult = _ocr.Read(valueInput);
Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
var result = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;
// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));
if (totalLabel != null)
{
var valueRegion = new CropRectangle(
totalLabel.X + totalLabel.Width + 5,
totalLabel.Y - 5,
200,
totalLabel.Height + 10);
using var valueInput = new OcrInput();
valueInput.LoadImage("invoice.jpg", valueRegion);
var valueResult = _ocr.Read(valueInput);
Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
Imports System
Imports System.Linq
Dim result = _ocr.Read("invoice.jpg")
Dim allWords = result.Pages.First().Words
' Find the word "Total" and read the region 50px to its right
Dim totalLabel = allWords.FirstOrDefault(Function(w) w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase))
If totalLabel IsNot Nothing Then
Dim valueRegion = New CropRectangle(totalLabel.X + totalLabel.Width + 5, totalLabel.Y - 5, 200, totalLabel.Height + 10)
Using valueInput As New OcrInput()
valueInput.LoadImage("invoice.jpg", valueRegion)
Dim valueResult = _ocr.Read(valueInput)
Console.WriteLine($"Total: {valueResult.Text.Trim()}")
End Using
End If
Das regionenbasierte OCR-Beispiel zeigt dieses dynamische Zonenmuster in einem vollständig funktionierenden Beispiel.
Problem 2: Asynchrone Aufrufseiten funktionieren nicht mehr nach dem Entfernen von Mindee
Mindee: Die gesamte Mindee-API-Oberfläche ist asynchron, da es sich um Netzwerk-I/O handelt. Controller und Servicemethoden, die auf await _client.ParseAsync<t>() basieren, sind asynchron im gesamten Aufrufkette.
Lösung: IronOCR's Read() Methode ist synchron. Bestehende asynchrone Aufrufstellen arbeiten sofort, indem der synchrone Aufruf in Task.Run umhüllt wird:
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
return await Task.Run(() =>
{
var result = new IronTesseract().Read(filePath);
return Regex.Match(result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
});
}
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
return await Task.Run(() =>
{
var result = new IronTesseract().Read(filePath);
return Regex.Match(result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
});
}
Imports System.Text.RegularExpressions
Imports System.Threading.Tasks
' Existing async signature preserved for callers
Public Async Function ExtractInvoiceNumberAsync(filePath As String) As Task(Of String)
Return Await Task.Run(Function()
Dim result = New IronTesseract().Read(filePath)
Return Regex.Match(result.Text,
"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value
End Function)
End Function
Für hochdurchsatzstarke ASP.NET-Szenarien lesen Sie den asynchronen OCR-Leitfaden bevor Sie sich zwischen Task.Run Umhüllungen und dem nativen asynchronen Pfad entscheiden.
Problem 3: Die Extraktionsgenauigkeit sinkt bei Scans mit geringer Qualität
Mindee: Die Vorverarbeitung bei Mindeeerfolgt intern und automatisch. Probleme mit der Scanqualität verringern die Zuverlässigkeit der Feldmessungen, und es besteht keine Möglichkeit zum Eingreifen der Entwickler vor der erneuten Einreichung.
Lösung: Wenden Sie die Vorverarbeitungspipeline von IronOCR vor der Erkennung an. Die Kombination von Deskew() und DeNoise() stellt die meiste Genauigkeit bei typischen Flachbettscan-Defekten wieder her, die in Kreditoren-Workflows auftreten:
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast(); // for faded or low-contrast thermal paper receipts
input.Binarize(); // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast(); // for faded or low-contrast thermal paper receipts
input.Binarize(); // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
Imports IronOcr
Using input As New OcrInput()
input.LoadImage("faded-invoice.jpg")
input.Deskew()
input.DeNoise()
input.Contrast() ' for faded or low-contrast thermal paper receipts
input.Binarize() ' convert to clean black-and-white before recognition
Dim result = New IronTesseract().Read(input)
End Using
Bei stark verschlechterten Scans wendet input.DeepCleanBackgroundNoise() einen stärkeren Hintergrundentfernungsdurchgang an. Der Leitfaden zur Bildqualitätskorrektur dokumentiert Schwellenwerte, und der Filterassistent hilft dabei, diejenigen Filter zu identifizieren, die die Genauigkeit bei einem gegebenen Dokumentenmuster verbessern.
Problem 4: API-Schlüssel und Netzwerk-Ausgangskonfiguration entfernt
Mindee: Der API-Schlüssel, der in den App-Einstellungen gespeichert ist, die Netzausgangsregeln, die ausgehendes HTTPS zu api.mindee.net zulassen, und die Wiederholungslogik um HttpRequestException sind alle erforderliche Infrastruktur.
Lösung: Alle drei werden zusammen entfernt. Der Mindee-API-Schlüsseleintrag wurde aus der Konfiguration gelöscht. Die Netzwerkausgangsregel wurde widerrufen. Der try/catch (HttpRequestException) Block wird entfernt. Die einzige verbleibende Zugangsberechtigung ist der IronOCR Lizenzschlüssel, der einmalig beim Start festgelegt wird:
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
//Neinrotation schedule, no secure storage beyond standard config secret management
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
//Neinrotation schedule, no secure storage beyond standard config secret management
' appsettings.json: remove "Mindee:ApiKey"
' Firewall: revoke egress rule for api.mindee.net
' Startup.vb or Program.vb:
IronOcr.License.LicenseKey = Configuration("IronOcr:LicenseKey")
' Neinrotation schedule, no secure storage beyond standard config secret management
Ausgabe 5: Abrechnung der Seitenanzahl bei mehrseitigen PDFs
Mindee: Eine 12-seitige Lieferantenabrechnung, die auf Mindeehochgeladen wird, verbraucht 12 Seiten des monatlichen Kontingents. Bei den Tarifen für die Überschreitung des monatlichen Kontingents (Pro-Tarif) fallen für dieses einzelne Dokument zusätzliche Kosten in Höhe von 1,20 US-Dollar an, wenn Sie sich dem Limit nähern.
Lösung:IronOCR verarbeitet mehrseitige PDFs ohne Kosten pro Seite. Laden Sie das gesamte Dokument und verarbeiten Sie die Seiten nacheinander:
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf"); // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf"); // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
Imports IronOcr
Using input As New OcrInput()
input.LoadPdf("vendor-statement.pdf") ' 12 pages — no billing unit increment
Dim result = New IronTesseract().Read(input)
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized")
Next
End Using
Problem 6: Die Abhängigkeit von FinancialDocumentV1 kann nicht durch ein einzelnes Muster ersetzt werden.
Mindee: FinancialDocumentV1 erkennt den Dokumenttyp automatisch und extrahiert Felder, ohne dass der aufrufende Code beim Dokumenttyp abzweigt.
Lösung: Entwicklung eines leichtgewichtigen Detektors, der auf der Präsenz von Schlüsselwörtern basiert. Finanzdokumente lassen sich klar in Rechnungen (enthalten "Rechnung", "Rechnungsempfänger" oder "Fälligkeitsdatum") und Quittungen (enthalten "Quittung", "Vielen Dank" oder weisen keine Rechnungskennzeichnungen auf) unterteilen. Zwei Extraktionsmethoden Plus ein Drei-Zeilen-Detektor ersetzen den Mindee-API-Aufruf, ohne die Genauigkeit bei wohlgeformten Dokumenten zu beeinträchtigen:
var result = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
? ExtractInvoiceFields(result)
: ExtractReceiptFields(result);
var result = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
? ExtractInvoiceFields(result)
: ExtractReceiptFields(result);
Dim result = _ocr.Read(filePath)
Dim summary = If(result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0, ExtractInvoiceFields(result), ExtractReceiptFields(result))
Checkliste für die Migration von Mindee
Vor der Migration
Überprüfen Sie die gesamte Mindee-Nutzung im gesamten Quellcode:
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
Ergebnisse der Inventur:
- Zählen Sie einzigartige Aufrufstellen mit
ParseAsync<t> - Notieren Sie sich, welche Dokumenttypen verwendet werden (
InvoiceV4,ReceiptV5,PassportV1,FinancialDocumentV1) - Identifizieren Sie alle asynchronen Methodenketten, die von Mindee-Aufrufen ausgehen.
- API-Schlüsselreferenzen in Konfigurationsdateien und Geheimnisspeichern suchen
- Wiederholungslogik für Mindee-Netzwerkaufrufe identifizieren
- Identifizieren Sie Netzwerkausgangsregeln, die ausgehenden Datenverkehr zu Mindee-Endpunkten zulassen.
Code-Migration
- Entfernen Sie das
MindeeNuGet-Paket aus der Projektdatei - Führen Sie
dotnet add package IronOcraus, um IronOCR zu installieren - Fügen Sie
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"zum Anwendungsstart hinzu - Entfernen Sie alle
using Mindee,using Mindee.Inputundusing Mindee.Product.*Direktiven - Ersetzen Sie
MindeeClientKonstruktor-Injektion durchIronTesseractInstanziierung oder DI-Registrierung - Ersetzen Sie jeden
ParseAsync<InvoiceV4>Aufruf durch_ocr.Read(filePath)plus Rechnungsextraktionsmethoden - Ersetzen Sie jeden
ParseAsync<ReceiptV5>Aufruf durch_ocr.Read(filePath)plus Quittungsextraktionsmethoden - Ersetzen Sie jeden
ParseAsync<PassportV1>Aufruf durch zonenbasierte OCR mitCropRectangleauf dem MRZ-Bereich - Ersetzen Sie jeden
ParseAsync<FinancialDocumentV1>Aufruf durch den Dokumenttyp-Detektor + Weiterleitungsmuster - Entfernen Sie alle
await Task.Delay(...)Ratenbegrenzungs-Leitungen - Entfernen Sie alle
catch (MindeeException)undcatch (HttpRequestException)Blöcke aus OCR-Pfaden - Fügen Sie
OcrInputVorverarbeitungsaufrufe (Deskew,DeNoise,Contrast) für gescannte Dokumentenpfade hinzu - Fügen Sie
result.SaveAsSearchablePdf(archivePath)zu allen Dokumentarchivierungspfaden hinzu - Entfernen Sie den Mindee-API-Schlüssel aus allen Konfigurationsdateien und Geheimnisspeichern.
- Integrationstests so aktualisieren, dass sie synchron und ohne Netzwerk-Mocking ausgeführt werden.
Nach der Migration
- Überprüfen Sie die Genauigkeit der Datenextraktion anhand eines Stichprobensatzes von 20-30 Dokumenten, der jeden Dokumententyp abdeckt.
- Bestätigen Sie
result.ConfidenceWerte über 80 bei repräsentativen sauberen Scans; preprocessing anwenden, falls unten angegeben - Testen Sie die Vorverarbeitungspipeline (
Deskew,DeNoise) an geneigten und verschlechterten Scanbeispielen - Überprüfen Sie, ob mehrseitige PDFs korrektes
result.Pages.Countund seitenweise Inhalte erzeugen - Bestätigen Sie, dass
result.SaveAsSearchablePdf()Ausgabe in Adobe Acrobat und System-PDF-Viewer durchsuchbar ist - Testen Sie alle zonenbasierte Extraktion (
CropRectangle) gegen Dokumentlayout-Variationen in Ihrem Lieferantenset - Führen Sie die Stapelverarbeitung ohne
Task.DelayAufrufe aus und vergewissern Sie sich, dass keine Threading-Probleme auftreten - Prüfen Sie, ob die Anwendung ohne ausgehenden Internetzugang korrekt startet und ausgeführt wird.
- Verifizieren Sie, dass die Lizenzschlüsselinitialisierung vor dem ersten
_ocr.Read()Aufruf in jedem Einstiegspunkt läuft - Prüfen Sie, ob in der Konfiguration, den Umgebungsvariablen oder den Geheimnissen noch Verweise auf Mindee-API-Schlüssel vorhanden sind.
Wichtigste Vorteile der Migration zu IronOCR
Vollständige Datensouveränität über Finanzdokumente. Nach der Migration bleiben Lieferanten-IBANs, Bankleitzahlen, Kunden-EINs und detaillierte Posteninformationen dauerhaft in Ihrer Infrastruktur gespeichert. Der Geltungsbereich der Compliance erstreckt sich ausschließlich auf Ihre Organisation. Prozessorvereinbarungen mit Drittanbietern von KI-Systemen werden aus Ihrem Prüfprotokoll entfernt. Teams, die den GLBA-, CMMC-, FedRAMP- oder Datenresidenz-Anforderungen unterliegen, können Finanzdokumente ohne die für die externe Cloud-Übertragung erforderliche Compliance-Prüfung verarbeiten.
Planbare Kosten unabhängig vom Volumen. Die IronOCR Lite Lizenz bei $999 deckt einen Entwickler mit unbegrenzter Dokumentverarbeitung ab. Die Steigerung von 500 Rechnungen pro Monat auf 50.000 Rechnungen pro Monat verursacht keine zusätzlichen Kosten. Verarbeitungsspitzen zum Jahresende, Stapelkorrekturläufe und Entwicklungstestzyklen erhöhen keinen Abrechnungszähler. Die Gesamtbetriebskosten über drei Jahre für ein Team, das 5.000 Seiten pro Monat verarbeitet, sinken von etwa 17.964 US-Dollar (Mindee Pro) auf 1.499 bis 2.999 US-Dollar (Dauerlizenz für IronOCR). Die vollständigen Informationen zu den Preisstufen finden Sie auf der IronOCR Produktseite .
Die Extraktionslogik gehört Ihnen dauerhaft.IronOCR Extraktionsmuster sind einfacher C#-Code in Ihrem Repository. Sie sind versionskontrolliert, überprüfbar, testbar und unabhängig von den Release-Zeitplänen oder API-Versionsentscheidungen externer Anbieter einsetzbar. Wenn MindeeInvoiceV5 veröffentlicht und InvoiceV4 außer Betrieb nimmt, ist das eine Migrationsfrist, die vom Anbieter auferlegt wird. Wenn Sie Extraktionsmuster pflegen, ist die Verbesserung ein git commit.
Vorverarbeitungskontrolle für Scanqualität in der Praxis. Die Kreditorenbuchhaltung erhält Rechnungen von Dutzenden oder Hunderten von Lieferanten, die jeweils mit unterschiedlichen Geräten und Einstellungen gescannt werden. IronOCR's Vorverarbeitungspipeline — Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — geht auf die spezifischen Defekte in jeder Scankategorie ein. Ein Thermofoto eines Kassenbons weist andere Mängel auf als eine mit einem Flachbettscanner eingescannte, mehrseitige Rechnung; Sie wenden für jeden Pfad die entsprechenden Filter an. Die Vorverarbeitung von Mindeeist unsichtbar und nicht konfigurierbar. Den vollständigen Filterkatalog finden Sie auf der Seite zu den Vorverarbeitungsfunktionen .
Durchsuchbares PDF-Archivieren in einem Schritt. Jede mit IronOCR verarbeitete Rechnung kann als durchsuchbares PDF mit result.SaveAsSearchablePdf() archiviert werden. Die erkannte Textebene wird in die Ausgabedatei eingebettet, sodass jedes Wort in Dokumentenmanagementsystemen, SharePoint-Bibliotheken und Enterprise -Content-Repositories per Volltextsuche durchsucht werden kann. Mindeebietet überhaupt keine PDF-Ausgabe an; Für die durchsuchbare Archivierung waren bisher eine separate Bibliothek, ein separater Verarbeitungsschritt und zusätzlicher Wartungsaufwand erforderlich. Nach der Migration kollabieren Extraktion und Archivierung zu einem _ocr.Read(input) Aufruf.
Bereitstellung überall ohne Architekturänderungen.IronOCR lässt sich unter Windows, Linux, macOS, Docker, Azure App Service, AWS Lambda und Google Cloud Run mit demselben NuGet Paket und derselben Initialisierung bereitstellen. Luftabgeschottete Umgebungen, Produktionsanlagen und gesicherte Regierungseinrichtungen funktionieren alle ohne Modifikation. Der Docker-Bereitstellungsleitfaden , der Azure-Leitfaden und der AWS-Leitfaden beschreiben die umgebungsspezifische Konfiguration für jede Plattform.
Häufig gestellte Fragen
Warum sollte ich von Mindee OCR API zu IronOCR migrieren?
Zu den häufigsten Gründen gehören die Beseitigung der Komplexität der COM-Interoperabilität, die Ablösung der dateibasierten Lizenzverwaltung, die Vermeidung der seitenweisen Abrechnung, die Ermöglichung der Docker-/Container-Bereitstellung und die Einführung eines NuGet-nativen Workflows, der sich in die Standard-.NET-Werkzeuge integriert.
Was sind die wichtigsten Code-Änderungen bei der Migration von Mindee OCR API zu IronOCR?
Ersetzen Sie Mindee-Initialisierungssequenzen durch IronTesseract-Instanziierung, entfernen Sie das COM-Lebenszyklusmanagement (explizite Create/Load/Close-Muster) und aktualisieren Sie die Namen der Ergebniseigenschaften. Das Ergebnis sind deutlich weniger Boilerplate-Zeilen.
Wie installiere ich IronOCR, um die Migration zu beginnen?
Führen Sie 'Install-Package IronOcr' in der Paketmanager-Konsole oder 'dotnet add package IronOcr' in der CLI aus. Sprachpakete sind separate Pakete: 'dotnet add package IronOcr.Languages.French' für Französisch, zum Beispiel.
Erreicht IronOCR die OCR-Genauigkeit von Mindee OCR API für Standardgeschäftsdokumente?
IronOCR erzielt eine hohe Genauigkeit bei Standardgeschäftsinhalten wie Rechnungen, Verträgen, Quittungen und getippten Formularen. Bildvorverarbeitungsfilter (Schräglagenkorrektur, Rauschunterdrückung, Kontrastverbesserung) verbessern die Erkennungsgenauigkeit bei verschlechterten Eingaben weiter.
Wie geht IronOCR mit den Sprachdaten um, die die Mindee OCR API separat installiert?
Die Sprachdaten in IronOCR werden als NuGet-Pakete verteilt. mit 'dotnet add package IronOcr.Languages.German' wird die deutsche Unterstützung installiert. Es sind keine manuellen Dateiplatzierungen oder Verzeichnispfade erforderlich.
Erfordert die Migration von Mindee OCR API zu IronOCR Änderungen an der Bereitstellungsinfrastruktur?
IronOCR erfordert weniger Änderungen an der Infrastruktur als Mindee OCR API. Es gibt keine SDK-Binärpfade, keine Platzierung von Lizenzdateien oder Lizenzserverkonfigurationen. Das NuGet-Paket enthält die komplette OCR-Engine, und der Lizenzschlüssel ist ein String, der im Anwendungscode festgelegt wird.
Wie konfiguriere ich die IronOCR-Lizenzierung nach der Migration?
Weisen Sie IronOcr.License.LicenseKey = "YOUR-KEY" im Startup-Code der Anwendung zu. In Docker oder Kubernetes speichern Sie den Schlüssel als Umgebungsvariable und lesen ihn beim Starten aus. Verwenden Sie License.IsValidLicense zur Validierung, bevor Sie den Datenverkehr akzeptieren.
Kann IronOCR PDFs auf die gleiche Weise verarbeiten wie Mindee?
Ja, IronOCR liest sowohl native als auch gescannte PDFs. Instanziieren Sie IronTesseract, rufen Sie ocr.Read(input) auf, wobei input ein PDF-Pfad oder OcrPdfInput ist, und iterieren Sie die OcrResult-Seiten. Es ist keine separate PDF-Rendering-Pipeline erforderlich.
Wie handhabt IronOCR das Threading bei der Verarbeitung großer Datenmengen?
IronTesseract kann sicher pro Thread instanziiert werden. Sie können eine Instanz pro Thread in einem Parallel.ForEach- oder Task-Pool aufsetzen, OCR gleichzeitig ausführen und jede Instanz nach Abschluss entsorgen. Es ist kein globaler Zustand oder Sperren erforderlich.
Welche Ausgabeformate unterstützt IronOCR nach der Textextraktion?
IronOCR liefert strukturierte Ergebnisse mit Text, Wortkoordinaten, Konfidenzwerten und Seitenstruktur. Zu den Exportoptionen gehören reiner Text, durchsuchbare PDF-Dateien und strukturierte Ergebnisobjekte für die nachgeschaltete Verarbeitung.
Ist die Preisgestaltung von IronOCR bei der Skalierung von Arbeitslasten berechenbarer als die von Mindee OCR API?
IronOCR verwendet eine pauschale, unbefristete Lizenzierung ohne Seiten- oder Volumengebühren. Egal, ob Sie 10.000 oder 10 Millionen Seiten verarbeiten, die Lizenzkosten bleiben konstant. Optionen für Volumen- und Teamlizenzen finden Sie auf der IronOCR-Preisseite.
Was passiert mit meinen bestehenden Tests nach der Migration von Mindee OCR API zu IronOCR?
Tests, die extrahierte Textinhalte überprüfen, sollten auch nach der Migration bestehen. Tests, die API-Aufrufmuster oder den Lebenszyklus von COM-Objekten validieren, müssen aktualisiert werden, um das einfachere Initialisierungs- und Ergebnismodell von IronOCR widerzuspiegeln.

