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
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
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
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
단계 3: 라이선스 초기화
애플리케이션 시작 시 라이선스 초기화를 추가하여, 어떤 IronTesseract 인스턴스가 만들어지기 전에:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
실제 운영 환경에서는 환경 변수 또는 시크릿 관리자에서 키를 읽어오세요.
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."))
코드 마이그레이션 예제
서비스 계정 자격 증명 구성 제거
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
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
작동상의 차이점은 명확합니다: Google Cloud Vision은 실행 시 다섯 가지 범주의 RpcException를 생성합니다 — PermissionDenied, ResourceExhausted, Unavailable, DeadlineExceeded, 및 Unauthenticated — 각각 다른 인프라 실패 모드를 나타냅니다. IronOCR의 실패 모드는 IOException (파일을 찾을 수 없거나 잠김)과 OcrException (처리 실패)가 있습니다. 구성 옵션은 IronTesseract 설치 가이드를 참조하고, 라이선스 세부 정보는 IronOCR 제품 페이지를 참조하십시오.
일괄 주석 요청을 여러 기능 유형으로 대체
Google Cloud Vision은 여러 이미지를 단일 BatchAnnotateImagesRequest로 배치하는 것을 지원하며, 각 이미지는 Feature 타입 목록으로 구성됩니다. 이 패턴은 단일 호출이 TEXT_DETECTION 및 DOCUMENT_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
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
IronOCR 네트워크 오버헤드 없이 CPU 코어 전체에 걸쳐 배치 작업을 병렬로 실행합니다. 네트워크나 인증 실패에 대한 BatchSize 한계가 없으며, 응답-인덱스 매칭 및 개별 항목 에러 처리가 없습니다. 여러 이미지를 단일 논리 문서로 결합하는 워크로드 — 예를 들어 개별 JPEG로 제출된 스캔된 다중 페이지 양식 — 에서 BatchAsDocument는 모든 이미지를 하나의 OcrInput로 로드하고 result.Pages가 각 입력 이미지에 인덱싱된 통합 OcrResult를 반환합니다. 멀티스레딩 예제는 병렬 처리 성능 벤치마크를 보여줍니다.
Protobuf 단어 수준 주석 추출 마이그레이션
Google Cloud Vision의 단어 수준 경계 상자 및 신뢰도 데이터를 사용하려면 Protobuf 계층 구조 전체(페이지, 블록, 단락, 단어, 기호)를 탐색해야 합니다. 단어 텍스트를 추출하려면 각 단어의 기호를 연결해야 합니다 — Word 객체에는 직접 .Text 속성이 없습니다. 경계 상자 좌표는 개별 X, Y, 폭, 높이 필드가 아닌 BoundingPoly 내 Vertices 목록으로 저장됩니다.
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
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
Google Cloud Vision 버전에서 심볼 연결 루프는 설계상의 선택이 아니라 Protobuf 스키마에서 요구하는 사항입니다. 응답 객체에 Word.Text 속성이 없습니다. 단어 수준 API를 사용하는 모든 팀은 등가의 루프를 작성합니다. IronOCR의 OcrResult.Words 컬렉션은 Text, X, Y, Width, Height 및 Confidence를 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
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
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
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
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()는 환경 변수가 설정되지 않은 경우 또는 잘못된 파일을 가리키는 경우 RpcException를 StatusCode.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."))
문제 2: 네임스페이스 제거 후 Protobuf 심볼 연결 코드 오류 발생
Google Cloud Vision: .SelectMany(w => w.Symbols).Select(s => s.Text)를 사용하여 단락 또는 단어 텍스트를 추출한 코드베이스의 모든 위치는 Google.Cloud.Vision.V1 네임스페이스가 제거된 후 컴파일 오류를 발생시킬 것입니다. 이러한 호출은 API 응답을 사용하는 헬퍼 또는 서비스 클래스 전체에 분산됩니다.
솔루션: 코드베이스에서 모든 SelectMany 및 w.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" .
각각의 항목을 바꾸십시오:
// 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
문제 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
새로운 코드를 위해, 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
문제 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)
프레임 범위 선택을 포함한 다중 프레임 처리 옵션은 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
구글 클라우드 비전 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" .
시작 전 작성해야 할 재고 목록 관련 메모:
- OCR 입력/출력을 위해 생성된 모든 GCS 버킷 목록을 생성하고, 마이그레이션 후 정리 작업을 예약합니다.
- 마이그레이션 후 GCP 콘솔에서 서비스 계정 사용을 비활성화할 수 있도록 서비스 계정 이메일을 기록해 두십시오.
GOOGLE_APPLICATION_CREDENTIALS가 구성된 모든 환경 식별- GCP 프로젝트 ID 또는 버킷 이름을 구성에서 읽는 코드가 있으면 마이그레이션 후 제거하십시오.
코드 마이그레이션
- 모든 프로젝트에서
Google.Cloud.Vision.V1NuGet 패키지를 제거하십시오 - 모든 프로젝트에서
Google.Cloud.Storage.V1NuGet 패키지를 제거하십시오 - OCR을 수행하는 모든 프로젝트에
IronOcrNuGet 패키지를 설치하십시오 - 애플리케이션 시작 시
IronOcr.License.LicenseKey초기화를 추가하십시오 - 모든
using Google.Cloud.Vision.V1임포트를using IronOcr로 교체하십시오 - 모든
using Google.Cloud.Storage.V1및using Grpc.Core임포트를 교체하십시오 ImageAnnotatorClient.Create()를new IronTesseract()로 교체하십시오- 모든 GCS 파이프라인 메소드를 삭제 (
StorageClient,UploadObjectAsync, 비동기 주석, 폴링, 다운로드, 삭제) - 모든 PDF 처리 경로에서
input.LoadPdf()를 교체하십시오 (비동기 GCS 오케스트레이션을 제거합니다) - 모든 Protobuf 기호 연결 루프를
.Text객체에서 직접 속성 접근으로 교체하십시오 BoundingPoly.Vertices인덱스 계산을word.X,word.Y,word.Width,word.Height으로 교체하십시오RpcException캐치 블록을ResourceExhausted,PermissionDenied,Unavailable,DeadlineExceeded,Unauthenticated로 제거하십시오- 프레임별 TIFF 루프를
input.LoadImageFrames()으로 교체하십시오 - 순차적 배치 루프를 스레드당
IronTesseract인스턴스를 가진Parallel.ForEach으로 변환하십시오 - 모든 환경 설정, 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 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의 더 간단한 초기화 및 결과 모델을 반영하도록 업데이트해야 합니다.

