Jak korzystać z Async i wielowątkowości w C#
Przejście zMindeena IronOCR przenosi przetwarzanie dokumentów z zewnętrznych serwerów w chmurze do własnej infrastruktury — eliminując rozliczenia za stronę, transmisję danych przez strony trzecie oraz dostępność sieci jako zależność środowiska uruchomieniowego. Poniższe sekcje obejmują wymianę pakietów, aktualizacje przestrzeni nazw oraz cztery praktyczne scenariusze kodu "przed i po": wyodrębnianie strefy faktury, przetwarzanie wielostronicowych paragonów w formacie PDF, wstępne przetwarzanie dokumentów finansowych oraz archiwizacja plików PDF z możliwością wyszukiwania.
Dlaczego warto przejść z Mindee
Mindee elegancko rozwiązuje rzeczywisty problem: prześlij dokument, otrzymaj ustrukturyzowane pola JSON. W przypadku zespołów, w których przesyłanie dokumentów finansowych do zewnętrznego API jest dopuszczalne, a ich ilość pozostaje umiarkowana, zapewnia to szybkie wyniki. Wyzwalacz migracji pojawia się zazwyczaj, gdy zmienia się jeden z kilku warunków.
Dane finansowe przekraczają granicę zgodności. OprogramowanieMindeeParseAsync<InvoiceV4> przesyła cały dokument na zewnętrzne serwery. Kody IBAN, numery routingu, numery EIN dostawców oraz szczegółowe pozycje przesyłane są przez publiczny internet i przetwarzane na sprzęcie pozostającym poza kontrolą użytkownika. Certyfikat SOC 2 typu II potwierdza, że firmaMindeeprzestrzega zasad bezpieczeństwa; nie przechowuje dokumentów w obrębie strefy bezpieczeństwa. Gdy przegląd zgodności, nowa umowa z klientem lub przepisy dotyczące lokalizacji danych wyznaczają granicę w zakresie przetwarzania w chmurze przez strony trzecie, architekturaMindeestaje się nieakceptowalna, niezależnie od dokładności ekstrakcji.
Koszty za stronę są niekorzystne. Plan Starter zapewnia 1000 stron miesięcznie za 49 USD/miesiąc. Plan Pro zapewnia 5000 stron miesięcznie za 499 USD/miesiąc. W przypadku wielostronicowych plików PDF liczy się każdą stronę. Liczba uruchomień kompilacji wlicza się do limitu. Zespół przetwarzający 4000 faktur miesięcznie płaci 499 USD miesięcznie — 5988 USD rocznie — przy czym kwota ta jest resetowana co roku i rośnie wraz ze wzrostem wolumenu. Przy 24 000 stron miesięcznie negocjujesz cennik Enterprise. Licencja wieczysta IronOCR w cenie 1499 USD (poziom Professional) obejmuje nieograniczoną liczbę dokumentów, a jej koszt zwraca się w porównaniu zMindeePro w mniej niż cztery miesiące.
Asynchroniczne odpytywanie rozprzestrzenia się w całym stosie. Każda operacjaMindeejest asynchroniczna, ponieważ każda operacja jest żądaniem sieciowym. Ten obowiązkowy łańcuch asynchroniczny rozprzestrzenia się kaskadowo w górę przez kontrolery, warstwy usług i klasy robocze. Gdy sieć jest niedostępna lub usługaMindeeulega awarii, kaskada powoduje niepowodzenie bez lokalnego rozwiązania awaryjnego.IronOCR przetwarza synchronizacyjnie — lokalne działanie CPU — i opakowuje w Task.Run, gdy wymagane są asynchroniczne interfejsy dla spójności architektonicznej.
Predefiniowany katalog API ma sztywny limit.Mindeeobejmuje InvoiceV4, ReceiptV5, PassportV1 i mały katalog innych typów dokumentów. Każdy typ dokumentu spoza tej listy wymaga niestandardowego szkolenia modelu w planie Enterprise: zbieranie próbek, etykietowanie, czas szkolenia i czas dostępu przed dostępnością API. Każdy nowy typ dokumentu, którego potrzebuje Twoja firma, wymaga nawiązania współpracy z innym dostawcą.IronOCR przetwarza każdy typ dokumentu za pomocą tego samego wywołania IronTesseract().Read(); Dodanie nowego typu dokumentu oznacza napisanie wzorców ekstrakcji, a nie negocjowanie umowy szkoleniowej.
Zarządzanie kluczami API zwiększa powierzchnię operacyjną. Dane uwierzytelniająceMindeemuszą być bezpiecznie przechowywane po stronie serwera, okresowo zmieniane i chronione przed ujawnieniem. Reguły ruchu wychodzącego w sieci muszą zezwalać na wychodzący ruch HTTPS do punktów końcowych Mindee. Logika ponawiania musi obsługiwać HttpRequestException i MindeeException z nieuniknionych awarii sieci. UsunięcieMindeeeliminuje całą tę powierzchnię operacyjną: brak poświadczeń, brak reguł wyjścia, brak opakowań ponownych prób wokół operacji wejścia/wyjścia sieciowego.
Środowiska odizolowane i środowiska o ograniczonym dostępie są wykluczone. Wykonawcy rządowi, podwykonawcy z sektóra obronnego, sieci opieki zdrowotnej z rygorystycznymi kontrolami ruchu wychodzącego do Internetu oraz systemy przemysłowe wdrożone w obiektach bez niezawodnego połączenia internetowego nie mogą korzystać zMindeezgodnie z przeznaczeniem.IronOCR działa identycznie w kontenerach Docker bez dostępu do sieci zewnętrznej, w Azure Functions z ograniczeniami ruchu wychodzącego oraz w Środowiskach całkowicie odizolowanych od sieci. W czasie wykonywania nie ma zależności od chmury. Szczegóły dotyczące poziomów wdrożenia można znaleźć na stronie licencyjnej IronOCR.
Podstawowy problem
Każdy dokument finansowy przetwarzany przezMindeeprzekracza granicę sieci, nad którą nie masz kontroli:
// 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 toMindeecloud
// 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 toMindeecloud
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 Mindee cloud
// 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: Porównanie funkcji
Poniższa tabela przedstawia różnice w architekturze i możliwościach, które mają wpływ na decyzje dotyczące migracji.
| Funkcja | Mindee | IronOCR |
|---|---|---|
| Miejsce przetwarzania | Serwery chmuroweMindee | Lokalna maszyna / Twoja infrastruktura |
| Wymagane połączenie z Internetem w czasie wykonywania | Zawsze | Nigdy |
| Transmisja danych | Pełny dokument przesłany zgodnie z prośbą | None |
| Koszt za dokument | Tak (za stronę, w oparciu o plan) | Nie (Licencja wieczysta) |
| Cena wywoławcza | 49 USD/miesiąc (1000 stron) | $999 jednorazowo (Lite) |
| Obsługa trybu offline / air-gapped | Nieobsługiwane | Pełne wsparcie |
| Wdrożenie lokalne | Niedostępne | Jedyna opcja |
| Analiza faktur | Predefiniowane strukturalne API (InvoiceV4) |
OCR + ekstrakcja oparta na regionach lub wyrażeniach regularnych |
| Analiza paragonów | Predefiniowane strukturalne API (ReceiptV5) |
OCR + wzorce ekstrakcji |
| Dowolne typy dokumentów | Nie jest obsługiwane bez niestandardowego szkolenia | Pełne wsparcie, dowolny dokument |
| Szkolenie w zakresie modeli niestandardowych | Plan Enterprise + czas realizacji | Nie jest wymagane |
| Wzorzec wywołania API | Wymagana obsługa asynchroniczna (operacje wejścia/wyjścia sieciowego) | Synchroniczne (opcjonalnie asynchroniczne) |
| Ograniczanie szybkości | Zależne od planu | None |
| Plik wejściowy PDF | Tak | Tak (język ojczysty, bez konwersji) |
| Przetwarzanie wielostronicowych plików PDF | Tak | Tak |
| Wynik w formacie PDF z możliwością wyszukiwania | Nie | Tak (result.SaveAsSearchablePdf()) |
| Wstępne przetwarzanie obrazów | Brak | Wyrównanie, usuwanie szumów, kontrast, binarizacja, wyostrzanie, skalowanie |
| Pobieranie danych na podstawie regionu | Współrzędne pola w odpowiedzi | CropRectangle na wejściu |
| Obsługa ponad 125 języków | Ograniczone | Tak (w pakiecie, bez zarządzania danymi) |
| Odczytywanie BarCode | Nie | Tak (równolegle z OCR) |
| Strukturalne współrzędne słów | Nie | Tak (strony, akapity, wiersze, WORDy) |
| Bezpieczeństwo wątków | N/A (operacje wejścia/wyjścia sieciowego na wywołanie) | Wbudowane |
| Wielopłatformowe | Chmura (niezależna od platformy) | Windows, Linux, macOS, Docker, Azure, AWS |
| Kompatybilność z platformą .NET | .NET Standard 2.0+ | .NET Framework 4.6.2+, .NET 5/6/7/8/9 |
| Wsparcie komercyjne | Tak | Tak |
Szybki start: Migracja zMindeedo IronOCR
Krok 1: Zastąp pakiet NuGet
Usuń pakiet Mindee:
dotnet remove package Mindee
dotnet remove package Mindee
Zainstaluj IronOCR z NuGet:
dotnet add package IronOcr
Krok 2: Aktualizacja przestrzeni nazw
Zastąp blok przestrzeni nazwMindeeprzestrzenią nazw IronOCR:
// 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
Krok 3: Inicjalizacja licencji
Dodaj klucz licencyjny podczas uruchamiania aplikacji (przed pierwszym wywołaniem funkcji OCR):
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Przykłady migracji kodu
Wyodrębnianie pól faktury przy użyciu stref dokumentu
Mindee zwraca wstępnie przeanalizowane pola w znanych lokalizacjach, ponieważ jej model po stronie serwera wie, gdzie zazwyczaj pojawiają się numery faktur, daty i sumy. Odpowiednik IronOCR wykorzystuje CropRectangle do odczytu określonych stref dokumentu, odpowiadając za świadomość przestrzenną ekstrakcjiMindeebez przesyłania dokumentu.
Podejście Mindee:
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 toMindee— 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 toMindee— 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
Podejście IronOCR:
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
OCR oparta na strefach odpowiada zamierzeniom wykrywania pól przestrzennych Mindee: odczytuje określone obszary dokumentu zamiast stosować wyrażenia regularne do całej strony. CropRectangle(x, y, width, height) przyjmuje współrzędne pikseli, więc dostrojenie wymaga pomiaru na podstawie reprezentatywnej próbki faktury. Przewodnik o OCR opartego na regionach pokrywa pomiar współrzędnych i strategie nakładania się stref. Dla faktur z różnymi układami, połączenie ekstrakcji strefy z pełnostronicowym wyrażeniem regularnym na result.Text zapewnia najbardziej wiarygodne wyniki.
Przetwarzanie wielostronicowych raportów wydatków w formacie PDF
Mindee akceptuje pliki PDF i przetwarza każdą stronę jako odrębną jednostkę dokumentu, zwracając uporządkowany wynik dla każdej strony.IronOCR czyta natywnie wielostronicowe PDF-y za pomocą OcrInput.LoadPdf(), przetwarzając wszystkie strony jednym wywołaniem i zwracając result.Pages z zawartością na stronę i współrzędnymi słów.
Podejście Mindee:
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
Podejście IronOCR:
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
Przetworzenie 10-stronicowego pliku PDF z raportem wydatków za pomocąMindeegeneruje 10 wywołań API i 10 rozliczonych stron.IronOCR przetwarza ten sam plik w ramach jednego wywołania lokalnego, bez kosztów za stronę i bez konieczności przesyłania danych przez sieć dla każdej strony. Kolekcja result.Pages dostarcza tekst strony, ramki ograniczające dla poziomu słowa i pozycje linii dla ekstrakcji uwzględniającej układ. Zapoznaj się z przewodnikiem dotyczącym plików PDF, aby dowiedzieć się, jak ładować pliki PDF chronione hasłem i wybierać zakres stron, oraz z przewodnikiem dotyczącym wyników odczytu strukturalnego, aby dowiedzieć się, jak pracować ze współrzędnymi słów.
Proces przetwarzania wstępnego zeskanowanych faktur
Przetwarzanie w chmurzeMindeestosuje własną normalizację obrazów przed uruchomieniem ekstrakcji pól. Jakość tej normalizacji jest niejasna — nie masz nad nią kontroli i nie wiesz, jakie przetwarzanie wstępne zostało przeprowadzone. Gdy zeskanowana faktura jest przekrzywiona, ma cienie lub niski kontrast,Mindeezwraca niższe wyniki pewności bez mechanizmu umożliwiającego poprawę skanu przed przesłaniem.IronOCR wyraźnie eksponuje proces przetwarzania wstępnego, umożliwiając zastosowanie dokładnie tych poprawek, których wymaga dany dokument, przed uruchomieniem rozpoznawania.
Podejście Mindee:
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
Podejście IronOCR:
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
Schemat dwuetapowy — najpierw szybki odczyt, a następnie przetwarzanie wstępne w przypadku niskiego poziomu pewności — pozwala uniknąć obciążenia związanego z pełnym przetwarzaniem wstępnym w przypadku czystych skanów. Dla scenariuszy jakości skanów najczęstszych w działach rozliczeń (lekko przekrzywione skany płaskie, artefakty faksowe, kopiowane faktury), Deskew() i DeNoise() razem przywracają większość dokładności rozpoznawania. Przewodnik po korekcji jakości obrazu dokumentuje każdy dostępny filtr wraz z wskazówkami dotyczącymi tego, które kombinacje najlepiej sprawdzają się w przypadku różnych wad skanowania. Przewodnik dotyczący korekcji orientacji obrazu obejmuje automatyczne obracanie dokumentów zeskanowanych do góry nogami.
Archiwizacja dokumentów finansowych w formacie PDF z możliwością wyszukiwania
Mindee nie generuje żadnych plików PDF. Jej architektura opiera się wyłącznie na danych wejściowych: przesyłasz dokument, otrzymujesz pola JSON. Zespoły archiwizujące zeskanowane faktury w formacie umożliwiającym wyszukiwanie muszą utrzymywać ten proces oddzielnie od ekstrakcji danych przeprowadzanej przez Mindee.IronOCR generuje pliki PDF z możliwością wyszukiwania bezpośrednio na podstawie tego samego procesu rozpoznawania, który służy do wyodrębniania pól — bez dodatkowej biblioteki i bez drugiego etapu przetwarzania.
Podejście Mindee:
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 viaMindee(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 viaMindee(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
' Mindee returns 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
Podejście IronOCR:
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() osadza rozpoznaną warstwę tekstową bezpośrednio w wyjściowym PDF, tworząc plik, w którym każde słowo jest wybieralne i przeszukiwalne w dowolnym przeglądarce PDF lub systemie zarządzania treścią przedsiębiorstwa. Ekstrakcja pól i archiwizacja odbywają się w tym samym przejściu — jedno wywołanie _ocr.Read(input) dostarcza zarówno result.Text do dopasowania wzorców, jak i result.SaveAsSearchablePdf() do archiwum. Przewodnik o przeszukiwalnych PDF-ach obejmuje opcje wyjścia w tym eksport HOCR i strona przypadku użycia PDF OCR obejmuje wzorce wdrożenia produkcyjnego dla linii archiwizacyjnych dokumentów.
Usunięcie niestandardowego punktu końcowego FinancialDocumentV1
APIMindeeFinancialDocumentV1 obsługuje zarówno faktury, jak i paragony poprzez automatyczne wykrywanie typu dokumentu. Zespoły korzystające z tego rozwiązania eliminują logikę rozgałęzień kosztem większej zależności od chmury — każdy dokument, niezależnie od typu, jest przesyłany. Zastąpienie go IronOCR wykorzystuje dane pozycyjne na poziomie słów do wykrywania struktury dokumentu i kierowania logiką ekstrakcji bez żadnych wywołań do chmury.
Podejście Mindee:
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
Podejście IronOCR:
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
Logika wykrywania typu dokumentu zastępuje pole DocumentTypeMindeena serwerze prostym skanem słów kluczowych przeciwko result.Text. W przypadku zdecydowanej większości dokumentów finansowych obecność słów "Invoice" lub "Bill To" jednoznacznie identyfikuje faktury; brak zidentyfikuje paragony. Dane o położeniu na poziomie słowa w result.Pages umożliwiają bardziej zaawansowane wykrywanie oparte na układzie, jeśli twoja set dokumentów zawiera przypadki brzegowe. Przewodnik do odczytu wyników wyjaśnia pełny model obiektu, w tym Words, Lines, i Paragraphs z ich współrzędnymi ograniczającymi.
Przewodnik po mapowaniu APIMindeedo IronOCR
| Mindee | Odpowiednik IronOCR |
|---|---|
new MindeeClient(apiKey) |
new IronTesseract() — nie jest wymagany klucz API |
new LocalInputSource(filePath) |
new OcrInput() + input.LoadImage(filePath) lub ocr.Read(filePath) |
_client.ParseAsync<InvoiceV4>(input) |
_ocr.Read(filePath) + wyodrębnianie wyrażeniami regularnymi na result.Text |
_client.ParseAsync<ReceiptV5>(input) |
_ocr.Read(filePath) + wzorce wyodrębniania paragonów |
_client.ParseAsync<PassportV1>(input) |
_ocr.Read(filePath) + wyodrębnianie strefy MRZ przez CropRectangle |
_client.ParseAsync<FinancialDocumentV1>(input) |
_ocr.Read(filePath) + wykrywanie typu dokumentu na 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 |
Nagłówek pierwszej strony za pomocą result.Pages[0].Lines.First() |
prediction.TotalAmount?.Value |
Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})") |
prediction.LineItems |
result.Lines filtrowane przez wzorce wyrażeń regularnych pozycji liniowych |
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 (poziom dokumentu) lub word.Confidence (na słowo) |
catch (MindeeException ex) |
Brak odpowiednika — brak błędów sieciowych w momencie rozpoznawania |
await Task.Delay(100) (limit szybkości) |
Nie jest wymagane — brak limitów stawek |
input.LoadPdf(path) |
ocrInput.LoadPdf(path) — ta sama operacja, całkowicie lokalnie |
| Klucz API w konfiguracji | Klucz licencyjny przez IronOcr.License.LicenseKey = "..." — nie wymaga rotacji |
Typowe problemy związane z migracją i ich rozwiązania
Problem 1: Współrzędne CropRectangle nie pasują do układu dokumentu
Mindee: Współrzędne przestrzenne są obsługiwane po stronie serwera. ModelMindeedostosowuje się do różnych układów faktur bez konieczności konfiguracji współrzędnych przez programistę.
Rozwiązanie: Porównaj współrzędy stref z reprezentatywną próbką dokumentów z zestawu dostawcy. Otwórz przykładową fakturę w edytorze obrazu, odczytaj wymiary pikseli i zdefiniuj CropRectangle(x, y, width, height) odpowiednio. W przypadku faktur o zmiennym układzie należy najpierw przeczytać cały dokument i wykorzystać dane dotyczące pozycji WORD do dynamicznego lokalizowania stref:
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
Przykład OCR oparty na regionach pokazuje ten wzorzec stref dynamicznych w kompletnym, działającym przykładzie.
Problem 2: Miejsca wywołań asynchronicznych przestają działać po usunięciu Mindee
Mindee: Cała powierzchnia APIMindeejest asynchroniczna, ponieważ jest to operacja wejścia/wyjścia sieciowego. Konrolery i metody usług zbudowane na await _client.ParseAsync<t>() są asynchroniczne w całym łańcuchu wywołań.
Rozwiązanie: Metoda Read()IronOCR jest synchroniczna. Istniejące miejsca wywołań asynchronicznych działają natychmiast przez opakowanie wywołania synchronicznego w Task.Run:
// 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
Dla scenariuszy ASP.NET o wysokiej przepustowości, zapoznaj się z Przewodnikiem o OCR asynchronicznym przed decyzją między Task.Run opakowaniami a natywną ścieżką asynchroniczną.
Problem 3: Spadek dokładności ekstrakcji w przypadku skanów niskiej jakości
Mindee: Przetwarzanie wstępne wMindeeodbywa się wewnętrznie i automatycznie. Problemy z jakością skanowania obniżają wyniki zaufania do pól, a przed ponownym przesłaniem nie ma możliwości interwencji programisty.
Rozwiązanie: Zastosuj proces przetwarzania wstępnego IronOCR przed rozpoznaniem. Połączenie Deskew() i DeNoise() przywraca większość dokładności w typowych defektach skanu płaskiego spotykanych w przepływach pracy działów rozliczeń:
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
Dla poważnie zdegradowanych skanów, input.DeepCleanBackgroundNoise() stosuje cięższe usuwanie tła. Przewodnik po korekcji jakości obrazu dokumentuje progi, a kreator filtrów pomaga zidentyfikować, które filtry poprawiają dokładność w przypadku danej próbki dokumentu.
Problem 4: Usunięto konfigurację klucza API i ruchu wychodzącego
Mindee: Klucz API przechowywany w ustawieniach aplikacji, zasady wyjścia sieciowego umożliwiające wychodzące połączenia HTTPS do api.mindee.net oraz logika retry opakowująca HttpRequestException, są koniecznym wyposażeniem infrastrukturalnym.
Rozwiązanie: Wszystkie trzy elementy zostały usunięte łącznie. Wpis klucza APIMindeezostał usunięty z konfiguracji. Reguła wychodząca z sieci została cofnięta. Blok try/catch (HttpRequestException) zostaje usunięty. Jedynym pozostałym poświadczeniem jest klucz licencyjny IronOCR, ustawiany jednorazowo podczas uruchamiania:
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// Nie rotation 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"];
// Nie rotation 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")
' Nie rotation schedule, no secure storage beyond standard config secret management
Problem 5: Rozliczanie liczby stron w wielostronicowych plikach PDF
Mindee: 12-stronicowe oświadczenie dostawcy przesłane do serwisuMindeezużywa 12 stron z miesięcznego limitu. Przy stawkach za przekroczenie limitu w pakiecie Pro ten pojedynczy dokument generuje dodatkowy koszt w wysokości 1,20 USD ponad miesięczny limit, jeśli zbliżasz się do limitu.
Rozwiązanie:IronOCR przetwarza wielostronicowe pliki PDF bez żadnych kosztów za stronę. Załaduj cały dokument i przeglądaj strony:
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: Zależności FinancialDocumentV1 nie można zastąpić jednym wzorcem
Mindee: FinancialDocumentV1 automatycznie wykrywa typ dokumentu i wyodrębnia pola bez konieczności branżowania kodu wywołującego w oparciu o typ dokumentu.
Rozwiązanie: Stwórz lekki detektor wykorzystujący obecność słów kluczowych. Dokumenty finansowe można łatwo podzielić na faktury (zawierające słowa "Invoice", "Bill To" lub "Due Date") i pokwitowania (zawierające słowa "Receipt", "Thank You" lub pozbawione oznaczeń faktury). Dwie metody ekstrakcji Plus detektor trzech wierszy zastępują wywołanie APIMindeebez utraty dokładności w przypadku poprawnie sformułowanych dokumentów:
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))
Lista kontrolna migracji Mindee
Przed migracją
Sprawdź wszystkie użyciaMindeew całym kodzie:
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" .
Wyniki przeglądu zasobów:
- Policz unikalne miejsca wywołań za pomocą
ParseAsync<t> - Zauważ, które typy dokumentów są używane (
InvoiceV4,ReceiptV5,PassportV1,FinancialDocumentV1) - Zidentyfikuj wszystkie łańcuchy metod asynchronicznych, które rozprzestrzeniają się z wywołań Mindee
- Znajdź odniesienia do kluczy API w plikach konfiguracyjnych i magazynach sekretów
- Zidentyfikuj logikę ponownych prób otaczającą wywołania sieciowe Mindee
- Zidentyfikuj reguły ruchu wychodzącego z sieci, zezwalające na ruch wychodzący do punktów końcowych Mindee
Migracja kodu
- Usuń pakiet NuGet
Mindeez pliku projektu - Uruchom
dotnet add package IronOcr, aby zainstalować IronOCR - Dodaj
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"do uruchamiania aplikacji - Usuń wszystkie dyrektywy
using Mindee,using Mindee.Inputiusing Mindee.Product.* - Zastąp wstrzykiwanie konstruktorów
MindeeClientprzezIronTesseractinstancjonowanie lub rejestrację DI - Zastąp każde wywołanie
ParseAsync<InvoiceV4>przez_ocr.Read(filePath)plus metody wyodrębniania faktur - Zastąp każde wywołanie
ParseAsync<ReceiptV5>przez_ocr.Read(filePath)plus metody wyodrębniania paragonów - Zastąp każde wywołanie
ParseAsync<PassportV1>przez OCR oparte na strefach za pomocąCropRectanglew obszarze MRZ - Zastąp każde wywołanie
ParseAsync<FinancialDocumentV1>przez wykrywacz typu dokumentu + wzorzec routingu - Usuń wszystkie linie zgodności z ograniczeniami szybkości
await Task.Delay(...) - Usuń wszystkie bloki
catch (MindeeException)icatch (HttpRequestException)ze ścieżek OCR - Dodaj wywołania przetwarzania wstępnego
OcrInput(Deskew,DeNoise,Contrast) dla ścieżek dokumentów skanowanych - Dodaj
result.SaveAsSearchablePdf(archivePath)do dowolnych ścieżek archiwizacji dokumentów - Usuń klucz APIMindeeze wszystkich plików konfiguracyjnych i magazynów sekretów
- Zaktualizuj testy integracyjne, aby działały synchronicznie bez symulacji sieci
Po migracji
- Sprawdź dokładność ekstrakcji na próbce 20–30 dokumentów obejmującej każdy typ dokumentu
- Potwierdź, że wartości
result.Confidencesą powyżej 80 na reprezentatywnych czystych skanach; zastosuj przetwarzanie wstępne, jeśli poniżej - Przetestuj pipeline przetwarzania wstępnego (
Deskew,DeNoise) na przekrzywionych i zepsutych przykładach skanów - Zweryfikuj, czy wielostronicowe PDF-y produkują poprawne
result.Pages.Counti zawartość na stronę - Potwierdź, że
result.SaveAsSearchablePdf()wyjście jest przeszukiwalne w Adobe Acrobat i przeglądarkach PDF systemu - Przetestuj wszystkie wyodrębnia pokój z baz zalania (
CropRectangle) zróżnicowani układu dokumentów u Twojego zespołu dostawcy - Uruchom przetwarzanie zbiorcze bez wywołań
Task.Delayi potwierdź brak problemów z wątkami - Sprawdź, czy aplikacja uruchamia się i działa poprawnie bez dostępu do Internetu
- Zweryfikuj, że inicjalizacja klucza licencyjnego jest uruchamiana przed pierwszym wywołaniem
_ocr.Read()w każdym punkcie wejścia - Sprawdź, czy w konfiguracji, zmiennych środowiskowych lub sekretach nie pozostały żadne odniesienia do klucza API Mindee
Kluczowe korzyści z migracji do IronOCR
Pełna suwerenność danych dotyczących dokumentów finansowych. Po migracji kody IBAN dostawców, numery routingu, numery EIN klientów oraz szczegółowe pozycje nigdy nie opuszczają Twojej infrastruktury. Zakres zgodności obejmuje wyłącznie Państwa organizację. Umowy dotyczące przetwarzania danych zawarte z zewnętrznymi dostawcami rozwiązań AI są usuwane z rejestru audytowego. Zespoły działające zgodnie z wymogąmi GLBA, CMMC, FedRAMP lub wymogąmi dotyczącymi lokalizacji danych mogą przetwarzać dokumenty finansowe bez konieczności przeprowadzania kontroli zgodności, której wymaga zewnętrzna transmisja w chmurze.
Przewidywalny koszt niezależny od wolumenu. Lite License IronOCR na $999 obejmuje jednego programistę z nieograniczoną liczbą przetwarzania dokumentów. Skalowanie z 500 faktur miesięcznie do 50 000 faktur miesięcznie nie wiąże się z żadnymi dodatkowymi kosztami. Szczyty obciążenia związane z przetwarzaniem na koniec roku, seryjne działania naprawcze oraz cykle testów programistycznych nie powodują wzrostu licznika rozliczeniowego. Całkowity koszt posiadania w ciągu trzech lat dla zespołu przetwarzającego 5000 stron miesięcznie spada z około 17 964 USD (Mindee Pro) do 1499–2999 USD (Licencja wieczysta IronOCR). Szczegółowe informacje na temat poziomów dostępności można znaleźć na pełnej stronie produktu IronOCR.
Logika ekstrakcji, którą posiadasz na stałe. Wzory ekstrakcji IronOCR to zwykły kod C# w Twoim repozytorium. Są one kontrolowane pod kątem wersji, można je przeglądać, testować i wdrażać niezależnie od harmonogramu wydawniczego zewnętrznych dostawców lub decyzji dotyczących wersjonowania API. KiedyMindeewydaje InvoiceV5 i wycofuje InvoiceV4, jest to termin migracji narzucony przez dostawcę. Kiedy utrzymujesz wzorce wyodrębniania, poprawa jest git commit.
Kontrola przetwarzania wstępnego zapewniająca rzeczywistą jakość skanowania. Dział księgowości otrzymuje faktury od dziesiątek lub setek dostawców, z których każda została zeskanowana przy użyciu innego sprzętu i przy różnych ustawieniach. Pipeline przetwarzania wstępnego IronOCR— Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — dotyczy specyficznych defektów w każdej kategorii skanów. Zdjęcie paragonu termicznego ma inne wady niż wielostronicowa faktura zeskanowana na skanerze płaskim; do każdej ścieżki należy zastosować odpowiednie filtry. Przetwarzanie wstępneMindeejest niewidoczne i nie można go konfigurować. Pełny katalog filtrów można znaleźć na stronie poświęconej funkcjom przetwarzania wstępnego.
Archiwizacja przeszukiwalnych PDF-ów w jednym kroku. Każda faktura przetworzona przez IronOCR może być archiwizowana jako przeszukiwalny PDF z result.SaveAsSearchablePdf(). Rozpoznana warstwa tekstowa jest osadzona w pliku wyjściowym, dzięki czemu każde słowo można wyszukiwać w systemach zarządzania dokumentami, bibliotekach SharePoint i korporacyjnych repozytoriach treści.Mindeew ogóle nie generuje plików PDF; Archiwizacja z możliwością wyszukiwania wymagała wcześniej osobnej biblioteki, osobnego etapu przetwarzania i dodatkowej konserwacji. Po migracji, wyodrębnianie i archiwizacja zanikają w jedno wywołanie _ocr.Read(input).
Wdrażanie wszędzie bez zmian w architekturze.IronOCR wdraża się na systemach Windows, Linux, macOS, Docker, Azure App Service, AWS Lambda i Google Cloud Run przy użyciu tego samego pakietu NuGet i tej samej inicjalizacji. Środowiska odizolowane, systemy na halach produkcyjnych oraz rządowe obiekty o podwyższonym poziomie bezpieczeństwa działają bez konieczności modyfikacji. Przewodnik wdrażania Docker, przewodnik Azure i przewodnik AWS obejmują konfigurację specyficzną dla środowiska każdej platformy.
Często Zadawane Pytania
Dlaczego warto przejść z Mindee OCR API na IronOCR?
Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.
Jakie są główne zmiany w kodzie podczas migracji z Mindee OCR API do IronOCR?
Zastąp sekwencje inicjalizacji Mindee instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.
Jak zainstalować IronOCR, aby rozpocząć migrację?
Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.
Czy IronOCR dorównuje dokładnością OCR interfejsowi API Mindee OCR w przypadku standardowych dokumentów biznesowych?
IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.
W jaki sposób IronOCR obsługuje dane językowe, które Mindee OCR API instaluje oddzielnie?
Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.
Czy migracja z Mindee OCR API do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?
IronOCR wymaga mniej zmian w infrastrukturze niż Mindee OCR API. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.
Jak skonfigurować licencjonowanie IronOCR po migracji?
W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.
Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak Mindee?
Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.
W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?
IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.
Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?
IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.
Czy ceny IronOCR są bardziej przewidywalne niż ceny Mindee OCR API w przypadku skalowania obciążeń?
IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.
Co stanie się z moimi istniejącymi testami po migracji z Mindee OCR API do IronOCR?
Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

