Przejdź do treści stopki
PORóWNAJ Z INNYMI KOMPONENTAMI

ABBYY FineReader vs Tesseract: Porównanie OCR

OCR.space nie posiada pakietu NuGet. Ten jeden fakt definiuje każdą decyzję integracyjną, jaką podejmuje programista .NET, wybierając go: piszesz niestandardowy HttpClient, kodujesz swoje dokumenty w base64, wysyłasz do https://api.ocr.space/parse/image, ręcznie analizujesz odpowiedź JSON, łapiesz błędy HTTP 429, implementujesz wykładniczy backoff, budujesz księgowanie limitów częstości i definiujesz własne typy wyjątków - wszystko to, zanim jakikolwiek znak tekstu dotrze do twojej aplikacji. W tym artykułe przeanalizowano, ile faktycznie kosztuje takie samodzielne rozwiązanie pod względem kodu, czasu i niezawodności produkcji, a także porównano je bezpośrednio z biblioteką IronOCR, natywną biblioteką .NET dostarczaną jako pojedynczy pakiet NuGet.

Zrozumienie OCR.space

OCR.space to freemium REST API, które przetwarza obrazy i pliki PDF na zdalnych serwerach obsługiwanych przez OCR.space. Usługa wyróżnia się dzięki bezpłatnemu pakietowi: 25 000 żądań miesięcznie bez konieczności podawania danych karty kredytowej, co jest atrakcyjne dla programistów eksperymentujących z OCR lub tworzących osobiste projekty. Nie ma pakietu NuGet, oficjalnego zestawu SDK .NET ani biblioteki klienckiej o silnym typowaniu w żadnym języku. Każda integracja .NET jest tworzona ręcznie na wierzchu HttpClient.

Rzeczywistość architektoniczna dla programistów .NET:

  • Brak pakietu NuGet: zerowa obsługa .NET na najwyższym poziomie. Brak IntelliSense, brak modeli typowanych, brak zintegrowanej obsługi błędów.
  • Przetwarzanie wyłącznie w chmurze: każdy dokument opuszcza infrastrukturę klienta. Nie ma opcji lokalnej ani ścieżki wdrożenia z własnym hostingiem.
  • Ręczna integracja REST: Programiści kodują pliki w base64, konstruują FormUrlEncodedContent lub MultipartFormDataContent i analizują surowe odpowiedzi JSON.
  • Ograniczanie przepustowości DIY: Wersja darmowa nakłada limit 60 żądań na minutę oraz sztywny limit 500 żądań dziennie na adres IP. Aplikacje produkcyjne muszą same wbudować tę logikę.
  • Ograniczenia dotyczące rozmiaru plików: Wersja bezpłatna odrzuca pliki większe niż 5 MB. Wielostronicowe pliki PDF często przekraczają ten limit.
  • Pliki PDF z znakiem wodnym: Generowanie plików PDF z możliwością wyszukiwania w ramach bezpłatnego planu zawiera znaki wodne OCR.space, co sprawia, że pliki te nie nadają się do dostarczenia klientom ani do archiwizacji dokumentów.
  • Brak umowy SLA: Wersja bezpłatna nie obejmuje gwarancji dostępności. Płatne plany oferują wyższe limity, ale te same ograniczenia architektoniczne.

Integracja REST, którą programiści muszą napisać

Dokumentacja OCR.space wskazuje programistom punkt końcowy REST i pozostawia im zadanie zbudowania wszystkiego innego. Minimalny klient .NET — przed uwzględnieniem jakichkolwiek wzmocnień produkcyjnych — wygląda następująco:

// OCR.space: what you build before the first line of your actual application
public class OcrSpaceApiClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;
    private readonly SemaphoreSlim _rateLimiter;
    private const string ApiEndpoint = "https://api.ocr.space/parse/image";
    private const int MaxFileSizeFree = 5 * 1024 * 1024; // 5MB — hard limit on free tier
    private const int RateLimitPerMinute = 60;

    public OcrSpaceApiClient(string apiKey)
    {
        _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
        _httpClient = new HttpClient();
        _httpClient.Timeout = TimeSpan.FromSeconds(120);
        _rateLimiter = new SemaphoreSlim(RateLimitPerMinute, RateLimitPerMinute);
    }

    public async Task<OcrResult> ExtractTextFromFileAsync(
        string filePath,
        string language = "eng",
        CancellationToken cancellationToken = default)
    {
        if (!File.Exists(filePath))
            throw new FileNotFoundException("Image file not found", filePath);

        var fileInfo = new FileInfo(filePath);
        if (fileInfo.Length > MaxFileSizeFree)
        {
            throw new InvalidOperationException(
                $"File size {fileInfo.Length / 1024 / 1024}MB exceeds free tier limit of 5MB.");
        }

        await _rateLimiter.WaitAsync(cancellationToken);

        try
        {
            // Document leaves your infrastructure here
            byte[] imageBytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
            string base64Image = Convert.ToBase64String(imageBytes);
            string mimeType = GetMimeType(filePath);

            var formContent = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("apikey", _apiKey),
                new KeyValuePair<string, string>("base64Image", $"data:{mimeType};base64,{base64Image}"),
                new KeyValuePair<string, string>("language", language),
                new KeyValuePair<string, string>("isOverlayRequired", "false"),
                new KeyValuePair<string, string>("filetype", Path.GetExtension(filePath).TrimStart('.')),
                new KeyValuePair<string, string>("detectOrientation", "true"),
                new KeyValuePair<string, string>("scale", "true"),
                new KeyValuePair<string, string>("OCREngine", "2")
            });

            var response = await _httpClient.PostAsync(ApiEndpoint, formContent, cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                string errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
                throw new OcrSpaceException(
                    $"OCR.space API returned {response.StatusCode}: {errorBody}",
                    (int)response.StatusCode);
            }

            string jsonResponse = await response.Content.ReadAsStringAsync(cancellationToken);
            return ParseOcrResponse(jsonResponse);
        }
        finally
        {
            _ = Task.Run(async () =>
            {
                await Task.Delay(60000 / RateLimitPerMinute);
                _rateLimiter.Release();
            });
        }
    }

    private OcrResult ParseOcrResponse(string jsonResponse)
    {
        using var doc = JsonDocument.Parse(jsonResponse);
        var root = doc.RootElement;

        if (root.TryGetProperty("IsErroredOnProcessing", out var isErrored) && isErrored.GetBoolean())
        {
            string errorMessage = "OCR processing failed";
            if (root.TryGetProperty("ErrorMessage", out var errorMessages))
            {
                // Parse the array of error strings manually
                var messages = new List<string>();
                if (errorMessages.ValueKind == JsonValueKind.Array)
                {
                    foreach (var msg in errorMessages.EnumerateArray())
                        messages.Add(msg.GetString() ?? "Unknown error");
                }
                errorMessage = string.Join("; ", messages);
            }
            throw new OcrSpaceException(errorMessage, 500);
        }

        var result = new OcrResult();

        if (root.TryGetProperty("ParsedResults", out var parsedResults))
        {
            foreach (var parsedResult in parsedResults.EnumerateArray())
            {
                if (parsedResult.TryGetProperty("ParsedText", out var parsedText))
                    result.ParsedText += parsedText.GetString();

                if (parsedResult.TryGetProperty("FileParseExitCode", out var exitCode))
                    result.ExitCodes.Add(exitCode.GetInt32());
            }
        }

        return result;
    }

    private string GetMimeType(string filePath) =>
        Path.GetExtension(filePath).ToLowerInvariant() switch
        {
            ".png"          => "image/png",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".bmp"          => "image/bmp",
            ".tiff" or ".tif" => "image/tiff",
            ".pdf"          => "application/pdf",
            _               => "application/octet-stream"
        };

    public void Dispose()
    {
        _httpClient?.Dispose();
        _rateLimiter?.Dispose();
    }
}

// Custom models — no SDK means you define these yourself
public class OcrResult
{
    public string ParsedText { get; set; } = string.Empty;
    public List<string> Warnings { get; } = new();
    public List<int> ExitCodes { get; } = new();
}

