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

C#을 사용하여 Windows에 Tesseract OCR을 설치하는 방법

이 가이드는 .NET 개발자가 Google Cloud Vision을 IronOCR 로 교체하여 온프레미스 OCR 엔진으로 구현하는 과정을 안내합니다. 자격 증명 제거, Protobuf 주석 구문 분석 대체, 일괄 주석 간소화, 다중 페이지 문서 처리 등 마이그레이션 작업의 대부분을 차지하는 네 가지 구조적 변경 사항을 다룹니다.

구글 클라우드 비전 OCR에서 마이그레이션해야 하는 이유는 무엇일까요?

클라우드 마이그레이션을 결정하는 것은 거의 항상 다음 두 가지 중 하나를 깨닫는 데서 시작됩니다. 규정 준수 감사로 인해 클라우드 문서 전송이 차단되거나, GCP 자격 증명, GCS 버킷, 비동기 폴링 및 이미지별 요금 청구 관리와 관련된 운영 비용이 OCR 자체 비용보다 더 커지는 경우입니다.

서비스 계정 JSON 키 수명 주기. 애플리케이션의 모든 배포 환경(개발 환경, CI/CD 파이프라인, 스테이징 서버, 프로덕션 서버, Docker 컨테이너, Kubernetes Pod)에는 동일한 서비스 계정 JSON 키 파일이 필요합니다. 이 파일에는 RSA 개인 키가 포함되어 있습니다. 해당 파일은 소스 제어 시스템에 절대 포함되어서는 안 되며, 정해진 일정에 따라 교체되어야 하고, 파일 시스템 권한으로 보호되어야 하며, 교체 시 모든 환경에서 동시에 업데이트되어야 합니다. 하나의 유출된 키는 GCP 콘솔에서 수동으로 취소할 때까지 API 액세스 권한을 부여합니다.IronOCR이러한 전체 운영 인터페이스를 애플리케이션 시작 시 한 번만 설정하는 단일 라이선스 키 문자열로 대체합니다.

대규모 주문 건당 청구 방식. 이미지 1,000개당 1.50달러, PDF 페이지당 0.0015달러라는 저렴한 비용으로 개발 단계에서는 비용 부담이 거의 없지만, 최종 제작 시에는 확실한 효과를 볼 수 있습니다. 한 달에 20만 페이지를 처리하는 문서 처리 파이프라인은 GCS 저장 비용 및 데이터 송출 비용을 제외하고도 API 사용료만으로 월 300달러의 비용이 발생합니다. 그 300달러는 매달 무기한으로 반복됩니다. IronOCR의 영구 라이선스를 통해 OCR은 운영 비용에서 고정 자본 항목으로 전환되어 2년 차 또는 3년 차부터는 운영 비용이 전혀 들지 않습니다.

PDF용 GCS 비동기 파이프라인. Google Cloud Vision은 PDF를 API 입력으로 직접 허용하지 않습니다. 전체 파이프라인에는 두 번째 NuGet 패키지(Google.Cloud.Storage.V1), 프로비저닝된 GCS 버킷, 비동기 업로드, AsyncBatchAnnotateFilesAsync 호출, 폴링 루프, GCS로부터의 JSON 출력 파싱 및 정리 단계가 필요합니다. 이 파이프라인은 텍스트가 추출되기 전에 50줄 이상의 코드가 필요합니다.IronOCR외부 의존성 없이 동기식으로 단 세 줄 만에 PDF 파일을 읽습니다.

Protobuf 기호 연결. DOCUMENT_TEXT_DETECTION 응답은 페이지, 블록, 단락, 단어 및 기호의 Protobuf 계층 구조 내에 기호 수준에서 텍스트를 저장합니다. 단락 텍스트를 읽으려면 다섯 개의 중첩 루프를 반복하고 .SelectMany(w => w.Symbols).Select(s => s.Text)를 호출해야 합니다. IronOCR는 단락 텍스트를 paragraph.Text (타입 문자열 속성)로 반환합니다.

기본 할당량: 분당 1,800 요청. 기본 할당량을 초과하는 배치 워크로드는 StatusCode.ResourceExhausted 응답을 받아 파이프라인이 초과 1회당 60초 동안 멈춥니다. 할당량을 늘리려면 GCP 콘솔에 요청하고 Google의 승인을 받아야 합니다.IronOCR사용 가능한 CPU 코어의 속도로 로컬에서 처리되므로 할당량을 관리할 필요도 없고, 승인을 요청할 필요도 없으며, 속도 제한을 위한 재시도 로직도 필요하지 않습니다.

오프라인 또는 에어갭 환경은 지원하지 않습니다. Google Cloud Vision을 사용하려면 Google 엔드포인트에 인터넷 연결이 필요합니다. 에어갭 네트워크, 기밀 데이터 센터 및 산업 제어 시스템은 아키텍처의 복잡성 수준에 관계없이 이를 사용할 수 없습니다.IronOCR초기 라이선스 인증 이후에는 외부 네트워크 연결이 전혀 없이 실행됩니다.

근본적인 문제

Google Cloud Vision은 OCR 코드의 첫 번째 줄이 실행되기 전에 디스크에 JSON 키 파일이 있어야 합니다.

// Google Cloud Vision: JSON key file deployed to every server before this line works
// GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
// Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create();
var image = Image.FromFile("document.jpg");
var response = _client.DetectText(image);   // document leaves your infrastructure
string text = response[0].Description;
// Google Cloud Vision: JSON key file deployed to every server before this line works
// GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
// Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create();
var image = Image.FromFile("document.jpg");
var response = _client.DetectText(image);   // document leaves your infrastructure
string text = response[0].Description;
' Google Cloud Vision: JSON key file deployed to every server before this line works
' GOOGLE_APPLICATION_CREDENTIALS="/etc/secrets/service-account.json" must be set
' Key contains RSA private key — rotate manually, revoke if compromised
_client = ImageAnnotatorClient.Create()
Dim image = Image.FromFile("document.jpg")
Dim response = _client.DetectText(image)   ' document leaves your infrastructure
Dim text As String = response(0).Description
$vbLabelText   $csharpLabel

IronOCR 하나의 문자열로 시작하여 로컬 컴퓨터에서 완전히 실행됩니다.

// IronOCR: one string at startup, no key files, no environment variables
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
string text = new IronTesseract().Read("document.jpg").Text;  // local, no cloud
// IronOCR: one string at startup, no key files, no environment variables
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
string text = new IronTesseract().Read("document.jpg").Text;  // local, no cloud
Imports IronOcr

' IronOCR: one string at startup, no key files, no environment variables
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text As String = New IronTesseract().Read("document.jpg").Text ' local, no cloud
$vbLabelText   $csharpLabel

IronOCR과 구글 클라우드 비전 OCR: 기능 비교

아래 표는 마이그레이션에 대한 사업 타당성 분석을 수행하는 팀을 위해 주요 기능을 직접적으로 보여줍니다.

기능 구글 클라우드 비전 OCR IronOCR
처리 위치 구글 클라우드(원격) 온프레미스(로컬)
입증 서비스 계정 JSON 키 + 환경 변수 라이선스 키 문자열
PDF 입력 GCS에 업로드 + 비동기 API input.LoadPdf() 직접
비밀번호로 보호된 PDF 지원되지 않음 LoadPdf(path, Password: "...")
여러 페이지로 구성된 TIFF 입력 제한적 input.LoadImageFrames()
검색 가능한 PDF 출력 사용 불가 result.SaveAsSearchablePdf()
구조화된 데이터 액세스 Protobuf: 페이지 > 블록 > 단락 > 단어 > 기호 result.Paragraphs, result.Lines, result.Words (타입된 .NET 객체들)
단락 텍스트 속성 아니요 — 기호 연결이 필요합니다. paragraph.Text 직접 속성
신뢰도 점수 기호당 (루프 필요) result.Confidence, word.Confidence
자동 이미지 전처리 없음 (머신러닝이 처리합니다) 기울기 보정, 노이즈 제거, 대비 조정, 이진화, 선명도 향상
영역 기반 OCR 토종 작물 없음 CropRectangle on OcrInput
바코드 판독 별도의 API 기능 ocr.Configuration.ReadBarCodes = true
요금 제한 기본값은 분당 1,800건의 요청입니다. 없음 (CPU 부하가 높음)
오프라인/에어갭 아니요
문서당 비용 이미지 1,000장당 1.5달러; PDF 페이지당 0.0015달러 없음 (영구 라이선스)
지원되는 언어 ~50 125+
FedRAMP 인증 권한이 없습니다 해당 사항 없음 (현장 설치)
HIPAA 준수 경로 사업 제휴 계약서 필수 제3자 데이터 처리 없음
.NET Framework 지원 .NET Standard 2.0 이상 .NET Framework 4.6.2 이상 및 .NET 5/6/7/8/9
NuGet 패키지가 필요합니다. Google.Cloud.Vision.V1 + Google.Cloud.Storage.V1 IronOcr 전용
가격 모델 요청 건별 사용량 기반 요금 청구 Perpetual ($999 Lite / $1,499 Plus / $2,999 Professional / $5,999 Unlimited)

빠른 시작: 구글 클라우드 비전 OCR에서IronOCR로 마이그레이션

1단계: NuGet 패키지 교체

Google Cloud 패키지를 제거하세요.

dotnet remove package Google.Cloud.Vision.V1
dotnet remove package Google.Cloud.Storage.V1
dotnet remove package Google.Cloud.Vision.V1
dotnet remove package Google.Cloud.Storage.V1
SHELL

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

dotnet add package IronOcr

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

Google Cloud 네임스페이스를IronOCR네임스페이스로 교체하십시오:

