푸터 콘텐츠로 바로가기
다른 구성 요소와 비교

ABBYY FineReader와 Tesseract의 OCR 비교

OCR.space에는 NuGet 패키지가 없습니다. 그 단일 사실은 .NET 개발자가 그것을 선택할 때 내리는 모든 통합 결정을 정의합니다: 사용자 정의 HttpClient를 작성하고, 문서를 base64로 인코딩하여 https://api.ocr.space/parse/image에 POST하고, JSON 응답을 수동으로 구문 분석하고, HTTP 429를 잡고, 지수 백오프를 구현하고, 속도 제한 부기 관리를 구축하고, 예외 유형을 정의합니다 - 이 모든 것을 애플리케이션에 한 글자도 들어오기 전에 완료해야 합니다. 이 글에서는 DIY 방식의 오버헤드가 코드, 시간, 그리고 프로덕션 안정성 측면에서 실제로 얼마나 큰 비용을 초래하는지 살펴보고, 단일 NuGet 패키지로 제공되는 네이티브 .NET 라이브러리인 IronOCR 과 직접 비교합니다.

OCR.space 이해하기

OCR.space는 OCR.space에서 운영하는 원격 서버에서 이미지와 PDF를 처리하는 프리미엄 REST API입니다. 이 서비스는 무료 요금제를 중심으로 마케팅 전략을 펼치고 있습니다. 무료 요금제는 신용카드 정보 없이 월 25,000건의 요청을 처리할 수 있으며, OCR을 실험하거나 개인 프로젝트를 구축하는 개발자들에게 매력적입니다. NuGet 패키지도 없고, 공식 .NET SDK도 없으며, 어떤 언어로든 강력한 형식의 클라이언트 라이브러리도 없습니다. 모든 .NET 통합은 HttpClient를 기반으로 수작업으로 구축됩니다.

.NET 개발자를 위한 아키텍처 현실:

  • NuGet 패키지 없음: .NET 에 대한 완벽한 지원이 전혀 없습니다. IntelliSense 기능도 없고, 타입 모델도 없고, 통합 오류 처리 기능도 없습니다.
  • 클라우드 전용 처리: 모든 문서는 귀사 인프라를 벗어나 처리됩니다. 온프레미스 옵션이나 자체 호스팅 배포 경로는 제공되지 않습니다.
  • 수동 REST 통합: 개발자는 파일을 base64로 인코딩하여 FormUrlEncodedContent 또는 MultipartFormDataContent를 구성하고 원시 JSON 응답을 구문 분석합니다.
  • 자체 구현 속도 제한: 무료 티어에서는 분당 60건의 요청과 IP 주소당 하루 500건의 요청으로 제한됩니다. 운영 환경에서는 애플리케이션이 이러한 제한 로직을 직접 구현해야 합니다.
  • 파일 크기 제한: 무료 요금제에서는 5MB를 초과하는 파일을 허용하지 않습니다. 여러 페이지로 구성된 PDF 파일은 종종 이 제한을 초과합니다.
  • 워터마크가 포함된 PDF 출력: 무료 버전에서 검색 가능한 PDF를 생성할 때 OCR.space 워터마크가 삽입되어 고객에게 전달하거나 문서를 보관하는 데 사용할 수 없습니다.
  • SLA 없음: 무료 요금제는 가동 시간 보장이 없습니다. 유료 플랜은 더 높은 한도를 제공하지만, 아키텍처 제약 조건은 동일합니다.

REST 통합 개발자가 작성해야 하는 내용

OCR.space의 문서는 개발자에게 REST 엔드포인트를 안내하고 나머지는 개발자가 직접 구축하도록 맡겨둡니다. 실제 운영 환경에 맞게 보안을 강화하기 전의 최소한의 필수 기능을 갖춘 .NET 클라이언트는 다음과 같습니다.

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

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

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

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

        await _rateLimiter.WaitAsync(cancellationToken);

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

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

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

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

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

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

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

        var result = new OcrResult();

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

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

        return result;
    }

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

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

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

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

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

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

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

        await _rateLimiter.WaitAsync(cancellationToken);

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

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

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

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

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

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

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

        var result = new OcrResult();

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

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

        return result;
    }

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

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

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

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

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

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

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

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

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

        Await _rateLimiter.WaitAsync(cancellationToken)

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

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

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

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

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

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

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

            Dim result = New OcrResult()

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

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

            Return result
        End Using
    End Function

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

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

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

Public Class OcrSpaceException
    Inherits Exception

    Public ReadOnly Property StatusCode As Integer

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

이는 첫 번째 비즈니스 로직 메서드 이전에 존재하는 90줄 이상의 인프라 코드입니다. 모든 OCR.space 통합에는 이러한 기본 템플릿(또는 그와 유사한 것)이 포함되어 있으며, 이를 작성하는 모든 팀은 OCR.space가 해결해주지 않은 동일한 문제를 해결하고 있습니다.

IronOCR 이해하기

IronOCR는 .NET용 상업용 OCR 라이브러리로, IronOcr NuGet 패키지를 통해 설치됩니다. 이 서비스는 최적화된 Tesseract 5 엔진에 자동 전처리, 네이티브 PDF 입력, 검색 가능한 PDF 출력, 125개 이상의 언어 팩, 구조화된 결과 추출 기능을 통합했으며, 클라우드 종속성, API 키 또는 요청당 요금 없이 모든 기능을 제공합니다. 문서는 .NET 애플리케이션이 실행되는 인프라에서 로컬로 처리됩니다.

주요 특징:

  • 단일 NuGet 패키지: dotnet add package IronOcr는 완전한 설치입니다. 관리해야 할 네이티브 바이너리도 없고, tessdata 디렉터리도 없고, 환경 변수도 없습니다.
  • 강력한 유형의 API: IronTesseract, OcrInput, OcrResult, CropRectangle는 전체 IntelliSense 지원을 제공하는 일류 .NET 유형입니다.
  • 자동 전처리: 기울기 보정, 노이즈 제거, 대비 조정, 이진화 및 해상도 향상 필터가 외부 라이브러리 없이 자동으로 또는 필요에 따라 적용됩니다.
  • PDF 기본 지원: 스캔한 PDF 파일을 읽는 기능과 검색 가능한 PDF 파일을 생성하는 기능이 모두 내장되어 있습니다. 사용 가능한 메모리 용량 외에는 크기 제한이 없습니다.
  • 로컬 실행: OCR 실행 중에는 네트워크 호출이 발생하지 않습니다. 이 라이브러리는 프로세스 내에서 완전히 실행됩니다. 에어갭 환경은 구성 변경 없이 작동합니다.
  • 스레드 안전: 여러 IronTesseract 인스턴스가 동기화 또는 경합 관리 없이 동시에 실행됩니다.
  • 영구 라이선스: $999 Lite / $1,499 Plus / $2,999 Professional. 요청량에 관계없이 건당 추가 요금이 없습니다.

기능 비교

기능 OCR.space IronOCR
NuGet 패키지 없음 — REST API만 사용 IronOcr — 전체 .NET 지원
처리 위치 OCR.space 클라우드 서버 로컬 — 귀사의 인프라
무료 티어 월 25,000개 요청 (한정 수량) 체험 가능
유료 가격 현재 가격 문의는 OCR.space에 연락하십시오 $2,999 일회성 영구
속도 제한 분당 60건, 하루 500건 (무료) None
오프라인 기능 아니요 네, 완전히 공기층이 분리되어 있습니다.
데이터 개인정보 보호 제3자에게 발송된 문서 문서는 서버에 보관됩니다.

상세 기능 비교

기능 OCR.space IronOCR
통합
NuGet 패키지 None 네 (IronOcr)
강력한 타입 모델 없음 — 수동 JSON 파싱 네 (OcrResult, OcrInput)
IntelliSense 지원 None 전체
사용자 지정 예외 유형 스스로를 정의해야 합니다 내장형
재시도 로직 직접 만들어야 합니다 내장형
처리 중
처리 위치 클라우드 서버 현지 진행 중
인터넷 필요 네, 모든 통화에 대해 아니요
에어갭 서포트 아니요
요금 제한 네, 모든 플랜 None
파일 크기 제한 5MB (무료 요금제) 메모리 전용
OCR 기능
이미지 OCR
PDF OCR 네 (제한 사항이 있습니다) 예 (네이티브 파일이며 크기 제한이 없습니다)
검색 가능한 PDF 출력 무료 버전에 워터마크가 표시됩니다. 깨끗한 출력, 워터마크 없음
자동 전처리 없음 (서버 측에서만 사용) 기울기 보정, 노이즈 제거, 대비 조정, 이진화, 해상도 향상
수동 전처리 제어 None 전체 필터 API
언어 지원 약 25개 언어 NuGet 언어 팩을 통해 125개 이상 추가 가능
문서별 다국어 지원 제한적 네 — 모국어와 제2언어 모두 해당됩니다.
구조화된 출력 (단어/줄/페이지) 아니요, 일반 텍스트만 가능합니다. 네, 페이지, 단락, 줄, 좌표가 있는 단어까지 모두 포함됩니다.
신뢰도 점수 아니요 네, 결과별로 그리고 단어별로 그렇습니다.
영역 기반 OCR 아니요 네 — CropRectangle
OCR 중 바코드 판독 아니요 네 — ReadBarCodes = true
배포
Docker 외부 인터넷 연결이 필요합니다. 별도의 설정 없이 바로 사용 가능합니다.
Linux 외부 인터넷 연결이 필요합니다. 완전히 지원
HIPAA 규정 준수 위험 — 문서가 당신의 통제에서 벗어나게 됩니다 예, 외부 전송 없음
GDPR 준수 위험 요소 - EU 데이터 전송 불확실 예, 제3자를 통한 데이터 처리는 없습니다.
가격
가격 모델 월간 구독 일회성 영구 라이선스
입장료 현재 가격 문의는 OCR.space에 연락하십시오 $999 일회성
대규모 문서당 비용 None
SLA 없음(무료), 유료는 종류에 따라 다름 Enterprise SLA이용 가능

