푸터 콘텐츠로 바로가기
동영상

C#에서 읽기 위해 이미지 색상을 수정하는 방법

이 가이드는 .NET 개발자가 OCR.space의 REST API 통합을 단일 NuGet 패키지로 제공되는 네이티브 .NET 라이브러리인 IronOCR 로 교체하는 과정을 안내합니다. 이 문서에서는 패키지 교체, 네임스페이스 정리, 그리고 REST에서 로컬로의 전환에 특화된 네 가지 구체적인 코드 마이그레이션 시나리오(멀티파트 업로드 제거, base64 인코딩 제거, OCR 엔진 선택 교체, 구조화된 데이터 추출)를 다룹니다. 1단계 비교 글을 읽으신 개발자분들은 이 가이드가 기능 비교보다는 마이그레이션 자체의 구체적인 단계에 초점을 맞추고 있다는 것을 알게 되실 겁니다.

OCR.space에서 마이그레이션해야 하는 이유

OCR.space는 개발자들이 아무것도 설치하지 않고도 오후 시간 동안 OCR을 테스트해 볼 수 있도록 비용 부담 없는 실험 환경을 제공함으로써 진정한 틈새시장을 공략하고 있습니다. 문제는 무료 버전이 상용 제품이 아닌 프로토타입 제작을 위해 설계되었다는 점입니다. .NET 애플리케이션이 실제 문서 처리량 증가, 규정 준수 요구 사항 충족 또는 팀 개발 단계로 접어들면 OCR.space 통합의 모든 특성이 애플리케이션에 불리하게 작용합니다.

NuGet 패키지가 없으면 SDK 및 IntelliSense를 사용할 수 없습니다. OCR.space는 REST 엔드포인트와 문서를 제공합니다. .NET 통합(HTTP 클라이언트 구성, 요청 직렬화, 응답 역직렬화, 오류 처리 및 재시도 로직)은 전적으로 개발자의 책임입니다. 이는 사소한 불편함이 아닙니다. 최소한의 실행 가능한 클라이언트는 첫 번째 비즈니스 로직 메서드가 작성되기 전에 80줄 이상의 인프라 코드를 포함해야 합니다. 해당 코드는 모든 .NET 코드베이스의 모든 OCR.space 통합에서 동일하게 사용되며, 시간이 지남에 따라 버그가 누적되고 유지 관리 부담이 커집니다.

속도 제한은 실제 운영 환경에 사용되는 애플리케이션에 인위적인 제약을 가합니다. 무료 요금제는 IP 주소당 분당 60건, 하루 500건의 요청으로 제한됩니다. 두 한계 모두 확고부동한 벽입니다. 자정부터 다음 자정까지 500건 이상의 요청을 보내는 애플리케이션은 카운터가 초기화될 때까지 오류 응답을 받습니다. 공유 오피스 네트워크 또는 공유 CI/CD 환경에서 실행되는 프로덕션 시스템은 업무 시간이 끝나기 전에 일일 할당량을 소진할 수 있습니다.

모든 통화 시 문서가 사용자 인프라를 벗어납니다. OCR.space는 온프레미스 배포 옵션을 제공하지 않습니다. 모든 요청은 청구서, 의료 기록, 계약서, 신분증 등의 문서를 OCR.space의 클라우드 서버로 전송합니다. HIPAA, GDPR 및 민감한 문서의 제3자 전송을 금지하는 내부 데이터 분류 정책으로 인해 계약상의 통제와 관계없이 OCR.space는 아키텍처적으로 호환되지 않습니다.

무료 버전은 워터마크가 포함된 검색 가능한 PDF 파일을 생성합니다. 문서 보관 시스템, 규정 준수 플랫폼, 고객용 문서 포털 등 검색 가능한 PDF 파일을 출력물로 생성하는 애플리케이션은 OCR.space의 무료 버전을 이러한 목적으로 사용할 수 없습니다. 워터마크는 출력 PDF에 포함되어 있으며 유료 플랜 없이는 제거할 수 없습니다.

구독료는 판매량에 따라 상승합니다. OCR.space PRO 단계는 연간 $144로 6년 전에 IronOCR의 $999 영구 초입 가격을 웃돌게 됩니다. 무료 등급 임계값을 넘는 문서 수량 증가를 예측하는 팀은 고정 영구 라이선스에 대해 복합적인 구독 비용에 직면하게 됩니다. $999 Lite 라이선스는 1명 개발자와 1개 배포 위치를 대상으로 하며, 모든 볼륨에서 요청당 요금이 없습니다. 자세한 등급 정보는 IronOCR 라이선스 페이지를 참조하십시오.

근본적인 문제