// Before (Google Cloud Vision)
using Google.Cloud.Vision.V1;
using Google.Cloud.Storage.V1;
using Google.Protobuf;
using Grpc.Core;

// After (IronOCR)
using IronOcr;
// Before (Google Cloud Vision)
using Google.Cloud.Vision.V1;
using Google.Cloud.Storage.V1;
using Google.Protobuf;
using Grpc.Core;

// After (IronOCR)
using IronOcr;
Imports IronOcr
$vbLabelText   $csharpLabel

단계 3: 라이선스 초기화

애플리케이션 시작 시 라이선스 초기화를 추가하여, 어떤 IronTesseract 인스턴스가 만들어지기 전에:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

실제 운영 환경에서는 환경 변수 또는 시크릿 관리자에서 키를 읽어오세요.

IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
    ?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set.");
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
    ?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set.");
Imports System
Imports IronOcr

IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE"), Throw New InvalidOperationException("IRONOCR_LICENSE environment variable not set."))
$vbLabelText   $csharpLabel

코드 마이그레이션 예제

서비스 계정 자격 증명 구성 제거

Google Cloud Vision 클라이언트 초기화는 한 줄짜리 코드처럼 보이지만, 광범위한 사전 인프라 구축이 필요합니다. 프로젝트에 참여하는 모든 개발자, 모든 배포 환경, 그리고 모든 CI/CD 파이프라인은 생성자가 예외 없이 완료되기 전에 완전한 자격 증명 구성이 완료되어 있어야 합니다.

Google 클라우드 비전 접근 방식:

using Google.Cloud.Vision.V1;

// Prerequisites before this class can be instantiated:
// 1. GCP project created and Vision API enabled in GCP Console
// 2. Service account created with roles/cloudvision.user IAM role
// 3. JSON key file downloaded to every server that runs this code
// 4. GOOGLE_APPLICATION_CREDENTIALS env var pointing to the JSON file
// 5. JSON file excluded from source control via .gitignore
// 6. Key rotation schedule established (recommended: 90 days)
// 7. Separate credentials per environment (dev/staging/prod)

public class DocumentOcrService
{
    private readonly ImageAnnotatorClient _client;
    private readonly string _projectId;

    public DocumentOcrService(string projectId)
    {
        _projectId = projectId;
        // Throws RpcException(StatusCode.PermissionDenied) if any prerequisite is missing
        _client = ImageAnnotatorClient.Create();
    }

    public string ReadDocument(string imagePath)
    {
        var image = Image.FromFile(imagePath);
        var response = _client.DetectText(image);
        return response.Count > 0 ? response[0].Description : string.Empty;
    }
}
using Google.Cloud.Vision.V1;

// Prerequisites before this class can be instantiated:
// 1. GCP project created and Vision API enabled in GCP Console
// 2. Service account created with roles/cloudvision.user IAM role
// 3. JSON key file downloaded to every server that runs this code
// 4. GOOGLE_APPLICATION_CREDENTIALS env var pointing to the JSON file
// 5. JSON file excluded from source control via .gitignore
// 6. Key rotation schedule established (recommended: 90 days)
// 7. Separate credentials per environment (dev/staging/prod)

public class DocumentOcrService
{
    private readonly ImageAnnotatorClient _client;
    private readonly string _projectId;

    public DocumentOcrService(string projectId)
    {
        _projectId = projectId;
        // Throws RpcException(StatusCode.PermissionDenied) if any prerequisite is missing
        _client = ImageAnnotatorClient.Create();
    }

    public string ReadDocument(string imagePath)
    {
        var image = Image.FromFile(imagePath);
        var response = _client.DetectText(image);
        return response.Count > 0 ? response[0].Description : string.Empty;
    }
}
Imports Google.Cloud.Vision.V1

' Prerequisites before this class can be instantiated:
' 1. GCP project created and Vision API enabled in GCP Console
' 2. Service account created with roles/cloudvision.user IAM role
' 3. JSON key file downloaded to every server that runs this code
' 4. GOOGLE_APPLICATION_CREDENTIALS env var pointing to the JSON file
' 5. JSON file excluded from source control via .gitignore
' 6. Key rotation schedule established (recommended: 90 days)
' 7. Separate credentials per environment (dev/staging/prod)

Public Class DocumentOcrService
    Private ReadOnly _client As ImageAnnotatorClient
    Private ReadOnly _projectId As String

    Public Sub New(projectId As String)
        _projectId = projectId
        ' Throws RpcException(StatusCode.PermissionDenied) if any prerequisite is missing
        _client = ImageAnnotatorClient.Create()
    End Sub

    Public Function ReadDocument(imagePath As String) As String
        Dim image = Image.FromFile(imagePath)
        Dim response = _client.DetectText(image)
        Return If(response.Count > 0, response(0).Description, String.Empty)
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 접근 방식:

using IronOcr;

// Prerequisites: set the license key once at app startup
// 아니요 JSON files, no environment variables beyond the key, no GCP Console configuration
// 아니요 key rotation, no IAM roles, no per-environment credential sets

public class DocumentOcrService
{
    private readonly IronTesseract _ocr;

    public DocumentOcrService()
    {
        // IronTesseract is ready immediately — no external validation required
        _ocr = new IronTesseract();
    }

    public string ReadDocument(string imagePath)
    {
        return _ocr.Read(imagePath).Text;
    }
}
using IronOcr;

// Prerequisites: set the license key once at app startup
// 아니요 JSON files, no environment variables beyond the key, no GCP Console configuration
// 아니요 key rotation, no IAM roles, no per-environment credential sets

public class DocumentOcrService
{
    private readonly IronTesseract _ocr;

    public DocumentOcrService()
    {
        // IronTesseract is ready immediately — no external validation required
        _ocr = new IronTesseract();
    }

    public string ReadDocument(string imagePath)
    {
        return _ocr.Read(imagePath).Text;
    }
}
Imports IronOcr

' Prerequisites: set the license key once at app startup
' 아니요 JSON files, no environment variables beyond the key, no GCP Console configuration
' 아니요 key rotation, no IAM roles, no per-environment credential sets

Public Class DocumentOcrService
    Private ReadOnly _ocr As IronTesseract

    Public Sub New()
        ' IronTesseract is ready immediately — no external validation required
        _ocr = New IronTesseract()
    End Sub

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

작동상의 차이점은 명확합니다: Google Cloud Vision은 실행 시 다섯 가지 범주의 RpcException를 생성합니다 — PermissionDenied, ResourceExhausted, Unavailable, DeadlineExceeded, 및 Unauthenticated — 각각 다른 인프라 실패 모드를 나타냅니다. IronOCR의 실패 모드는 IOException (파일을 찾을 수 없거나 잠김)과 OcrException (처리 실패)가 있습니다. 구성 옵션은 IronTesseract 설치 가이드를 참조하고, 라이선스 세부 정보는 IronOCR 제품 페이지를 참조하십시오.

일괄 주석 요청을 여러 기능 유형으로 대체

Google Cloud Vision은 여러 이미지를 단일 BatchAnnotateImagesRequest로 배치하는 것을 지원하며, 각 이미지는 Feature 타입 목록으로 구성됩니다. 이 패턴은 단일 호출이 TEXT_DETECTIONDOCUMENT_TEXT_DETECTION 결과를 수집해야 하거나, 많은 이미지를 제출하여 왕복 오버헤드를 최소화할 때 사용됩니다. Protobuf 응답은 각 AnnotateImageResponse를 인덱스로 원래 요청에 맞추는 것이 필요합니다.

Google 클라우드 비전 접근 방식:

using Google.Cloud.Vision.V1;
using System.Collections.Generic;

public class BatchAnnotationService
{
    private readonly ImageAnnotatorClient _client;

    public BatchAnnotationService()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<string> BatchAnnotateImages(string[] imagePaths)
    {
        // Build one request per image with TEXT_DETECTION feature
        var requests = imagePaths.Select(path => new AnnotateImageRequest
        {
            Image = Image.FromFile(path),
            Features =
            {
                new기능{ Type = Feature.Types.Type.TextDetection },
                new기능{ Type = Feature.Types.Type.DocumentTextDetection }
            }
        }).ToList();

        // Single round-trip for all images in the batch
        var batchResponse = _client.BatchAnnotateImages(requests);

        // Match responses back to requests by index
        var results = new List<string>();
        for (int i = 0; i < batchResponse.Responses.Count; i++)
        {
            var response = batchResponse.Responses[i];
            if (response.Error != null)
            {
                // Per-item error in batch — must handle individually
                results.Add($"Error on {imagePaths[i]}: {response.Error.Message}");
                continue;
            }

            // Prefer DOCUMENT_TEXT_DETECTION full text if available
            var fullText = response.FullTextAnnotation?.Text
                ?? response.TextAnnotations.FirstOrDefault()?.Description
                ?? string.Empty;
            results.Add(fullText);
        }

        return results;
    }
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;

public class BatchAnnotationService
{
    private readonly ImageAnnotatorClient _client;

    public BatchAnnotationService()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<string> BatchAnnotateImages(string[] imagePaths)
    {
        // Build one request per image with TEXT_DETECTION feature
        var requests = imagePaths.Select(path => new AnnotateImageRequest
        {
            Image = Image.FromFile(path),
            Features =
            {
                new기능{ Type = Feature.Types.Type.TextDetection },
                new기능{ Type = Feature.Types.Type.DocumentTextDetection }
            }
        }).ToList();

        // Single round-trip for all images in the batch
        var batchResponse = _client.BatchAnnotateImages(requests);

        // Match responses back to requests by index
        var results = new List<string>();
        for (int i = 0; i < batchResponse.Responses.Count; i++)
        {
            var response = batchResponse.Responses[i];
            if (response.Error != null)
            {
                // Per-item error in batch — must handle individually
                results.Add($"Error on {imagePaths[i]}: {response.Error.Message}");
                continue;
            }

            // Prefer DOCUMENT_TEXT_DETECTION full text if available
            var fullText = response.FullTextAnnotation?.Text
                ?? response.TextAnnotations.FirstOrDefault()?.Description
                ?? string.Empty;
            results.Add(fullText);
        }

        return results;
    }
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Linq

Public Class BatchAnnotationService
    Private ReadOnly _client As ImageAnnotatorClient