public class OcrSpaceException : Exception
{
    public int StatusCode { get; }
    public OcrSpaceException(string message, int statusCode) : base(message)
        => StatusCode = statusCode;
}
// OCR.space: what you build before the first line of your actual application
public class OcrSpaceApiClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;
    private readonly SemaphoreSlim _rateLimiter;
    private const string ApiEndpoint = "https://api.ocr.space/parse/image";
    private const int MaxFileSizeFree = 5 * 1024 * 1024; // 5MB — hard limit on free tier
    private const int RateLimitPerMinute = 60;

    public OcrSpaceApiClient(string apiKey)
    {
        _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
        _httpClient = new HttpClient();
        _httpClient.Timeout = TimeSpan.FromSeconds(120);
        _rateLimiter = new SemaphoreSlim(RateLimitPerMinute, RateLimitPerMinute);
    }

    public async Task<OcrResult> ExtractTextFromFileAsync(
        string filePath,
        string language = "eng",
        CancellationToken cancellationToken = default)
    {
        if (!File.Exists(filePath))
            throw new FileNotFoundException("Image file not found", filePath);

        var fileInfo = new FileInfo(filePath);
        if (fileInfo.Length > MaxFileSizeFree)
        {
            throw new InvalidOperationException(
                $"File size {fileInfo.Length / 1024 / 1024}MB exceeds free tier limit of 5MB.");
        }

        await _rateLimiter.WaitAsync(cancellationToken);

        try
        {
            // Document leaves your infrastructure here
            byte[] imageBytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
            string base64Image = Convert.ToBase64String(imageBytes);
            string mimeType = GetMimeType(filePath);

            var formContent = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("apikey", _apiKey),
                new KeyValuePair<string, string>("base64Image", $"data:{mimeType};base64,{base64Image}"),
                new KeyValuePair<string, string>("language", language),
                new KeyValuePair<string, string>("isOverlayRequired", "false"),
                new KeyValuePair<string, string>("filetype", Path.GetExtension(filePath).TrimStart('.')),
                new KeyValuePair<string, string>("detectOrientation", "true"),
                new KeyValuePair<string, string>("scale", "true"),
                new KeyValuePair<string, string>("OCREngine", "2")
            });

            var response = await _httpClient.PostAsync(ApiEndpoint, formContent, cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                string errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
                throw new OcrSpaceException(
                    $"OCR.space API returned {response.StatusCode}: {errorBody}",
                    (int)response.StatusCode);
            }

            string jsonResponse = await response.Content.ReadAsStringAsync(cancellationToken);
            return ParseOcrResponse(jsonResponse);
        }
        finally
        {
            _ = Task.Run(async () =>
            {
                await Task.Delay(60000 / RateLimitPerMinute);
                _rateLimiter.Release();
            });
        }
    }

    private OcrResult ParseOcrResponse(string jsonResponse)
    {
        using var doc = JsonDocument.Parse(jsonResponse);
        var root = doc.RootElement;

        if (root.TryGetProperty("IsErroredOnProcessing", out var isErrored) && isErrored.GetBoolean())
        {
            string errorMessage = "OCR processing failed";
            if (root.TryGetProperty("ErrorMessage", out var errorMessages))
            {
                // Parse the array of error strings manually
                var messages = new List<string>();
                if (errorMessages.ValueKind == JsonValueKind.Array)
                {
                    foreach (var msg in errorMessages.EnumerateArray())
                        messages.Add(msg.GetString() ?? "Unknown error");
                }
                errorMessage = string.Join("; ", messages);
            }
            throw new OcrSpaceException(errorMessage, 500);
        }

        var result = new OcrResult();

        if (root.TryGetProperty("ParsedResults", out var parsedResults))
        {
            foreach (var parsedResult in parsedResults.EnumerateArray())
            {
                if (parsedResult.TryGetProperty("ParsedText", out var parsedText))
                    result.ParsedText += parsedText.GetString();

                if (parsedResult.TryGetProperty("FileParseExitCode", out var exitCode))
                    result.ExitCodes.Add(exitCode.GetInt32());
            }
        }

        return result;
    }

    private string GetMimeType(string filePath) =>
        Path.GetExtension(filePath).ToLowerInvariant() switch
        {
            ".png"          => "image/png",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".bmp"          => "image/bmp",
            ".tiff" or ".tif" => "image/tiff",
            ".pdf"          => "application/pdf",
            _               => "application/octet-stream"
        };

    public void Dispose()
    {
        _httpClient?.Dispose();
        _rateLimiter?.Dispose();
    }
}

// Custom models — no SDK means you define these yourself
public class OcrResult
{
    public string ParsedText { get; set; } = string.Empty;
    public List<string> Warnings { get; } = new();
    public List<int> ExitCodes { get; } = new();
}

public class OcrSpaceException : Exception
{
    public int StatusCode { get; }
    public OcrSpaceException(string message, int statusCode) : base(message)
        => StatusCode = statusCode;
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks

' OCR.space: what you build before the first line of your actual application
Public Class OcrSpaceApiClient
    Implements IDisposable

    Private ReadOnly _httpClient As HttpClient
    Private ReadOnly _apiKey As String
    Private ReadOnly _rateLimiter As SemaphoreSlim
    Private Const ApiEndpoint As String = "https://api.ocr.space/parse/image"
    Private Const MaxFileSizeFree As Integer = 5 * 1024 * 1024 ' 5MB — hard limit on free tier
    Private Const RateLimitPerMinute As Integer = 60

    Public Sub New(apiKey As String)
        _apiKey = If(apiKey, Throw New ArgumentNullException(NameOf(apiKey)))
        _httpClient = New HttpClient()
        _httpClient.Timeout = TimeSpan.FromSeconds(120)
        _rateLimiter = New SemaphoreSlim(RateLimitPerMinute, RateLimitPerMinute)
    End Sub

    Public Async Function ExtractTextFromFileAsync(filePath As String, Optional language As String = "eng", Optional cancellationToken As CancellationToken = Nothing) As Task(Of OcrResult)
        If Not File.Exists(filePath) Then
            Throw New FileNotFoundException("Image file not found", filePath)
        End If

        Dim fileInfo = New FileInfo(filePath)
        If fileInfo.Length > MaxFileSizeFree Then
            Throw New InvalidOperationException($"File size {fileInfo.Length \ 1024 \ 1024}MB exceeds free tier limit of 5MB.")
        End If

        Await _rateLimiter.WaitAsync(cancellationToken)

        Try
            ' Document leaves your infrastructure here
            Dim imageBytes As Byte() = Await File.ReadAllBytesAsync(filePath, cancellationToken)
            Dim base64Image As String = Convert.ToBase64String(imageBytes)
            Dim mimeType As String = GetMimeType(filePath)

            Dim formContent = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
                New KeyValuePair(Of String, String)("apikey", _apiKey),
                New KeyValuePair(Of String, String)("base64Image", $"data:{mimeType};base64,{base64Image}"),
                New KeyValuePair(Of String, String)("language", language),
                New KeyValuePair(Of String, String)("isOverlayRequired", "false"),
                New KeyValuePair(Of String, String)("filetype", Path.GetExtension(filePath).TrimStart("."c)),
                New KeyValuePair(Of String, String)("detectOrientation", "true"),
                New KeyValuePair(Of String, String)("scale", "true"),
                New KeyValuePair(Of String, String)("OCREngine", "2")
            })

            Dim response = Await _httpClient.PostAsync(ApiEndpoint, formContent, cancellationToken)

            If Not response.IsSuccessStatusCode Then
                Dim errorBody = Await response.Content.ReadAsStringAsync(cancellationToken)
                Throw New OcrSpaceException($"OCR.space API returned {response.StatusCode}: {errorBody}", CInt(response.StatusCode))
            End If

            Dim jsonResponse = Await response.Content.ReadAsStringAsync(cancellationToken)
            Return ParseOcrResponse(jsonResponse)
        Finally
            _ = Task.Run(Async Function()
                             Await Task.Delay(60000 \ RateLimitPerMinute)
                             _rateLimiter.Release()
                         End Function)
        End Try
    End Function

    Private Function ParseOcrResponse(jsonResponse As String) As OcrResult
        Using doc = JsonDocument.Parse(jsonResponse)
            Dim root = doc.RootElement

            If root.TryGetProperty("IsErroredOnProcessing", ByRef isErrored) AndAlso isErrored.GetBoolean() Then
                Dim errorMessage As String = "OCR processing failed"
                If root.TryGetProperty("ErrorMessage", ByRef errorMessages) Then
                    ' Parse the array of error strings manually
                    Dim messages = New List(Of String)()
                    If errorMessages.ValueKind = JsonValueKind.Array Then
                        For Each msg In errorMessages.EnumerateArray()
                            messages.Add(msg.GetString() OrElse "Unknown error")
                        Next
                    End If
                    errorMessage = String.Join("; ", messages)
                End If
                Throw New OcrSpaceException(errorMessage, 500)
            End If

            Dim result = New OcrResult()

            If root.TryGetProperty("ParsedResults", ByRef parsedResults) Then
                For Each parsedResult In parsedResults.EnumerateArray()
                    If parsedResult.TryGetProperty("ParsedText", ByRef parsedText) Then
                        result.ParsedText &= parsedText.GetString()
                    End If

                    If parsedResult.TryGetProperty("FileParseExitCode", ByRef exitCode) Then
                        result.ExitCodes.Add(exitCode.GetInt32())
                    End If
                Next
            End If

            Return result
        End Using
    End Function

    Private Function GetMimeType(filePath As String) As String
        Return Path.GetExtension(filePath).ToLowerInvariant() Select Case
            Case ".png" : Return "image/png"
            Case ".jpg", ".jpeg" : Return "image/jpeg"
            Case ".bmp" : Return "image/bmp"
            Case ".tiff", ".tif" : Return "image/tiff"
            Case ".pdf" : Return "application/pdf"
            Case Else : Return "application/octet-stream"
        End Select
    End Function

    Public Sub Dispose() Implements IDisposable.Dispose
        _httpClient?.Dispose()
        _rateLimiter?.Dispose()
    End Sub
End Class

' Custom models — no SDK means you define these yourself
Public Class OcrResult
    Public Property ParsedText As String = String.Empty
    Public ReadOnly Property Warnings As New List(Of String)()
    Public ReadOnly Property ExitCodes As New List(Of Integer)()
End Class

Public Class OcrSpaceException
    Inherits Exception

    Public ReadOnly Property StatusCode As Integer

    Public Sub New(message As String, statusCode As Integer)
        MyBase.New(message)
        Me.StatusCode = statusCode
    End Sub
End Class
$vbLabelText   $csharpLabel

Jest to ponad 90 wierszy kodu infrastruktury, który znajduje się przed pierwszą metodą logiki biznesowej. Każda integracja OCR.space zawiera ten szablon (lub jego odpowiednik), a każdy zespół, który go pisze, rozwiązuje ten sam problem, którego OCR.space postanowiło dla nich nie rozwiązywać.

Zrozumienie IronOCR

IronOCR to komercyjna biblioteka OCR dla .NET, instalowana za pośrednictwem pakietu NuGet IronOcr. Zawiera zoptymalizowany silnik Tesseract 5 z automatycznym przetwarzaniem wstępnym, natywnym wejściem PDF, wyjściem PDF z możliwością wyszukiwania, ponad 125 pakietami językowymi oraz ekstrakcją wyników w formacie strukturalnym — wszystko to bez zależności od chmury, kluczy API czy opłat za żądanie. Dokumenty są przetwarzane lokalnie na dowolnej infrastrukturze, na której działa aplikacja .NET.