SDK 심층 활용 vs. 순수 HTTP: 통합 비용

OCR.space와 네이티브 SDK 간의 차이는 스타일 선호도의 문제가 아닙니다. 이는 개발 시간, 버그 발생 가능성, 그리고 모든 배포 과정에서의 유지 관리 부담을 기준으로 측정됩니다.

OCR.space 접근법

OCR.space를 사용하는 모든 .NET 개발자는 자체 HTTP 클라이언트를 작성합니다. 가장 간단한 구현 방식, 즉 재시도 로직, 일괄 처리 기능, 전처리 과정이 없는 기본적인 텍스트 추출 방식조차도 50줄이 넘습니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        Dim content 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")
        })

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

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

        ' Parse JSON manually — no typed models
        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

일괄 처리를 추가하면 모든 일괄 요청에 대해 속도 제한 스로틀링, 지수 백오프를 사용하는 요청별 재시도 로직, 그리고 HTTP 429 응답에 대한 명시적 처리가 필요하기 때문에 코드 줄이 80줄 이상으로 늘어납니다. PDF 처리 기능을 추가하면 모든 호출 전에 5MB 파일 크기 검사가 추가됩니다. 무료 요금제는 이보다 큰 파일을 경고가 아닌 오류와 함께 거부하기 때문입니다.

IronOCR 접근법

NuGet 에서 IronOCR 설치하면 해당 인프라 코드가 모두 기존 메서드 호출로 대체됩니다.

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

IronTesseract 클래스는 파일 읽기, 전처리, 엔진 실행 및 결과 생성 작업을 처리합니다. HTTP 클라이언트가 없습니다. base64 인코딩을 사용하지 않습니다. JSON 파싱은 지원하지 않습니다. API 키가 필요하지 않습니다. 속도 제한 없음. 이미지에서 텍스트를 읽는 방법에 대한 튜토리얼은 파일 경로, 바이트 배열, 스트림, 비트맵 등 다양한 입력 유형을 다루지만, 모두 동일한 단일 함수 호출 패턴을 따릅니다.

소스 파일의 코드 복잡도 수치는 가상적인 것이 아닙니다.

시나리오 OCR.space 라인 IronOCR라인
기본 추출 50세 이상 3
PDF 처리 60세 이상 12
일괄 처리 80+ 15
오류 처리 70+ 15
완벽한 클라이언트 인프라 200+ 0 (NuGet 제공합니다)

일괄 처리 및 속도 제한

OCR.space의 무료 요금제가 실제 운영 환경에서 가장 눈에 띄게 무너지는 부분은 바로 사용량 제한 문제입니다.

OCR.space 접근법

OCR.space에서 배치 처리를 위해서는 60회/분 요청 한도를 준수할 수 있도록 SemaphoreSlim을 생성 및 관리해야 하며, 세마포어 논리가 부정확하거나 공유 인프라가 IP 주소를 공유할 때 발생하는 HTTP 429 응답에 대한 지수 백오프 재시도 논리가 필요합니다:

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

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

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

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

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

        return results;
    }

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

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

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

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

                response.EnsureSuccessStatusCode();

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

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

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

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

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

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

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

        return results;
    }

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

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

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

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

                response.EnsureSuccessStatusCode();

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

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

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

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

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

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

        For Each imagePath In imagePaths
            Await _rateLimiter.WaitAsync()

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

        Return results
    End Function

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

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

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

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

                response.EnsureSuccessStatusCode()

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

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

무료 요금제의 하루 500건 요청 제한은 이러한 제약을 더욱 강화합니다. 하루에 600건의 문서를 처리하는 애플리케이션은 처리 도중 실패하며, 다음 날까지 자동으로 이월되지 않습니다.

IronOCR 접근법

IronOCR 에는 사용량 제한이 없습니다. 일괄 처리는 반복문입니다.

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

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

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

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

    return results;
}
Imports System.Collections.Generic

Public Function ProcessBatch(imagePaths As String()) As Dictionary(Of String, String)
    Dim results As New Dictionary(Of String, String)()
    Dim ocr As New IronTesseract()

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

    Return results
End Function
$vbLabelText   $csharpLabel

배치 내에서 IronTesseract 인스턴스를 재사용하면 반복되는 엔진 초기화 오버헤드를 피할 수 있습니다. 처리 속도는 원격 API의 속도 제한 정책이 아니라 로컬 CPU 및 디스크 I/O에 의해 제한됩니다. 멀티스레딩 예제는 처리량이 중요할 때 코어 간 병렬화를 수행하는 방법을 보여줍니다.

PDF 처리

PDF 지원은 두 번째 주요 구조적 격차를 보여줍니다.

OCR.space 접근법

OCR.space는 유료 플랜에서 PDF 업로드를 지원하며, 무료 플랜에서도 일부 PDF 업로드를 지원합니다. 하지만 무료 계정의 파일 크기 제한(5MB)으로 인해 여러 페이지로 구성된 문서는 대부분 업로드할 수 없습니다. 무료 버전에서 생성되는 검색 가능한 PDF 출력물에는 워터마크가 포함됩니다. 이 구현 방식은 페이지별 결과 추출과 모든 요청 전에 명시적인 파일 크기 검사를 필요로 합니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        Return pageTexts
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 접근법

IronOCR PDF 파일을 기본적으로 읽습니다. 제한 요소는 임의의 파일 크기 임계값이 아니라 사용 가능한 메모리입니다. 검색 가능한 PDF 출력은 모든 라이선스 등급에서 워터마크가 없는 깔끔한 파일을 생성합니다.

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

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

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

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

    return pageTexts;
}

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

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

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

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

    return pageTexts;
}

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

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

    Using input As New OcrInput()
        input.LoadPdf(pdfPath) ' 아니요 5MB ceiling

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

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

    Return pageTexts
End Function

Public Sub CreateSearchablePdf(inputPath As String, outputPath As String)
    Dim result = New IronTesseract().Read(inputPath)
    result.SaveAsSearchablePdf(outputPath)
End Sub
$vbLabelText   $csharpLabel

암호로 보호된 PDF는 단일 매개변수입니다: input.LoadPdf("encrypted.pdf", Password: "secret"). PDF OCR 사용법 안내서 는 페이지 범위 선택 및 암호 처리 방법을 자세히 다룹니다. 검색 가능한 PDF 생성 패턴에 대해서는 검색 가능한 PDF 사용법을 참조하세요.

오류 처리

OCR.space는 HTTP 상태 코드와 JSON IsErroredOnProcessing 플래그라는 두 가지 채널을 통해 오류를 전달합니다. 생산 코드는 두 가지를 모두 확인하고, IsErroredOnProcessing가 true일 때 JSON 오류 배열을 구문 분석하며, 부분 오류에 대한 페이지별 FileParseExitCode 값을 검사해야 합니다. 이 중 아무 것도 형식화되지 않았으며 모든 것이 JsonDocument에서 문자열 분석입니다:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

IronOCR 명확한 메시지와 함께 표준 .NET 예외를 발생시킵니다. JSON 파싱 없음, HTTP 상태 코드 전환 없음, 계층형 오류 추출 없음:

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

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

IronTesseract API 참조OcrResult API 참조는 추출 품질 평가를 위한 Confidence 속성을 포함하여 형식화된 결과 구조를 문서화합니다.

API 매핑 참조

OCR.space 컨셉 IronOCR에 상응하는
POST https://api.ocr.space/parse/image new IronTesseract().Read(path)
FormUrlEncodedContent / MultipartFormDataContent OcrInput
base64Image 매개변수 input.LoadImage(path) 또는 input.LoadImage(bytes)
language 매개변수 ocr.Language = OcrLanguage.English
OCREngine 매개변수 엔진은 내부적으로 관리됩니다.
isOverlayRequired 매개변수 result.Words / result.Lines (항상 사용 가능)
isCreateSearchablePdf 매개변수 result.SaveAsSearchablePdf(outputPath)
filetype=PDF 매개변수 input.LoadPdf(path)
ParsedResults[0].ParsedText result.Text
ParsedResults[n] (페이지별) result.Pages[n].Text
IsErroredOnProcessing JSON 플래그 표준 .NET 예외
FileParseExitCode 페이지별 플래그 표준 .NET 예외
ProcessingTimeInMilliseconds 암시적 — 추가 구문 분석이 필요 없음
HTTP 429 / 속도 제한 해당 사항 없음 - 요금 제한 없음
사용자 정의 OcrResult POCO (사용자 정의) IronOcr.OcrResult (NuGet에서 제공)
사용자 정의 OcrSpaceException (사용자 정의) 표준 .NET 예외 유형
SemaphoreSlim 속도 제한기 (사용자가 제작) 필요 없음
모든 요청에 ​​API 키가 포함됩니다. IronOcr.License.LicenseKey (시작 시 한 번만)

팀이 OCR.space에서IronOCR로 이전하는 것을 고려할 때

부하 테스트 중 무료 티어에 도달함

OCR.space의 IP 주소당 하루 500건 요청 제한은 권고 사항이 아니라 엄격한 제한입니다. 팀들은 여러 개발자가 사무실 IP 주소를 공유하는 부하 테스트 또는 스테이징 배포 중에 이러한 문제를 발견합니다. OCR.space에 대한 통합 테스트를 실행하는 공유 CI/CD 파이프라인은 업무일이 끝나기 전에 일일 할당량을 모두 소진할 것입니다. 그 시점에서 애플리케이션이 실패하는 이유는 코드가 잘못되었기 때문이 아니라, 제3자 카운터가 임의의 임계값에 도달했기 때문입니다. 로컬 처리로 전환하면 이러한 유형의 오류가 완전히 사라집니다. 처리가 프로세스 내에서 실행되므로 할당량 소진 문제가 발생하지 않습니다.

규정 준수 및 데이터 분류 검토

문서를 개인 식별 정보(PII), 개인 건강 정보(PHI) 또는 재정적으로 민감한 정보로 분류하는 보안 검토는 OCR.space에 이분법적인 문제를 야기합니다. 즉, 해당 서비스는 온프레미스 배포 경로가 없습니다. HIPAA 시나리오에 대한 비즈니스 제휴 계약(BAA)과 동등한 문서화된 계약은 없으며, GDPR에 따른 EU 데이터 전송을 명확하게 규정하는 데이터 처리 계약 구조도 없고, 계약상 통제가 있더라도 문서 전송을 방지할 수 있는 기술적 메커니즘도 없습니다. 클라우드 기반 문서 처리와 관련하여 규정 준수 지적을 받은 팀은 로컬 솔루션이 필요합니다.IronOCR설계상 문서가 애플리케이션 서버를 벗어나지 않도록 하여 이러한 요구 사항을 충족합니다. 라이선스 페이지에는 영구 라이선스 구조가 명시되어 있으며, 클라우드 공급업체 검토 범위를 제외함으로써 규정 준수 문서 작성을 간소화합니다.

통합 코드 유지 관리 부담

OCR.space 통합은 기능 요구 사항에 비례하여 기술적 부채를 축적합니다. 초기 HTTP 클라이언트는 관리가 용이합니다. 그런 다음 팀은 재시도 로직을 추가합니다. 그런 다음 여러 서비스가 IP 주소를 공유하기 때문에 IP 주소별 속도 제한 추적 기능을 추가합니다. 그런 다음 파일 크기 사전 유효성 검사 단계를 추가합니다. 그러다가 엔진 1과 엔진 2의 응답에서 JSON 오류 구조가 다르다는 것을 발견하고 분기를 추가합니다. 6개월 후, OCR.space 통합은 모든 신규 팀원이 OCR 관련 작업을 시작하기 전에 반드시 이해해야 하는 400줄의 인프라 코드로 발전했습니다. 그 팀이 유지 보수 비용과 $999 일회성IronOCR라이선스를 평가할 때, 계산은 간단합니다.

구조화된 출력 요구사항

OCR.space는 일반 텍스트만 반환합니다. 응답에는 단어 좌표, 줄 경계, 단락 구분, 단어별 신뢰도 점수가 없습니다. 송장에서 특정 필드(예: 알려진 지역의 공급업체 이름, 오른쪽 하단의 총액)를 추출해야 하는 애플리케이션은 OCR.space의 출력에서 ​​구조화된 기반을 활용할 수 없습니다.IronOCR페이지, 단락, 줄, 개별 단어 등 모든 세분화 수준에서 좌표와 신뢰도 값을 포함한 전체 문서 계층 구조를 보여줍니다. 영역 기반 처리를 통해 전체 페이지 출력을 후처리하지 않고도 문서의 특정 영역을 대상으로 처리할 수 있습니다. 판독 결과 활용 방법영역 기반 OCR 가이드에서는 이러한 패턴을 자세히 다룹니다.

무료 티어를 넘어선 볼륨 성장

월 25,000건의 요청까지 무료 요금제는 소량 처리 시나리오에 적합합니다. 그 외에도 유료 티어가 적용됩니다 — 현재 가격 문의는 OCR.space에 연락하십시오. 이러한 비용은 시간이 지남에 따라 증가하며, 한 번 구매한 $999 영구 라이선스와 비교할 때, 요청당 청구가 전혀 없기 때문에 그렇습니다. 월 문서 처리량 증가가 25,000건을 초과할 것으로 예상되는 팀은 현지 처리 비용에 대한 명확한 근거를 갖게 됩니다.

일반적인 마이그레이션 고려사항

HTTP 클라이언트를 메서드 호출로 대체

OCR.space에서IronOCR로의 마이그레이션은 두 프로그램 모두 문서에서 텍스트를 반환하기 때문에 아키텍처적으로 간단합니다. HTTP 클라이언트, JSON 파서, 속도 제한기 및 사용자 지정 예외 유형이 사라집니다. 그것들을 대체하는 것은 단일 NuGet 패키지와 메서드 호출입니다. 서비스 경계에서의 이전/이후:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Public Class OcrSpaceService
    Implements IDisposable

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

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

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

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

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

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

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

Public Class IronOcrService
    Private ReadOnly _ocr As New IronTesseract()

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

IDisposable 구현이 사라지는 이유는 관리해야 할 HttpClient가 없기 때문입니다.

API 키 삭제

OCR.space는 모든 HTTP 요청에 API 키를 요구합니다. 해당 키는 안전하게 보관해야 하며, 노출될 경우 주기적으로 변경하고, 소스 코드 관리 시스템에서 제외해야 합니다.IronOCR애플리케이션 시작 시 한 번 설정된 라이선스 키를 사용하며, 일반적으로 환경 변수 또는 구성 시스템에서 가져옵니다.

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

마이그레이션 후에는 API 키 순환 프로세스, 비밀 관리 구성 및 요청별 키 주입 로직이 모두 불필요해집니다.

마이그레이션 후 전처리

OCR.space는 결과를 반환하기 전에 서버 측 처리를 수행하지만, 개발자는 해당 처리에 무엇이 포함되거나 포함되지 않는지에 대해 제어할 수 없습니다.IronOCR명시적인 전처리 방법을 제공합니다. 문서 품질이 문제가 되는 경우(예: 스캔 이미지 왜곡, 저대비 복사본, 노이즈가 심한 팩스 출력물) 전처리 파이프라인은 3~5개의 메서드 호출로 구성됩니다.

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

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

이미지 품질 보정 가이드이미지 색상 보정 가이드 에는 사용 가능한 모든 필터와 보정 전후의 정확도 차이를 보여주는 예시가 포함되어 있습니다.

비동기 vs. 동기

OCR.space 호출은 HTTP 왕복을 포함하므로 본질적으로 비동기적입니다.IronOCR호출은 기본적으로 동기식으로 이루어지므로 웹 환경이 아닌 곳에서는 코드 작성이 간소화됩니다. 논 블로킹 실행을 요구하는 ASP.NET 및 서버 시나리오에서는 IronOCR가 Task.Run 래핑 또는 직접 비동기 API를 통해 비동기 작업을 지원합니다. 비동기 OCR 가이드에서는 패턴을 다룹니다. 이전에는 클라우드 호출을 대기하던 서비스 메서드에서 await를 제거하면 OCR.space가 그것을 요구했던 환경에서 비동기화를 단독으로 채택한 경우 호출 스택이 간소화됩니다.

IronOCR의 추가 기능