    Public Sub New()
        _client = ImageAnnotatorClient.Create()
    End Sub

    Public Function BatchAnnotateImages(imagePaths As String()) As List(Of String)
        ' Build one request per image with TEXT_DETECTION feature
        Dim requests = imagePaths.Select(Function(path) New AnnotateImageRequest With {
            .Image = Image.FromFile(path),
            .Features = {
                New Feature With {.Type = Feature.Types.Type.TextDetection},
                New Feature With {.Type = Feature.Types.Type.DocumentTextDetection}
            }
        }).ToList()

        ' Single round-trip for all images in the batch
        Dim batchResponse = _client.BatchAnnotateImages(requests)

        ' Match responses back to requests by index
        Dim results = New List(Of String)()
        For i As Integer = 0 To batchResponse.Responses.Count - 1
            Dim response = batchResponse.Responses(i)
            If response.Error IsNot Nothing Then
                ' Per-item error in batch — must handle individually
                results.Add($"Error on {imagePaths(i)}: {response.Error.Message}")
                Continue For
            End If

            ' Prefer DOCUMENT_TEXT_DETECTION full text if available
            Dim fullText = If(response.FullTextAnnotation?.Text, response.TextAnnotations.FirstOrDefault()?.Description, String.Empty)
            results.Add(fullText)
        Next

        Return results
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 접근 방식:

using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

public class BatchAnnotationService
{
    public List<string> BatchAnnotateImages(string[] imagePaths)
    {
        var results = new ConcurrentDictionary<int, string>();

        // Parallel processing — no batch size limit, no network round-trips
        Parallel.For(0, imagePaths.Length, i =>
        {
            var ocr = new IronTesseract();   // thread-safe: one instance per thread
            results[i] = ocr.Read(imagePaths[i]).Text;
        });

        // Reconstruct in original order
        return Enumerable.Range(0, imagePaths.Length)
            .Select(i => results[i])
            .ToList();
    }

    public OcrResult BatchAsDocument(string[] imagePaths)
    {
        // Load all images into a single OcrInput for combined document output
        using var input = new OcrInput();
        foreach (var path in imagePaths)
            input.LoadImage(path);

        return new IronTesseract().Read(input);
    }
}
using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

public class BatchAnnotationService
{
    public List<string> BatchAnnotateImages(string[] imagePaths)
    {
        var results = new ConcurrentDictionary<int, string>();

        // Parallel processing — no batch size limit, no network round-trips
        Parallel.For(0, imagePaths.Length, i =>
        {
            var ocr = new IronTesseract();   // thread-safe: one instance per thread
            results[i] = ocr.Read(imagePaths[i]).Text;
        });

        // Reconstruct in original order
        return Enumerable.Range(0, imagePaths.Length)
            .Select(i => results[i])
            .ToList();
    }

    public OcrResult BatchAsDocument(string[] imagePaths)
    {
        // Load all images into a single OcrInput for combined document output
        using var input = new OcrInput();
        foreach (var path in imagePaths)
            input.LoadImage(path);

        return new IronTesseract().Read(input);
    }
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks

Public Class BatchAnnotationService
    Public Function BatchAnnotateImages(imagePaths As String()) As List(Of String)
        Dim results = New ConcurrentDictionary(Of Integer, String)()

        ' Parallel processing — no batch size limit, no network round-trips
        Parallel.For(0, imagePaths.Length, Sub(i)
                                               Dim ocr = New IronTesseract() ' thread-safe: one instance per thread
                                               results(i) = ocr.Read(imagePaths(i)).Text
                                           End Sub)

        ' Reconstruct in original order
        Return Enumerable.Range(0, imagePaths.Length) _
            .Select(Function(i) results(i)) _
            .ToList()
    End Function

    Public Function BatchAsDocument(imagePaths As String()) As OcrResult
        ' Load all images into a single OcrInput for combined document output
        Using input As New OcrInput()
            For Each path In imagePaths
                input.LoadImage(path)
            Next
            Return New IronTesseract().Read(input)
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 네트워크 오버헤드 없이 CPU 코어 전체에 걸쳐 배치 작업을 병렬로 실행합니다. 네트워크나 인증 실패에 대한 BatchSize 한계가 없으며, 응답-인덱스 매칭 및 개별 항목 에러 처리가 없습니다. 여러 이미지를 단일 논리 문서로 결합하는 워크로드 — 예를 들어 개별 JPEG로 제출된 스캔된 다중 페이지 양식 — 에서 BatchAsDocument는 모든 이미지를 하나의 OcrInput로 로드하고 result.Pages가 각 입력 이미지에 인덱싱된 통합 OcrResult를 반환합니다. 멀티스레딩 예제는 병렬 처리 성능 벤치마크를 보여줍니다.

Protobuf 단어 수준 주석 추출 마이그레이션

Google Cloud Vision의 단어 수준 경계 상자 및 신뢰도 데이터를 사용하려면 Protobuf 계층 구조 전체(페이지, 블록, 단락, 단어, 기호)를 탐색해야 합니다. 단어 텍스트를 추출하려면 각 단어의 기호를 연결해야 합니다 — Word 객체에는 직접 .Text 속성이 없습니다. 경계 상자 좌표는 개별 X, Y, 폭, 높이 필드가 아닌 BoundingPolyVertices 목록으로 저장됩니다.

Google 클라우드 비전 접근 방식:

using Google.Cloud.Vision.V1;
using System.Collections.Generic;

public record WordAnnotation(string Text, int X, int Y, int Width, int Height, float Confidence);

public class WordLevelExtractor
{
    private readonly ImageAnnotatorClient _client;

    public WordLevelExtractor()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
    {
        var image = Image.FromFile(imagePath);
        var annotation = _client.DetectDocumentText(image);

        var words = new List<WordAnnotation>();

        // Navigate: Pages -> Blocks -> Paragraphs -> Words
        foreach (var page in annotation.Pages)
        {
            foreach (var block in page.Blocks)
            {
                foreach (var paragraph in block.Paragraphs)
                {
                    foreach (var word in paragraph.Words)
                    {
                        // Word.Text does not exist — must concatenate Symbols
                        var text = string.Concat(word.Symbols.Select(s => s.Text));

                        // BoundingPoly has Vertices, not X/Y/Width/Height
                        var vertices = word.BoundingBox.Vertices;
                        int x = vertices[0].X;
                        int y = vertices[0].Y;
                        int width = vertices.Count > 1 ? vertices[1].X - vertices[0].X : 0;
                        int height = vertices.Count > 2 ? vertices[2].Y - vertices[0].Y : 0;

                        words.Add(new WordAnnotation(text, x, y, width, height, word.Confidence));
                    }
                }
            }
        }

        return words;
    }
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;

public record WordAnnotation(string Text, int X, int Y, int Width, int Height, float Confidence);

public class WordLevelExtractor
{
    private readonly ImageAnnotatorClient _client;

    public WordLevelExtractor()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
    {
        var image = Image.FromFile(imagePath);
        var annotation = _client.DetectDocumentText(image);

        var words = new List<WordAnnotation>();

        // Navigate: Pages -> Blocks -> Paragraphs -> Words
        foreach (var page in annotation.Pages)
        {
            foreach (var block in page.Blocks)
            {
                foreach (var paragraph in block.Paragraphs)
                {
                    foreach (var word in paragraph.Words)
                    {
                        // Word.Text does not exist — must concatenate Symbols
                        var text = string.Concat(word.Symbols.Select(s => s.Text));

                        // BoundingPoly has Vertices, not X/Y/Width/Height
                        var vertices = word.BoundingBox.Vertices;
                        int x = vertices[0].X;
                        int y = vertices[0].Y;
                        int width = vertices.Count > 1 ? vertices[1].X - vertices[0].X : 0;
                        int height = vertices.Count > 2 ? vertices[2].Y - vertices[0].Y : 0;

                        words.Add(new WordAnnotation(text, x, y, width, height, word.Confidence));
                    }
                }
            }
        }

        return words;
    }
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Linq

Public Class WordAnnotation
    Public Property Text As String
    Public Property X As Integer
    Public Property Y As Integer
    Public Property Width As Integer
    Public Property Height As Integer
    Public Property Confidence As Single

    Public Sub New(text As String, x As Integer, y As Integer, width As Integer, height As Integer, confidence As Single)
        Me.Text = text
        Me.X = x
        Me.Y = y
        Me.Width = width
        Me.Height = height
        Me.Confidence = confidence
    End Sub
End Class

Public Class WordLevelExtractor
    Private ReadOnly _client As ImageAnnotatorClient

    Public Sub New()
        _client = ImageAnnotatorClient.Create()
    End Sub

    Public Function ExtractWordAnnotations(imagePath As String) As List(Of WordAnnotation)
        Dim image = Image.FromFile(imagePath)
        Dim annotation = _client.DetectDocumentText(image)

        Dim words As New List(Of WordAnnotation)()

        ' Navigate: Pages -> Blocks -> Paragraphs -> Words
        For Each page In annotation.Pages
            For Each block In page.Blocks
                For Each paragraph In block.Paragraphs
                    For Each word In paragraph.Words
                        ' Word.Text does not exist — must concatenate Symbols
                        Dim text = String.Concat(word.Symbols.Select(Function(s) s.Text))

                        ' BoundingPoly has Vertices, not X/Y/Width/Height
                        Dim vertices = word.BoundingBox.Vertices
                        Dim x As Integer = vertices(0).X
                        Dim y As Integer = vertices(0).Y
                        Dim width As Integer = If(vertices.Count > 1, vertices(1).X - vertices(0).X, 0)
                        Dim height As Integer = If(vertices.Count > 2, vertices(2).Y - vertices(0).Y, 0)

                        words.Add(New WordAnnotation(text, x, y, width, height, word.Confidence))
                    Next
                Next
            Next
        Next

        Return words
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 접근 방식:

using IronOcr;
using System.Collections.Generic;

public record WordAnnotation(string Text, int X, int Y, int Width, int Height, double Confidence);

public class WordLevelExtractor
{
    private readonly IronTesseract _ocr;

    public WordLevelExtractor()
    {
        _ocr = new IronTesseract();
    }

    public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
    {
        var result = _ocr.Read(imagePath);

        // Words are a flat collection — no hierarchy traversal, no symbol concatenation
        return result.Words.Select(w => new WordAnnotation(
            Text:       w.Text,         // direct string property
            X:          w.X,
            Y:          w.Y,
            Width:      w.Width,
            Height:     w.Height,
            Confidence: w.Confidence    // double, no conversion needed
        )).ToList();
    }
}
using IronOcr;
using System.Collections.Generic;

public record WordAnnotation(string Text, int X, int Y, int Width, int Height, double Confidence);

public class WordLevelExtractor
{
    private readonly IronTesseract _ocr;

    public WordLevelExtractor()
    {
        _ocr = new IronTesseract();
    }

    public List<WordAnnotation> ExtractWordAnnotations(string imagePath)
    {
        var result = _ocr.Read(imagePath);

        // Words are a flat collection — no hierarchy traversal, no symbol concatenation
        return result.Words.Select(w => new WordAnnotation(
            Text:       w.Text,         // direct string property
            X:          w.X,
            Y:          w.Y,
            Width:      w.Width,
            Height:     w.Height,
            Confidence: w.Confidence    // double, no conversion needed
        )).ToList();
    }
}
Imports IronOcr
Imports System.Collections.Generic

Public Class WordAnnotation
    Public Property Text As String
    Public Property X As Integer
    Public Property Y As Integer
    Public Property Width As Integer
    Public Property Height As Integer
    Public Property Confidence As Double

    Public Sub New(text As String, x As Integer, y As Integer, width As Integer, height As Integer, confidence As Double)
        Me.Text = text
        Me.X = x
        Me.Y = y
        Me.Width = width
        Me.Height = height
        Me.Confidence = confidence
    End Sub
End Class

Public Class WordLevelExtractor
    Private ReadOnly _ocr As IronTesseract

    Public Sub New()
        _ocr = New IronTesseract()
    End Sub

    Public Function ExtractWordAnnotations(imagePath As String) As List(Of WordAnnotation)
        Dim result = _ocr.Read(imagePath)

        ' Words are a flat collection — no hierarchy traversal, no symbol concatenation
        Return result.Words.Select(Function(w) New WordAnnotation(
            Text:=w.Text,         ' direct string property
            X:=w.X,
            Y:=w.Y,
            Width:=w.Width,
            Height:=w.Height,
            Confidence:=w.Confidence    ' double, no conversion needed
        )).ToList()
    End Function
End Class
$vbLabelText   $csharpLabel

Google Cloud Vision 버전에서 심볼 연결 루프는 설계상의 선택이 아니라 Protobuf 스키마에서 요구하는 사항입니다. 응답 객체에 Word.Text 속성이 없습니다. 단어 수준 API를 사용하는 모든 팀은 등가의 루프를 작성합니다. IronOCR의 OcrResult.Words 컬렉션은 Text, X, Y, Width, HeightConfidence를 1급 속성으로 제공합니다. 읽기 결과 가이드에는 모든 세분화 수준에서 사용 가능한 전체 속성 세트가 문서화되어 있습니다.

여러 페이지로 구성된 TIFF 파일을 검색 가능한 PDF로 변환

Google Cloud Vision은 여러 페이지로 구성된 TIFF 파일을 순차적인 이미지 시리즈로 처리하며, 각 이미지마다 별도의 API 호출이 필요합니다. TIFF 파일을 입력받아 모든 프레임에 대한 구조화된 출력을 반환하는 단일 API 호출은 없습니다. 구글 클라우드 비전 결과를 검색 가능한 PDF로 생성하려면 별도의 PDF 생성 라이브러리가 필요합니다. API는 텍스트만 반환합니다.

Google 클라우드 비전 접근 방식:

using Google.Cloud.Vision.V1;
using System.Collections.Generic;
using System.Drawing;          // for TIFF frame extraction
using System.Drawing.Imaging;

public class TiffProcessingService
{
    private readonly ImageAnnotatorClient _client;

    public TiffProcessingService()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<string> ProcessMultiPageTiff(string tiffPath)
    {
        var pageTexts = new List<string>();

        // Load TIFF and extract frames manually using System.Drawing
        using var tiff = System.Drawing.Image.FromFile(tiffPath);
        var frameDimension = new FrameDimension(tiff.FrameDimensionsList[0]);
        int frameCount = tiff.GetFrameCount(frameDimension);

        for (int i = 0; i < frameCount; i++)
        {
            tiff.SelectActiveFrame(frameDimension, i);

            // Save each frame to a temp file — Vision API does not accept TIFF frames directly
            var tempPath = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg");
            tiff.Save(tempPath, ImageFormat.Jpeg);

            // One API call per frame — each call = one unit of quota
            var visionImage = Google.Cloud.Vision.V1.Image.FromFile(tempPath);
            var response = _client.DetectText(visionImage);
            pageTexts.Add(response.FirstOrDefault()?.Description ?? string.Empty);

            File.Delete(tempPath);
        }

        // Producing a searchable PDF requires a separate library (e.g., iTextSharp)
        // 구글 클라우드 비전 has no PDF output capability
        return pageTexts;
    }
}
using Google.Cloud.Vision.V1;
using System.Collections.Generic;
using System.Drawing;          // for TIFF frame extraction
using System.Drawing.Imaging;

public class TiffProcessingService
{
    private readonly ImageAnnotatorClient _client;

    public TiffProcessingService()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public List<string> ProcessMultiPageTiff(string tiffPath)
    {
        var pageTexts = new List<string>();

        // Load TIFF and extract frames manually using System.Drawing
        using var tiff = System.Drawing.Image.FromFile(tiffPath);
        var frameDimension = new FrameDimension(tiff.FrameDimensionsList[0]);
        int frameCount = tiff.GetFrameCount(frameDimension);

        for (int i = 0; i < frameCount; i++)
        {
            tiff.SelectActiveFrame(frameDimension, i);

            // Save each frame to a temp file — Vision API does not accept TIFF frames directly
            var tempPath = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg");
            tiff.Save(tempPath, ImageFormat.Jpeg);

            // One API call per frame — each call = one unit of quota
            var visionImage = Google.Cloud.Vision.V1.Image.FromFile(tempPath);
            var response = _client.DetectText(visionImage);
            pageTexts.Add(response.FirstOrDefault()?.Description ?? string.Empty);

            File.Delete(tempPath);
        }

        // Producing a searchable PDF requires a separate library (e.g., iTextSharp)
        // 구글 클라우드 비전 has no PDF output capability
        return pageTexts;
    }
}
Imports Google.Cloud.Vision.V1
Imports System.Collections.Generic
Imports System.Drawing          ' for TIFF frame extraction
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Linq

Public Class TiffProcessingService
    Private ReadOnly _client As ImageAnnotatorClient

    Public Sub New()
        _client = ImageAnnotatorClient.Create()
    End Sub

    Public Function ProcessMultiPageTiff(tiffPath As String) As List(Of String)
        Dim pageTexts As New List(Of String)()

        ' Load TIFF and extract frames manually using System.Drawing
        Using tiff As System.Drawing.Image = System.Drawing.Image.FromFile(tiffPath)
            Dim frameDimension As New FrameDimension(tiff.FrameDimensionsList(0))
            Dim frameCount As Integer = tiff.GetFrameCount(frameDimension)

            For i As Integer = 0 To frameCount - 1
                tiff.SelectActiveFrame(frameDimension, i)

                ' Save each frame to a temp file — Vision API does not accept TIFF frames directly
                Dim tempPath As String = Path.Combine(Path.GetTempPath(), $"tiff-frame-{i}.jpg")
                tiff.Save(tempPath, ImageFormat.Jpeg)

                ' One API call per frame — each call = one unit of quota
                Dim visionImage As Google.Cloud.Vision.V1.Image = Google.Cloud.Vision.V1.Image.FromFile(tempPath)
                Dim response = _client.DetectText(visionImage)
                pageTexts.Add(If(response.FirstOrDefault()?.Description, String.Empty))

                File.Delete(tempPath)
            Next
        End Using

        ' Producing a searchable PDF requires a separate library (e.g., iTextSharp)
        ' 구글 클라우드 비전 has no PDF output capability
        Return pageTexts
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 접근 방식:

using IronOcr;

public class TiffProcessingService
{
    private readonly IronTesseract _ocr;

    public TiffProcessingService()
    {
        _ocr = new IronTesseract();
    }

    public string ProcessMultiPageTiff(string tiffPath)
    {
        using var input = new OcrInput();
        // LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);
        return result.Text;
    }

    public void ProcessMultiPageTiffToSearchablePdf(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);

        // 구글 클라우드 비전 has no equivalent — this single call produces a searchable PDF
        result.SaveAsSearchablePdf(outputPdfPath);
    }

    public void ProcessLowQualityTiff(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        // Preprocessing before OCR improves accuracy on degraded scans
        input.Deskew();
        input.DeNoise();
        input.Contrast();

        var result = _ocr.Read(input);
        result.SaveAsSearchablePdf(outputPdfPath);
    }
}
using IronOcr;

public class TiffProcessingService
{
    private readonly IronTesseract _ocr;

    public TiffProcessingService()
    {
        _ocr = new IronTesseract();
    }

    public string ProcessMultiPageTiff(string tiffPath)
    {
        using var input = new OcrInput();
        // LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);
        return result.Text;
    }

    public void ProcessMultiPageTiffToSearchablePdf(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);

        // 구글 클라우드 비전 has no equivalent — this single call produces a searchable PDF
        result.SaveAsSearchablePdf(outputPdfPath);
    }

    public void ProcessLowQualityTiff(string tiffPath, string outputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        // Preprocessing before OCR improves accuracy on degraded scans
        input.Deskew();
        input.DeNoise();
        input.Contrast();

        var result = _ocr.Read(input);
        result.SaveAsSearchablePdf(outputPdfPath);
    }
}
Imports IronOcr

Public Class TiffProcessingService
    Private ReadOnly _ocr As IronTesseract

    Public Sub New()
        _ocr = New IronTesseract()
    End Sub

    Public Function ProcessMultiPageTiff(tiffPath As String) As String
        Using input As New OcrInput()
            ' LoadImageFrames handles all TIFF frames in one call — no temp files, no frame loop
            input.LoadImageFrames(tiffPath)

            Dim result = _ocr.Read(input)
            Return result.Text
        End Using
    End Function

    Public Sub ProcessMultiPageTiffToSearchablePdf(tiffPath As String, outputPdfPath As String)
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath)

            Dim result = _ocr.Read(input)

            ' 구글 클라우드 비전 has no equivalent — this single call produces a searchable PDF
            result.SaveAsSearchablePdf(outputPdfPath)
        End Using
    End Sub

    Public Sub ProcessLowQualityTiff(tiffPath As String, outputPdfPath As String)
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath)

            ' Preprocessing before OCR improves accuracy on degraded scans
            input.Deskew()
            input.DeNoise()
            input.Contrast()

            Dim result = _ocr.Read(input)
            result.SaveAsSearchablePdf(outputPdfPath)
        End Using
    End Sub
End Class
$vbLabelText   $csharpLabel

Google Cloud Vision 버전은 프레임 추출을 위해 System.Drawing이 필요하고, 임시 JPEG 파일이 디스크에 기록되며, 프레임당 하나의 API 호출이 필요하고(프레임 수에 비례하여 할당량을 소모함), 일반 텍스트 이외의 출력을 생성하려면 별도의 PDF 라이브러리가 필요합니다. IronOCR는 LoadImageFrames를 통하여 다중 프레임 TIFF를 네이티브로 처리하고, 모든 프레임을 단일 Read 호출에서 처리하며, SaveAsSearchablePdf를 통해 검색 가능한 PDF 출력을 생성합니다. 스캔된 아카이브 TIFF 문서의 경우, ProcessLowQualityTiff의 전처리 파이프라인은 가장 일반적인 품질 문제를 한 번에 해결합니다. 전체 API는 TIFF 및 GIF 입력검색 가능한 PDF 출력 부분을 참조하세요.

진행 상황 추적 기능이 포함된 속도 제한 배치 마이그레이션

실제 운영 환경에서 Google Cloud Vision의 기본 요청 수량인 분당 1,800건을 처리하려면 요청 수를 제한하거나 지수 백오프를 사용하는 재시도 로직을 적용해야 합니다. 하룻밤 사이에 5,000건의 문서를 처리하면 할당량을 여러 번 초과하게 됩니다. 할당량을 초과할 때마다 파이프라인이 60초 동안 의무적으로 차단됩니다.IronOCR속도 제한이 없습니다. 파이프라인은 CPU 코어 수와 사용 가능한 스레드 수에 의해서만 제한됩니다.

Google 클라우드 비전 접근 방식:

using Google.Cloud.Vision.V1;
using Grpc.Core;
using System.Collections.Generic;

public class ThrottledBatchProcessor
{
    private readonly ImageAnnotatorClient _client;
    private const int MaxRequestsPerMinute = 1800;
    private const int RetryDelayMs = 60_000;

    public ThrottledBatchProcessor()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public async Task<Dictionary<string, string>> ProcessWithThrottlingAsync(
        string[] imagePaths,
        IProgress<(int completed, int total)> progress)
    {
        var results = new Dictionary<string, string>();
        int completed = 0;

        foreach (var path in imagePaths)
        {
            bool succeeded = false;
            while (!succeeded)
            {
                try
                {
                    var image = Google.Cloud.Vision.V1.Image.FromFile(path);
                    var response = _client.DetectText(image);
                    results[path] = response.FirstOrDefault()?.Description ?? string.Empty;
                    succeeded = true;
                }
                catch (RpcException ex) when (ex.StatusCode == StatusCode.ResourceExhausted)
                {
                    // Rate limit exceeded — wait and retry
                    await Task.Delay(RetryDelayMs);
                }
            }

            progress.Report((++completed, imagePaths.Length));
        }

        return results;
    }
}
using Google.Cloud.Vision.V1;
using Grpc.Core;
using System.Collections.Generic;

public class ThrottledBatchProcessor
{
    private readonly ImageAnnotatorClient _client;
    private const int MaxRequestsPerMinute = 1800;
    private const int RetryDelayMs = 60_000;

    public ThrottledBatchProcessor()
    {
        _client = ImageAnnotatorClient.Create();
    }

    public async Task<Dictionary<string, string>> ProcessWithThrottlingAsync(
        string[] imagePaths,
        IProgress<(int completed, int total)> progress)
    {
        var results = new Dictionary<string, string>();
        int completed = 0;

        foreach (var path in imagePaths)
        {
            bool succeeded = false;
            while (!succeeded)
            {
                try
                {
                    var image = Google.Cloud.Vision.V1.Image.FromFile(path);
                    var response = _client.DetectText(image);
                    results[path] = response.FirstOrDefault()?.Description ?? string.Empty;
                    succeeded = true;
                }
                catch (RpcException ex) when (ex.StatusCode == StatusCode.ResourceExhausted)
                {
                    // Rate limit exceeded — wait and retry
                    await Task.Delay(RetryDelayMs);
                }
            }

            progress.Report((++completed, imagePaths.Length));
        }

        return results;
    }
}
Imports Google.Cloud.Vision.V1
Imports Grpc.Core
Imports System.Collections.Generic
Imports System.Threading.Tasks

Public Class ThrottledBatchProcessor
    Private ReadOnly _client As ImageAnnotatorClient
    Private Const MaxRequestsPerMinute As Integer = 1800
    Private Const RetryDelayMs As Integer = 60000

    Public Sub New()
        _client = ImageAnnotatorClient.Create()
    End Sub

    Public Async Function ProcessWithThrottlingAsync(
        imagePaths As String(),
        progress As IProgress(Of (completed As Integer, total As Integer))) As Task(Of Dictionary(Of String, String))

        Dim results As New Dictionary(Of String, String)()
        Dim completed As Integer = 0

        For Each path In imagePaths
            Dim succeeded As Boolean = False
            While Not succeeded
                Try
                    Dim image = Google.Cloud.Vision.V1.Image.FromFile(path)
                    Dim response = _client.DetectText(image)
                    results(path) = If(response.FirstOrDefault()?.Description, String.Empty)
                    succeeded = True
                Catch ex As RpcException When ex.StatusCode = StatusCode.ResourceExhausted
                    ' Rate limit exceeded — wait and retry
                    Await Task.Delay(RetryDelayMs)
                End Try
            End While

            progress.Report((Threading.Interlocked.Increment(completed), imagePaths.Length))
        Next