Kluczowe cechy:

  • Pojedynczy pakiet NuGet: dotnet add package IronOcr to kompletna instalacja. Nie ma potrzeby zarządzania natywnymi plikami binarnymi, katalogami tessdata ani zmiennymi środowiskowymi.
  • Silnie typowane API: IronTesseract, OcrInput, OcrResult, oraz CropRectangle są pierwszorzędnymi typami .NET z pełną obsługą IntelliSense.
  • Automatyczne przetwarzanie wstępne: filtry Deskew, DeNoise, Contrast, Binarize i EnhanceResolution działają automatycznie lub na żądanie bez zewnętrznych bibliotek.
  • Natywna obsługa plików PDF: wbudowana jest zarówno funkcja odczytu zeskanowanych plików PDF, jak i generowania plików PDF z możliwością wyszukiwania. Brak ograniczeń rozmiaru poza dostępną pamięcią.
  • Lokalne wykonywanie: podczas OCR nie dochodzi do żadnych wywołań sieciowych. Biblioteka działa całkowicie w ramach procesu. Środowiska odizolowane działają bez zmian w konfiguracji.
  • Bezpieczeństwo współbieżności: Wiele instancji IronTesseract działa jednocześnie bez blokowania lub zarządzania konkurencją.
  • Licencjonowanie wieczyste: $999 Lite / $1,499 Plus / $2,999 Professional. Brak opłat za pojedyncze zlecenia niezależnie od ich liczby.

Porównanie funkcji

Funkcja OCR.space IronOCR
Pakiet NuGet Brak — tylko REST API IronOcr — pełne wsparcie .NET
Miejsce przetwarzania Serwery w chmurze OCR.space Lokalnie — Twoja infrastruktura
Wersja bezpłatna 25 000 zleceń miesięcznie (ograniczona liczba) Dostępna wersja próbna
Ceny płatnych usług Skontaktuj się z OCR.space, aby uzyskać aktualne ceny $2,999 jednorazowe wieczyste
Ograniczanie szybkości 60 żądań/min, 500/dzień (bezpłatnie) None
Możliwość pracy w trybie offline Nie Tak — całkowicie odizolowany
Ochrona danych Dokumenty wysyłane do podmiotów zewnętrznych Dokumenty pozostają na serwerach użytkownika

Szczegółowe porównanie funkcji

Funkcja OCR.space IronOCR
Integracja
Pakiet NuGet None Tak (IronOcr)
Modele silnie typowane Brak — ręczne parsowanie JSON Tak (OcrResult, OcrInput)
Obsługa IntelliSense None Pełna
Niestandardowe typy wyjątków Musisz się zdefiniować Wbudowane
Logika ponownej próby Należy samodzielnie skompilować Wbudowane
Przetwarzanie
Miejsce przetwarzania Serwery w chmurze Lokalizacja w trakcie procesu
Wymagane połączenie z Internetem Tak — każde wywołanie Nie
Obsługa środowisk izolowanych Nie Tak
Limity szybkości Tak — wszystkie plany None
Ograniczenia dotyczące rozmiaru plików 5 MB (poziom bezpłatny) Tylko pamięć
Funkcja OCR
OCR obrazów Tak Tak
OCR PDF Tak (z pewnymi ograniczeniami) Tak (język ojczysty, bez ograniczeń co do objętości)
Wynik w formacie PDF z możliwością wyszukiwania Znaczek wodny w wersji darmowej Czysty wynik, bez znaków wodnych
Automatyczne przetwarzanie wstępne Brak (tylko po stronie serwera) Deskew, DeNoise, Contrast, Binarize, EnhanceResolution
Ręczna kontrola przetwarzania wstępnego None Pełny interfejs API filtru
Obsługa języków ~25 języków Ponad 125 pakietów językowych dostępnych za pośrednictwem NuGet
Wiele języków w jednym dokumencie Ograniczone Tak — języki główne + dodatkowe
Struktura wyjściowa (słowa/wiersze/strony) Nie — tylko zwykły tekst Tak — strony, akapity, wiersze, słowa z współrzędnymi
Wyniki pewności Nie Tak — za wynik i za WORD
OCR oparte na regionie Nie Tak — CropRectangle
Odczytywanie BarCode podczas OCR Nie Tak — ReadBarCodes = true
Wdrożenie
Docker Wymagane połączenie z Internetem Działa od razu po uruchomieniu
Linux Wymagane połączenie z Internetem W pełni obsługiwane
Zgodność z HIPAA Ryzyko — dokumenty wymykają się spod kontroli Tak — bez transmisji zewnętrznej
Zgodność z RODO Ryzyko — niejasności dotyczące przekazywania danych do UE Tak — brak przetwarzania danych stron trzecich
Ceny
Model cenowy Miesięczna subskrypcja Jednorazowa Licencja wieczysta
Cena wywoławcza Skontaktuj się z OCR.space, aby uzyskać aktualne ceny $999 jednorazowe
Koszt jednostkowy w skali Tak None
Umowa SLA Brak (bezpłatne), różne (płatne) Dostępna umowa Umowa SLA dla Enterprise

SDK Depth a Raw HTTP: koszt integracji SDK

Różnica między OCR.space a natywnym SDK nie wynika z preferencji stylistycznych. Mierzy się to w godzinach pracy programistów, powierzchni podatnej na błędy oraz nakładzie pracy związanym z utrzymaniem w każdym wdrożeniu.

Podejście OCR.space

Każdy programista .NET korzystający z OCR.space pisze własnego klienta HTTP. Najprostsza możliwa implementacja — podstawowe wyodrębnianie tekstu bez logiki ponownych prób, bez obsługi przetwarzania wsadowego i bez przetwarzania wstępnego — nadal liczy ponad 50 wierszy:

// OCR.space: minimum viable extraction —50+lines before business logic
public class OcrSpaceBasicExtraction
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;

    public OcrSpaceBasicExtraction(string apiKey)
    {
        _apiKey = apiKey;
        _httpClient = new HttpClient();
    }

    public async Task<string> ExtractText(string imagePath)
    {
        // Read file and encode — document leaves your infrastructure
        byte[] imageBytes = File.ReadAllBytes(imagePath);
        string base64 = Convert.ToBase64String(imageBytes);

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("apikey", _apiKey),
            new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
            new KeyValuePair<string, string>("language", "eng")
        });

        // Send to cloud
        var response = await _httpClient.PostAsync(
            "https://api.ocr.space/parse/image", content);

        if (!response.IsSuccessStatusCode)
            throw new Exception($"API error: {response.StatusCode}");

        // Parse JSON manually — no typed models
        string json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);

        return doc.RootElement
            .GetProperty("ParsedResults")[0]
            .GetProperty("ParsedText")
            .GetString() ?? string.Empty;
    }
}
// OCR.space: minimum viable extraction —50+lines before business logic
public class OcrSpaceBasicExtraction
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;

    public OcrSpaceBasicExtraction(string apiKey)
    {
        _apiKey = apiKey;
        _httpClient = new HttpClient();
    }

    public async Task<string> ExtractText(string imagePath)
    {
        // Read file and encode — document leaves your infrastructure
        byte[] imageBytes = File.ReadAllBytes(imagePath);
        string base64 = Convert.ToBase64String(imageBytes);

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("apikey", _apiKey),
            new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
            new KeyValuePair<string, string>("language", "eng")
        });

        // Send to cloud
        var response = await _httpClient.PostAsync(
            "https://api.ocr.space/parse/image", content);

        if (!response.IsSuccessStatusCode)
            throw new Exception($"API error: {response.StatusCode}");

        // Parse JSON manually — no typed models
        string json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);

        return doc.RootElement
            .GetProperty("ParsedResults")[0]
            .GetProperty("ParsedText")
            .GetString() ?? string.Empty;
    }
}
Imports System.Net.Http
Imports System.IO
Imports System.Threading.Tasks
Imports System.Text.Json

Public Class OcrSpaceBasicExtraction
    Private ReadOnly _httpClient As HttpClient
    Private ReadOnly _apiKey As String

    Public Sub New(apiKey As String)
        _apiKey = apiKey
        _httpClient = New HttpClient()
    End Sub

    Public Async Function ExtractText(imagePath As String) As Task(Of String)
        ' Read file and encode — document leaves your infrastructure
        Dim imageBytes As Byte() = File.ReadAllBytes(imagePath)
        Dim base64 As String = Convert.ToBase64String(imageBytes)

        Dim content = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
            New KeyValuePair(Of String, String)("apikey", _apiKey),
            New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}"),
            New KeyValuePair(Of String, String)("language", "eng")
        })

        ' Send to cloud
        Dim response = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)

        If Not response.IsSuccessStatusCode Then
            Throw New Exception($"API error: {response.StatusCode}")
        End If

        ' Parse JSON manually — no typed models
        Dim json As String = Await response.Content.ReadAsStringAsync()
        Using doc = JsonDocument.Parse(json)
            Return doc.RootElement _
                .GetProperty("ParsedResults")(0) _
                .GetProperty("ParsedText") _
                .GetString() OrElse String.Empty
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Dodaj przetwarzanie wsadowe, a liczba wierszy wzrośnie do ponad 80, ponieważ każde żądanie wsadowe wymaga ograniczania szybkości, logiki ponawiania prób dla każdego żądania z wykładniczym opóźnieniem oraz wyraźnej obsługi odpowiedzi HTTP 429. Dodaj przetwarzanie plików PDF i nałóż kontrolę rozmiaru pliku 5 MB przed każdym wywołaniem, ponieważ bezpłatny plan odrzuca większe pliki z błędem, a nie ostrzeżeniem.

Podejście IronOCR

Zainstalowanie IronOCR z NuGet zastępuje cały ten kod infrastruktury istniejącymi już wywołaniami metod:

// IronOCR: complete implementation
var result = new IronTesseract().Read("invoice.png");
Console.WriteLine(result.Text);
// IronOCR: complete implementation
var result = new IronTesseract().Read("invoice.png");
Console.WriteLine(result.Text);
$vbLabelText   $csharpLabel

Klasa IronTesseract obsługuje odczyt plików, przetwarzanie wstępne, wykonanie silnika i konstrukcję wyników. Brak klienta HTTP. Bez kodowania base64. Bez analizowania JSON. Brak klucza API. Brak ograniczeń dotyczących stawki. Samouczek dotyczący odczytywania tekstu z obrazów obejmuje różne rodzaje danych wejściowych — ścieżki plików, tablice bajtów, strumienie, mapy bitowe — które wszystkie działają według tego samego schematu jednego wywołania.

Wartości złożoności kodu z plików źródłowych nie są hipotetyczne:

Scenariusz Linie OCR.space Linie IronOCR
Podstawowe wyciąganie danych 50+ 3
Przetwarzanie plików PDF 60+ 12
Przetwarzanie wsadowe 80+ 15
Obsługa błędów 70+ 15
Pełna infrastruktura klienta 200+ 0 (udostępnia to NuGet)

Przetwarzanie wsadowe i limity szybkości

Problem z limitem szybkości jest tym, gdzie bezpłatny plan OCR.space najbardziej widocznie zawodzi w rzeczywistych warunkach produkcyjnych.

Podejście OCR.space

Przetwarzanie wsadowe w ramach OCR.space wymaga budowy i zarządzania SemaphoreSlim, aby pozostać w granicach 60 zapytań na minutę, oraz logiki powtórzeń z wykładniczym backoff dla odpowiedzi HTTP 429, które pojawiają się, gdy logika semafora jest niedokładna lub gdy współdzielona infrastruktura dzieli adres IP:

// OCR.space batch:80+lines of rate-limit plumbing before actual work
public class OcrSpaceBatchProcessing
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;
    private readonly SemaphoreSlim _rateLimiter;

    public OcrSpaceBatchProcessing(string apiKey)
    {
        _apiKey = apiKey;
        _httpClient = new HttpClient();
        _rateLimiter = new SemaphoreSlim(60, 60); // Free tier: 60/minute
    }

    public async Task<Dictionary<string, string>> ProcessBatch(string[] imagePaths)
    {
        var results = new Dictionary<string, string>();

        foreach (var imagePath in imagePaths)
        {
            await _rateLimiter.WaitAsync();

            try
            {
                string text = await ProcessWithRetry(imagePath);
                results[imagePath] = text;
            }
            finally
            {
                _ = Task.Run(async () =>
                {
                    await Task.Delay(1000); // 1 second spacing
                    _rateLimiter.Release();
                });
            }
        }

        return results;
    }

    private async Task<string> ProcessWithRetry(string imagePath, int maxRetries = 3)
    {
        for (int attempt = 0; attempt < maxRetries; attempt++)
        {
            try
            {
                byte[] imageBytes = File.ReadAllBytes(imagePath);
                string base64 = Convert.ToBase64String(imageBytes);

                var content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("apikey", _apiKey),
                    new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
                    new KeyValuePair<string, string>("language", "eng")
                });

                var response = await _httpClient.PostAsync(
                    "https://api.ocr.space/parse/image", content);

                if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
                {
                    // Rate limited — exponential backoff
                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
                    continue;
                }

                response.EnsureSuccessStatusCode();

                string json = await response.Content.ReadAsStringAsync();
                using var doc = JsonDocument.Parse(json);

                return doc.RootElement
                    .GetProperty("ParsedResults")[0]
                    .GetProperty("ParsedText")
                    .GetString() ?? string.Empty;
            }
            catch (HttpRequestException) when (attempt < maxRetries - 1)
            {
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
            }
        }

        throw new Exception($"Failed to process {imagePath} after {maxRetries} attempts");
    }
}
// OCR.space batch:80+lines of rate-limit plumbing before actual work
public class OcrSpaceBatchProcessing
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;
    private readonly SemaphoreSlim _rateLimiter;

    public OcrSpaceBatchProcessing(string apiKey)
    {
        _apiKey = apiKey;
        _httpClient = new HttpClient();
        _rateLimiter = new SemaphoreSlim(60, 60); // Free tier: 60/minute
    }

    public async Task<Dictionary<string, string>> ProcessBatch(string[] imagePaths)
    {
        var results = new Dictionary<string, string>();

        foreach (var imagePath in imagePaths)
        {
            await _rateLimiter.WaitAsync();

            try
            {
                string text = await ProcessWithRetry(imagePath);
                results[imagePath] = text;
            }
            finally
            {
                _ = Task.Run(async () =>
                {
                    await Task.Delay(1000); // 1 second spacing
                    _rateLimiter.Release();
                });
            }
        }

        return results;
    }

    private async Task<string> ProcessWithRetry(string imagePath, int maxRetries = 3)
    {
        for (int attempt = 0; attempt < maxRetries; attempt++)
        {
            try
            {
                byte[] imageBytes = File.ReadAllBytes(imagePath);
                string base64 = Convert.ToBase64String(imageBytes);

                var content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("apikey", _apiKey),
                    new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
                    new KeyValuePair<string, string>("language", "eng")
                });

                var response = await _httpClient.PostAsync(
                    "https://api.ocr.space/parse/image", content);

                if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
                {
                    // Rate limited — exponential backoff
                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
                    continue;
                }

                response.EnsureSuccessStatusCode();

                string json = await response.Content.ReadAsStringAsync();
                using var doc = JsonDocument.Parse(json);

                return doc.RootElement
                    .GetProperty("ParsedResults")[0]
                    .GetProperty("ParsedText")
                    .GetString() ?? string.Empty;
            }
            catch (HttpRequestException) when (attempt < maxRetries - 1)
            {
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
            }
        }

        throw new Exception($"Failed to process {imagePath} after {maxRetries} attempts");
    }
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks

Public Class OcrSpaceBatchProcessing
    Private ReadOnly _httpClient As HttpClient
    Private ReadOnly _apiKey As String
    Private ReadOnly _rateLimiter As SemaphoreSlim

    Public Sub New(apiKey As String)
        _apiKey = apiKey
        _httpClient = New HttpClient()
        _rateLimiter = New SemaphoreSlim(60, 60) ' Free tier: 60/minute
    End Sub

    Public Async Function ProcessBatch(imagePaths As String()) As Task(Of Dictionary(Of String, String))
        Dim results As New Dictionary(Of String, String)()

        For Each imagePath In imagePaths
            Await _rateLimiter.WaitAsync()

            Try
                Dim text As String = Await ProcessWithRetry(imagePath)
                results(imagePath) = text
            Finally
                _ = Task.Run(Async Function()
                                 Await Task.Delay(1000) ' 1 second spacing
                                 _rateLimiter.Release()
                             End Function)
            End Try
        Next

        Return results
    End Function

    Private Async Function ProcessWithRetry(imagePath As String, Optional maxRetries As Integer = 3) As Task(Of String)
        For attempt As Integer = 0 To maxRetries - 1
            Try
                Dim imageBytes As Byte() = File.ReadAllBytes(imagePath)
                Dim base64 As String = Convert.ToBase64String(imageBytes)

                Dim content = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
                    New KeyValuePair(Of String, String)("apikey", _apiKey),
                    New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}"),
                    New KeyValuePair(Of String, String)("language", "eng")
                })

                Dim response = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)

                If response.StatusCode = System.Net.HttpStatusCode.TooManyRequests Then
                    ' Rate limited — exponential backoff
                    Await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)))
                    Continue For
                End If

                response.EnsureSuccessStatusCode()

                Dim json As String = Await response.Content.ReadAsStringAsync()
                Using doc = JsonDocument.Parse(json)
                    Return doc.RootElement _
                        .GetProperty("ParsedResults")(0) _
                        .GetProperty("ParsedText") _
                        .GetString() OrElse String.Empty
                End Using
            Catch ex As HttpRequestException When attempt < maxRetries - 1
                Await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)))
            End Try
        Next

        Throw New Exception($"Failed to process {imagePath} after {maxRetries} attempts")
    End Function
End Class
$vbLabelText   $csharpLabel

Dodatkowym ograniczeniem jest limit 500 żądań dziennie w ramach bezpłatnego planu. Aplikacja przetwarzająca 600 dokumentów w ciągu jednego dnia ulegnie awarii w trakcie pracy i nie nastąpi automatyczne przeniesienie danych do następnego dnia kalendarzowego.

Podejście IronOCR

IronOCR nie ma limitów szybkości. Przetwarzanie wsadowe to pętla:

//IronOCR batch: 8 lines, no rate limits, no retries needed
public Dictionary<string, string> ProcessBatch(string[] imagePaths)
{
    var results = new Dictionary<string, string>();
    var ocr = new IronTesseract();

    foreach (var imagePath in imagePaths)
    {
        // Nie rate limits, no retries, no cloud dependency
        var result = ocr.Read(imagePath);
        results[imagePath] = result.Text;
    }

    return results;
}
//IronOCR batch: 8 lines, no rate limits, no retries needed
public Dictionary<string, string> ProcessBatch(string[] imagePaths)
{
    var results = new Dictionary<string, string>();
    var ocr = new IronTesseract();

    foreach (var imagePath in imagePaths)
    {
        // Nie rate limits, no retries, no cloud dependency
        var result = ocr.Read(imagePath);
        results[imagePath] = result.Text;
    }

    return results;
}
Imports System.Collections.Generic

'IronOCR batch: 8 lines, no rate limits, no retries needed
Public Function ProcessBatch(imagePaths As String()) As Dictionary(Of String, String)
    Dim results As New Dictionary(Of String, String)()
    Dim ocr As New IronTesseract()

    For Each imagePath In imagePaths
        ' Nie rate limits, no retries, no cloud dependency
        Dim result = ocr.Read(imagePath)
        results(imagePath) = result.Text
    Next

    Return results
End Function
$vbLabelText   $csharpLabel

Ponowne użycie instancji IronTesseract w całej partii unika powtarzanych kosztów inicjalizacji silnika. Szybkość przetwarzania jest ograniczona przez lokalny procesor i operacje wejścia/wyjścia dysku, a nie przez politykę ograniczania przepustowości zdalnego interfejsu API. Przykład wielowątkowości pokazuje, jak równolegle wykorzystywać rdzenie, gdy liczy się przepustowość.

Przetwarzanie plików PDF

Obsługa plików PDF ilustruje drugą poważną lukę strukturalną.

Podejście OCR.space

OCR.space akceptuje pliki PDF w planach płatnych oraz częściowo w wersji darmowej, ale limit rozmiaru pliku wynoszący 5 MB na kontach darmowych wyklucza większość dokumentów wielostronicowych. Wersja darmowa z możliwością wyszukiwania w plikach PDF zawiera znak wodny. Wdrożenie wymaga wyodrębniania wyników dla każdej strony oraz wyraźnego sprawdzania rozmiaru pliku przed każdym żądaniem:

// OCR.space PDF: size check required, watermarks on free tier
public async Task<List<string>> ExtractTextFromPdf(string pdfPath)
{
    var pageTexts = new List<string>();

    // Reject before wasting a request quota entry
    var fileInfo = new FileInfo(pdfPath);
    if (fileInfo.Length > 5 * 1024 * 1024)
        throw new InvalidOperationException("File exceeds 5MB free tier limit. Upgrade or split PDF.");

    byte[] pdfBytes = File.ReadAllBytes(pdfPath);
    string base64 = Convert.ToBase64String(pdfBytes);

    var content = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("apikey", _apiKey),
        new KeyValuePair<string, string>("base64Image", $"data:application/pdf;base64,{base64}"),
        new KeyValuePair<string, string>("language", "eng"),
        new KeyValuePair<string, string>("filetype", "PDF"),
        new KeyValuePair<string, string>("isCreateSearchablePdf", "false") // Watermarked on free tier
    });

    var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);

    if (!response.IsSuccessStatusCode)
        throw new Exception($"API error: {response.StatusCode}");

    string json = await response.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(json);

    if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
    {
        foreach (var result in results.EnumerateArray())
        {
            if (result.TryGetProperty("ParsedText", out var text))
                pageTexts.Add(text.GetString() ?? string.Empty);
        }
    }

    return pageTexts;
}
// OCR.space PDF: size check required, watermarks on free tier
public async Task<List<string>> ExtractTextFromPdf(string pdfPath)
{
    var pageTexts = new List<string>();

    // Reject before wasting a request quota entry
    var fileInfo = new FileInfo(pdfPath);
    if (fileInfo.Length > 5 * 1024 * 1024)
        throw new InvalidOperationException("File exceeds 5MB free tier limit. Upgrade or split PDF.");

    byte[] pdfBytes = File.ReadAllBytes(pdfPath);
    string base64 = Convert.ToBase64String(pdfBytes);

    var content = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("apikey", _apiKey),
        new KeyValuePair<string, string>("base64Image", $"data:application/pdf;base64,{base64}"),
        new KeyValuePair<string, string>("language", "eng"),
        new KeyValuePair<string, string>("filetype", "PDF"),
        new KeyValuePair<string, string>("isCreateSearchablePdf", "false") // Watermarked on free tier
    });

    var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);

    if (!response.IsSuccessStatusCode)
        throw new Exception($"API error: {response.StatusCode}");

    string json = await response.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(json);

    if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
    {
        foreach (var result in results.EnumerateArray())
        {
            if (result.TryGetProperty("ParsedText", out var text))
                pageTexts.Add(text.GetString() ?? string.Empty);
        }
    }

    return pageTexts;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks

Public Class OCRSpaceClient
    Private ReadOnly _apiKey As String
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(apiKey As String, httpClient As HttpClient)
        _apiKey = apiKey
        _httpClient = httpClient
    End Sub

    Public Async Function ExtractTextFromPdf(pdfPath As String) As Task(Of List(Of String))
        Dim pageTexts As New List(Of String)()

        ' Reject before wasting a request quota entry
        Dim fileInfo As New FileInfo(pdfPath)
        If fileInfo.Length > 5 * 1024 * 1024 Then
            Throw New InvalidOperationException("File exceeds 5MB free tier limit. Upgrade or split PDF.")
        End If

        Dim pdfBytes As Byte() = File.ReadAllBytes(pdfPath)
        Dim base64 As String = Convert.ToBase64String(pdfBytes)

        Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
            New KeyValuePair(Of String, String)("apikey", _apiKey),
            New KeyValuePair(Of String, String)("base64Image", $"data:application/pdf;base64,{base64}"),
            New KeyValuePair(Of String, String)("language", "eng"),
            New KeyValuePair(Of String, String)("filetype", "PDF"),
            New KeyValuePair(Of String, String)("isCreateSearchablePdf", "false") ' Watermarked on free tier
        })

        Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)

        If Not response.IsSuccessStatusCode Then
            Throw New Exception($"API error: {response.StatusCode}")
        End If

        Dim json As String = Await response.Content.ReadAsStringAsync()
        Using doc As JsonDocument = JsonDocument.Parse(json)
            If doc.RootElement.TryGetProperty("ParsedResults", results) Then
                For Each result In results.EnumerateArray()
                    If result.TryGetProperty("ParsedText", text) Then
                        pageTexts.Add(If(text.GetString(), String.Empty))
                    End If
                Next
            End If
        End Using

        Return pageTexts
    End Function
End Class
$vbLabelText   $csharpLabel

Podejście IronOCR

IronOCR odczytuje pliki PDF w sposób natywny. Ograniczeniem jest dostępna pamięć, a nie arbitralny próg rozmiaru pliku. Wynik w formacie PDF z możliwością wyszukiwania to przejrzyste pliki bez znaków wodnych, niezależnie od poziomu licencji:

//IronOCR PDF: native support, no size limit, no watermarks
public List<string> ExtractTextFromPdf(string pdfPath)
{
    var pageTexts = new List<string>();

    using var input = new OcrInput();
    input.LoadPdf(pdfPath); // Nie 5MB ceiling

    var result = new IronTesseract().Read(input);

    foreach (var page in result.Pages)
        pageTexts.Add(page.Text);

    return pageTexts;
}

// Generate searchable PDF — no watermarks
public void CreateSearchablePdf(string inputPath, string outputPath)
{
    var result = new IronTesseract().Read(inputPath);
    result.SaveAsSearchablePdf(outputPath);
}
//IronOCR PDF: native support, no size limit, no watermarks
public List<string> ExtractTextFromPdf(string pdfPath)
{
    var pageTexts = new List<string>();

    using var input = new OcrInput();
    input.LoadPdf(pdfPath); // Nie 5MB ceiling

    var result = new IronTesseract().Read(input);

    foreach (var page in result.Pages)
        pageTexts.Add(page.Text);

    return pageTexts;
}

// Generate searchable PDF — no watermarks
public void CreateSearchablePdf(string inputPath, string outputPath)
{
    var result = new IronTesseract().Read(inputPath);
    result.SaveAsSearchablePdf(outputPath);
}
Imports System.Collections.Generic

'IronOCR PDF: native support, no size limit, no watermarks
Public Function ExtractTextFromPdf(pdfPath As String) As List(Of String)
    Dim pageTexts As New List(Of String)()

    Using input As New OcrInput()
        input.LoadPdf(pdfPath) ' Nie 5MB ceiling

        Dim result = New IronTesseract().Read(input)

        For Each page In result.Pages
            pageTexts.Add(page.Text)
        Next
    End Using

    Return pageTexts
End Function

' Generate searchable PDF — no watermarks
Public Sub CreateSearchablePdf(inputPath As String, outputPath As String)
    Dim result = New IronTesseract().Read(inputPath)
    result.SaveAsSearchablePdf(outputPath)
End Sub
$vbLabelText   $csharpLabel

Chronione hasłem pliki PDF to pojedynczy parametr: input.LoadPdf("encrypted.pdf", Password: "secret"). Poradnik dotyczący OCR plików PDF szczegółowo omawia wybór zakresu stron oraz obsługę haseł. Wzory generowania plików PDF z możliwością wyszukiwania można znaleźć w poradniku dotyczącym plików PDF z możliwością wyszukiwania.

Obsługa błędów

OCR.space dostarcza błędów za pośrednictwem dwóch oddzielnych kanałów: kodów statusu HTTP i flagi JSON IsErroredOnProcessing. Kod produkcyjny musi sprawdzać oba, analizować tablicę błędów JSON, gdy IsErroredOnProcessing jest prawdą, i sprawdzać wartości FileParseExitCode na stronę dla częściowych błędów. Nic z tego nie jest typowane - wszystko to jest analizą ciągów znaków z JsonDocument:

// OCR.space: two error layers, all string-based
public async Task<string> SafeExtract(HttpClient client, string apiKey, string imagePath)
{
    try
    {
        byte[] imageBytes = File.ReadAllBytes(imagePath);
        string base64 = Convert.ToBase64String(imageBytes);

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("apikey", apiKey),
            new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
        });

        var response = await client.PostAsync("https://api.ocr.space/parse/image", content);

        if (!response.IsSuccessStatusCode)
        {
            switch (response.StatusCode)
            {
                case System.Net.HttpStatusCode.Unauthorized:
                    throw new Exception("Invalid API key");
                case System.Net.HttpStatusCode.TooManyRequests:
                    throw new Exception("Rate limit exceeded — wait and retry");
                case System.Net.HttpStatusCode.PaymentRequired:
                    throw new Exception("Quota exceeded — upgrade plan");
                default:
                    throw new Exception($"API error: {response.StatusCode}");
            }
        }

        // Second error layer: JSON-level failures
        string json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);

        if (doc.RootElement.TryGetProperty("IsErroredOnProcessing", out var isError)
            && isError.GetBoolean())
        {
            var messages = new List<string>();
            if (doc.RootElement.TryGetProperty("ErrorMessage", out var errors))
            {
                foreach (var e in errors.EnumerateArray())
                    messages.Add(e.GetString() ?? "Unknown error");
            }
            throw new Exception(string.Join("; ", messages));
        }

        // Third layer: per-page exit codes
        if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
        {
            foreach (var result in results.EnumerateArray())
            {
                if (result.TryGetProperty("FileParseExitCode", out var exitCode)
                    && exitCode.GetInt32() != 1)
                    throw new Exception($"Page parse failed: exit code {exitCode.GetInt32()}");
            }
        }

        return doc.RootElement
            .GetProperty("ParsedResults")[0]
            .GetProperty("ParsedText")
            .GetString() ?? string.Empty;
    }
    catch (JsonException ex)
    {
        throw new Exception($"Invalid API response: {ex.Message}");
    }
    catch (HttpRequestException ex)
    {
        throw new Exception($"Network error: {ex.Message}");
    }
}
// OCR.space: two error layers, all string-based
public async Task<string> SafeExtract(HttpClient client, string apiKey, string imagePath)
{
    try
    {
        byte[] imageBytes = File.ReadAllBytes(imagePath);
        string base64 = Convert.ToBase64String(imageBytes);

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("apikey", apiKey),
            new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
        });

        var response = await client.PostAsync("https://api.ocr.space/parse/image", content);

        if (!response.IsSuccessStatusCode)
        {
            switch (response.StatusCode)
            {
                case System.Net.HttpStatusCode.Unauthorized:
                    throw new Exception("Invalid API key");
                case System.Net.HttpStatusCode.TooManyRequests:
                    throw new Exception("Rate limit exceeded — wait and retry");
                case System.Net.HttpStatusCode.PaymentRequired:
                    throw new Exception("Quota exceeded — upgrade plan");
                default:
                    throw new Exception($"API error: {response.StatusCode}");
            }
        }

        // Second error layer: JSON-level failures
        string json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);

        if (doc.RootElement.TryGetProperty("IsErroredOnProcessing", out var isError)
            && isError.GetBoolean())
        {
            var messages = new List<string>();
            if (doc.RootElement.TryGetProperty("ErrorMessage", out var errors))
            {
                foreach (var e in errors.EnumerateArray())
                    messages.Add(e.GetString() ?? "Unknown error");
            }
            throw new Exception(string.Join("; ", messages));
        }

        // Third layer: per-page exit codes
        if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
        {
            foreach (var result in results.EnumerateArray())
            {
                if (result.TryGetProperty("FileParseExitCode", out var exitCode)
                    && exitCode.GetInt32() != 1)
                    throw new Exception($"Page parse failed: exit code {exitCode.GetInt32()}");
            }
        }

        return doc.RootElement
            .GetProperty("ParsedResults")[0]
            .GetProperty("ParsedText")
            .GetString() ?? string.Empty;
    }
    catch (JsonException ex)
    {
        throw new Exception($"Invalid API response: {ex.Message}");
    }
    catch (HttpRequestException ex)
    {
        throw new Exception($"Network error: {ex.Message}");
    }
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks

Public Class OCRSpace
    Public Async Function SafeExtract(client As HttpClient, apiKey As String, imagePath As String) As Task(Of String)
        Try
            Dim imageBytes As Byte() = File.ReadAllBytes(imagePath)
            Dim base64 As String = Convert.ToBase64String(imageBytes)

            Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
                New KeyValuePair(Of String, String)("apikey", apiKey),
                New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}")
            })

            Dim response As HttpResponseMessage = Await client.PostAsync("https://api.ocr.space/parse/image", content)

            If Not response.IsSuccessStatusCode Then
                Select Case response.StatusCode
                    Case System.Net.HttpStatusCode.Unauthorized
                        Throw New Exception("Invalid API key")
                    Case System.Net.HttpStatusCode.TooManyRequests
                        Throw New Exception("Rate limit exceeded — wait and retry")
                    Case System.Net.HttpStatusCode.PaymentRequired
                        Throw New Exception("Quota exceeded — upgrade plan")
                    Case Else
                        Throw New Exception($"API error: {response.StatusCode}")
                End Select
            End If

            ' Second error layer: JSON-level failures
            Dim json As String = Await response.Content.ReadAsStringAsync()
            Using doc As JsonDocument = JsonDocument.Parse(json)
                If doc.RootElement.TryGetProperty("IsErroredOnProcessing", ByRef isError) AndAlso isError.GetBoolean() Then
                    Dim messages As New List(Of String)()
                    If doc.RootElement.TryGetProperty("ErrorMessage", ByRef errors) Then
                        For Each e In errors.EnumerateArray()
                            messages.Add(e.GetString() ?? "Unknown error")
                        Next
                    End If
                    Throw New Exception(String.Join("; ", messages))
                End If

                ' Third layer: per-page exit codes
                If doc.RootElement.TryGetProperty("ParsedResults", ByRef results) Then
                    For Each result In results.EnumerateArray()
                        If result.TryGetProperty("FileParseExitCode", ByRef exitCode) AndAlso exitCode.GetInt32() <> 1 Then
                            Throw New Exception($"Page parse failed: exit code {exitCode.GetInt32()}")
                        End If
                    Next
                End If

                Return doc.RootElement _
                    .GetProperty("ParsedResults")(0) _
                    .GetProperty("ParsedText") _
                    .GetString() ?? String.Empty
            End Using
        Catch ex As JsonException
            Throw New Exception($"Invalid API response: {ex.Message}")
        Catch ex As HttpRequestException
            Throw New Exception($"Network error: {ex.Message}")
        End Try
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR generuje standardowe wyjątki .NET z jasnymi komunikatami. Bez analizowania JSON, bez zmiany kodów statusu HTTP, bez warstwowego wyodrębniania błędów:

// IronOCR: standard .NET exceptions
public string SafeExtract(string imagePath)
{
    try
    {
        var result = new IronTesseract().Read(imagePath);
        return result.Text;
    }
    catch (FileNotFoundException)
    {
        throw; // File not found — straightforward
    }
    catch (Exception ex) when (ex.Message.Contains("corrupt"))
    {
        throw new Exception($"Image file is corrupt: {imagePath}");
    }
    // Nie JSON errors. Nie HTTP errors. Nie rate limit errors.
}
// IronOCR: standard .NET exceptions
public string SafeExtract(string imagePath)
{
    try
    {
        var result = new IronTesseract().Read(imagePath);
        return result.Text;
    }
    catch (FileNotFoundException)
    {
        throw; // File not found — straightforward
    }
    catch (Exception ex) when (ex.Message.Contains("corrupt"))
    {
        throw new Exception($"Image file is corrupt: {imagePath}");
    }
    // Nie JSON errors. Nie HTTP errors. Nie rate limit errors.
}
Imports IronOcr

Public Function SafeExtract(imagePath As String) As String
    Try
        Dim result = New IronTesseract().Read(imagePath)
        Return result.Text
    Catch ex As FileNotFoundException
        Throw ' File not found — straightforward
    Catch ex As Exception When ex.Message.Contains("corrupt")
        Throw New Exception($"Image file is corrupt: {imagePath}")
    End Try
End Function
$vbLabelText   $csharpLabel

Referencja API IronTesseract i Referencja API OcrResult dokumentują typowaną strukturę wyników, w tym właściwość Confidence do oceny jakości ekstrakcji.

Przewodnik po mapowaniu API

Koncepcja OCR.space Odpowiednik IronOCR
POST https://api.ocr.space/parse/image new IronTesseract().Read(path)
FormUrlEncodedContent / MultipartFormDataContent OcrInput
base64Image parametr input.LoadImage(path) lub input.LoadImage(bytes)
language parametr ocr.Language = OcrLanguage.English
OCREngine parametr Silnik zarządzany wewnętrznie
isOverlayRequired parametr result.Words / result.Lines (zawsze dostępne)
isCreateSearchablePdf parametr result.SaveAsSearchablePdf(outputPath)
filetype=PDF parametr input.LoadPdf(path)
ParsedResults[0].ParsedText result.Text
ParsedResults[n] (na stronę) result.Pages[n].Text
IsErroredOnProcessing flaga JSON Standardowy wyjątek .NET Standard
FileParseExitCode flaga na stronę Standardowy wyjątek .NET Standard
ProcessingTimeInMilliseconds Implicite — nie wymaga dodatkowego parsowania
HTTP 429 / limit częstotliwości Nie dotyczy — brak limitów stawek
Niestandardowy OcrResult POCO (zdefiniowany przez użytkownika) IronOcr.OcrResult (dostarczone przez NuGet)
Niestandardowy OcrSpaceException (zdefiniowany przez użytkownika) Standardowe typy wyjątków .NET Standard
SemaphoreSlim (zbudowany przez użytkownika) ogranicznik stawki Nie jest potrzebne
Klucz API w każdym żądaniu IronOcr.License.LicenseKey (raz na starcie)

Kiedy zespoły rozważają przejście z OCR.space na IronOCR

Osiągnięcie limitu bezpłatnego pakietu podczas testów obciążeniowych

Limit 500 żądań dziennie na adres IP w serwisie OCR.space to twarda bariera, a nie miękka rekomendacja. Zespoły odkrywają to podczas testów obciążeniowych lub wdrożeń stagingowych, gdzie wielu programistów korzysta z tego samego adresu IP biura. Wspólny potok CI/CD, który uruchamia testy integracyjne na OCR.space, wyczerpie dzienny limit przed końcem dnia roboczego. W tym momencie aplikacja zawodzi nie dlatego, że kod jest błędny, ale dlatego, że licznik strony trzeciej osiągnął arbitralny próg. Przejście na przetwarzanie lokalne całkowicie eliminuje tę kategorię awarii — nie ma limitu do wyczerpania, ponieważ przetwarzanie odbywa się w ramach procesu.

