Przejdź do treści stopki
FILMY

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
$vbLabelText   $csharpLabel
// 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
$vbLabelText   $csharpLabel

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
SHELL

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
$vbLabelText   $csharpLabel

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"
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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))
$vbLabelText   $csharpLabel

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" .
SHELL

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

  1. Usuń pakiet NuGet Mindee z pliku projektu
  2. Uruchom dotnet add package IronOcr, aby zainstalować IronOCR
  3. Dodaj IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" do uruchamiania aplikacji
  4. Usuń wszystkie dyrektywy using Mindee, using Mindee.Input i using Mindee.Product.*
  5. Zastąp wstrzykiwanie konstruktorów MindeeClient przez IronTesseract instancjonowanie lub rejestrację DI
  6. Zastąp każde wywołanie ParseAsync<InvoiceV4> przez _ocr.Read(filePath) plus metody wyodrębniania faktur
  7. Zastąp każde wywołanie ParseAsync<ReceiptV5> przez _ocr.Read(filePath) plus metody wyodrębniania paragonów
  8. Zastąp każde wywołanie ParseAsync<PassportV1> przez OCR oparte na strefach za pomocą CropRectangle w obszarze MRZ
  9. Zastąp każde wywołanie ParseAsync<FinancialDocumentV1> przez wykrywacz typu dokumentu + wzorzec routingu
  10. Usuń wszystkie linie zgodności z ograniczeniami szybkości await Task.Delay(...)
  11. Usuń wszystkie bloki catch (MindeeException) i catch (HttpRequestException) ze ścieżek OCR
  12. Dodaj wywołania przetwarzania wstępnego OcrInput (Deskew, DeNoise, Contrast) dla ścieżek dokumentów skanowanych
  13. Dodaj result.SaveAsSearchablePdf(archivePath) do dowolnych ścieżek archiwizacji dokumentów
  14. Usuń klucz APIMindeeze wszystkich plików konfiguracyjnych i magazynów sekretów
  15. 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.Confidence są 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.Count i 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.Delay i 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.

Zwróć uwagęAdobe Acrobat, Mindee, PDFSharp, Tesseract i iText są zarejestrowanymi znakami towarowymi odpowiednich właścicieli. Ta strona nie jest powiązana z, popierana ani sponsorowana przez Adobe Inc., Google, Mindee, empira Software GmbH ani iText Group. Wszystkie nazwy produktów, loga i marki są własnością ich właścicieli. Porównania mają charakter wyłącznie informacyjny i odzwierciedlają informacje dostępne publicznie w momencie pisania.

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.

Kannaopat Udonpant
Inżynier oprogramowania
Zanim stał się inżynierem oprogramowania, Kannapat ukończył doktorat z zasobów środowiskowych na Uniwersytecie Hokkaido w Japonii. W czasie studiowania, Kannapat również został członkiem Laboratorium Robotyki Pojazdów, które jest częścią Wydziału Inżynierii Bioprodukcji. W 2022 roku wykorzystał swoje umiejętności w ...
Czytaj więcej

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie