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ą
FormUrlEncodedContentlubMultipartFormDataContenti 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
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 IronOcrto kompletna instalacja. Nie ma potrzeby zarządzania natywnymi plikami binarnymi, katalogami tessdata ani zmiennymi środowiskowymi. - Silnie typowane API:
IronTesseract,OcrInput,OcrResult, orazCropRectanglesą 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
IronTesseractdział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
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);
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
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
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
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
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
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
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
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")
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
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.
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.