        Return results
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR 접근 방식:

using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class BatchProcessor
{
    public Dictionary<string, string> ProcessBatch(
        string[] imagePaths,
        IProgress<(int completed, int total)> progress)
    {
        var results = new ConcurrentDictionary<string, string>();
        int completed = 0;

        // 요금 제한 없음 — full parallelism, no retry logic needed
        Parallel.ForEach(imagePaths, imagePath =>
        {
            var ocr = new IronTesseract();
            results[imagePath] = ocr.Read(imagePath).Text;
            progress.Report((Interlocked.Increment(ref completed), imagePaths.Length));
        });

        return new Dictionary<string, string>(results);
    }

    public void ProcessBatchToSearchablePdfs(
        string[] imagePaths,
        string outputDirectory)
    {
        Parallel.ForEach(imagePaths, imagePath =>
        {
            var ocr = new IronTesseract();
            var result = ocr.Read(imagePath);

            var outputPath = Path.Combine(
                outputDirectory,
                Path.GetFileNameWithoutExtension(imagePath) + "-searchable.pdf");

            result.SaveAsSearchablePdf(outputPath);
        });
    }
}
using IronOcr;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class BatchProcessor
{
    public Dictionary<string, string> ProcessBatch(
        string[] imagePaths,
        IProgress<(int completed, int total)> progress)
    {
        var results = new ConcurrentDictionary<string, string>();
        int completed = 0;

        // 요금 제한 없음 — full parallelism, no retry logic needed
        Parallel.ForEach(imagePaths, imagePath =>
        {
            var ocr = new IronTesseract();
            results[imagePath] = ocr.Read(imagePath).Text;
            progress.Report((Interlocked.Increment(ref completed), imagePaths.Length));
        });

        return new Dictionary<string, string>(results);
    }

    public void ProcessBatchToSearchablePdfs(
        string[] imagePaths,
        string outputDirectory)
    {
        Parallel.ForEach(imagePaths, imagePath =>
        {
            var ocr = new IronTesseract();
            var result = ocr.Read(imagePath);

            var outputPath = Path.Combine(
                outputDirectory,
                Path.GetFileNameWithoutExtension(imagePath) + "-searchable.pdf");

            result.SaveAsSearchablePdf(outputPath);
        });
    }
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Public Class BatchProcessor
    Public Function ProcessBatch(imagePaths As String(), progress As IProgress(Of (completed As Integer, total As Integer))) As Dictionary(Of String, String)
        Dim results As New ConcurrentDictionary(Of String, String)()
        Dim completed As Integer = 0

        ' 요금 제한 없음 — full parallelism, no retry logic needed
        Parallel.ForEach(imagePaths, Sub(imagePath)
                                         Dim ocr As New IronTesseract()
                                         results(imagePath) = ocr.Read(imagePath).Text
                                         progress.Report((Interlocked.Increment(completed), imagePaths.Length))
                                     End Sub)

        Return New Dictionary(Of String, String)(results)
    End Function

    Public Sub ProcessBatchToSearchablePdfs(imagePaths As String(), outputDirectory As String)
        Parallel.ForEach(imagePaths, Sub(imagePath)
                                         Dim ocr As New IronTesseract()
                                         Dim result = ocr.Read(imagePath)

                                         Dim outputPath = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(imagePath) & "-searchable.pdf")

                                         result.SaveAsSearchablePdf(outputPath)
                                     End Sub)
    End Sub
End Class
$vbLabelText   $csharpLabel

Google Cloud Vision 버전은 병렬 요청이 요청 속도 제한에 미치는 영향을 최소화하기 위해 순차적으로 실행됩니다. 각 ResourceExhausted 예외는 전체 60초 간 정지를 추가합니다. 5,000건의 문서 묶음이 할당량을 10번 초과하면 10분의 대기 시간이 추가됩니다.IronOCR버전은 대기 시간 없이 사용 가능한 모든 코어에서 병렬 처리를 수행합니다. 장시간 실행 배치의 경우, 진척 추적 API는 수동 Interlocked.Increment 배선을 요구하지 않고 내장 진척 콜백을 제공합니다. 배치 스캔에서 이미지 품질 문제에 대한 이미지 품질 수정 가이드는 각 Read 호출 전에 추가할 수 있는 전처리 파이프라인을 다룹니다.

구글 클라우드 비전 OCR API와IronOCR매핑 참조

구글 클라우드 비전 IronOCR 노트
ImageAnnotatorClient.Create() new IronTesseract() 클라이언트 초기화; 자격 증명 파일 필요 없음
Image.FromFile(path) _ocr.Read(path) 또는 input.LoadImage(path) 직접 경로 읽기가 IronTesseract에서 가능합니다.
_client.DetectText(image) _ocr.Read(path).Text TEXT_DETECTION 등가
_client.DetectDocumentText(image) _ocr.Read(path) DOCUMENT_TEXT_DETECTION 등가; 모드는 자동입니다
response[0].Description result.Text 문서 전문
TextAnnotation OcrResult 최상위 결과 컨테이너
annotation.Text result.Text 전체 텍스트 문자열
annotation.Pages[i] result.Pages[i] 페이지별 접근 권한
page.Blocks[i].Paragraphs[j] result.Paragraphs[i] IronOCR단락을 평면적인 모음으로 드러냅니다.
paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text) paragraph.Text 직접 문자열 속성; 기호 반복 없음
word.BoundingBox.Vertices word.X, word.Y, word.Width, word.Height 정점 목록 대신 이산 정수 속성
word.Confidence word.Confidence 단어별 신뢰도 점수
page.Confidence result.Confidence 전반적인 결과 신뢰도
Feature.Types.Type.DocumentTextDetection 자동 IronOCR처리 모드를 자동으로 선택합니다.
BatchAnnotateImagesRequest Parallel.ForEach + new IronTesseract() 스레드당 병렬 로컬 처리; 배치 크기 제한 없음
_client.BatchAnnotateImages(requests) new IronTesseract().Read(input) 다중 이미지 OcrInput와 함께 여러 이미지를 한 번에 입력하는 단일 호출
AsyncBatchAnnotateFilesAsync() input.LoadPdf(); _ocr.Read(input) PDF 처리는 동기식으로 이루어집니다. GCS 필요 없음
StorageClient.Create() 필요 없음 GCS에 대한 의존성 없음
storageClient.UploadObjectAsync() 필요 없음 PDF 파일은 로컬 경로 또는 스트림에서 직접 로드됩니다.
operation.PollUntilCompletedAsync() 필요 없음 처리는 동기식으로 진행됩니다.
RpcException (StatusCode.ResourceExhausted) 적용 안 됨 요금 제한 없음
RpcException (StatusCode.PermissionDenied) 적용 안 됨 런타임 인증 없음
GOOGLE_APPLICATION_CREDENTIALS env var IronOcr.License.LicenseKey 문자열 할당이지 파일 경로 할당이 아닙니다.

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

문제 1: GOOGLE_APPLICATION_CREDENTIALS를 지정하지 않으면 생성자에서 예외가 발생합니다.

Google Cloud Vision: ImageAnnotatorClient.Create()는 환경 변수가 설정되지 않은 경우 또는 잘못된 파일을 가리키는 경우 RpcExceptionStatusCode.Unauthenticated 또는 StatusCode.PermissionDenied와 함께 발생시킵니다. 이 실패는 첫 번째 API 호출 시점이 아닌 애플리케이션 시작 시 발생하며, 이는 모든 환경에서 자격 증명이 누락된 경우 전체 애플리케이션 초기화가 실패한다는 것을 의미합니다.

솔루션: 구글 클라우드 비전 패키지를 제거한 후, 환경 구성, CI/CD 파이프라인 비밀, Kubernetes 비밀, Docker Compose 파일에서 GOOGLE_APPLICATION_CREDENTIALS에 대한 모든 참조를 삭제하십시오. 하나의 IRONOCR_LICENSE 환경 변수로 교체하십시오:

// Remove this from every deployment environment:
// GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

// Add this once at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
    ?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable is required.");
// Remove this from every deployment environment:
// GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

// Add this once at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
    ?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable is required.");
' Remove this from every deployment environment:
' GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

' Add this once at application startup:
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE"), Throw New InvalidOperationException("IRONOCR_LICENSE environment variable is required."))
$vbLabelText   $csharpLabel

문제 2: 네임스페이스 제거 후 Protobuf 심볼 연결 코드 오류 발생

Google Cloud Vision: .SelectMany(w => w.Symbols).Select(s => s.Text)를 사용하여 단락 또는 단어 텍스트를 추출한 코드베이스의 모든 위치는 Google.Cloud.Vision.V1 네임스페이스가 제거된 후 컴파일 오류를 발생시킬 것입니다. 이러한 호출은 API 응답을 사용하는 헬퍼 또는 서비스 클래스 전체에 분산됩니다.

솔루션: 코드베이스에서 모든 SelectManyw.Symbols 패턴을 검색하고IronOCR결과 객체에 직접 속성 액세스로 교체하십시오. 결과 읽기 가이드OcrResult, OcrResult.Page, OcrResult.Paragraph, OcrResult.Line, OcrResult.Word의 모든 사용 가능한 속성을 다룹니다:

# Find all Protobuf symbol concatenation patterns
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .
grep -rn "w\.Symbols\.Select" --include="*.cs" .
# Find all Protobuf symbol concatenation patterns
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .
grep -rn "w\.Symbols\.Select" --include="*.cs" .
SHELL

각각의 항목을 바꾸십시오:

// Before: symbol concatenation required by Protobuf schema
var text = string.Join("", paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text));

// After: direct property on OcrResult.Paragraph
var text = paragraph.Text;
// Before: symbol concatenation required by Protobuf schema
var text = string.Join("", paragraph.Words.SelectMany(w => w.Symbols).Select(s => s.Text));

// After: direct property on OcrResult.Paragraph
var text = paragraph.Text;
' Before: symbol concatenation required by Protobuf schema
Dim text = String.Join("", paragraph.Words.SelectMany(Function(w) w.Symbols).Select(Function(s) s.Text))

' After: direct property on OcrResult.Paragraph
Dim text = paragraph.Text
$vbLabelText   $csharpLabel

문제 3: Storage.V1 제거 후 PDF 처리 코드가 컴파일되지 않음

Google Cloud Vision: Google.Cloud.Storage.V1를 제거한 후, StorageClient, UploadObjectAsync, DeleteObjectAsync, AsyncAnnotateFileRequest, GcsSource, GcsDestination, PollUntilCompletedAsync를 참조하는 모든 코드는 컴파일되지 않을 것입니다. 이 코드는 여러 서비스 클래스에 걸쳐 있을 수 있으며 일반적으로 가장 큰 단일 변경 블록을 나타냅니다.

해결 방법: GCS 파이프라인 전체를 삭제합니다. 50 Plus 비동기 메서드를IronOCR 3줄짜리 코드로 대체합니다. 호출자 호환을 위해 비동기 서명을 유지했던 코드는 Task.Run로 래핑하십시오:

// Delete: StorageClient, GCS upload, AsyncBatchAnnotateFilesAsync,
//         PollUntilCompletedAsync, output download, DeleteObjectAsync