OCR.space를 사용하려면 문서 하나를 처리하기 전에 완전한 HTTP 클라이언트를 구축해야 합니다.

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

IronOCR 은 NuGet 패키지입니다. 클라이언트의 모든 정보는 이미 작성되었습니다.

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

IronOCR과 OCR.space의 기능 비교

아래 표는 OCR.space의 개념과 제약 조건을IronOCR해당 개념 및 제약 조건에 직접 매핑한 것입니다.

기능 OCR.space IronOCR
NuGet 패키지 없음 — REST API만 사용 IronOcr — 네이티브 .NET
SDK / IntelliSense 없음 — 수동 JSON 완전한 타입의 API
맞춤형 모델 필요 아니요 아니요
처리 위치 OCR.space 클라우드 서버 현지 — 진행 중
인터넷 의존성 모든 호출에 필수 None
에어갭 배포 지원되지 않음 완전히 지원
요금 제한 분당 60, 하루 500 (무료) None
파일 크기 제한 5MB (무료 요금제) 사용 가능한 메모리만
PDF 입력 예 (용량 제한: 5MB) 예, 네이티브 앱이며 크기 제한이 없습니다.
검색 가능한 PDF 출력 무료 버전에 워터마크가 표시됩니다. 깔끔한 출력, 모든 단계
자동 전처리 서버 측 처리 방식이므로 개발자가 제어할 수 없습니다. 기울기 보정, 노이즈 제거, 대비 조정, 이진화, 선명도 향상
언어 지원 약 25개 언어 NuGet 언어 팩을 통해 125개 이상 추가 가능
문서당 다국어 지원 지원되지 않음 예 — OcrLanguage.French + OcrLanguage.German
구조화된 출력 (단어, 줄) 일반 텍스트만 페이지, 단락, 줄, 단어와 좌표
단어 수준 신뢰도 점수 사용 불가 예 — word.Confidence
영역 기반 OCR 지원되지 않음 예 — CropRectangle
바코드 판독 지원되지 않음 예 — ReadBarCodes = true
검색 가능한 PDF 생성 워터마크 포함(무료), 워터마크 없음(유료) 깔끔한 출력 — 모든 라이선스 등급
HIPAA/GDPR 호환성 위험 - 외부로 전송되는 데이터 예, 외부 데이터 전송은 없습니다.
가격 모델 월간 구독 일회성 영구
입장료 월 12달러(연 144달러) $999 일회
.NET 호환성 HttpClient — 모든 .NET .NET 4.6.2 이상, .NET 5/6/7/8/9
크로스 플랫폼 배포 외부 인터넷 연결이 필요합니다. 윈도우, 리눅스, macOS, Docker, Azure, AWS

빠른 시작: OCR.space에서IronOCR로 마이그레이션

1단계: NuGet 패키지 교체

OCR.space에는 제거할 NuGet 패키지가 없습니다. 프로젝트에서 모든 OCR.space 관련 인프라 코드를 제거: HttpClient 래퍼 클래스, SemaphoreSlim 속도 제한기, 사용자 정의 결과 모델, 사용자 정의 예외 유형. 이 모두는 IronOCR의 NuGet 패키지로 교체됩니다.

IronOCR NuGet 페이지 에서IronOCR설치하세요.

dotnet add package IronOcr

단계 2: 네임스페이스 업데이트

OCR.space HTTP 및 JSON 네임스페이스를 제거합니다.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
$vbLabelText   $csharpLabel

단계 3: 라이선스 초기화

애플리케이션 시작 시 한 번만 라이선스를 초기화하도록 추가하십시오. 요청 시마다 초기화하는 것이 아닙니다.

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

코드 마이그레이션 예제

MultipartFormDataContent 파일 업로드 대체

OCR.space는 파일 바이트 및 API 키를 사용해 MultipartFormDataContent을 생성한 후 클라우드 엔드포인트로 POST 해야 합니다. 해당 문서는 모든 통화 시 인프라에서 삭제됩니다.

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

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

OcrInput은(는) MultipartFormDataContent의 로컬 대체입니다. 이 라이브러리는 일관된 API를 통해 파일 경로, 바이트 배열, 스트림 및 다중 페이지 TIFF 파일을 허용합니다. HttpClient, API 키 삽입 및 JSON 탐색이 완전히 사라집니다. 이미지 입력 ​​방법 안내에는 지원되는 모든 입력 형식이 포함되어 있습니다.

Base64 인코딩 제거

OCR.space 통합이 파일 업로드 매개변수 대신 base64Image 양식 매개변수를 사용할 때 코드는 파일을 바이트로 읽고 Base64로 인코딩하고 데이터 URI 문자열을 구성하여 FormUrlEncodedContent에 내장합니다.IronOCR인코딩 단계 없이 원시 바이트를 직접 입력으로 받습니다.

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

