Jak naprawić kolory obrazu do odczytu w C#
Ten przewodnik przeprowadza programistów .NET przez proces zastąpienia integracji REST API OCR.space biblioteką IronOCR, natywną biblioteką .NET dostarczaną jako pojedynczy pakiet NuGet. Obejmuje to zamianę pakietów, czyszczenie przestrzeni nazw oraz cztery konkretne scenariusze migracji kodu, które są specyficzne dla przejścia z REST na lokalny: eliminację przesyłania wieloczęściowego, usunięcie kodowania base64, wymianę silnika OCR oraz ekstrakcję danych strukturalnych. Programiści, którzy przeczytali artykuł porównawczy z fazy 1, zauważą, że niniejszy przewodnik skupia się na mechanicznych etapach samej migracji, a nie na porównaniu funkcji.
Dlaczego warto przejść z OCR.space
OCR.space wypełnia prawdziwą niszę: bezpłatne eksperymenty dla programistów, którzy chcą przetestować OCR w ciągu jednego popołudnia bez konieczności instalowania czegokolwiek. Problem polega na tym, że bezpłatny poziom jest przeznaczony do tworzenia prototypów, a nie do produkcji. Gdy aplikacja .NET zaczyna obsługiwać duże ilości dokumentów, musi spełniać wymogi zgodności lub jest tworzona przez zespół, wszystkie cechy integracji OCR.space działają na niekorzyść aplikacji.
Brak pakietu NuGet oznacza brak SDK i IntelliSense. OCR.space udostępnia punkt końcowy REST oraz dokumentację. Integracja z platformą .NET — tworzenie klienta HTTP, serializacja żądań, deserializacja odpowiedzi, obsługa błędów i logika ponownych prób — leży całkowicie w gestii programisty. Nie jest to drobna niedogodność. Minimalny klient to ponad 80 linii kodu infrastruktury przed napisaniem pierwszej metody logiki biznesowej. Kod ten jest niezmienny we wszystkich integracjach OCR.space w każdym kodzie źródłowym .NET, co z czasem powoduje gromadzenie się błędów i obciążenie związane z utrzymaniem.
Limity szybkości nakładają sztuczne ograniczenia na aplikacje produkcyjne. W ramach bezpłatnego planu obowiązuje limit 60 żądań na minutę i 500 żądań dziennie na adres IP. Oba ograniczenia są nieprzekraczalne. Aplikacja, która przekroczy 500 żądań między północą a następną północą, otrzymuje odpowiedzi o błędzie do momentu zresetowania licznika. Systemy produkcyjne działające w wspólnych sieciach biurowych lub wspólnych środowiskach CI/CD mogą wyczerpać dzienny limit jeszcze przed końcem godzin pracy.
Dokumenty opuszczają infrastrukturę użytkownika przy każdym wywołaniu. OCR.space nie oferuje opcji wdrożenia lokalnego. Każde zgłoszenie przekazuje dokument — faktury, dokumentację medyczną, umowy, dokumenty tożsamości — do serwerów w chmurze OCR.space. HIPAA, RODO oraz wewnętrzne zasady klasyfikacji danych, które zabraniają przekazywania poufnych dokumentów stronom trzecim, sprawiają, że OCR.space jest architektonicznie niekompatybilny, niezależnie od kontroli umownych.
Wersja bezpłatna generuje pliki PDF z możliwością wyszukiwania, opatrzone znakiem wodnym. Aplikacje, których wynikiem jest plik PDF z możliwością wyszukiwania — systemy archiwizacji dokumentów, platformy zapewniające zgodność z przepisami, portale dokumentów dla klientów — nie mogą korzystać z bezpłatnej wersji OCR.space w tym celu. Znak wodny jest osadzony w pliku PDF i nie można go usunąć bez wykupienia płatnego planu.
**Ceny subskrypcji rosną wraz z wielkością; poziom PRO OCR.space przy $144 rocznie przekracza wieczysta cene wejscia IronOCR$999 przed szesnastym rokiem. Zespoly, ktore przewiduja wzrost wolumenu dokumentow poza progiem poziomu darmowego, stoja w obliczu narastajacych kosztow subskrypcji w porownaniu z stala wieczysta licencja. Licencja $999 Lite pokrywa jednego dewelopera i jedno miejsce wdrozenia bez oplat za zadanie przy dowolnym wolumenie. Szczegóły dotyczące poziomów licencji można znaleźć na stronie licencyjnej IronOCR.
Podstawowy problem
OCR.space wymaga zbudowania kompletnego klienta HTTP przed przetworzeniem pojedynczego dokumentu:
// OCR.space: 80+ lines of infrastructure before business logic
public class OcrSpaceApiClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly SemaphoreSlim _rateLimiter; // You implement this
public OcrSpaceApiClient(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(120);
_rateLimiter = new SemaphoreSlim(60, 60); // Free tier: 60/min
}
// ... 70+ more lines of HTTP plumbing follow
}
// OCR.space: 80+ lines of infrastructure before business logic
public class OcrSpaceApiClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly SemaphoreSlim _rateLimiter; // You implement this
public OcrSpaceApiClient(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(120);
_rateLimiter = new SemaphoreSlim(60, 60); // Free tier: 60/min
}
// ... 70+ more lines of HTTP plumbing follow
}
Imports System
Imports System.Net.Http
Imports System.Threading
' OCR.space: 80+ lines of infrastructure before business logic
Public Class OcrSpaceApiClient
Implements IDisposable
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _apiKey As String
Private ReadOnly _rateLimiter As SemaphoreSlim ' You implement this
Public Sub New(apiKey As String)
_httpClient = New HttpClient()
_httpClient.Timeout = TimeSpan.FromSeconds(120)
_rateLimiter = New SemaphoreSlim(60, 60) ' Free tier: 60/min
End Sub
' ... 70+ more lines of HTTP plumbing follow
Public Sub Dispose() Implements IDisposable.Dispose
_httpClient.Dispose()
_rateLimiter.Dispose()
End Sub
End Class
IronOCR to pakiet NuGet. Cały tekst zlecenia został już napisany:
// IronOCR: no client to build, no rate limiter to manage
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var result = new IronTesseract().Read("document.jpg");
Console.WriteLine(result.Text);
// IronOCR: no client to build, no rate limiter to manage
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var result = new IronTesseract().Read("document.jpg");
Console.WriteLine(result.Text);
Imports IronOcr
' IronOCR: no client to build, no rate limiter to manage
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim result = New IronTesseract().Read("document.jpg")
Console.WriteLine(result.Text)
IronOCR vs OCR.space: Porównanie funkcji
Poniższa tabela przedstawia bezpośrednie odpowiedniki pojęć i ograniczeń OCR.space w IronOCR.
| Funkcja | OCR.space | IronOCR |
|---|---|---|
| Pakiet NuGet | Brak — tylko REST API | IronOcr — natywny .NET |
| SDK / IntelliSense | Brak — ręczny JSON | Pełne — typowane API |
| Wymagane są modele niestandardowe | Nie | Nie |
| Miejsce przetwarzania | Serwery w chmurze OCR.space | Lokalne — w trakcie realizacji |
| Zależność od Internetu | Wymagane dla każdego wywołania | None |
| Wdrożenie w środowisku izolowanym | Nieobsługiwane | W pełni obsługiwane |
| Limity szybkości | 60/min, 500/dzień (bezpłatnie) | None |
| Limit rozmiaru pliku | 5 MB (poziom bezpłatny) | Dostępna pamięć |
| Plik wejściowy PDF | Tak (ograniczony, 5 MB) | Tak — tłumaczenie wykonane przez native speakera, bez ograniczeń co do objętości |
| Wynik w formacie PDF z możliwością wyszukiwania | Znaczek wodny w wersji darmowej | Czysty wynik, wszystkie poziomy |
| Automatyczne przetwarzanie wstępne | Po stronie serwera, bez kontroli programisty | Wyrównanie, usuwanie szumów, kontrast, binarizacja, wyostrzanie |
| 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 | Nieobsługiwane | Tak — OcrLanguage.French + OcrLanguage.German |
| Struktura wyjściowa (słowa, wiersze) | Tylko zwykły tekst | Strony, akapity, wiersze, słowa z współrzędnymi |
| Wskaźniki pewności na poziomie słów | Niedostępne | Tak — word.Confidence |
| OCR oparte na regionie | Nieobsługiwane | Tak — CropRectangle |
| Odczytywanie BarCode | Nieobsługiwane | Tak — ReadBarCodes = true |
| Generowanie plików PDF z możliwością wyszukiwania | Z znakiem wodnym (bezpłatna), bez znaku wodnego (płatna) | Czysty wynik — wszystkie poziomy licencji |
| Zgodność z HIPAA / RODO | Ryzyko — dane przesyłane na zewnątrz | Tak — brak zewnętrznej transmisji danych |
| Model cenowy | Miesięczna subskrypcja | Jednorazowa, wieczysta |
| Cena wywoławcza | 12 USD/miesiąc (144 USD/rok) | $999 jednorazowy |
| Kompatybilność z platformą .NET | HttpClient — dowolny .NET |
.NET 4.6.2+, .NET 5/6/7/8/9 |
| Wdrażanie na wielu platformach | Wymagane połączenie z Internetem | Windows, Linux, macOS, Docker, Azure, AWS |
Szybki start: Migracja z OCR.space do IronOCR
Krok 1: Zastąp pakiet NuGet
OCR.space nie posiada pakietu NuGet, który można odinstalować. Usun caly kod infrastrukturalny zwiazany z OCR.space z projektu: klase opakowujaca HttpClient, ograniczenie szybkosci SemaphoreSlim, modele wynikow niestandardowych i typy wyjatkow niestandardowych. Wszystkie one sa zastepowane przez pakiet NuGet IronOCR.
Zainstaluj IronOCR ze strony IronOCR NuGet:
dotnet add package IronOcr
Krok 2: Aktualizacja przestrzeni nazw
Usuń przestrzenie nazw OCR.space HTTP i JSON. Dodaj przestrzeń nazw IronOCR:
// Before (OCR.space — manually written infrastructure)
using System.Net.Http;
using System.Text.Json;
using System.Threading;
// After (IronOCR)
using IronOcr;
// Before (OCR.space — manually written infrastructure)
using System.Net.Http;
using System.Text.Json;
using System.Threading;
// After (IronOCR)
using IronOcr;
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports IronOcr
Krok 3: Inicjalizacja licencji
Dodaj inicjalizację licencji raz podczas uruchamiania aplikacji — nie przy każdym żądaniu:
// Program.cs or application startup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Program.cs or application startup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Przykłady migracji kodu
Zastąpienie przesyłania plików za pomocą MultipartFormDataContent
OCR.space wymaga budowania MultipartFormDataContent z bajtami pliku i kluczem API, nastepnie wysylania POST-em do koncowego punktu w chmurze. Dokument opuszcza infrastrukturę przy każdym wywołaniu.
Podejście OCR.space:
// MultipartFormDataContent: file upload to cloud on every request
public async Task<string> UploadAndExtract(string imagePath)
{
using var content = new MultipartFormDataContent();
var imageBytes = File.ReadAllBytes(imagePath);
// Document is transmitted to OCR.space servers here
content.Add(new ByteArrayContent(imageBytes), "file", Path.GetFileName(imagePath));
content.Add(new StringContent(_apiKey), "apikey");
content.Add(new StringContent("eng"), "language");
content.Add(new StringContent("2"), "OCREngine"); // Select Engine 2
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
// Navigate JSON tree manually — no typed result
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
// MultipartFormDataContent: file upload to cloud on every request
public async Task<string> UploadAndExtract(string imagePath)
{
using var content = new MultipartFormDataContent();
var imageBytes = File.ReadAllBytes(imagePath);
// Document is transmitted to OCR.space servers here
content.Add(new ByteArrayContent(imageBytes), "file", Path.GetFileName(imagePath));
content.Add(new StringContent(_apiKey), "apikey");
content.Add(new StringContent("eng"), "language");
content.Add(new StringContent("2"), "OCREngine"); // Select Engine 2
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
// Navigate JSON tree manually — no typed result
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class YourClassName
Private _apiKey As String
Private _httpClient As HttpClient
Public Async Function UploadAndExtract(imagePath As String) As Task(Of String)
Using content As New MultipartFormDataContent()
Dim imageBytes = File.ReadAllBytes(imagePath)
' Document is transmitted to OCR.space servers here
content.Add(New ByteArrayContent(imageBytes), "file", Path.GetFileName(imagePath))
content.Add(New StringContent(_apiKey), "apikey")
content.Add(New StringContent("eng"), "language")
content.Add(New StringContent("2"), "OCREngine") ' Select Engine 2
Dim response = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)
response.EnsureSuccessStatusCode()
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc = JsonDocument.Parse(json)
' Navigate JSON tree manually — no typed result
Return doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("ParsedText") _
.GetString() OrElse String.Empty
End Using
End Using
End Function
End Class
Podejście IronOCR:
// OcrInput replaces the entire upload + JSON pipeline
public string ExtractFromFile(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath); // Stays local — no network call
var ocr = new IronTesseract();
var result = ocr.Read(input);
return result.Text; // Typed property — no JSON navigation
}
// OcrInput replaces the entire upload + JSON pipeline
public string ExtractFromFile(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath); // Stays local — no network call
var ocr = new IronTesseract();
var result = ocr.Read(input);
return result.Text; // Typed property — no JSON navigation
}
Imports IronTesseract
Public Function ExtractFromFile(ByVal imagePath As String) As String
Using input As New OcrInput()
input.LoadImage(imagePath) ' Stays local — no network call
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
Return result.Text ' Typed property — no JSON navigation
End Using
End Function
OcrInput to lokalny zastepnik dla MultipartFormDataContent. Obsługuje ścieżki plików, tablice bajtów, strumienie i wielostronicowe pliki TIFF za pośrednictwem spójnego interfejsu API. HttpClient, wstawianie kluczy API i nawigacja po JSON zupelnie znikaja. Instrukcja dotycząca wprowadzania obrazów obejmuje wszystkie obsługiwane formaty wejściowe.
Eliminacja kodowania Base64
Kiedy integracje OCR.space uzywaja parametru base64Image w formularzu zamiast parametru przesylania pliku, kod czyta plik do bajtow, koduje do Base64, buduje lancuch URI danych i osadza go w FormUrlEncodedContent.IronOCR akceptuje surowe bajty bezpośrednio, bez etapu kodowania.
Podejście OCR.space:
// base64Image parameter: read → encode → embed in form → POST → parse
public async Task<string> ExtractViaBase64(string imagePath)
{
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
string base64Image = Convert.ToBase64String(imageBytes); // Mandatory encoding step
// Embed as data URI — adds 33% overhead to payload size
string mimeType = "image/png";
string dataUri = $"data:{mimeType};base64,{base64Image}";
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", dataUri),
new KeyValuePair<string, string>("language", "eng"),
new KeyValuePair<string, string>("isOverlayRequired", "false")
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", formContent);
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
// base64Image parameter: read → encode → embed in form → POST → parse
public async Task<string> ExtractViaBase64(string imagePath)
{
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
string base64Image = Convert.ToBase64String(imageBytes); // Mandatory encoding step
// Embed as data URI — adds 33% overhead to payload size
string mimeType = "image/png";
string dataUri = $"data:{mimeType};base64,{base64Image}";
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", dataUri),
new KeyValuePair<string, string>("language", "eng"),
new KeyValuePair<string, string>("isOverlayRequired", "false")
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", formContent);
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
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class ImageProcessor
Private _apiKey As String
Private _httpClient As HttpClient
Public Sub New(apiKey As String, httpClient As HttpClient)
_apiKey = apiKey
_httpClient = httpClient
End Sub
' base64Image parameter: read → encode → embed in form → POST → parse
Public Async Function ExtractViaBase64(imagePath As String) As Task(Of String)
Dim imageBytes As Byte() = Await File.ReadAllBytesAsync(imagePath)
Dim base64Image As String = Convert.ToBase64String(imageBytes) ' Mandatory encoding step
' Embed as data URI — adds 33% overhead to payload size
Dim mimeType As String = "image/png"
Dim dataUri As String = $"data:{mimeType};base64,{base64Image}"
Dim formContent As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", _apiKey),
New KeyValuePair(Of String, String)("base64Image", dataUri),
New KeyValuePair(Of String, String)("language", "eng"),
New KeyValuePair(Of String, String)("isOverlayRequired", "false")
})
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", formContent)
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
Return doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("ParsedText") _
.GetString() OrElse String.Empty
End Using
End Function
End Class
Podejście IronOCR:
// LoadImage(bytes): raw bytes accepted directly — no encoding
public string ExtractFromBytes(byte[] imageBytes)
{
using var input = new OcrInput();
input.LoadImage(imageBytes); // Nie Base64, no data URI, no overhead
var result = new IronTesseract().Read(input);
return result.Text;
}
// LoadImage(bytes): raw bytes accepted directly — no encoding
public string ExtractFromBytes(byte[] imageBytes)
{
using var input = new OcrInput();
input.LoadImage(imageBytes); // Nie Base64, no data URI, no overhead
var result = new IronTesseract().Read(input);
return result.Text;
}
Imports IronOcr
Public Function ExtractFromBytes(imageBytes As Byte()) As String
Using input As New OcrInput()
input.LoadImage(imageBytes) ' Nie Base64, no data URI, no overhead
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function
Etap kodowania Base64 nie występuje w IronOCR, ponieważ nie ma warstwy transportowej HTTP. Surowe bajty trafiaja bezposrednio do OcrInput.LoadImage(). Znika również obciążenie związane z URI danych — kodowanie Base64 zwiększa rozmiar ładunku o około 33%. Przewodnik po wejsciu strumieniowym pokazuje ten sam wzorzec dla wejsci Stream, co jest przydatne, gdy bajty pochodza z obslugi przesylan lub bufora pamieci, zamiast z pliku.
Zastąpienie wyboru silnika OCR przetwarzaniem wstępnym obrazu
OCR.space udostepnia dwa silniki OCR poprzez parametr formularza OCREngine: Silnik 1 jest szybszy, ale z nizsza dokladnoscia przy zlozonych schematach; Silnik 2 działa wolniej, ale zapewnia wyższą dokładność w przypadku większości typów dokumentów. Programiści wybierają silnik dla każdego wywołania w oparciu o cechy dokumentu.IronOCR korzysta z jednego zoptymalizowanego silnika Tesseract 5, ale udostępnia wyraźne filtry przetwarzania wstępnego, które zajmują się podstawową przyczyną — jakością dokumentu — zamiast przełączać się między trybami silnika.
Podejście OCR.space:
// OCREngine parameter: binary choice, no control over why accuracy differs
public async Task<string> ExtractWithEngineSelection(
string imagePath,
bool useHighAccuracyEngine = true)
{
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var formContent = 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"),
// Engine 1 = faster, Engine 2 = higher accuracy — binary choice only
new KeyValuePair<string, string>("OCREngine", useHighAccuracyEngine ? "2" : "1"),
new KeyValuePair<string, string>("scale", "true"),
new KeyValuePair<string, string>("detectOrientation", "true")
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", formContent);
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
// OCREngine parameter: binary choice, no control over why accuracy differs
public async Task<string> ExtractWithEngineSelection(
string imagePath,
bool useHighAccuracyEngine = true)
{
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var formContent = 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"),
// Engine 1 = faster, Engine 2 = higher accuracy — binary choice only
new KeyValuePair<string, string>("OCREngine", useHighAccuracyEngine ? "2" : "1"),
new KeyValuePair<string, string>("scale", "true"),
new KeyValuePair<string, string>("detectOrientation", "true")
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", formContent);
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.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class OCRService
Private _apiKey As String
Private _httpClient As HttpClient
Public Sub New(apiKey As String, httpClient As HttpClient)
_apiKey = apiKey
_httpClient = httpClient
End Sub
' OCREngine parameter: binary choice, no control over why accuracy differs
Public Async Function ExtractWithEngineSelection(imagePath As String, Optional useHighAccuracyEngine As Boolean = True) As Task(Of String)
Dim imageBytes As Byte() = Await File.ReadAllBytesAsync(imagePath)
Dim base64 As String = Convert.ToBase64String(imageBytes)
Dim formContent 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}"),
New KeyValuePair(Of String, String)("language", "eng"),
' Engine 1 = faster, Engine 2 = higher accuracy — binary choice only
New KeyValuePair(Of String, String)("OCREngine", If(useHighAccuracyEngine, "2", "1")),
New KeyValuePair(Of String, String)("scale", "true"),
New KeyValuePair(Of String, String)("detectOrientation", "true")
})
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", formContent)
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
Return doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("ParsedText") _
.GetString() OrElse String.Empty
End Using
End Function
End Class
Podejście IronOCR:
// Preprocessing pipeline: fix the document, not the engine selection
public string ExtractWithPreprocessing(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
// Apply filters that match the document's specific quality issues
input.Deskew(); // Correct rotation — replaces detectOrientation
input.DeNoise(); // Remove noise from fax/photocopier artifacts
input.Contrast(); // Enhance contrast on low-quality scans
input.Scale(200); // Upscale small or low-DPI images
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%"); // Nie equivalent in OCR.space
return result.Text;
}
// Preprocessing pipeline: fix the document, not the engine selection
public string ExtractWithPreprocessing(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
// Apply filters that match the document's specific quality issues
input.Deskew(); // Correct rotation — replaces detectOrientation
input.DeNoise(); // Remove noise from fax/photocopier artifacts
input.Contrast(); // Enhance contrast on low-quality scans
input.Scale(200); // Upscale small or low-DPI images
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%"); // Nie equivalent in OCR.space
return result.Text;
}
Imports IronOcr
Public Function ExtractWithPreprocessing(imagePath As String) As String
Using input As New OcrInput()
input.LoadImage(imagePath)
' Apply filters that match the document's specific quality issues
input.Deskew() ' Correct rotation — replaces detectOrientation
input.DeNoise() ' Remove noise from fax/photocopier artifacts
input.Contrast() ' Enhance contrast on low-quality scans
input.Scale(200) ' Upscale small or low-DPI images
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
Console.WriteLine($"Confidence: {result.Confidence}%") ' Nie equivalent in OCR.space
Return result.Text
End Using
End Function
Parametr OCREngine OCR.space jest proxy dla jakosci dokumentu — gdy Silnik 1 zawiedzie na dokumencie, deweloperzy przechodza na Silnik 2, majac nadzieje, ze inny algorytm zrekompensuje. Przetwarzanie wstepne IronOCR rozwiazuje problem jakosci bezposrednio: Deskew() koryguje przechylone skany, DeNoise() radzi sobie z artefaktami faksowymi, a Contrast() odzyskuje tekst z kserokopii o niskim kontraście. Wlasciwosc Confidence wynikow kwantyfikuje jakosc ekstrakcji, co OCREngine przelaczanie nie moze zapewnic. Przewodnik po korekcji jakosci obrazu i asystent filtrow dokumentuja wplyw kazdego filtru na rozne typy dokumentow.
Wielojęzyczne OCR bez konieczności zmiany języka przy każdym wywołaniu
OCR.space akceptuje jeden parametr language na kazde wywolanie API. Dokumenty zawierające różne języki wymagają oddzielnych zleceń dla każdego języka, a wyniki należy połączyć ręcznie.IronOCR przetwarza jednoczesnie wiele jezykow w jednej operacji odczytu uzywajac operatora + na wartosciach OcrLanguage.
Podejście OCR.space:
// OCR.space: one language per call — multi-language requires multiple requests
public async Task<string> ExtractMultiLanguage(string imagePath)
{
// First pass: English
string englishText = await ExtractWithLanguage(imagePath, "eng");
// Second pass: French (consumes another rate-limit slot, another API call)
string frenchText = await ExtractWithLanguage(imagePath, "fre");
// Manually merge results — no way to know which text belongs to which language
return $"{englishText}\n{frenchText}";
}
private async Task<string> ExtractWithLanguage(string imagePath, string langCode)
{
byte[] imageBytes = await File.ReadAllBytesAsync(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", langCode) // One language per call
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
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: one language per call — multi-language requires multiple requests
public async Task<string> ExtractMultiLanguage(string imagePath)
{
// First pass: English
string englishText = await ExtractWithLanguage(imagePath, "eng");
// Second pass: French (consumes another rate-limit slot, another API call)
string frenchText = await ExtractWithLanguage(imagePath, "fre");
// Manually merge results — no way to know which text belongs to which language
return $"{englishText}\n{frenchText}";
}
private async Task<string> ExtractWithLanguage(string imagePath, string langCode)
{
byte[] imageBytes = await File.ReadAllBytesAsync(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", langCode) // One language per call
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
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.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class OCRSpace
Private _apiKey As String
Private _httpClient As HttpClient
Public Async Function ExtractMultiLanguage(imagePath As String) As Task(Of String)
' First pass: English
Dim englishText As String = Await ExtractWithLanguage(imagePath, "eng")
' Second pass: French (consumes another rate-limit slot, another API call)
Dim frenchText As String = Await ExtractWithLanguage(imagePath, "fre")
' Manually merge results — no way to know which text belongs to which language
Return $"{englishText}{vbLf}{frenchText}"
End Function
Private Async Function ExtractWithLanguage(imagePath As String, langCode As String) As Task(Of String)
Dim imageBytes As Byte() = Await File.ReadAllBytesAsync(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", langCode) ' One language per call
})
Dim response = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)
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
Podejście IronOCR:
// IronOCR: multiple languages in a single read — one pass, correct output
public string ExtractMultiLanguage(string imagePath)
{
var ocr = new IronTesseract();
// Combine languages with + operator — processed simultaneously
ocr.Language = OcrLanguage.English + OcrLanguage.French + OcrLanguage.German;
var result = ocr.Read(imagePath);
return result.Text; // Correctly interleaved multilingual output
}
// IronOCR: multiple languages in a single read — one pass, correct output
public string ExtractMultiLanguage(string imagePath)
{
var ocr = new IronTesseract();
// Combine languages with + operator — processed simultaneously
ocr.Language = OcrLanguage.English + OcrLanguage.French + OcrLanguage.German;
var result = ocr.Read(imagePath);
return result.Text; // Correctly interleaved multilingual output
}
Imports IronOcr
Public Function ExtractMultiLanguage(imagePath As String) As String
Dim ocr As New IronTesseract()
' Combine languages with + operator — processed simultaneously
ocr.Language = OcrLanguage.English + OcrLanguage.French + OcrLanguage.German
Dim result = ocr.Read(imagePath)
Return result.Text ' Correctly interleaved multilingual output
End Function
Ograniczenie OCR.space dotyczące jednego języka na wywołanie zmusza programistów do wykonania N wywołań API dla dokumentu w N językach i zgadywania, jak pogodzić wyniki.IronOCRłączy modele językowe w jednym przebiegu silnika, co pozwala uzyskać poprawnie przeplatane dane wyjściowe bez konieczności przetwarzania końcowego. Paczkie jezykowe instaluja sie jako pakiety NuGet — IronOcr.Languages.French, IronOcr.Languages.German i tak dalej — i dzialaja offline. Jak-to "wiele jezykow" obejmuje instalacje paczek i skladnie operatora + dla wszystkich 125+ obsługiwanych jezykow.
Pobieranie danych ustrukturyzowanych za pomocą współrzędnych słów WORD
OCR.space zwraca zwykly tekst z ParsedResults[0].ParsedText. Brak danych na poziomie WORD, ramek ograniczających, granic wierszy oraz wyników pewności dla poszczególnych elementów. Aplikacje, które muszą zlokalizować konkretne pola — datę w prawym górnym rogu faktury, sumę w prawej dolnej komórce tabeli — nie mają ustrukturyzowanej podstawy, na której mogłyby się oprzeć, korzystając z odpowiedzi OCR.space.IronOCR zapewnia pełną hierarchię dokumentu: strony, akapity, wiersze, słowa i znaki, z których każdy posiada współrzędne pikselowe i wskaźniki pewności.
Podejście OCR.space:
// OCR.space: plain text only — no structure, no coordinates
public async Task<string> ExtractInvoiceFields(string invoicePath)
{
byte[] invoiceBytes = await File.ReadAllBytesAsync(invoicePath);
string base64 = Convert.ToBase64String(invoiceBytes);
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>("filetype", "PDF"),
new KeyValuePair<string, string>("language", "eng"),
// isOverlayRequired=true returns word boxes, but only as raw JSON coordinates
new KeyValuePair<string, string>("isOverlayRequired", "true")
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
string json = await response.Content.ReadAsStringAsync();
// Navigate deeply-nested JSON to find word boxes — no typed models
using var doc = JsonDocument.Parse(json);
var overlay = doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("TextOverlay");
// Parse word coordinate arrays manually — fragile JSON path traversal
var wordData = new List<(string word, int x, int y)>();
foreach (var line in overlay.GetProperty("Lines").EnumerateArray())
{
foreach (var word in line.GetProperty("Words").EnumerateArray())
{
string wordText = word.GetProperty("WordText").GetString() ?? "";
int left = word.GetProperty("Left").GetInt32();
int top = word.GetProperty("Top").GetInt32();
wordData.Add((wordText, left, top));
}
}
// Reconstruct full text from raw JSON — still no typed result
return string.Join(" ", wordData.Select(w => w.word));
}
// OCR.space: plain text only — no structure, no coordinates
public async Task<string> ExtractInvoiceFields(string invoicePath)
{
byte[] invoiceBytes = await File.ReadAllBytesAsync(invoicePath);
string base64 = Convert.ToBase64String(invoiceBytes);
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>("filetype", "PDF"),
new KeyValuePair<string, string>("language", "eng"),
// isOverlayRequired=true returns word boxes, but only as raw JSON coordinates
new KeyValuePair<string, string>("isOverlayRequired", "true")
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
string json = await response.Content.ReadAsStringAsync();
// Navigate deeply-nested JSON to find word boxes — no typed models
using var doc = JsonDocument.Parse(json);
var overlay = doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("TextOverlay");
// Parse word coordinate arrays manually — fragile JSON path traversal
var wordData = new List<(string word, int x, int y)>();
foreach (var line in overlay.GetProperty("Lines").EnumerateArray())
{
foreach (var word in line.GetProperty("Words").EnumerateArray())
{
string wordText = word.GetProperty("WordText").GetString() ?? "";
int left = word.GetProperty("Left").GetInt32();
int top = word.GetProperty("Top").GetInt32();
wordData.Add((wordText, left, top));
}
}
// Reconstruct full text from raw JSON — still no typed result
return string.Join(" ", wordData.Select(w => w.word));
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class InvoiceProcessor
Private _apiKey As String
Private _httpClient As HttpClient
Public Sub New(apiKey As String, httpClient As HttpClient)
_apiKey = apiKey
_httpClient = httpClient
End Sub
Public Async Function ExtractInvoiceFields(invoicePath As String) As Task(Of String)
Dim invoiceBytes As Byte() = Await File.ReadAllBytesAsync(invoicePath)
Dim base64 As String = Convert.ToBase64String(invoiceBytes)
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)("filetype", "PDF"),
New KeyValuePair(Of String, String)("language", "eng"),
New KeyValuePair(Of String, String)("isOverlayRequired", "true")
})
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
Dim overlay = doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("TextOverlay")
Dim wordData As New List(Of (word As String, x As Integer, y As Integer))()
For Each line In overlay.GetProperty("Lines").EnumerateArray()
For Each word In line.GetProperty("Words").EnumerateArray()
Dim wordText As String = word.GetProperty("WordText").GetString() OrElse ""
Dim left As Integer = word.GetProperty("Left").GetInt32()
Dim top As Integer = word.GetProperty("Top").GetInt32()
wordData.Add((wordText, left, top))
Next
Next
Return String.Join(" ", wordData.Select(Function(w) w.word))
End Using
End Function
End Class
Podejście IronOCR:
// IronOCR: full document hierarchy — typed, no JSON, no coordinates parsing
public void ExtractInvoiceFields(string invoicePath)
{
var ocr = new IronTesseract();
var result = ocr.Read(invoicePath);
// Access the full document hierarchy — all strongly typed
foreach (var page in result.Pages)
{
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($"Paragraph at ({paragraph.X}, {paragraph.Y}): {paragraph.Text}");
}
foreach (var word in page.Words)
{
// Word-level confidence — identify low-quality extractions
if (word.Confidence < 70)
Console.WriteLine($"Low confidence word '{word.Text}' at ({word.X}, {word.Y})");
}
}
// Or use region-based OCR to target specific invoice zones directly
var totalRegion = new CropRectangle(400, 700, 200, 50); // Bottom-right total field
using var input = new OcrInput();
input.LoadImage(invoicePath, totalRegion);
string totalText = ocr.Read(input).Text;
Console.WriteLine($"Invoice total: {totalText}");
}
// IronOCR: full document hierarchy — typed, no JSON, no coordinates parsing
public void ExtractInvoiceFields(string invoicePath)
{
var ocr = new IronTesseract();
var result = ocr.Read(invoicePath);
// Access the full document hierarchy — all strongly typed
foreach (var page in result.Pages)
{
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($"Paragraph at ({paragraph.X}, {paragraph.Y}): {paragraph.Text}");
}
foreach (var word in page.Words)
{
// Word-level confidence — identify low-quality extractions
if (word.Confidence < 70)
Console.WriteLine($"Low confidence word '{word.Text}' at ({word.X}, {word.Y})");
}
}
// Or use region-based OCR to target specific invoice zones directly
var totalRegion = new CropRectangle(400, 700, 200, 50); // Bottom-right total field
using var input = new OcrInput();
input.LoadImage(invoicePath, totalRegion);
string totalText = ocr.Read(input).Text;
Console.WriteLine($"Invoice total: {totalText}");
}
Imports IronOcr
Public Sub ExtractInvoiceFields(invoicePath As String)
Dim ocr As New IronTesseract()
Dim result = ocr.Read(invoicePath)
' Access the full document hierarchy — all strongly typed
For Each page In result.Pages
For Each paragraph In page.Paragraphs
Console.WriteLine($"Paragraph at ({paragraph.X}, {paragraph.Y}): {paragraph.Text}")
Next
For Each word In page.Words
' Word-level confidence — identify low-quality extractions
If word.Confidence < 70 Then
Console.WriteLine($"Low confidence word '{word.Text}' at ({word.X}, {word.Y})")
End If
Next
Next
' Or use region-based OCR to target specific invoice zones directly
Dim totalRegion As New CropRectangle(400, 700, 200, 50) ' Bottom-right total field
Using input As New OcrInput()
input.LoadImage(invoicePath, totalRegion)
Dim totalText As String = ocr.Read(input).Text
Console.WriteLine($"Invoice total: {totalText}")
End Using
End Sub
Flaga isOverlayRequired=true OCR.space zwraca wspolrzedne slow w formacie JSON, lecz struktura odpowiedzi wymaga nawigacji po zagniezdzonych tablicach JSON z dostepem do wlasciwosci o kluczach lancuchowych — brak modelu typowanego, brak IntelliSense i krucha nawigacja sciezki, ktora lamie sie, jesli struktura odpowiedzi sie zmienia.IronOCRresult.Pages, result.Words i result.Lines sa typowanymi obiektami .NET. Podejscie CropRectangle skupia sie na bezposrednim docieraniu do specyficznych regionow dokumentu, zamiast wyodrebniac caly dokument i pożniej filtrowac przez wspolrzedne. Przewodnik dotyczący wyników odczytu oraz przewodnik po OCR opartym na regionach szczegółowo omawiają oba wzorce.
Dokumentacja API OCR.space do IronOCR
| Koncepcja OCR.space | Odpowiednik IronOCR |
|---|---|
| Brak pakietu NuGet | dotnet add package IronOcr |
Konstrukcja HttpClient |
Nie jest potrzebne — brak warstwy HTTP |
Ogranicznik szybkosci SemaphoreSlim |
Nie jest wymagane — brak limitów stawek |
FormUrlEncodedContent / MultipartFormDataContent |
OcrInput |
Parametr URI danych base64Image |
input.LoadImage(bytes) |
Parametr przesyłania file |
input.LoadImage(path) |
Naglowek / pole formularza apikey |
IronOcr.License.LicenseKey (raz przy uruchomieniu) |
Parametr language (jeden na wywolanie) |
ocr.Language = OcrLanguage.English + OcrLanguage.French |
OCREngine=1 (szybko) |
Domyślny silnik (zoptymalizowany Tesseract 5) |
OCREngine=2 (wysoka dokladnosc) |
input.Deskew(); input.DeNoise(); input.Contrast(); |
Parametr scale=true |
input.Scale(200) |
Parametr detectOrientation=true |
input.Deskew() |
Parametr isOverlayRequired=true |
result.Pages[n].Words (zawsze dostepne, typowane) |
Parametr isCreateSearchablePdf=true |
result.SaveAsSearchablePdf("output.pdf") |
Parametr filetype=PDF |
input.LoadPdf(path) |
ParsedResults[0].ParsedText |
result.Text |
ParsedResults[n] (tekst na strone) |
result.Pages[n].Text |
TextOverlay.Lines[n].Words[n].WordText |
result.Pages[n].Words[n].Text |
TextOverlay.Lines[n].Words[n].Left/Top |
result.Pages[n].Words[n].X / .Y |
Flaga JSON IsErroredOnProcessing |
Standard Exception z wiadomoscia |
Flaga na strone FileParseExitCode |
Standard Exception z wiadomoscia |
| HTTP 429 Zbyt wiele żądań | Nie dotyczy — brak limitów stawek |
Niestandardowy OcrResult POCO (zdefiniowane przez uzytkownika) |
IronOcr.OcrResult (dostarczane przez NuGet) |
Niestandardowy OcrSpaceException (zdefiniowane przez uzytkownika) |
Standardowe typy wyjątków .NET Standard |
Typowe problemy związane z migracją i ich rozwiązania
Problem 1: Kod asynchroniczny, który istniał wyłącznie dla HTTP
OCR.space: Kazde wywolanie OCR jest async, poniewaz obejmuje okrazenie HTTP do chmury. Metody usług, akcje kontrolerów i zadania w tle zostały zrealizowane asynchronicznie, aby uniknąć blokowania wątku podczas oczekiwania na sieć.
Rozwiazanie: Metoda Read()IronOCR jest synchroniczna. Usun await z metod, ktore byly asynchroniczne tylko dlatego, ze wymagalo tego OCR.space. W kontekstach ASP.NET Core, gdzie blokowanie nie ma znaczenia, owin wywolanie synchroniczne w Task.Run() lub uzyj wzorcow asynchronicznych udokumentowanych w przewodniku po OCR asynchronicznym. Nie dodawaj odruchowo await do wywolan IronOCR— nie jest to wymagane i dodaje niepotrzebne obciazenie w kontekstach innych niz webowe.
// Before: async because OCR.space required network I/O
public async Task<string> ProcessDocumentAsync(string path)
{
return await _ocrSpaceClient.ExtractTextAsync(path); // Network wait
}
// After: synchronous — no network, no async needed
public string ProcessDocument(string path)
{
return _ocr.Read(path).Text; // Local execution
}
// Before: async because OCR.space required network I/O
public async Task<string> ProcessDocumentAsync(string path)
{
return await _ocrSpaceClient.ExtractTextAsync(path); // Network wait
}
// After: synchronous — no network, no async needed
public string ProcessDocument(string path)
{
return _ocr.Read(path).Text; // Local execution
}
Imports System.Threading.Tasks
' Before: async because OCR.space required network I/O
Public Async Function ProcessDocumentAsync(path As String) As Task(Of String)
Return Await _ocrSpaceClient.ExtractTextAsync(path) ' Network wait
End Function
' After: synchronous — no network, no async needed
Public Function ProcessDocument(path As String) As String
Return _ocr.Read(path).Text ' Local execution
End Function
Problem 2: Infrastruktura przechowywania i rotacji kluczy API
OCR.space: Klucz API musi byc wstrzykiwany do kazdego zadania. Zespoly zwykle przechowuja go w appsettings.json lub zmiennych srodowiskowych, wstrzykuja przez IOptions<t> lub przez wstrzykiwanie konstruktora i rotacja, gdy zostanie ujawniony. Rotacja kluczy wymaga aktualizacji każdego środowiska wdrożeniowego i ponownego uruchomienia aplikacji.
Rozwiązanie: Klucz licencyjny IronOCR jest ustawiany jednorazowo podczas uruchamiania i nie jest już nigdy więcej wykorzystywany podczas działania programu. Usuń wzorzec wstrzykiwania klucza na żądanie. Usun klase konfiguracji IOptions<OcrSpaceSettings>. Kluczowy wzorzec inicjalizacji to jedna linia:
// Startup.cs or Program.cs — once only
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY");
// Startup.cs or Program.cs — once only
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY");
Imports System
' Startup.vb or Program.vb — once only
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")
Nie ma wstrzykiwania poświadczeń na żądanie, nie ma procedury rotacji kluczy i nie ma ryzyka przypadkowego zarejestrowania klucza w śladach żądań.
Problem 3: Logika wstępnej walidacji rozmiaru pliku
OCR.space: Wersja darmowa odrzuca pliki większe niż 5 MB, wyświetlając komunikat o błędzie. Kod produkcyjny dodaje sprawdzanie rozmiaru pliku przed każdym żądaniem, aby uniknąć marnowania limitu wywołań na żądanie, które zakończy się niepowodzeniem:
// OCR.space: pre-validation to avoid wasting quota on large files
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > 5 * 1024 * 1024)
throw new InvalidOperationException("File exceeds 5MB free tier limit.");
// OCR.space: pre-validation to avoid wasting quota on large files
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > 5 * 1024 * 1024)
throw new InvalidOperationException("File exceeds 5MB free tier limit.");
Dim fileInfo As New FileInfo(filePath)
If fileInfo.Length > 5 * 1024 * 1024 Then
Throw New InvalidOperationException("File exceeds 5MB free tier limit.")
End If
Rozwiązanie: Całkowicie usunąć tę kontrolę.IronOCROcrInput.LoadPdf() i OcrInput.LoadImage() nie maja limitu rozmiaru poza dostepna pamiecia systemowa. Sztuczny limit 5 MB istnieje wyłącznie dlatego, że bezpłatny plan OCR.space nakłada go ze względu na pojemność serwerów. Skanowany plik PDF o rozmiarze 50 MB ładuje się tak samo jak plik o rozmiarze 500 KB.
Problem 4: Niestabilność nawigacji w odpowiedzi JSON
OCR.space: Parsowanie odpowiedzi opiera sie na nawigacji JsonDocument z dostepem do wlasciwosci o kluczach lancuchowych. Kod taki jak doc.RootElement.GetProperty("ParsedResults")[0].GetProperty("ParsedText") zglasza KeyNotFoundException, jezeli ksztalt odpowiedzi ulegnie zmianie i IndexOutOfRangeException, jezeli ParsedResults jest puste. Oba wymagają stosowania instrukcji try-catch lub sprawdzania wartości null w całym tekście.
Rozwiazanie:IronOCR zwraca typowany obiekt OcrResult. Wlasciwosc .Text jest zawsze string — nigdy nie null, nigdy nie brak. Jezeli OCR nie generuje wyjscia (pusta strona, nieczytelny obraz), result.Text jest pustym lancuchem. Nie ma JSON-u do nawigacji i zadnej kruchosci sciezki wlasciwosci do ochrony. Dla filtrowania opartego na pewnosci result.Confidence zwraca double, ktory mozesz porownac bezposrednio:
// IronOCR: typed result — no JSON path fragility
var result = new IronTesseract().Read("document.jpg");
if (result.Confidence < 50)
Console.WriteLine("Low confidence — consider preprocessing");
else
Console.WriteLine(result.Text);
// IronOCR: typed result — no JSON path fragility
var result = new IronTesseract().Read("document.jpg");
if (result.Confidence < 50)
Console.WriteLine("Low confidence — consider preprocessing");
else
Console.WriteLine(result.Text);
Imports IronOcr
Dim result = New IronTesseract().Read("document.jpg")
If result.Confidence < 50 Then
Console.WriteLine("Low confidence — consider preprocessing")
Else
Console.WriteLine(result.Text)
End If
Wskazówki dotyczące oceny pewności obejmują progi pewności dla poszczególnych słów i dokumentów.
Problem 5: Wyczerpanie limitu szybkości dla współdzielonego adresu IP w CI/CD
OCR.space: Potoki CI/CD, które przeprowadzają testy integracyjne na OCR.space, korzystają z tego samego adresu IP wychodzącego, co sieć biura programistów. Konta w wersji bezpłatnej mają limit 500 żądań dziennie na adres IP. Potok przetwarzający 200 dokumentów testowych na jeden przebieg może wyczerpać dzienny limit, zanim pierwszy programista przeprowadzi test ręczny. Zespoły radzą sobie z tym problemem, symulując odpowiedzi OCR.space w testach, co jednak mija się z celem testów integracyjnych.
Rozwiązanie:IronOCR przetwarza dane lokalnie. Zestaw testowy wywoluje new IronTesseract().Read(testImagePath).Text bezposrednio — bez potrzeby makingu, bez wyczerpywania limitu, bez zaleznosci sieciowych. Testy integracyjne są uruchamiane w CI/CD z wykorzystaniem tych samych rzeczywistych wyników OCR, co w środowisku produkcyjnym, bez zarządzania limitami szybkości ani wzorców izolacji testów.
Problem 6: IDisposable Wzorzec w zarzadzaniu HttpClient
OCR.space: Klasa owijajaca HttpClient implementuje IDisposable, aby zwolnic pule polaczen HTTP. Kazdy uzytkownik uslugi OCR musi albo wstrzyknac singleton, uzyc using, albo zarejestrowac z cyklem zycia usuwania w pojemniku DI. Zapomnienie o zwolnieniu zasobów powoduje wyczerpanie gniazd przy dużym obciążeniu.
Rozwiazanie: IronTesseract nie zarzadza polaczeniami sieciowymi. Nie implementuje IDisposable. Stworz jeden instancje na watek (lub na zadanie w ASP.NET), wywolaj .Read() i pozwol GC ja zebrac. Klasa OcrInput implementuje IDisposable i powinna byc owijana w using, gdy stosuje się przetwarzanie wstępne, ale glowna klasa IronTesseract nie wymaga zarzadzania cyklem zycia. Usun implementacje IDisposable z uslugi opakowania OCR i uprosc rejestracje DI z zakresu/transient z usuwaniem do prostej fabryki lub singleton.
Lista kontrolna migracji OCR.space
Zadania przed migracją
Przeprowadź audyt kodu źródłowego w celu zidentyfikowania wszystkich punktów integracji OCR.space:
# Find all OCR.space HTTP calls
grep -rn "ocr.space" --include="*.cs" .
# Find all base64 encoding related to OCR
grep -rn "base64Image\|Convert.ToBase64String" --include="*.cs" .
# Find rate limiter and retry logic
grep -rn "SemaphoreSlim\|TooManyRequests\|exponential" --include="*.cs" .
# Find JSON parsing for OCR responses
grep -rn "ParsedResults\|IsErroredOnProcessing\|FileParseExitCode" --include="*.cs" .
# Find custom OCR models and exception types
grep -rn "OcrSpaceException\|OcrResult\b" --include="*.cs" .
# Find API key configuration
grep -rn "apikey\|OcrSpaceApiKey\|ocr_space" --include="*.cs" --include="*.json" .
# Find all OCR.space HTTP calls
grep -rn "ocr.space" --include="*.cs" .
# Find all base64 encoding related to OCR
grep -rn "base64Image\|Convert.ToBase64String" --include="*.cs" .
# Find rate limiter and retry logic
grep -rn "SemaphoreSlim\|TooManyRequests\|exponential" --include="*.cs" .
# Find JSON parsing for OCR responses
grep -rn "ParsedResults\|IsErroredOnProcessing\|FileParseExitCode" --include="*.cs" .
# Find custom OCR models and exception types
grep -rn "OcrSpaceException\|OcrResult\b" --include="*.cs" .
# Find API key configuration
grep -rn "apikey\|OcrSpaceApiKey\|ocr_space" --include="*.cs" --include="*.json" .
Sporządź listę plików zawierających kod OCR.space. Zauwaz, ktore metody sa async tylko z powodu uzaleznienia HTTP OCR.space — po migracji moga byc zrobione synchroniczne.
Zadania związane z aktualizacją kodu
- Zainstaluj pakiet NuGet
IronOcr:dotnet add package IronOcr - Dodaj
IronOcr.License.LicenseKey = "..."do uruchamiania aplikacji - Usun klase
OcrSpaceApiClienti cala wspierajaca infrastrukture - Usun niestandardowy
OcrResultPOCO (zastepowany przezIronOcr.OcrResult) - Usun niestandardowa klase
OcrSpaceException(zastepowana przez standardowe wyjatki .NET) - Usun ogranicznik szybkosci
SemaphoreSlimi powiazana logike Task.Delay - Usun wszystkie wywolania
Convert.ToBase64String()uzywane do kodowania obrazu OCR - Zastap konstrukcje
FormUrlEncodedContent/MultipartFormDataContentprzezOcrInput - Zastap wywolania
_httpClient.PostAsync(...)przeznew IronTesseract().Read(input) - Zastap
JsonDocumentparsowanieParsedResults[0].ParsedTextprzezresult.Text - Zastap
TextOverlayparsowanie wspolrzednych JSON przezresult.Pages[n].Words - Zastap przelaczanie parametru
OCREngineprzez odpowiednie filtry wstepne - Zastap lancuchy parametrow
languageprzez wartosci wyliczeniaOcrLanguage - Usunąć wstępne sprawdzanie rozmiaru pliku (limit 5 MB nie obowiązuje już)
- Przeksztalcaj metody OCR
async Task<string>na synchronicznestringtam, gdzie HTTP bylo jedynym powodem asynchronicznym - Usuń klucz API OCR.space z plików konfiguracyjnych i ustawień zmiennych środowiskowych
Testy po migracji
- Sprawdź, czy ekstrakcja tekstu zapewnia taką samą lub wyższą dokładność w przypadku tych samych dokumentów testowych
- Sprawdź, czy przetwarzanie dużych plików (ponad 5 MB) przebiega bez błędów
- Testuj dokumenty wielojezykowe z
OcrLanguage.English + OcrLanguage.Frenchi weryfikuj wyjscie przelotne - Uruchom potok CI/CD z rzeczywistymi wywołaniami OCR — upewnij się, że nie występują błędy związane z limitami szybkości przy żadnej objętości dokumentów
- Sprawdź, czy plik PDF z możliwością wyszukiwania nie zawiera znaków wodnych
- Sprawdź, czy akcje kontrolera, które wcześniej były asynchroniczne, nadal działają poprawnie po konwersji na synchroniczne
- Sprawdź, czy środowiska wdrożeniowe odizolowane lub ograniczone sieciowo przetwarzają dokumenty bez błędów
- Potwierdz, ze wartosci
result.Confidencesa akceptowalne dla dokumentow, ktore wczesniej wymagalyOCREngine=2 - Zweryfikuj, ze wspolrzedne
result.Pages[n].Wordspasuja do oczekiwanych pozycji pol w dokumentach strukturalnych - Sprawdź, czy inicjalizacja licencji przy uruchomieniu aplikacji przebiega pomyślnie przed pierwszym wywołaniem funkcji OCR
Kluczowe korzyści z migracji do IronOCR
Znikają koszty związane z infrastrukturą obejmującą ponad 80 linii kodu. Każda integracja OCR.space zawiera klienta HTTP, ogranicznik szybkości, deserializator JSON, niestandardowe typy wyjątków oraz niestandardowe modele wyników. Żaden z tych kodów nie wykonuje czynności faktycznie potrzebnych aplikacji — istnieje on w celu zrekompensowania braku SDK w OCR.space. Po migracji kod ten jest usuwany. Powierzchnia OCR w bazie kodu kurczy sie do new IronTesseract().Read(path).Text w miejscu wywolania i jednej linii inicjalizacji licencji przy starcie.
Szybkość przetwarzania dokumentów zależy od lokalnego sprzętu. OCR.space uwzględnia opóźnienia sieciowe, głębokość kolejki serwera OCR.space oraz czas przesyłu w obie strony w każdej operacji przetwarzania.IronOCR działa w trybie wbudowanym. Lokalna stacja robocza przetwarza dokumenty szybciej niż jakikolwiek interfejs API w chmurze przy dowolnej przepustowości, bez ograniczenia do 60 żądań na minutę, które powoduje sekwencyjność przetwarzania wsadowego. Przetwarzanie rownolegle z Parallel.ForEach w wielu instancjach IronTesseract skaluje sie z rdzeniami CPU — zobacz przyklad na rownoleglenia.
Dokumenty wrażliwe pozostają na stałe w Twojej infrastrukturze. Po migracji dokumentacja medyczna, dokumenty finansowe, umowy prawne i dokumenty tożsamości nigdy nie opuszczają serwera aplikacji. W ramach przeglądów zgodności z HIPAA, RODO, SOC 2 oraz wewnętrznymi zasadami klasyfikacji danych nie ma już potrzeby uwzględniania praktyk OCR.space w zakresie przetwarzania danych. Zakres audytu ogranicza się do Twojej własnej infrastruktury. Przewodnik wdrażania Docker oraz przewodnik wdrażania Azure obejmują wdrażanie IronOCR w środowiskach kontenerowych i chmurowych, które wymagają zgodności z przepisami dotyczącymi lokalizacji danych.
Strukturalne wyjscie umozliwia aplikacje inteligencji dokumentu. ParsedText lancuch OCR.space to koniec drogi dla analizy dokumentow.IronOCRresult.Pages, result.Words i result.Lines ze wspolrzednymi i ocenami pewnosci po slowie umozliwia aplikacje do lokalizowania specyficznych pol, weryfikowania jakosci ekstrakcji, wydobywania danych tabelarycznych i budowania dolnych rzek dokumentow z inteligencja. Funkcje, które wymagały tworzenia niestandardowej analizy układu na podstawie zwykłego tekstu generowanego przez OCR.space, stały się bezpośrednimi wywołaniami API. Przewodnik po ekstrakcji tabel oraz przewodnik po przetwarzaniu zeskanowanych dokumentów pokazują, jakie możliwości daje ta ustrukturyzowana podstawa.
Koszt jest stały i przewidywalny niezależnie od wielkości zlecenia. Bezpłatny pakiet OCR.space obejmuje 25 000 zapytań miesięcznie. Ponadto koszty subskrypcji są uzależnione od intensywności użytkowania. Wieczysta licencja $999 Lite IronOCR nie wiąze sie z oplatami za dokument przy dowolnym wolumenie. Zespół przetwarzający 100 000 dokumentów miesięcznie płaci taką samą opłatę licencyjną jak zespół przetwarzający 1 000 dokumentów miesięcznie. Prognozowanie budżetu dla aplikacji do przetwarzania dokumentów staje się stałym kosztem rocznym, a nie zmienną pozycją, która rośnie wraz z sukcesem biznesowym. Strona produktu IronOCR zawiera bezpłatną wersję próbną, która pozwala zespołom sprawdzić dokładność działania na konkretnych typach dokumentów przed zakupem.
Często Zadawane Pytania
Dlaczego warto przejść z API OCR.space na IronOCR?
Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.
Jakie są główne zmiany w kodzie podczas migracji z API OCR.space do IronOCR?
Zastąp sekwencje inicjalizacji OCR.space instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.
Jak zainstalować IronOCR, aby rozpocząć migrację?
Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.
Czy IronOCR dorównuje dokładnością OCR API OCR.space w przypadku standardowych dokumentów biznesowych?
IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.
W jaki sposób IronOCR obsługuje dane językowe, które API OCR.space instaluje oddzielnie?
Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.
Czy migracja z API OCR.space do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?
IronOCR wymaga mniej zmian w infrastrukturze niż API OCR.space. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.
Jak skonfigurować licencjonowanie IronOCR po migracji?
W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.
Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak OCR.space?
Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.
W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?
IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.
Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?
IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.
Czy ceny IronOCR są bardziej przewidywalne niż API OCR.space w przypadku skalowania obciążeń?
IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.
Co stanie się z moimi istniejącymi testami po migracji z API OCR.space do IronOCR?
Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