위 섹션에서 다루지 않은 기능들은 각각 OCR.space에는 없는 고유한 기능들입니다.

  • hOCR 내보내기: result.SaveAsHocrFile("output.hocr")는 다운스트림 문서 처리 파이프라인을 위한 표준 준수 hOCR 출력을 생성합니다.
  • 진행 상황 추적 : 장시간 실행되는 다중 페이지 일괄 작업에 대한 진행 상황 이벤트를 구독하여 UI 애플리케이션에서 진행률 표시줄과 예상 완료 시간을 계산할 수 있습니다.
  • 테이블 추출 : 구조화된 테이블 데이터는 결과 모델을 통해 접근할 수 있으며, 열 정렬이 필요한 송장 품목 추출과 같은 사용 사례를 지원합니다.
  • 전문화된 문서 읽기: 여권 MRZ 구역, 차량 번호판, MICR 수표 줄, 필기 인식은 같은 IronTesseract API를 통해 접근할 수 있는 맞춤형 기능입니다.

.NET 호환성 및 미래 준비

IronOCR .NET 6, .NET 7, .NET 8 및 .NET 9를 대상으로 하며, 각 LTS 및 STS 릴리스를 적극적으로 지원합니다. 이 라이브러리는 Windows x64, Windows x86, Linuxx64, macOS, Docker, AWS Lambda 및 Azure App Service에서 실행되며, 플랫폼별 구성 없이 동일한 NuGet 패키지에서 모두 사용할 수 있습니다. OCR.space는 모든 배포 환경에서 외부 HTTPS 연결을 요구합니다. 따라서 에어갭 네트워크, 제한적인 기업 프록시, 그리고 정책에 의해 타사 API로의 외부 트래픽이 차단된 인프라는 제외됩니다.IronOCR런타임에 네트워크 요구 사항이 없습니다. 이 기능은 프로세스 내에서 완전히 실행됩니다. .NET 10이 2026년 말 출시를 향해 나아가고 있는 가운데, IronOCR은 .NET 주요 버전 전반에 걸쳐 호환성을 유지해 온 오랜 경험을 바탕으로 통합 변경 없이 연속성을 보장합니다.

결론

OCR.space는 실질적이고 유용한 틈새시장을 공략합니다. 주말 동안 OCR 기능을 프로토타입으로 제작해야 하는 개발자, 텍스트 추출 개념을 탐구하는 학생, 또는 월 25,000건 미만의 문서를 처리하고 규정 준수 요구 사항이 없는 개인용 도구를 사용하는 사용자에게 적합합니다. 해당 사용자층에게 무료 서비스는 진정한 가치를 제공합니다.

문제는 .NET 개발자들이 일반적으로 주말에 프로토타입을 만드는 것이 아니라는 점입니다. 그들은 청구 시스템, 의료 기록 처리 시스템, 문서 보관 파이프라인, 그리고 규정 준수에 민감한 비즈니스 애플리케이션을 개발합니다. 그러한 맥락에서 OCR.space의 NuGet 패키지가 없는 것은 단순한 불편함이 아니라 구조적인 비호환성입니다. HTTP 클라이언트, 속도 제한기, JSON 파서 및 재시도 인프라를 구축하는 데 소요되는 매 시간은 애플리케이션의 실제 요구 사항을 충족하는 데 사용할 수 없는 시간입니다. 그 비용은 초기 투자 비용이지만, 유지 보수 비용은 통합 시스템의 수명 기간 동안 계속 발생합니다.

IronOCR 모든 OCR.space 통합을 특징짓는 특정 실패 모드, 즉 실제 SDK의 부재 문제를 해결합니다. 하나의 NuGet 패키지, 하나의 메서드 호출, HTTP 연결 불필요, 속도 제한 없음, 외부 서버로 문서 전송 없음. $999 진입 가격은 일회성 비용이며, 반복적인 OCR.space 구독 비용은 시간이 지남에 따라 특히 볼륨이 증가함에 따라 이를 초과할 것입니다. 규정을 준수해야 하는 팀에게는, 비용에 상관없이 지역 처리만이 유일한 옵션입니다.

결국 비교는 단 하나의 질문으로 귀결됩니다. 애플리케이션이 OCR을 외부 REST 종속성으로 필요로 하는가, 아니면 라이브러리 함수로 필요로 하는가? 실제 운영 환경에서 사용되는 .NET 애플리케이션의 경우, 답은 거의 항상 후자입니다.

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

자주 묻는 질문

OCR.space API란 무엇인가요?

OCR.space API는 개발자와 기업이 이미지와 문서에서 텍스트를 추출하는 데 사용하는 OCR 솔루션입니다. 이 솔루션은 .NET 애플리케이션 개발용 IronOCR과 함께 평가된 여러 OCR 옵션 중 하나입니다.

IronOCR은 .NET 개발자용 OCR.space API와 어떻게 다른가요?