// Replace with:
public async Task<string> ProcessPdfAsync(string pdfPath)
{
    return await Task.Run(() =>
    {
        using var input = new OcrInput();
        input.LoadPdf(pdfPath);
        return new IronTesseract().Read(input).Text;
    });
}
// Delete: StorageClient, GCS upload, AsyncBatchAnnotateFilesAsync,
//         PollUntilCompletedAsync, output download, DeleteObjectAsync

// Replace with:
public async Task<string> ProcessPdfAsync(string pdfPath)
{
    return await Task.Run(() =>
    {
        using var input = new OcrInput();
        input.LoadPdf(pdfPath);
        return new IronTesseract().Read(input).Text;
    });
}
Imports System.Threading.Tasks

Public Async Function ProcessPdfAsync(pdfPath As String) As Task(Of String)
    Return Await Task.Run(Function()
                              Using input As New OcrInput()
                                  input.LoadPdf(pdfPath)
                                  Return New IronTesseract().Read(input).Text
                              End Using
                          End Function)
End Function
$vbLabelText   $csharpLabel

새로운 코드를 위해, Task.Run 래퍼 대신 네이티브 비동기 OCR 지원을 사용하십시오. PDF 입력 가이드에는 페이지 범위 선택 및 암호로 보호된 PDF 불러오기가 포함됩니다.

문제 4: 속도 제한 재시도 로직이 더 이상 필요하지 않습니다.

Google Cloud Vision: RpcException와 함께 StatusCode.ResourceExhausted를 포착하고 대기 후 재시도 패턴을 구현한 코드는 분당 1,800 요청 할당량을 처리하기 위해 작성되었습니다. 이 재시도 로직은 미들웨어, 파이프라인 단계 또는 배치 처리 루프에 포함될 수 있습니다.

해결 방법: 할당량 오류와 관련된 모든 재시도 로직을 제거합니다.IronOCR외부 할당량 없이 로컬에서 처리됩니다. 에러 처리 계약이 다섯 RpcException 케이스에서 두 개로 변경됩니다.

// Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
//         Unavailable, DeadlineExceeded, Unauthenticated

//IronOCR error surface:
try
{
    var result = new IronTesseract().Read(imagePath);
    if (result.Confidence < 50)
        input.DeNoise(); // add preprocessing for low-confidence results
    return result.Text;
}
catch (IOException ex)
{
    // File not found or locked
    throw new InvalidOperationException($"Cannot read: {imagePath}", ex);
}
catch (IronOcr.Exceptions.OcrException ex)
{
    // Processing failure — not a transient network error
    throw new InvalidOperationException($"OCR failed: {ex.Message}", ex);
}
// Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
//         Unavailable, DeadlineExceeded, Unauthenticated

//IronOCR error surface:
try
{
    var result = new IronTesseract().Read(imagePath);
    if (result.Confidence < 50)
        input.DeNoise(); // add preprocessing for low-confidence results
    return result.Text;
}
catch (IOException ex)
{
    // File not found or locked
    throw new InvalidOperationException($"Cannot read: {imagePath}", ex);
}
catch (IronOcr.Exceptions.OcrException ex)
{
    // Processing failure — not a transient network error
    throw new InvalidOperationException($"OCR failed: {ex.Message}", ex);
}
Imports IronOcr
Imports System.IO

' Remove: all RpcException handlers for ResourceExhausted, PermissionDenied,
'         Unavailable, DeadlineExceeded, Unauthenticated

'IronOCR error surface:
Try
    Dim result = New IronTesseract().Read(imagePath)
    If result.Confidence < 50 Then
        input.DeNoise() ' add preprocessing for low-confidence results
    End If
    Return result.Text
Catch ex As IOException
    ' File not found or locked
    Throw New InvalidOperationException($"Cannot read: {imagePath}", ex)
Catch ex As IronOcr.Exceptions.OcrException
    ' Processing failure — not a transient network error
    Throw New InvalidOperationException($"OCR failed: {ex.Message}", ex)
End Try
$vbLabelText   $csharpLabel

문제 5: 다중 페이지 TIFF 파일 처리 시 프레임 추출 루프 필요

Google Cloud Vision: 기존 TIFF 처리 코드는 System.Drawing.Image를 사용하여 프레임을 추출하고, 각 프레임을 JPEG로 임시 디렉터리에 저장하고, 각 JPEG를 별도의 API 호출로 제출하고, 이후 임시 파일을 삭제하는 가능성이 있습니다. 이 패턴은 프레임당 할당량 단위 1개를 소모하며, 충돌 시 사용되지 않는 임시 파일이 남을 수 있습니다.

솔루션: 프레임 추출 루프와 임시 파일 관리를 input.LoadImageFrames()로 교체하십시오. System.Drawing 프레임 루프 전체가 삭제됩니다.

// Remove: System.Drawing frame extraction, temp file writes, per-frame API calls

// Replace with:
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);   // all frames, no temp files
var result = new IronTesseract().Read(input);
// Remove: System.Drawing frame extraction, temp file writes, per-frame API calls

// Replace with:
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);   // all frames, no temp files
var result = new IronTesseract().Read(input);
Imports IronOcr

Dim input As New OcrInput()
input.LoadImageFrames(tiffPath) ' all frames, no temp files
Dim result = (New IronTesseract()).Read(input)
$vbLabelText   $csharpLabel

프레임 범위 선택을 포함한 다중 프레임 처리 옵션은 TIFF 및 GIF 입력 가이드를 참조하십시오.

문제 6: BoundingPoly 정점 계산 오류

Google Cloud Vision: annotation.BoundingPoly.Vertices에서 경계 상자 좌표를 읽는 코드는 버텍스 인덱스 산술에서 X, Y, 폭, 높이를 계산했습니다: X에 vertices[0].X, 폭에 vertices[1].X - vertices[0].X, 높이에 vertices[2].Y - vertices[0].Y. 마이그레이션 후, 이러한 표현식은 IronOCR에서 Vertices가 존재하지 않기 때문에 등가물이 없습니다.

해결 방법: 정점 연산을 정수 속성에 직접 적용합니다. 계산이 필요하지 않습니다.

// Before: vertex index arithmetic
int x = word.BoundingBox.Vertices[0].X;
int y = word.BoundingBox.Vertices[0].Y;
int width  = word.BoundingBox.Vertices[1].X - word.BoundingBox.Vertices[0].X;
int height = word.BoundingBox.Vertices[2].Y - word.BoundingBox.Vertices[0].Y;

// After: direct properties
int x      = word.X;
int y      = word.Y;
int width  = word.Width;
int height = word.Height;
// Before: vertex index arithmetic
int x = word.BoundingBox.Vertices[0].X;
int y = word.BoundingBox.Vertices[0].Y;
int width  = word.BoundingBox.Vertices[1].X - word.BoundingBox.Vertices[0].X;
int height = word.BoundingBox.Vertices[2].Y - word.BoundingBox.Vertices[0].Y;

// After: direct properties
int x      = word.X;
int y      = word.Y;
int width  = word.Width;
int height = word.Height;
' Before: vertex index arithmetic
Dim x As Integer = word.BoundingBox.Vertices(0).X
Dim y As Integer = word.BoundingBox.Vertices(0).Y
Dim width As Integer = word.BoundingBox.Vertices(1).X - word.BoundingBox.Vertices(0).X
Dim height As Integer = word.BoundingBox.Vertices(2).Y - word.BoundingBox.Vertices(0).Y

' After: direct properties
Dim x As Integer = word.X
Dim y As Integer = word.Y
Dim width As Integer = word.Width
Dim height As Integer = word.Height
$vbLabelText   $csharpLabel

구글 클라우드 비전 OCR 마이그레이션 체크리스트

사전 마이그레이션

변경 작업을 진행하기 전에 코드베이스를 감사하여 모든 구글 클라우드 비전 종속성을 파악하십시오.

# Find all 구글 클라우드 비전 namespace imports
grep -rn "using Google.Cloud.Vision" --include="*.cs" .
grep -rn "using Google.Cloud.Storage" --include="*.cs" .
grep -rn "using Grpc.Core" --include="*.cs" .

# Find ImageAnnotatorClient usage
grep -rn "ImageAnnotatorClient" --include="*.cs" .

# Find GCS pipeline code
grep -rn "StorageClient\|UploadObjectAsync\|DeleteObjectAsync" --include="*.cs" .
grep -rn "AsyncBatchAnnotateFilesAsync\|PollUntilCompleted" --include="*.cs" .

# Find Protobuf symbol concatenation
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .

# Find BoundingPoly vertex calculations
grep -rn "BoundingPoly\|BoundingBox\.Vertices" --include="*.cs" .

# Find rate limit retry handlers
grep -rn "ResourceExhausted\|StatusCode\." --include="*.cs" .

# Find environment variable references
grep -rn "GOOGLE_APPLICATION_CREDENTIALS" .
# Find all 구글 클라우드 비전 namespace imports
grep -rn "using Google.Cloud.Vision" --include="*.cs" .
grep -rn "using Google.Cloud.Storage" --include="*.cs" .
grep -rn "using Grpc.Core" --include="*.cs" .

# Find ImageAnnotatorClient usage
grep -rn "ImageAnnotatorClient" --include="*.cs" .

# Find GCS pipeline code
grep -rn "StorageClient\|UploadObjectAsync\|DeleteObjectAsync" --include="*.cs" .
grep -rn "AsyncBatchAnnotateFilesAsync\|PollUntilCompleted" --include="*.cs" .

# Find Protobuf symbol concatenation
grep -rn "\.Symbols\." --include="*.cs" .
grep -rn "SelectMany.*Symbols" --include="*.cs" .

# Find BoundingPoly vertex calculations
grep -rn "BoundingPoly\|BoundingBox\.Vertices" --include="*.cs" .

# Find rate limit retry handlers
grep -rn "ResourceExhausted\|StatusCode\." --include="*.cs" .