IronOCR 접근 방식:

// LoadImage(bytes): raw bytes accepted directly — no encoding
public string ExtractFromBytes(byte[] imageBytes)
{
    using var input = new OcrInput();
    input.LoadImage(imageBytes); // 아니요 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); // 아니요 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) ' 아니요 Base64, no data URI, no overhead

        Dim result = New IronTesseract().Read(input)
        Return result.Text
    End Using
End Function
$vbLabelText   $csharpLabel

IronOCR 에는 HTTP 전송 계층이 없기 때문에 Base64 인코딩 단계가 없습니다. 원시 바이트가 OcrInput.LoadImage()에 직접 들어갑니다. 데이터 URI 오버헤드(Base64 인코딩으로 인해 페이로드 크기가 약 33% 증가함)도 사라집니다. 스트림 입력 가이드Stream 입력에 대해 동일한 패턴을 보여주며, 이는 바이트가 파일이 아닌 업로드 핸들러나 메모리 버퍼에서 비롯된 경우에 유용합니다.

OCR 엔진 선택을 이미지 전처리로 대체

OCR.space는 OCREngine 양식 매개변수를 통해 두 개의 OCR 엔진을 노출합니다: 엔진 1은 복잡한 레이아웃에서 낮은 정확도로 더 빠릅니다; 엔진 2는 속도는 느리지만 대부분의 문서 유형에서 정확도가 더 높습니다. 개발자는 문서 특성에 따라 호출할 때마다 엔진을 선택합니다.IronOCR최적화된 Tesseract 5 엔진 하나를 사용하지만, 엔진 모드 전환 대신 문서 품질이라는 근본 원인을 해결하는 명시적인 전처리 필터를 제공합니다.

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

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
    입력.노이즈 제거();        // 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}%"); // 아니요 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
    입력.노이즈 제거();        // 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}%"); // 아니요 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
        입력.노이즈 제거()        ' 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}%") ' 아니요 equivalent in OCR.space
        Return result.Text
    End Using
End Function
$vbLabelText   $csharpLabel

OCR.space OCREngine 매개변수는 문서 품질의 프록시이며 엔진 1이 문서에서 실패할 때 개발자는 다른 알고리즘이 보상되기를 희망하며 엔진 2로 전환합니다. IronOCR의 전처리 파이프라인은 품질 문제를 직접 해결합니다: Deskew()는 비뚤어진 스캔을 수정하고, DeNoise()는 팩스 아티팩트를 처리하며, Contrast()는 저대비 복사본에서 텍스트를 복구합니다. 결과의 Confidence 속성은 추출 품질을 정량화하며, 이는 OCREngine 전환이 제공할 수 없습니다. 이미지 품질 수정 가이드필터 마법사는 각 필터의 효과를 다른 문서 유형에 대한 설명합니다.

통화별 언어 전환 없이 다국어 OCR 지원

OCR.space는 API 호출당 하나의 language 매개변수를 허용합니다. 여러 언어가 혼합된 문서의 경우 각 언어별로 별도의 호출이 필요하며, 결과는 수동으로 병합됩니다. IronOCR는 단일 읽기 작업에서 + 연산자를 사용하여 OcrLanguage 값에 대해 여러 언어를 동시에 처리합니다.

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

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

OCR.space의 호출당 단일 언어 제약 조건으로 인해 개발자는 N개 언어로 된 문서를 처리하기 위해 N번의 API 호출을 해야 하며, 그 결과를 어떻게 일치시켜야 할지 추측해야 합니다.IronOCR언어 모델을 단일 엔진 패스로 결합하여 후처리 없이 올바르게 인터리빙된 출력을 생성합니다. 언어 팩은 NuGet 패키지로 설치되며 — IronOcr.Languages.French, IronOcr.Languages.German 등등 — 오프라인에서도 작동합니다. 다수 언어 사용 방법은 팩 설치와 125개 이상의 지원 언어에 대한 + 연산자 구문을 다룹니다.

단어 좌표를 이용한 구조화된 데이터 추출

OCR.space는 ParsedResults[0].ParsedText로부터 일반 텍스트를 반환합니다. 단어 수준 데이터, 경계 상자, 선 경계, 요소별 신뢰도 점수는 없습니다. 송장 오른쪽 상단의 날짜나 표 오른쪽 하단 셀의 합계와 같이 특정 필드를 찾아야 하는 애플리케이션은 OCR.space의 응답을 기반으로 삼을 수 있는 구조화된 토대가 없습니다.IronOCR페이지, 단락, 줄, 단어, 문자 등 문서의 전체 계층 구조를 제공하며, 각 요소에는 픽셀 좌표와 신뢰도 점수가 포함됩니다.

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

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

OCR.space isOverlayRequired=true 플래그는 JSON 단어 좌표를 반환하지만, 응답 구조는 문자열로 키 지정된 속성 액세스와 중첩된 JSON 배열을 탐색해야 하며, 유형 지정된 모델도 IntelliSense도 없고, 응답 구조가 변경되면 깨지는 취약한 경로 탐색이 필요합니다. IronOCR의 result.Pages, result.Wordsresult.Lines는 유형이 지정된 .NET 객체입니다. CropRectangle 접근 방식은 전체 문서를 추출하고 좌표별로 필터링합니까 보다 특정 문서 지역을 직접 겨냥합니다. 판독 결과 활용 방법영역 기반 OCR 가이드에서는 두 가지 패턴 모두를 자세히 다룹니다.

OCR.space API와IronOCR매핑 참조

OCR.space 컨셉 IronOCR에 상응하는
NuGet 패키지가 없습니다. dotnet add package IronOcr
HttpClient 생성 필요 없음 - HTTP 계층이 없음
SemaphoreSlim 속도 제한기 필요 없음 - 요금 제한 없음
FormUrlEncodedContent / MultipartFormDataContent OcrInput
base64Image 데이터 URI 매개변수 input.LoadImage(bytes)
file 업로드 매개변수 input.LoadImage(path)
apikey 헤더 / 양식 필드 IronOcr.License.LicenseKey (시작 시 한 번만)
language 매개변수(요청당 하나) ocr.Language = OcrLanguage.English + OcrLanguage.French
OCREngine=1 (빠름) 기본 엔진(최적화된 Tesseract 5)
OCREngine=2 (고정밀도) input.Deskew(); 입력.노이즈 제거(); 입력.Contrast();
scale=true 매개변수 input.Scale(200)
detectOrientation=true 매개변수 input.Deskew()
isOverlayRequired=true 매개변수 result.Pages[n].Words (항상 사용 가능, 유형)
isCreateSearchablePdf=true 매개변수 result.SaveAsSearchablePdf("output.pdf")
filetype=PDF 매개변수 input.LoadPdf(path)
ParsedResults[0].ParsedText result.Text
ParsedResults[n] (페이지별 텍스트) 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
IsErroredOnProcessing JSON 플래그 표준 Exception 메시지 포함
FileParseExitCode 페이지당 플래그 표준 Exception 메시지 포함
HTTP 429 요청 과다 해당 사항 없음 - 요금 제한 없음
사용자 정의 OcrResult POCO (사용자 정의) IronOcr.OcrResult (NuGet이 제공)
사용자 정의 OcrSpaceException (사용자 정의) 표준 .NET 예외 유형

일반적인 마이그레이션 문제와 해결책

문제 1: HTTP에서만 존재했던 비동기 코드

OCR.space: 모든 OCR 호출은 클라우드로의 HTTP 왕복을 포함하므로 async입니다. 네트워크 대기로 인해 스레드가 차단되는 것을 방지하기 위해 서비스 메서드, 컨트롤러 액션 및 백그라운드 작업이 비동기식으로 구현되었습니다.

솔루션: IronOCR의 Read() 메소드는 동기식입니다. OCR.space 때문만에 비동기 메소드였던 메소드에서 await 제거 비차단식 실행이 중요한 ASP.NET Core 컨텍스트에서 동기 호출을 Task.Run()로 래핑하거나 비동기 OCR 가이드에 문서화된 비동기 패턴을 사용하세요.IronOCR호출에 await를 반사적으로 추가하지 마십시오. 웹이 아닌 컨텍스트에서 불필요한 오버헤드를 추가하고 필요하지 않습니다.

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

문제 2: API 키 저장 및 순환 인프라

OCR.space: API 키는 모든 요청에 주입되어야 합니다. 팀은 보통 appsettings.json 또는 환경 변수에 저장하고, IOptions<t>이나 생성자 주입을 통해 주입하고 노출되면 회전합니다. 키 순환을 위해서는 모든 배포 환경을 업데이트하고 애플리케이션을 다시 시작해야 합니다.

해결책:IronOCR라이선스 키는 시작 시 한 번 설정되며 실행 중에는 다시 참조되지 않습니다. 요청별 키 주입 패턴을 제거합니다. IOptions<OcrSpaceSettings> 구성 클래스 제거. 핵심 초기화 패턴은 단 한 줄입니다.

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

요청별 자격 증명 주입이나 키 순환 절차가 없으며, 요청 추적에 키가 실수로 기록될 위험도 없습니다.

문제 3: 파일 크기 사전 유효성 검사 로직

OCR.space: 무료 요금제는 5MB가 넘는 파일을 오류 메시지와 함께 거부합니다. 실제 운영 환경에서는 실패할 호출에 대한 속도 제한 슬롯 낭비를 방지하기 위해 모든 요청 전에 파일 크기 검사를 추가합니다.

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

해결 방법: 이 항목을 완전히 삭제하세요. IronOCR의 OcrInput.LoadPdf()OcrInput.LoadImage()은 사용 가능한 시스템 메모리 이상으로 크기 제한이 없습니다. 5MB라는 인위적인 제한은 OCR.space의 무료 요금제가 서버 용량 문제로 인해 설정한 것일 뿐입니다. 50MB 크기의 스캔된 PDF 파일은 500KB 크기의 파일과 동일한 방식으로 로드됩니다.

문제 4: JSON 응답 탐색의 취약성

OCR.space: 응답 구문 분석은 문자열로 키 지정된 속성 액세스를 사용하여 JsonDocument 탐색에 의존합니다. doc.RootElement.GetProperty("ParsedResults")[0].GetProperty("ParsedText")과 같은 코드는 응답 형상이 변경되면 KeyNotFoundException을 던지고 IndexOutOfRangeException가 비어 있는 경우 ParsedResults을 던집니다. 두 경우 모두 코드 전체에 걸쳐 try-catch 블록이나 null 검사가 필요합니다.

솔루션: IronOCR는 유형이 지정된 OcrResult 객체를 반환합니다. .Text 속성은 항상 string입니다 — 절대 null이 아니며, 절대 누락되지 않습니다. OCR이 출력이 없는 경우(빈 페이지, 읽을 수 없는 이미지), result.Text는 빈 문자열입니다. 탐색할 JSON과 속성 경로의 취약성을 방어할 필요가 없습니다. 신뢰도 기반 필터링을 위해 result.Confidence는 직적으로 비교할 수 있는 double을 반환합니다:

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

신뢰도 점수 계산 방법 안내에는 단어별 및 문서별 신뢰도 임계값 설정 방법이 포함되어 있습니다.

문제 5: CI/CD 환경에서 공유 IP 사용량 제한 소진 문제

OCR.space: OCR.space에 대한 통합 테스트를 실행하는 CI/CD 파이프라인은 개발 사무실 네트워크와 동일한 아웃바운드 IP 주소를 사용합니다. 무료 계정은 IP 주소당 하루 500건의 요청 제한이 있습니다. 실행당 200개의 테스트 문서를 처리하는 파이프라인은 첫 번째 개발자가 수동 테스트를 실행하기도 전에 일일 할당량을 소진할 수 있습니다. 팀들은 이 문제를 해결하기 위해 테스트에서 OCR.space의 응답을 모킹하는 방식을 사용하는데, 이는 통합 테스트의 목적에 어긋납니다.

해결책:IronOCR은 로컬에서 처리됩니다. 테스트 스위트는 직접 new IronTesseract().Read(testImagePath).Text 호출 — 모의 테스트가 필요 없으며, 소진할 할당량도 없고, 네트워크 종속성도 없습니다. CI/CD 환경에서 실행되는 통합 테스트는 프로덕션 환경과 동일한 실제 OCR 결과를 사용하며, 속도 제한 관리나 테스트 격리 패턴이 적용되지 않습니다.

이슈 6: IDisposable HttpClient 관리 패턴

OCR.space: HttpClient 래퍼 클래스는 HTTP 연결 풀을 해제하기 위해 IDisposable을 구현합니다. OCR 서비스의 모든 소비자는 싱글톤을 주입하거나, using 블록을 사용하거나, DI 컨테이너의 디스포지션 생명주기로 등록해야 합니다. 폐기를 잊으면 부하가 걸렸을 때 소켓이 과열되어 고장이 발생할 수 있습니다.

솔루션: IronTesseract은 네트워크 연결을 관리하지 않습니다. IDisposable을 구현하지 않습니다. 스레드 당 하나의 인스턴스를 생성하거나 (ASP.NET에서는 요청당) .Read()을 호출하고 GC가 이를 회수하도록 합니다. OcrInput 클래스는 IDisposable를 구현하며 전처리가 적용될 때 using 블록으로 래핑되어야 하지만, 기본 IronTesseract 클래스는 생명주기 관리를 필요로 하지 않습니다. OCR 서비스 래퍼에서 IDisposable 구현을 제거하고 DI 등록을 간단한 팩토리 또는 싱글톤으로 스코프/일시 제거와 함께 단순화하세요.

OCR.space 마이그레이션 체크리스트

이동 전 작업

코드베이스를 감사하여 모든 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" .
SHELL

OCR.space 코드가 포함된 파일 목록을 문서화하십시오. OCR.space의 HTTP 종속성 때문에 전적으로 async인 메소드를 기록하세요 — 이들은 마이그레이션 후 동기식으로 전환할 수 있습니다.

코드 업데이트 작업

  1. IronOcr NuGet Install-Package: dotnet add package IronOcr
  2. 애플리케이션 시작 시 IronOcr.License.LicenseKey = "..." 추가
  3. OcrSpaceApiClient 클래스와 모든 지원 인프라 삭제
  4. 사용자 정의 OcrResult POCO 삭제 (이것은 IronOcr.OcrResult로 대체됨)
  5. 사용자 정의 OcrSpaceException 클래스 삭제 (표준 .NET 예외로 대체됨)
  6. SemaphoreSlim 속도 제한기 및 관련 Task.Delay 논리 삭제
  7. OCR 이미지 인코딩에 사용된 모든 Convert.ToBase64String() 호출 제거 8.FormUrlEncodedContent / MultipartFormDataContent생성을 OcrInput로 교체
  8. _httpClient.PostAsync(...) 호출을 new IronTesseract().Read(input)로 교체
  9. JsonDocumentParsedResults[0].ParsedText 구문 분석을 result.Text로 교체
  10. TextOverlay JSON 좌표 구문 분석을 result.Pages[n].Words로 교체
  11. OCREngine 매개변수 전환을 적절한 전처리 필터로 교체
  12. language 매개변수 문자열을 OcrLanguage 열거형 값으로 교체
  13. 파일 크기 사전 유효성 검사 제거 (5MB 제한이 더 이상 적용되지 않음)
  14. HTTP가 비동기의 유일한 이유였던 경우 async Task<string> OCR 메소드를 동기식 string로 변환
  15. 구성 파일 및 환경 변수 설정에서 OCR.space API 키를 제거합니다.

마이그레이션 후 테스트

  • 텍스트 추출이 동일한 테스트 문서에서 동등하거나 더 높은 정확도를 생성하는지 확인합니다.
  • 대용량 파일(5MB 이상) 처리 시 오류 없이 완료되는지 확인합니다.
  • OcrLanguage.English + OcrLanguage.French와 멀티 랭귀지 문서를 테스트하고 교차 출력 확인
  • 실제 OCR 호출을 사용하여 CI/CD 파이프라인을 실행하고, 모든 문서 볼륨에서 속도 제한 오류가 발생하지 않는지 확인합니다.
  • 검색 가능한 PDF 출력물에 워터마크가 없는지 확인합니다.
  • 이전에 비동기 방식으로 작동했던 컨트롤러 액션이 동기 방식으로 전환된 후에도 여전히 올바르게 반응하는지 확인합니다.
  • 네트워크 연결이 차단되거나 제한된 배포 환경에서 문서가 오류 없이 처리되는지 테스트합니다.
  • 이전에 OCREngine=2이 필요했던 문서에서 result.Confidence 값이 수용 가능한지 확인
  • 구조화된 문서에서 예상된 필드 위치와 result.Pages[n].Words 좌표가 일치하는지 확인
  • 첫 번째 OCR 호출 전에 애플리케이션 시작 라이선스 초기화가 성공하는지 테스트하십시오.

IronOCR로 마이그레이션할 때의 주요 이점

80줄이 넘는 인프라 비용이 사라집니다. 모든 OCR.space 통합에는 HTTP 클라이언트, 속도 제한기, JSON 역직렬화기, 사용자 지정 예외 유형 및 사용자 지정 결과 모델이 포함됩니다. 저 코드는 애플리케이션에 실제로 필요한 기능을 수행하는 것이 아니라, OCR.space의 SDK가 없는 것을 보완하기 위해 존재하는 코드입니다. 마이그레이션이 완료되면 해당 코드는 삭제됩니다. 코드베이스에서 OCR의 표면적은 호출 사이트의 new IronTesseract().Read(path).Text로 줄어들고 시작 시 하나의 라이선스 초기화 라인으로 줄어듭니다.

문서 처리 속도는 로컬 하드웨어에 따라 달라집니다. OCR.space는 모든 처리 작업에 네트워크 지연 시간, OCR.space 서버 대기열 깊이 및 지리적 왕복 시간을 고려합니다.IronOCR프로세스 내에서 실행됩니다. 로컬 워크스테이션은 배치 처리를 직렬화하는 분당 60건 요청 제한 없이, 어떤 처리량 수준에서도 클라우드 API보다 빠르게 문서를 처리합니다. 여러 IronTesseract 인스턴스에 걸쳐 Parallel.ForEach를 사용한 병렬 처리는 CPU 코어와 함께 확장됩니다 — 멀티스레딩 예제를 참조하세요.

중요 문서는 인프라 내에 영구적으로 보관됩니다. 마이그레이션 후 의료 기록, 금융 문서, 법률 계약서 및 신분증은 애플리케이션 서버를 벗어나지 않습니다. HIPAA, GDPR, SOC 2 및 내부 데이터 분류 정책에 대한 규정 준수 검토에서 더 이상 OCR.space의 데이터 처리 방식을 검토 범위에 포함할 필요가 없습니다. 감사 범위가 사용자 인프라로 축소됩니다. Docker 배포 가이드Azure 배포 가이드는 데이터 상주 규정 준수가 필요한 컨테이너 및 클라우드 환경에IronOCR배포하는 방법을 다룹니다.

구조화된 출력은 문서 인텔리전스 응용 프로그램을 가능하게 합니다. OCR.space의 ParsedText 문자열은 문서 분석의 최종 지점입니다. IronOCR의 result.Pages, result.Words, result.Lines와 좌표 및 단어 단위 신뢰 점수는 특정 필드를 위치, 추출 품질을 검증, 테이블 데이터를 추출하고 다운스트림 문서 인텔리전스 파이프라인을 구축할 수 있게 합니다. OCR.space의 일반 텍스트 출력 위에 사용자 지정 레이아웃 분석 기능을 구축해야 했던 작업이 이제는 API 직접 호출로 가능해졌습니다. 표 추출 가이드스캔 문서 처리 가이드는 이러한 구조화된 기반이 무엇을 가능하게 하는지 보여줍니다.

비용은 처리량에 관계없이 고정적이고 예측 가능합니다. OCR.space의 무료 요금제는 월 25,000건의 요청을 지원합니다. 그 외에도 구독료는 사용량에 따라 증가합니다. IronOCR의 $999 Lite 영구 라이선스는 볼륨에 관계없이 문서 당 요금이 없습니다. 한 달에 10만 건의 문서를 처리하는 팀과 한 달에 1천 건의 문서를 처리하는 팀은 동일한 라이선스 비용을 지불합니다. 문서 처리 애플리케이션에 대한 예산 예측은 사업 성공에 따라 증가하는 변동 항목이 아니라 고정된 연간 비용이 됩니다. IronOCR 제품 페이지 에는 팀에서 구매하기 전에 특정 문서 유형에 대한 정확도를 검증할 수 있는 무료 평가판이 포함되어 있습니다.

참고해 주세요OCR.space 및 Tesseract는 각 소유자의 등록 상표입니다. 이 사이트는 Google 또는 OCR.space와 관련이 없으며, 승인되지 않았으며, 후원도 받지 않습니다. 모든 제품명, 로고 및 브랜드는 해당 소유자의 자산입니다. 비교는 정보 제공 목적으로만 사용되며, 작성 시점에 공개적으로 이용 가능한 정보를 반영합니다.

자주 묻는 질문

OCR.space API에서 IronOCR로 마이그레이션해야 하는 이유는 무엇인가요?

일반적인 동인으로는 COM 상호 운용의 복잡성 제거, 파일 기반 라이선스 관리 대체, 페이지당 과금 방지, Docker/컨테이너 배포 활성화, 표준 .NET 툴링과 통합되는 NuGet 네이티브 워크플로 채택 등이 있습니다.

OCR.space API에서 IronOCR로 마이그레이션할 때 변경되는 주요 코드는 무엇인가요?

OCR.space 초기화 시퀀스를 IronTesseract 인스턴스화로 대체하고, COM 수명 주기 관리(명시적 생성/로드/폐쇄 패턴)를 제거하고, 결과 속성 이름을 업데이트합니다. 그 결과 상용구 줄이 현저히 줄어듭니다.

마이그레이션을 시작하려면 IronOCR을 어떻게 설치하나요?

패키지 관리자 콘솔에서 '설치-패키지 IronOcr'을 실행하거나 CLI에서 '닷넷 추가 패키지 IronOcr'을 실행하세요. 언어 팩은 별도의 패키지입니다: 예를 들어 프랑스어의 경우 '닷넷 추가 패키지 IronOcr.Languages.French'를 실행합니다.

IronOCR은 표준 비즈니스 문서에 대한 OCR.space API의 OCR 정확도와 일치하나요?

IronOCR은 송장, 계약서, 영수증, 타이핑된 양식 등 표준 비즈니스 콘텐츠에 대해 높은 정확도를 달성합니다. 이미지 전처리 필터(데스큐, 노이즈 제거, 대비 향상)는 품질이 저하된 입력에 대한 인식률을 더욱 향상시킵니다.

IronOCR은 OCR.space API가 별도로 설치하는 언어 데이터를 어떻게 처리하나요?

IronOCR의 언어 데이터는 NuGet 패키지로 배포됩니다. '닷넷 추가 패키지 IronOcr.Languages.German'은 독일어 지원을 설치합니다. 수동 파일 배치나 디렉터리 경로는 필요하지 않습니다.

OCR.space API에서 IronOCR로 마이그레이션하려면 배포 인프라를 변경해야 하나요?

IronOCR은 OCR.space API보다 인프라 변경이 덜 필요합니다. SDK 바이너리 경로, 라이선스 파일 배치 또는 라이선스 서버 구성이 필요하지 않습니다. NuGet 패키지에는 완전한 OCR 엔진이 포함되어 있으며 라이선스 키는 애플리케이션 코드에 설정된 문자열입니다.

마이그레이션 후 IronOCR 라이선싱은 어떻게 구성하나요?

애플리케이션 시작 코드에 IronOcr.License.LicenseKey = "YOUR-KEY"를 할당하세요. Docker 또는 Kubernetes에서 키를 환경 변수로 저장하고 시작 시 키를 읽습니다. 트래픽을 수락하기 전에 License.IsValidLicense를 사용하여 유효성을 검사하세요.

IronOCR은 OCR.space와 동일한 방식으로 PDF를 처리할 수 있나요?

예. IronOCR은 원본 및 스캔한 PDF를 모두 읽습니다. IronTesseract를 인스턴스화하고, 입력이 PDF 경로 또는 OcrPdfInput인 경우 ocr.Read(input)를 호출하고, OcrResult 페이지를 반복하면 됩니다. 별도의 PDF 렌더링 파이프라인이 필요하지 않습니다.

IronOCR은 대량 처리에서 스레딩을 어떻게 처리하나요?

IronTesseract는 스레드별로 인스턴스화해도 안전합니다. Parallel.ForEach 또는 Task 풀에서 스레드당 하나의 인스턴스를 스핀업하고, OCR을 동시에 실행하고, 완료되면 각 인스턴스를 폐기하면 됩니다. 전역 상태나 잠금이 필요하지 않습니다.

IronOCR은 텍스트 추출 후 어떤 출력 형식을 지원하나요?

IronOCR은 텍스트, 단어 좌표, 신뢰도 점수, 페이지 구조를 포함한 구조화된 결과를 반환합니다. 내보내기 옵션에는 일반 텍스트, 검색 가능한 PDF, 다운스트림 처리를 위한 구조화된 결과 개체가 포함됩니다.

워크로드 확장을 위해 IronOCR 가격이 OCR.space API보다 더 예측 가능한가요?

IronOCR은 페이지당 또는 볼륨당 요금이 없는 정액제 영구 라이선스를 사용합니다. 10,000페이지를 처리하든 1,000만 페이지를 처리하든 라이선스 비용은 일정하게 유지됩니다. 볼륨 및 팀 라이선스 옵션은 IronOCR 가격 페이지에서 확인할 수 있습니다.

OCR.space API에서 IronOCR로 마이그레이션한 후 기존 테스트는 어떻게 되나요?

추출된 텍스트 콘텐츠에 대해 어설트하는 테스트는 마이그레이션 후에도 계속 통과해야 합니다. API 호출 패턴 또는 COM 개체 수명 주기의 유효성을 검사하는 테스트는 IronOCR의 더 간단한 초기화 및 결과 모델을 반영하도록 업데이트해야 합니다.

칸나오팟 우돈판트
소프트웨어 엔지니어
카나팟은 소프트웨어 엔지니어가 되기 전 일본 홋카이도 대학교에서 환경 자원학 박사 학위를 취득했습니다. 학위 과정 중에는 생물생산공학과 소속 차량 로봇 연구실에서 활동하기도 했습니다. 2022년에는 C# 기술을 활용하여 Iron Software의 엔지니어링 팀에 합류했고, 현재 IronPDF 개발에 집중하고 있습니다. 카나팟은 IronPDF에 사용되는 대부분의 코드를 직접 작성하는 개발자로부터 배울 수 있다는 점에 만족하며, 동료들과의 소통을 통해 배우는 것 외에도 Iron Software에서 일하는 즐거움을 누리고 있습니다. 코딩이나 문서 작업을 하지 않을 때는 주로 PS5로 게임을 하거나 The Last of Us를 다시 시청하는 것을 즐깁니다.

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해