Przeglądy zgodności i klasyfikacji danych

Oceny bezpieczeństwa, które klasyfikują dokumenty jako PII, PHI lub wrażliwe finansowo, stwarzają problem dla OCR.space: usługa nie ma ścieżki wdrożenia lokalnego. Nie ma udokumentowanego odpowiednika umowy o współpracy biznesowej (BAA) dla scenariuszy HIPAA, nie ma struktury umowy o przetwarzaniu danych, która jasno regulowałaby transfer danych w UE zgodnie z RODO, ani nie ma mechanizmu technicznego zapobiegającego przekazywaniu dokumentów, nawet przy istniejących kontrolach umownych. Zespoły, które otrzymały uwagi dotyczące zgodności w zakresie przetwarzania dokumentów przesyłanych w chmurze, potrzebują lokalnego rozwiązania.IronOCR spełnia ten wymóg już w samej swojej konstrukcji — dokumenty nigdy nie opuszczają serwera aplikacji. Strona licencyjna dokumentuje strukturę Licencji wieczystej, co również upraszcza dokumentację zgodności, eliminując z zakresu przegląd dostawcy usług w chmurze.

Obciążenie związane z utrzymaniem kodu integracyjnego

Integracje OCR.space generują dług techniczny proporcjonalnie do wymagań dotyczących funkcji. Początkowy klient HTTP jest łatwy w obsłudze. Następnie zespół dodaje logikę ponownej próby. Następnie dodają śledzenie limitów szybkości na adres IP, ponieważ wiele usług korzysta z tego samego adresu. Następnie dodają etap wstępnej walidacji rozmiaru pliku. Potem odkrywają, że struktura błędu JSON różni się w odpowiedziach silnika 1 i silnika 2, i dodają gałąź. Sześć miesięcy później integracja OCR.space to 400 linii kodu infrastruktury, które każdy nowy członek zespołu musi zrozumieć, zanim zajmie się czymkolwiek związanym z OCR. Kiedy zespół ocenia koszty utrzymania w porównaniu do jednorazowej licencji IronOCR$999, arytmetyka jest prosta.

Wymagania dotyczące struktury wyników

OCR zwraca zwykły tekst i nic więcej. W odpowiedzi nie ma współrzędnych słów, granic wierszy, podziału na akapity ani wyników pewności dla poszczególnych słów. Aplikacje, które muszą wyodrębnić określone pola z faktur — nazwę dostawcy w znanym regionie, całkowitą kwotę w prawym dolnym rogu — nie mają ustrukturyzowanej podstawy, na której mogłyby się oprzeć, korzystając z wyników OCR.space.IronOCR udostępnia pełną hierarchię dokumentu wraz ze współrzędnymi i wartościami pewności na każdym poziomie szczegółowości: stronach, akapitach, wierszach i poszczególnych słowach. Przetwarzanie oparte na regionach pozwala na skupienie się na określonych obszarach dokumentu bez konieczności przetwarzania całej strony. Wyniki odczytu instrukcji obsługi oraz przewodnik po OCR opartym na regionach szczegółowo omawiają te wzorce.

Wzrost wolumenu poza poziomem bezpłatnym

Przy 25 000 żądań miesięcznie bezpłatny poziom jest funkcjonalny w scenariuszach o niewielkim natężeniu ruchu. Poza tym stosuje się płatne poziomy — skontaktuj się z OCR.space, aby uzyskać aktualne ceny. Te koszty rosną w czasie, aż porównanie zostanie dokonane z licencją wieczystą $999, bez opłat za każde żądanie. Zespoły, które przewidują wzrost liczby dokumentów powyżej 25 000 miesięcznie, mają oczywiste uzasadnienie kosztowe dla przetwarzania lokalnego.

Typowe kwestie związane z migracją

Zastąpienie klienta HTTP wywołaniem metody

Migracja z OCR.space do IronOCR jest prosta pod względem architektury, ponieważ oba rozwiązania zwracają tekst z dokumentów. Klient HTTP, parser JSON, ogranicznik szybkości i niestandardowe typy wyjątków znikają. Zastąpi je pojedynczy pakiet NuGet i wywołania metod. Porównanie przed i po na granicy usługi:

// Before: OCR.space service (simplified — actual implementation is200+lines)
public class OcrSpaceService : IDisposable
{
    private readonly HttpClient _client = new();
    private readonly string _apiKey;

    public OcrSpaceService(string apiKey) => _apiKey = apiKey;

    public async Task<string> ProcessDocument(string path)
    {
        byte[] bytes = File.ReadAllBytes(path);
        string base64 = Convert.ToBase64String(bytes);

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("apikey", _apiKey),
            new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
        });

        var response = await _client.PostAsync("https://api.ocr.space/parse/image", content);
        response.EnsureSuccessStatusCode();

        string json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        return doc.RootElement.GetProperty("ParsedResults")[0].GetProperty("ParsedText").GetString()!;
    }

    public void Dispose() => _client.Dispose();
}

// After:IronOCR service — no HttpClient, no JSON, no API key
public class IronOcrService
{
    private readonly IronTesseract _ocr = new();

    public string ProcessDocument(string path)
    {
        return _ocr.Read(path).Text;
    }
    // Nie Dispose — no external connections to close
}
// Before: OCR.space service (simplified — actual implementation is200+lines)
public class OcrSpaceService : IDisposable
{
    private readonly HttpClient _client = new();
    private readonly string _apiKey;

    public OcrSpaceService(string apiKey) => _apiKey = apiKey;

    public async Task<string> ProcessDocument(string path)
    {
        byte[] bytes = File.ReadAllBytes(path);
        string base64 = Convert.ToBase64String(bytes);

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("apikey", _apiKey),
            new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
        });

        var response = await _client.PostAsync("https://api.ocr.space/parse/image", content);
        response.EnsureSuccessStatusCode();

        string json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        return doc.RootElement.GetProperty("ParsedResults")[0].GetProperty("ParsedText").GetString()!;
    }

    public void Dispose() => _client.Dispose();
}

// After:IronOCR service — no HttpClient, no JSON, no API key
public class IronOcrService
{
    private readonly IronTesseract _ocr = new();

    public string ProcessDocument(string path)
    {
        return _ocr.Read(path).Text;
    }
    // Nie Dispose — no external connections to close
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Collections.Generic
Imports System.Text.Json

Public Class OcrSpaceService
    Implements IDisposable

    Private ReadOnly _client As New HttpClient()
    Private ReadOnly _apiKey As String

    Public Sub New(apiKey As String)
        _apiKey = apiKey
    End Sub

    Public Async Function ProcessDocument(path As String) As Task(Of String)
        Dim bytes As Byte() = File.ReadAllBytes(path)
        Dim base64 As String = Convert.ToBase64String(bytes)

        Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
            New KeyValuePair(Of String, String)("apikey", _apiKey),
            New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}")
        })

        Dim response As HttpResponseMessage = Await _client.PostAsync("https://api.ocr.space/parse/image", content)
        response.EnsureSuccessStatusCode()

        Dim json As String = Await response.Content.ReadAsStringAsync()
        Using doc As JsonDocument = JsonDocument.Parse(json)
            Return doc.RootElement.GetProperty("ParsedResults")(0).GetProperty("ParsedText").GetString()
        End Using
    End Function

    Public Sub Dispose() Implements IDisposable.Dispose
        _client.Dispose()
    End Sub
End Class

Public Class IronOcrService
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessDocument(path As String) As String
        Return _ocr.Read(path).Text
    End Function
End Class
$vbLabelText   $csharpLabel

Implementacja IDisposable znika, ponieważ nie ma HttpClient do zarządzania.

Usunięcie klucza API

OCR.space wymaga klucza API w każdym żądaniu HTTP. Klucz ten musi być bezpiecznie przechowywany, zmieniany w przypadku ujawnienia oraz wykluczony z kontroli źródła.IronOCR wykorzystuje klucz licencyjny ustawiany jednorazowo podczas uruchamiania aplikacji, zazwyczaj pobierany ze zmiennej środowiskowej lub systemu konfiguracyjnego:

// At application startup — once, not per request
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// At application startup — once, not per request
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
' At application startup — once, not per request
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
$vbLabelText   $csharpLabel

Po migracji proces rotacji kluczy API, konfiguracja zarządzania sekretami oraz logika wstrzykiwania klucza na żądanie stają się zbędne.

Przetwarzanie wstępne po migracji

OCR.space stosuje przetwarzanie po stronie serwera przed zwrotem wyników, ale programiści nie mają kontroli nad tym, co to przetwarzanie obejmuje, a czego nie.IronOCR udostępnia jawne metody przetwarzania wstępnego. Jeśli jakość dokumentów budzi obawy — przekrzywione skany, fotokopie o niskim kontraście, zakłócone wydruki faksowe — proces przetwarzania wstępnego składa się z trzech do pięciu wywołań metod:

using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();
input.Binarize();
input.EnhanceResolution(300);
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();
input.Binarize();
input.EnhanceResolution(300);
var result = new IronTesseract().Read(input);
Imports IronOcr

Using input As New OcrInput()
    input.LoadImage("low-quality-scan.jpg")
    input.Deskew()
    input.DeNoise()
    input.Contrast()
    input.Binarize()
    input.EnhanceResolution(300)
    Dim result = New IronTesseract().Read(input)
End Using
$vbLabelText   $csharpLabel

Przewodnik po korekcji jakości obrazu oraz przewodnik po korekcji kolorów obrazu opisują każdy dostępny filtr wraz z przykładami pokazującymi różnice w dokładności przed i po zastosowaniu.

Asynchroniczne vs. synchroniczne