# Find environment variable references
grep -rn "GOOGLE_APPLICATION_CREDENTIALS" .
SHELL

시작 전 작성해야 할 재고 목록 관련 메모:

  • OCR 입력/출력을 위해 생성된 모든 GCS 버킷 목록을 생성하고, 마이그레이션 후 정리 작업을 예약합니다.
  • 마이그레이션 후 GCP 콘솔에서 서비스 계정 사용을 비활성화할 수 있도록 서비스 계정 이메일을 기록해 두십시오.
  • GOOGLE_APPLICATION_CREDENTIALS가 구성된 모든 환경 식별
  • GCP 프로젝트 ID 또는 버킷 이름을 구성에서 읽는 코드가 있으면 마이그레이션 후 제거하십시오.

코드 마이그레이션

  1. 모든 프로젝트에서 Google.Cloud.Vision.V1 NuGet 패키지를 제거하십시오
  2. 모든 프로젝트에서 Google.Cloud.Storage.V1 NuGet 패키지를 제거하십시오
  3. OCR을 수행하는 모든 프로젝트에 IronOcr NuGet 패키지를 설치하십시오
  4. 애플리케이션 시작 시 IronOcr.License.LicenseKey 초기화를 추가하십시오
  5. 모든 using Google.Cloud.Vision.V1 임포트를 using IronOcr로 교체하십시오
  6. 모든 using Google.Cloud.Storage.V1using Grpc.Core 임포트를 교체하십시오
  7. ImageAnnotatorClient.Create()new IronTesseract()로 교체하십시오
  8. 모든 GCS 파이프라인 메소드를 삭제 (StorageClient, UploadObjectAsync, 비동기 주석, 폴링, 다운로드, 삭제)
  9. 모든 PDF 처리 경로에서 input.LoadPdf()를 교체하십시오 (비동기 GCS 오케스트레이션을 제거합니다)
  10. 모든 Protobuf 기호 연결 루프를 .Text 객체에서 직접 속성 접근으로 교체하십시오
  11. BoundingPoly.Vertices 인덱스 계산을 word.X, word.Y, word.Width, word.Height으로 교체하십시오
  12. RpcException 캐치 블록을 ResourceExhausted, PermissionDenied, Unavailable, DeadlineExceeded, Unauthenticated로 제거하십시오
  13. 프레임별 TIFF 루프를 input.LoadImageFrames()으로 교체하십시오
  14. 순차적 배치 루프를 스레드당 IronTesseract 인스턴스를 가진 Parallel.ForEach으로 변환하십시오
  15. 모든 환경 설정, CI/CD 파이프라인, Docker Compose 파일 및 Kubernetes 비밀에서 GOOGLE_APPLICATION_CREDENTIALS를 제거하십시오

마이그레이션 이후

  • 오류 처리 코드에서 RpcException 또는 GoogleApiException 유형이 남아 있는지 확인하십시오
  • 모든 배포 환경 구성에서 GOOGLE_APPLICATION_CREDENTIALS가 없는지 확인하십시오
  • 실제 운영 환경에서 사용한 것과 동일한 샘플 문서 세트에 대해 OCR 파이프라인을 실행하고 텍스트 출력 품질을 비교합니다.
  • GCS 비동기 파이프라인에서 이전에 처리했던 문서에 대해 PDF 처리 테스트를 수행하고 텍스트 출력이 동일한지 확인합니다.
  • input.LoadPdf(path, Password: "...")로 비밀번호 보호된 PDF를 테스트하십시오 — 이전에는 지원되지 않음
  • input.LoadImageFrames()를 사용하여 다중 페이지 TIFF 처리를 테스트하고 모든 프레임이 처리되었는지 확인하십시오
  • 대표 샘플에 대해 배치 프로세서를 실행하고 출력 품질이 이전 결과와 일치하는지 확인합니다.
  • 문서 집합에 대한 result.Confidence 값이 허용 가능한 범위 내에 있는지 확인하십시오
  • result.SaveAsSearchablePdf()를 사용하여 검색 가능한 PDF 출력을 확인하십시오 — 이전에는 별도의 PDF 라이브러리가 필요했습니다
  • 외부 인터넷 연결이 없는 환경에서 애플리케이션을 실행하고 OCR이 올바르게 작동하는지 확인하십시오.

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

자격 증명 표면이 0 파일로 감소됨. 마이그레이션 후에는 JSON 키 파일, GCS 버킷 구성, IAM 롤, 서비스 계정 및 인프라의 GOOGLE_APPLICATION_CREDENTIALS 환경 변수가 없습니다. 자격 증명 정보 전체는 라이선스 키 문자열을 포함하는 하나의 환경 변수로 구성됩니다. Google Cloud Vision에서 주기적으로 수행해야 했던 키 순환은 더 이상 적용되는 개념이 아닙니다. 여러 지역이나 클라우드 제공업체에서 운영하는 팀의 경우 배포 구성의 복잡성이 즉시 줄어듭니다.

외부 종속성 없이 PDF 및 TIFF를 처리합니다. GCS 비동기 파이프라인과 System.Drawing TIFF 프레임 루프가 완전히 삭제되었습니다. input.LoadPdf()input.LoadImageFrames()는 교체 요소이며, 둘 다 동기화되고, 둘 다 로컬이며, 호출에서 결과까지 세 줄입니다. Google Cloud Vision으로는 불가능했던 암호로 보호된 PDF 파일도 이제 단 하나의 매개변수만 추가하면 사용할 수 있습니다. PDF OCR 가이드TIFF 입력 가이드는 전체 입력 API를 다룹니다.

CPU 속도로 배치 처리가 가능합니다. 분당 1,800건의 요청 할당량과 60초의 의무적인 재시도 대기 시간이 제거되어, 이전에는 속도 제한이 있었던 배치 작업이 이제 사용 가능한 프로세서 코어의 속도로 실행됩니다. 16개의 코어를 가진 기계는 외부 승인 없이 16개의 문서를 동시에 처리합니다. 스레드당 IronTesseract 인스턴스와 함께 Parallel.ForEach 패턴은 제한된 순차적 루프의 직접 교체입니다. 속도 최적화 가이드는 특정 문서 유형에 대한 처리량을 맞추는 엔진 구성 옵션을 다룹니다.

Protobuf 없이 구조화된 데이터. 모든 OcrResult은 Protobuf 네임스페이스 종속성이 없고 기호 연결 없이 경계 상자에 대한 버텍스 산술 없이 타입이 지정된 .NET 속성으로 Text, Confidence, Pages, Paragraphs, Lines, Words, Characters을 노출합니다. 이전에 단락 텍스트를 추출하기 위해 20줄의 중첩 루프가 필요했던 코드는 result.Paragraphs.Select(p => p.Text)으로 줄어듭니다. 문서 레이아웃 분석을 위해 단어 수준의 위치가 필요한 사용 사례에서는 word.X, word.Y, word.Width, word.Height를 직접 사용할 수 있습니다. OCR 결과에는 결과 모델의 모든 속성이 포함된 페이지 문서가 표시됩니다 .

검색 가능한 PDF 출력 기능이 내장되어 있습니다. Google Cloud Vision은 텍스트만 반환하므로 검색 가능한 PDF를 생성하려면 별도의 PDF 생성 라이브러리가 필요하며, 이로 인해 NuGet 종속성이 추가되고, 학습해야 할 API가 늘어나고, 추가 라이선스를 평가해야 합니다. IronOCR의 result.SaveAsSearchablePdf(outputPath)는 어떤 OCR 결과로부터도 완전히 검색 가능한 PDF를 한 줄로 생성합니다. 문서 보관 워크플로우 및 법적 정보 수집 파이프라인에서는 전체 종속성을 제거합니다. 검색 가능한 PDF 예시는 해당 패턴을 처음부터 끝까지 보여줍니다.

규제 산업을 위한 데이터 주권.IronOCR에서 처리된 문서는 서버를 벗어나지 않습니다. HIPAA가 적용되는 의료 기록, ITAR이 통제하는 기술 데이터, CMMC 범위에 포함되는 방위 산업체 자료, 변호사-의뢰인 비밀유지 대상 법률 문서 및 PCI-DSS 범위에 포함되는 재무 기록의 경우, 온프레미스 아키텍처를 사용하면 제3자 데이터 처리업체 범주가 규정 준수 범위에서 완전히 제외됩니다. 협상해야 할 사업 제휴 계약도 없고, 체결해야 할 데이터 처리 계약도 없으며, 검토해야 할 구글의 데이터 보존 정책도 없습니다. IronOCR 문서 허브는 데이터 상주 요건이 적용되는 Docker, Linux, Azure 및 AWS 환경에 대한 배포 구성을 다룹니다.

참고해 주세요Google Cloud Vision, Tesseract, and iText are registered trademarks of their respective owners. 이 사이트는 Google 또는 iText Group과 관련이 없으며, 제휴, 지원 또는 후원받지 않았습니다. 모든 제품 이름, 로고 및 브랜드는 각각의 소유자의 자산입니다. 비교는 정보 제공 목적으로만 사용되며, 작성 시점에 공개적으로 이용 가능한 정보를 반영합니다.

자주 묻는 질문

Google Cloud Vision API에서 IronOCR로 마이그레이션해야 하는 이유는 무엇인가요?

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

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

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

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

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

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

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

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

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

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

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

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

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

IronOCR은 Google Cloud Vision과 동일한 방식으로 PDF를 처리할 수 있나요?

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

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

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

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

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

워크로드 확장에 대해 IronOCR 가격이 Google Cloud Vision API보다 더 예측 가능한가요?

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

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

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

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

아이언 서포트 팀

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