IronOCR은 IronTesseract를 핵심 엔진으로 사용하는 NuGet 네이티브 .NET OCR 라이브러리입니다. OCR.space API에 비해 배포가 간편하고(SDK 설치 프로그램 없음), 정액제 요금이 적용되며, COM 인터롭이나 클라우드 종속성이 없는 깔끔한 C# API를 제공합니다.

IronOCR이 OCR.space API보다 설정이 더 쉬운가요?

IronOCR은 단일 NuGet 패키지를 통해 설치됩니다. SDK 설치 프로그램, 복사할 라이선스 파일, 등록할 COM 구성 요소 또는 관리해야 할 별도의 런타임 바이너리가 없습니다. 전체 OCR 엔진이 패키지에 번들로 제공됩니다.

OCR.space API와 IronOCR 사이에는 어떤 정확도 차이가 있나요?

IronOCR은 표준 비즈니스 문서, 송장, 영수증, 스캔 양식에 대해 높은 인식 정확도를 달성합니다. 품질이 많이 저하된 문서나 일반적이지 않은 스크립트의 경우 정확도는 소스 품질에 따라 달라집니다. IronOCR에는 이미지 전처리 필터가 포함되어 있어 저품질 입력에 대한 인식률을 향상시킵니다.

IronOCR은 PDF 텍스트 추출을 지원하나요?

예. IronOCR은 한 번의 호출로 원본 PDF와 스캔한 PDF 이미지 모두에서 텍스트를 추출합니다. 또한 여러 페이지의 TIFF 파일, 이미지, 스트림도 지원합니다. 스캔한 PDF의 경우 OCR은 페이지별 결과 개체를 사용하여 페이지별로 적용됩니다.

OCR.space API 라이선싱은 IronOCR과 어떻게 비교되나요?

IronOCR은 페이지당 또는 스캔당 요금이 없는 정액제 영구 라이선스를 사용합니다. 대량의 문서를 처리하는 조직은 문서 양에 관계없이 동일한 라이선스 비용을 지불합니다. 자세한 내용과 볼륨 가격은 IronOCR 라이선스 페이지에서 확인할 수 있습니다.

IronOCR 어떤 언어를 지원하나요?

IronOCR은 별도의 NuGet 언어 팩을 통해 127개 언어를 지원합니다. 언어를 추가하려면 '닷넷 추가 패키지 IronOcr.Languages.{Language}' 명령 하나만 있으면 됩니다. 수동으로 파일을 배치하거나 경로를 구성할 필요가 없습니다.

.NET 프로젝트에 IronOCR 설치하는 방법은 무엇인가요?

NuGet을 통해 설치합니다: 패키지 관리자 콘솔에서 '설치-패키지 IronOcr' 또는 CLI에서 '닷넷 추가 패키지 IronOcr'을 실행합니다. 추가 언어 팩도 같은 방법으로 설치됩니다. 기본 SDK 인스톨러가 필요하지 않습니다.

IronOCR은 OCR.space와 달리 Docker 및 컨테이너화된 배포에 적합하나요?

예. IronOCR은 NuGet 패키지를 통해 Docker 컨테이너에서 작동합니다. 라이선스 키는 환경 변수를 통해 설정됩니다. OCR 엔진 자체에는 라이선스 파일, SDK 경로 또는 볼륨 마운트가 필요하지 않습니다.

OCR.space와 비교하여 구매하기 전에 IronOCR을 사용해 볼 수 있나요?

예. IronOCR 평가판 모드는 문서를 처리하고 출력물에 워터마크 오버레이가 포함된 OCR 결과를 반환합니다. 라이선스를 구매하기 전에 자신의 문서에서 정확성을 확인할 수 있습니다.

IronOCR은 텍스트 추출과 함께 바코드 판독을 지원하나요?

IronOCR은 텍스트 추출과 OCR에 중점을 둡니다. 바코드 판독을 위해 Iron Software는 동반 라이브러리로 IronBarcode를 제공합니다. 두 가지 모두 개별적으로 또는 Iron Suite 번들의 일부로 사용할 수 있습니다.

OCR.space API에서 IronOCR로 쉽게 마이그레이션할 수 있나요?

OCR.space API에서 IronOCR로 마이그레이션하려면 일반적으로 초기화 시퀀스를 IronTesseract 인스턴스화로 대체하고, COM 수명 주기 관리를 제거하며, API 호출을 업데이트해야 합니다. 대부분의 마이그레이션은 코드 복잡성을 크게 줄여줍니다.

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

아이언 서포트 팀

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