Wywołania OCR.space są z natury asynchroniczne, ponieważ obejmują komunikację HTTP w obie strony. Wywołania IronOCRsą domyślnie synchroniczne, co upraszcza kod w kontekstach innych niż internetowe. Dla ASP.NET i scenariuszy serwerowych wymagających nieblokującego wykonania,IronOCR wspiera asynchroniczne operacje za pośrednictwem owijania Task.Run lub bezpośredniego asynchronicznego API. Przewodnik po asynchronicznym OCR omawia te wzorce. Usunięcie await z metody usługi, która wcześniej czekała na zdalne wywołanie, upraszcza stos wywołań w kontekstach, gdzie asynchroniczność była przyjęta tylko dlatego, że OCR.space tego wymagał.

Dodatkowe możliwości IronOCR

Funkcje nieomówione w powyższych sekcjach, z których każda reprezentuje funkcjonalność nieposiadającą odpowiednika w OCR.space:

  • Eksport hOCR: result.SaveAsHocrFile("output.hocr") produkuje zgodne z normami wyjście hOCR dla kolejnych potoków przetwarzania dokumentów.
  • Śledzenie postępu: Obsługa zdarzeń postępu dla długotrwałych, wielostronicowych operacji wsadowych, umożliwiająca wyświetlanie pasków postępu i obliczanie przewidywanego czasu zakończenia (ETA) w aplikacjach UI.
  • Pobieranie tabel: Dostęp do ustrukturyzowanych danych tabel zapewnia model wyników, co pozwala na realizację takich zastosowań, jak pobieranie pozycji z faktur, wymagających wyrównania kolumn.
  • Specjalistyczne czytanie dokumentów: Strefy MRZ paszportowe, tablice rejestracyjne, linie czekowe MICR i rozpoznawanie pisma odręcznego to specjalnie zbudowane funkcje dostępne przez to samo API IronTesseract.

Zgodność z platformą .NET i gotowość na przyszłość

IronOCR jest przeznaczony dla platform .NET 6, .NET 7, .NET 8 i .NET 9, z aktywnym wsparciem dla każdej wersji LTS i STS. Biblioteka działa na systemach Windows x64, Windows x86,Linuxx64, macOS, Docker, AWS Lambda i Azure App Service — wszystko z tego samego pakietu NuGet, bez konfiguracji specyficznej dla danej platformy. OCR.space wymaga wychodzącego połączenia HTTPS z każdego środowiska wdrożeniowego, co wyklucza sieci typu odizolowane, restrykcyjne proxy korporacyjne oraz infrastrukturę, w której ruch wychodzący do zewnętrznych interfejsów API jest blokowany przez politykę bezpieczeństwa.IronOCR nie ma żadnych wymagań sieciowych w czasie wykonywania; działa całkowicie w ramach procesu. W miarę zbliżania się premiery .NET 10 pod koniec 2026 r. dotychczasowe osiągnięcia IronOCR w zakresie utrzymania kompatybilności między głównymi wersjami .NET zapewniają ciągłość bez konieczności wprowadzania zmian w integracji.

Wnioski

OCR.space zajmuje realną i użyteczną niszę: programiści, którzy muszą stworzyć prototyp funkcji OCR w ciągu weekendu, studenci zgłębiający koncepcje ekstrakcji tekstu lub narzędzia osobiste o niewielkim wolumenie poniżej 25 000 dokumentów miesięcznie, bez wymagań dotyczących zgodności. Dla tej grupy odbiorców bezpłatny poziom zapewnia prawdziwą wartość.

Problem polega na tym, że programiści .NET zazwyczaj nie tworzą prototypów w weekendy. Tworzą systemy fakturowania, procesory dokumentacji medycznej, potoki archiwizacji dokumentów oraz aplikacje biznesowe wymagające zgodności z przepisami. W tych kontekstach brak pakietu NuGet w OCR.space nie jest niedogodnością — jest to niezgodność strukturalna. Każda godzina poświęcona na tworzenie klienta HTTP, ogranicznika szybkości, parsera JSON i infrastruktury ponownych prób to godzina, której nie można poświęcić na rzeczywiste wymagania aplikacji. Koszt ten jest ponoszony na początku, ale koszty utrzymania są ponoszone przez cały okres trwania integracji.

IronOCR rozwiązuje konkretny problem, który charakteryzuje każdą integrację OCR.space: brak prawdziwego zestawu SDK. Jeden pakiet NuGet, jedno wywołanie metody, brak obsługi HTTP, brak limitów szybkości, brak przesyłania dokumentów na serwery zewnętrzne. Cena wejściowa $999 to jednorazowy koszt; powtarzające się koszty subskrypcji OCR.space przekroczą ją z czasem, zwłaszcza gdy rośnie wolumen. Dla zespołów z wymaganiami zgodności, lokalne przetwarzanie jest jedyną opcją bez względu na koszt.

Porównanie sprowadza się ostatecznie do jednego pytania: czy aplikacja wymaga OCR jako zewnętrznej zależności REST, czy jako funkcji biblioteki? W przypadku produkcyjnych aplikacji .NET odpowiedzią jest prawie zawsze to drugie.

Zwróć uwagęOCR.space i Tesseract są zarejestrowanymi znakami towarowymi ich właścicieli. Ta strona nie jest powiązana, zatwierdzana ani sponsorowana przez Google ani OCR.space. Wszystkie nazwy produktów, logo i marki są własnością ich odpowiednich właścicieli. Porównania mają charakter wyłącznie informacyjny i odzwierciedlają informacje dostępne publicznie w momencie pisania.

Często Zadawane Pytania

Czym jest API OCR.space?

OCR.space API to rozwiązanie OCR wykorzystywane przez programistów i przedsiębiorstwa do wyodrębniania tekstu z obrazów i dokumentów. Jest to jedna z kilku opcji OCR ocenianych obok IronOCR for .NET pod kątem tworzenia aplikacji .NET.

Jak IronOCR wypada w porównaniu z OCR.space API dla programistów .NET?

IronOCR to natywna dla NuGet biblioteka OCR dla platformy .NET, wykorzystująca IronTesseract jako główny silnik. W porównaniu z API OCR.space oferuje prostsze wdrożenie (bez instalatorów SDK), ryczałtowe ceny oraz przejrzyste API w języku C# bez interoperacyjności COM i zależności od chmury.

Czy IronOCR jest łatwiejszy w konfiguracji niż API OCR.space?

IronOCR instaluje się za pomocą jednego pakietu NuGet. Nie ma żadnych instalatorów SDK, plików licencyjnych do skopiowania, komponentów COM do zarejestrowania ani oddzielnych plików binarnych środowiska uruchomieniowego, którymi trzeba by zarządzać. Cały silnik OCR jest zawarty w pakiecie.

Jakie różnice w dokładności występują między API OCR.space a IronOCR?

IronOCR osiąga wysoką dokładność rozpoznawania standardowych dokumentów biznesowych, faktur, paragonów i zeskanowanych formularzy. W przypadku dokumentów o bardzo niskiej jakości lub rzadkich skryptów dokładność zależy od jakości źródła. IronOCR zawiera filtry wstępnego przetwarzania obrazu, które poprawiają rozpoznawanie danych wejściowych o niskiej jakości.

Czy IronOCR obsługuje wyodrębnianie tekstu z plików PDF?

Tak. IronOCR wyodrębnia tekst zarówno z natywnych plików PDF, jak i ze skanowanych obrazów PDF za pomocą jednego wywołania. Obsługuje również wielostronicowe pliki TIFF, obrazy i strumienie. W przypadku skanowanych plików PDF OCR jest stosowany strona po stronie, z obiektami wynikowymi dla każdej strony.

Jak wygląda licencjonowanie API OCR.space w porównaniu z IronOCR?

IronOCR korzysta z licencji wieczystej o stałej stawce, bez opłat za stronę lub skan. Organizacje przetwarzające duże ilości dokumentów płacą ten sam koszt licencji niezależnie od ilości. Szczegóły i ceny hurtowe znajdują się na stronie licencji IronOCR.

Jakie języki obsługuje IronOCR?

IronOCR obsługuje 127 języków za pośrednictwem oddzielnych pakietów językowych NuGet. Dodanie języka wymaga wykonania pojedynczego polecenia „dotnet add package IronOcr.Languages.{Language}”. Nie jest wymagane ręczne umieszczanie plików ani konfiguracja ścieżek.

Jak zainstalować IronOCR w projekcie .NET?

Instalacja przez NuGet: „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w CLI. Dodatkowe pakiety językowe instaluje się w ten sam sposób. Nie jest wymagany natywny instalator SDK.

Czy IronOCR nadaje się do wdrożeń w Dockerze i kontenerach, w przeciwieństwie do OCR.space?

Tak. IronOCR działa w kontenerach Docker za pośrednictwem pakietu NuGet. Klucz licencyjny jest ustawiany za pomocą zmiennej środowiskowej. Silnik OCR nie wymaga żadnych plików licencyjnych, ścieżek SDK ani montowania woluminów.

Czy mogę wypróbować IronOCR przed zakupem, w porównaniu z OCR.space?

Tak. Tryb próbny IronOCR przetwarza dokumenty i zwraca wyniki OCR z nakładką znaku wodnego na wyjściu. Przed zakupem licencji można sprawdzić dokładność na własnych dokumentach.

Czy IronOCR obsługuje odczytywanie kodów kreskowych oprócz wyodrębniania tekstu?

IronOCR koncentruje się na wyodrębnianiu tekstu i OCR. Do odczytu kodów kreskowych firma Iron Software udostępnia bibliotekę IronBarcode jako dodatek. Obie biblioteki są dostępne osobno lub w ramach pakietu Iron Suite.

Czy łatwo jest przejść z API OCR.space na IronOCR?

Migracja z API OCR.space do IronOCR zazwyczaj polega na zastąpieniu sekwencji inicjalizacyjnych instancjonowaniem IronTesseract, usunięciu zarządzania cyklem życia COM oraz aktualizacji wywołań API. Większość migracji znacznie zmniejsza złożoność kodu.

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