NET Maui에서 OCR을 수행하는 방법
이 가이드는 .NET 개발자가 LEADTOOLS OCR에서 IronOCR 로 완전히 마이그레이션하는 과정을 안내합니다. 이 문서에서는 NuGet 패키지 교체부터 전체 코드 마이그레이션까지 모든 단계를 다루며, LEADTOOLS의 초기화 절차, 다중 네임스페이스 구조 및 파일 기반 라이선스 배포 모델의 영향을 가장 많이 받는 패턴에 대한 마이그레이션 전후 예시를 제공합니다.
Leadtools OCR에서 마이그레이션해야 하는 이유는 무엇일까요?
LEADTOOLS OCR은 1990년대로 거슬러 올라가는 역사를 가진 Enterprise 이미지 처리 플랫폼에 포함되어 제공되며, API 또한 이러한 계보를 반영합니다. 최소 작동 구성에는 4개의 NuGet 패키지, 4개의 네임스페이스, 알려진 경로에 배포된 2개의 물리적 라이선스 파일, 엔진 전에 초기화되는 코덱 계층, 3가지 옵션 중에서 선택하는 엔진 유형, 그리고 런타임 바이너리를 메모리에 로드하는 차단 시작 호출이 필요합니다. 이 모든 과정이 문자 하나가 인식되기 전에 실행됩니다.IronOCR로 마이그레이션하는 팀은 해당 계층 전체를 제거합니다. 패키지 하나, 네임스페이스 하나, 라이선스 키를 설정하는 단 한 줄만 있으면 됩니다.
컨테이너에서 파일 기반의 라이선스 배포 중단. LEADTOOLS는 두 개의 물리적 파일, LEADTOOLS.LIC 및 LEADTOOLS.LIC.KEY,을 필요로 하며, 애플리케이션을 실행하는 모든 기계에서 특정 경로에서 접근 가능해야 합니다. Docker 컨테이너에서 이러한 파일은 이미지에 포함시키거나(레이어 기록에 노출) 런타임에 마운트해야 합니다(모든 오케스트레이션 환경에서 볼륨 조정 필요). Azure Functions와 AWS Lambda는 별도의 해결 방법 없이는 라이선스 파일을 배포할 수 있는 메커니즘을 제공하지 않습니다. CI/CD 파이프라인은 시작 호출에서 사용하는 정확한 경로에 파일이 있어야 합니다. 그렇지 않으면 애플리케이션이 단 하나의 문서도 처리하기 전에 오류가 발생합니다.IronOCR두 파일 모두를 환경 변수, Kubernetes 시크릿 또는 Azure Key Vault 참조에 맞는 문자열로 바꿉니다.
하나의 작업을 위한 네 개의 패키지. 작동하는 리드툴즈 OCR 프로젝트는 최소한 Leadtools, Leadtools.Ocr, Leadtools.Codecs 및 Leadtools.Forms.DocumentWriters가 필요합니다. 비밀번호로 보호된 PDF 지원은 구매한 번들에 포함되지 않을 수 있는 추가 Leadtools.Pdf 모듈을 필요로 합니다. 각 패키지는 반드시 존재해야 하고, 호환되어야 하며, 동일한 버전으로 제공되어야 합니다.IronOCR하나의 NuGet 패키지로 제공됩니다. PDF 입력, 검색 가능한 PDF 출력, 전처리, 바코드 판독 등 모든 기능이 포함되어 있습니다.
엔진 수명 주기는 유지보수 표면을 생성합니다. LEADTOOLS는 OCR 엔진을 IDisposable 서비스 클래스에 감싸고 있습니다. 이는 관용적 .NET 이유가 아닌, 애플리케이션이 오류를 생성하지 않도록 Dispose() 이전에 Shutdown() 호출을 해야 하기 때문입니다. LEADTOOLS 배치 프로세서의 생산 구현은 일반적으로 누적되는 RasterImage 인스턴스를 보상하기 위해 문서 청크 사이에 GC.Collect() / GC.WaitForPendingFinalizers() 호출을 포함합니다. IronOCR는 표준 using 블록을 사용합니다. OcrInput는 처분이 필요한 유일한 객체입니다.
번들 관련 혼란이 실제 운영 환경에서 발생합니다. LEADTOOLS는 가격 정보를 공개적으로 게시하지 않습니다. OCR 기능을 포함하는 문서 이미지 처리 SDK는 개발자 1인당 연간 약 3,000달러에서 8,000달러의 비용이 드는 것으로 추정됩니다. PDF 모듈 없이 OCR 모듈을 구매한 팀은 생산에서 비밀번호로 보호된 문서에 대해 RasterSupport.IsLocked()가 true를 반환할 때 간격을 발견합니다. OmniPage 엔진 정확도 등급을 사용하려면 LEADTOOLS 구매 외에 별도의 Kofax 라이선스 계약이 필요합니다.IronOCR각 등급별로 모든 기능에 대한 라이선스를 제공합니다. 별도의 공급업체와의 관계도 없고, 암호화된 문서를 위한 별도의 모듈도 없습니다.
초기화 비용이 콜드 스타트에 영향을 미칩니다. engine.Startup()는 런타임 파일을 메모리에 로드하는 블로킹 호출입니다. 콜드 스타트 지연 시간이 중요한 서버리스 환경(Azure Functions, AWS Lambda 등)에서 인식 작업이 시작되기 전에 500~2000ms 동안 지속되는 초기화 과정은 구조적인 문제입니다.IronOCR지연 초기화를 사용합니다. IronTesseract 인스턴스는 최초 사용 시 초기화되며, 동일한 프로세스 내 후속 호출에는 초기화 비용이 전혀 발생하지 않습니다.
근본적인 문제
LEADTOOLS를 사용하려면 네 개의 네임스페이스, 네 개의 NuGet 패키지, 그리고 문자를 인식하기 전에 반드시 거쳐야 하는 시작 절차가 필요합니다.
// LEADTOOLS: Four namespaces, four packages, six steps before recognition
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)); // Step 1: two files on disk
var codecs = new RasterCodecs(); // Step 2: codec layer
var engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD); // Step 3: engine factory
engine.Startup(codecs, null, null, runtimePath); // Step 4: blocking startup
// ... still need to create document, add page, call Recognize(), extract text
// LEADTOOLS: Four namespaces, four packages, six steps before recognition
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)); // Step 1: two files on disk
var codecs = new RasterCodecs(); // Step 2: codec layer
var engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD); // Step 3: engine factory
engine.Startup(codecs, null, null, runtimePath); // Step 4: blocking startup
// ... still need to create document, add page, call Recognize(), extract text
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports Leadtools.Forms.DocumentWriters
' LEADTOOLS: Four namespaces, four packages, six steps before recognition
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)) ' Step 1: two files on disk
Dim codecs As New RasterCodecs() ' Step 2: codec layer
Dim engine As IOcrEngine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD) ' Step 3: engine factory
engine.Startup(codecs, Nothing, Nothing, runtimePath) ' Step 4: blocking startup
' ... still need to create document, add page, call Recognize(), extract text
// IronOCR: One namespace, one package, one line
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
// IronOCR: One namespace, one package, one line
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text As String = New IronTesseract().Read("document.jpg").Text
IronOCR과 LEADTOOLS OCR: 기능 비교
다음 표는 두 라이브러리 간의 기능을 직접적으로 매핑합니다.
| 기능 | 리드툴즈 OCR | IronOCR |
|---|---|---|
| NuGet 패키지가 필요합니다. | 최소 4개 (PDF의 경우 더 많음) | 1 (IronOcr) |
| 라이선스 메커니즘 | .LIC + .LIC.KEY 파일 쌍 |
문자열 키 |
| 라이선스 배포 | 모든 생산 장비에 있는 파일 | 환경 변수 또는 설정 |
| 가격 모델 | 개발자당 연간 3,000달러~15,000달러 이상(추정치) | $999–$2,999 일회성 영구 |
| 엔진 초기화 | 런타임 경로와 함께 수동 Startup() |
자동, 게으름 |
| 엔진 정지 | Shutdown() 전에 수동 Dispose() |
필요하지 않음 |
| 코덱 레이어 | 모든 이미지 로딩에 필요한 RasterCodecs |
필요하지 않음 |
| PDF 입력 | 페이지별 래스터화 루프 | 네이티브 LoadPdf() |
| 비밀번호로 보호된 PDF | 별도의 Leadtools.Pdf 모듈 필요 |
내장된 Password 매개변수 |
| 검색 가능한 PDF 출력 | DocumentWriter + PdfDocumentOptions + document.Save() |
result.SaveAsSearchablePdf() |
| 전처리 | 별도의 명령 클래스 (DeskewCommand, DespeckleCommand 등) |
OcrInput에 대한 내장 필터 메서드 |
| 여러 페이지로 구성된 TIFF 파일 | CodecsLoadByteOrder로 수동 프레임 반복 |
input.LoadImageFrames() |
| 구조화된 출력 | 페이지 및 영역 수준 | 페이지, 단락, 줄, 단어, 문자의 좌표 |
| 자신감 점수 | OcrPageRecognizeStatus enum |
백분율로서 result.Confidence |
| 바코드 판독 | LEADTOOLS 바코드 모듈 별도 구매 | 내장 (ReadBarCodes = true) |
| 스레드 안전성 | 세심한 관리가 필요합니다. | (스레드당 하나 IronTesseract)으로 전체 |
| 지원되는 언어 | 60~120 (엔진 종류에 따라 다름) | NuGet 언어 패키지를 통해 125개 이상 |
| 언어 배포 | tessdata files or engine-bundled files | 언어별 NuGet 패키지 |
| 크로스 플랫폼 | 플랫폼별 런타임 구성이 지원됩니다. | 윈도우, 리눅스, macOS, Docker, Azure, AWS |
| Docker 배포 | .KEY 파일은 마운트되거나 포함되어야 함 |
표준 dotnet publish |
| 상업적 지원 | 예 | 예 |
빠른 시작: LEADTOOLS OCR에서IronOCR로 마이그레이션
1단계: NuGet 패키지 교체
LEADTOOLS 패키지를 모두 제거하세요.
dotnet remove package Leadtools
dotnet remove package Leadtools.Ocr
dotnet remove package Leadtools.Codecs
dotnet remove package Leadtools.Forms.DocumentWriters
dotnet remove package Leadtools.Pdf
dotnet remove package Leadtools
dotnet remove package Leadtools.Ocr
dotnet remove package Leadtools.Codecs
dotnet remove package Leadtools.Forms.DocumentWriters
dotnet remove package Leadtools.Pdf
NuGet 에서IronOCR설치하세요.
dotnet add package IronOcr
단계 2: 네임스페이스 업데이트
모든 LEADTOOLS using 지시어를 단일IronOCR임포트로 교체하십시오:
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
// After (IronOCR)
using IronOcr;
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
// After (IronOCR)
using IronOcr;
Imports IronOcr
단계 3: 라이선스 초기화
모든 RasterSupport.SetLicense() 호출 및 .LIC / .LIC.KEY 파일 참조를 제거하십시오. 애플리케이션 시작 시IronOCR라이선스 키를 추가하세요.
// Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
Imports System
' Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
라이선스 키는 appsettings.json, Azure Key Vault, AWS Secrets Manager 또는 Kubernetes 시크릿과 같은 표준 .NET 비밀 관리 패턴에 저장할 수 있습니다. 애플리케이션 실행 파일과 함께 배포할 파일은 없습니다.
코드 마이그레이션 예제
엔진 시동 및 정지 수명주기 제거
LEADTOOLS는 엔진 수명 주기를 명시적으로 관리해야 하므로 엄격한 서비스 클래스 패턴을 요구합니다. 생성자는 엔진을 시작하고, Dispose()는 올바른 순서로 종료하며, Shutdown()를 Dispose() 전에 건너뛰는 모든 코드 경로는 런타임 오류를 생성합니다.
LEADTOOLS OCR 접근 방식:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
// Service class required purely to manage engine lifecycle
public class LeadtoolsOcrService : IDisposable
{
private IOcrEngine _engine;
private RasterCodecs _codecs;
private readonly string _runtimePath;
public LeadtoolsOcrService(string licPath, string keyPath, string runtimePath)
{
_runtimePath = runtimePath;
// License setup — two files, both must be present
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Codec layer — required before engine creation
_codecs = new RasterCodecs();
// Engine factory — engine type determines capability and cost
_engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD);
// Blocking startup — loads runtime into memory (500–2000ms)
_engine.Startup(_codecs, null, null, _runtimePath);
}
public bool IsReady => _engine?.IsStarted ?? false;
public string Process(string imagePath)
{
if (!IsReady)
throw new InvalidOperationException("Engine not started");
using var image = _codecs.Load(imagePath);
using var doc = _engine.DocumentManager.CreateDocument();
var page = doc.Pages.AddPage(image, null);
page.Recognize(null);
return page.GetText(-1);
}
public void Dispose()
{
// Order is mandatory: Shutdown before Dispose
if (_engine?.IsStarted == true)
_engine.Shutdown();
_engine?.Dispose();
_codecs?.Dispose();
}
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
// Service class required purely to manage engine lifecycle
public class LeadtoolsOcrService : IDisposable
{
private IOcrEngine _engine;
private RasterCodecs _codecs;
private readonly string _runtimePath;
public LeadtoolsOcrService(string licPath, string keyPath, string runtimePath)
{
_runtimePath = runtimePath;
// License setup — two files, both must be present
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Codec layer — required before engine creation
_codecs = new RasterCodecs();
// Engine factory — engine type determines capability and cost
_engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD);
// Blocking startup — loads runtime into memory (500–2000ms)
_engine.Startup(_codecs, null, null, _runtimePath);
}
public bool IsReady => _engine?.IsStarted ?? false;
public string Process(string imagePath)
{
if (!IsReady)
throw new InvalidOperationException("Engine not started");
using var image = _codecs.Load(imagePath);
using var doc = _engine.DocumentManager.CreateDocument();
var page = doc.Pages.AddPage(image, null);
page.Recognize(null);
return page.GetText(-1);
}
public void Dispose()
{
// Order is mandatory: Shutdown before Dispose
if (_engine?.IsStarted == true)
_engine.Shutdown();
_engine?.Dispose();
_codecs?.Dispose();
}
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports System
Imports System.IO
' Service class required purely to manage engine lifecycle
Public Class LeadtoolsOcrService
Implements IDisposable
Private _engine As IOcrEngine
Private _codecs As RasterCodecs
Private ReadOnly _runtimePath As String
Public Sub New(licPath As String, keyPath As String, runtimePath As String)
_runtimePath = runtimePath
' License setup — two files, both must be present
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath))
' Codec layer — required before engine creation
_codecs = New RasterCodecs()
' Engine factory — engine type determines capability and cost
_engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD)
' Blocking startup — loads runtime into memory (500–2000ms)
_engine.Startup(_codecs, Nothing, Nothing, _runtimePath)
End Sub
Public ReadOnly Property IsReady As Boolean
Get
Return If(_engine?.IsStarted, False)
End Get
End Property
Public Function Process(imagePath As String) As String
If Not IsReady Then
Throw New InvalidOperationException("Engine not started")
End If
Using image = _codecs.Load(imagePath)
Using doc = _engine.DocumentManager.CreateDocument()
Dim page = doc.Pages.AddPage(image, Nothing)
page.Recognize(Nothing)
Return page.GetText(-1)
End Using
End Using
End Function
Public Sub Dispose() Implements IDisposable.Dispose
' Order is mandatory: Shutdown before Dispose
If _engine?.IsStarted = True Then
_engine.Shutdown()
End If
_engine?.Dispose()
_codecs?.Dispose()
End Sub
End Class
IronOCR 접근 방식:
using IronOcr;
// No lifecycle management needed — the service class becomes trivial
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
// Always ready — no IsStarted check needed
public string Process(string imagePath) => _ocr.Read(imagePath).Text;
// No Dispose() needed for the engine
// No Startup(), no Shutdown(), no codec layer
}
using IronOcr;
// No lifecycle management needed — the service class becomes trivial
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
// Always ready — no IsStarted check needed
public string Process(string imagePath) => _ocr.Read(imagePath).Text;
// No Dispose() needed for the engine
// No Startup(), no Shutdown(), no codec layer
}
Imports IronOcr
' No lifecycle management needed — the service class becomes trivial
Public Class OcrService
Private ReadOnly _ocr As New IronTesseract()
' Always ready — no IsStarted check needed
Public Function Process(imagePath As String) As String
Return _ocr.Read(imagePath).Text
End Function
' No Dispose() needed for the engine
' No Startup(), no Shutdown(), no codec layer
End Class
IronTesseract 인스턴스는 최초 사용 시 초기화됩니다. 생성자 인수가 없으며, 런타임 경로가 없으며, Startup() 호출이 없습니다. 위의 서비스 클래스는 전혀 IDisposable를 구현할 필요가 없습니다. 엔진은 상태가 없으며, 각 Read() 호출 내에서 사용되는 OcrInput 객체는 표준 using 패턴을 통해 자체 정리를 처리합니다. IronTesseract 설정 가이드에서는 언어 선택 및 프로덕션 시나리오에 대한 성능 튜닝을 포함한 구성 옵션을 다룹니다.
다중 프레임 TIFF 일괄 처리
LEADTOOLS는 코덱에서 총 프레임 수를 쿼리 한 다음, 각 _codecs.Load() 호출에서 명시적인 lastPage 매개변수로 반복하여 다중 프레임 TIFF 파일을 처리합니다. 모든 프레임 이미지는 수동으로 삭제해야 하며, 그렇지 않으면 메모리에 누적됩니다.
LEADTOOLS OCR 접근 방식:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsTiffBatchService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Must query page count before iterating
var info = _codecs.GetInformation(tiffPath, true);
int frameCount = info.TotalPages;
using var document = _engine.DocumentManager.CreateDocument();
for (int frameNum = 1; frameNum <= frameCount; frameNum++)
{
// Load one frame at a time — must specify firstPage/lastPage
using var frameImage = _codecs.Load(
tiffPath,
0, // bitsPerPixel
CodecsLoadByteOrder.BgrOrGray,
frameNum, // firstPage
frameNum); // lastPage
var page = document.Pages.AddPage(frameImage, null);
page.Recognize(null);
pageTexts.Add(page.GetText(-1));
// GC pressure accumulates if disposal is missed on any frame
}
return pageTexts;
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
// Manual GC between files to prevent memory growth
GC.Collect();
GC.WaitForPendingFinalizers();
}
return results;
}
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsTiffBatchService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Must query page count before iterating
var info = _codecs.GetInformation(tiffPath, true);
int frameCount = info.TotalPages;
using var document = _engine.DocumentManager.CreateDocument();
for (int frameNum = 1; frameNum <= frameCount; frameNum++)
{
// Load one frame at a time — must specify firstPage/lastPage
using var frameImage = _codecs.Load(
tiffPath,
0, // bitsPerPixel
CodecsLoadByteOrder.BgrOrGray,
frameNum, // firstPage
frameNum); // lastPage
var page = document.Pages.AddPage(frameImage, null);
page.Recognize(null);
pageTexts.Add(page.GetText(-1));
// GC pressure accumulates if disposal is missed on any frame
}
return pageTexts;
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
// Manual GC between files to prevent memory growth
GC.Collect();
GC.WaitForPendingFinalizers();
}
return results;
}
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports System.IO
Public Class LeadtoolsTiffBatchService
Private ReadOnly _engine As IOcrEngine
Private ReadOnly _codecs As RasterCodecs
Public Function ProcessMultiFrameTiff(tiffPath As String) As List(Of String)
Dim pageTexts As New List(Of String)()
' Must query page count before iterating
Dim info = _codecs.GetInformation(tiffPath, True)
Dim frameCount As Integer = info.TotalPages
Using document = _engine.DocumentManager.CreateDocument()
For frameNum As Integer = 1 To frameCount
' Load one frame at a time — must specify firstPage/lastPage
Using frameImage = _codecs.Load(tiffPath, 0, CodecsLoadByteOrder.BgrOrGray, frameNum, frameNum)
Dim page = document.Pages.AddPage(frameImage, Nothing)
page.Recognize(Nothing)
pageTexts.Add(page.GetText(-1))
' GC pressure accumulates if disposal is missed on any frame
End Using
Next
End Using
Return pageTexts
End Function
Public Function ProcessTiffDirectory(directoryPath As String) As Dictionary(Of String, List(Of String))
Dim results As New Dictionary(Of String, List(Of String))()
For Each tiffFile In Directory.GetFiles(directoryPath, "*.tiff")
results(tiffFile) = ProcessMultiFrameTiff(tiffFile)
' Manual GC between files to prevent memory growth
GC.Collect()
GC.WaitForPendingFinalizers()
Next
Return results
End Function
End Class
IronOCR 접근 방식:
using IronOcr;
public class TiffBatchService
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // All frames loaded automatically
var result = _ocr.Read(input);
// Per-frame text available through result.Pages
return result.Pages.Select(p => p.Text).ToList();
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
}
return results;
}
// Parallel processing across files — thread-safe out of the box
public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
{
var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");
Parallel.ForEach(tiffFiles, tiffFile =>
{
using var input = new OcrInput();
input.LoadImageFrames(tiffFile);
var result = new IronTesseract().Read(input);
concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
});
return new Dictionary<string, List<string>>(concurrentResults);
}
}
using IronOcr;
public class TiffBatchService
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // All frames loaded automatically
var result = _ocr.Read(input);
// Per-frame text available through result.Pages
return result.Pages.Select(p => p.Text).ToList();
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
}
return results;
}
// Parallel processing across files — thread-safe out of the box
public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
{
var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");
Parallel.ForEach(tiffFiles, tiffFile =>
{
using var input = new OcrInput();
input.LoadImageFrames(tiffFile);
var result = new IronTesseract().Read(input);
concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
});
return new Dictionary<string, List<string>>(concurrentResults);
}
}
Imports IronOcr
Imports System.IO
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Public Class TiffBatchService
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessMultiFrameTiff(tiffPath As String) As List(Of String)
Using input As New OcrInput()
input.LoadImageFrames(tiffPath) ' All frames loaded automatically
Dim result = _ocr.Read(input)
' Per-frame text available through result.Pages
Return result.Pages.Select(Function(p) p.Text).ToList()
End Using
End Function
Public Function ProcessTiffDirectory(directoryPath As String) As Dictionary(Of String, List(Of String))
Dim results As New Dictionary(Of String, List(Of String))()
For Each tiffFile In Directory.GetFiles(directoryPath, "*.tiff")
results(tiffFile) = ProcessMultiFrameTiff(tiffFile)
Next
Return results
End Function
' Parallel processing across files — thread-safe out of the box
Public Function ProcessTiffDirectoryParallel(directoryPath As String) As Dictionary(Of String, List(Of String))
Dim concurrentResults As New ConcurrentDictionary(Of String, List(Of String))()
Dim tiffFiles = Directory.GetFiles(directoryPath, "*.tiff")
Parallel.ForEach(tiffFiles, Sub(tiffFile)
Using input As New OcrInput()
input.LoadImageFrames(tiffFile)
Dim result = New IronTesseract().Read(input)
concurrentResults(tiffFile) = result.Pages.Select(Function(p) p.Text).ToList()
End Using
End Sub)
Return New Dictionary(Of String, List(Of String))(concurrentResults)
End Function
End Class
LoadImageFrames()는 한 번의 호출로 TIFF의 모든 프레임을 읽습니다. 프레임 수 쿼리도 없고, 루프도 없고, 프레임별로 명시적인 폐기 처리도 없습니다. 병렬 버전은 스레드당 하나의 IronTesseract 인스턴스를 생성하며, 이는 올바른 패턴입니다. 전체 스레딩 모델을 위해 멀티스레딩 예제를 참조하세요. TIFF 관련 입력 옵션에 대해서는 TIFF 및 GIF 입력 가이드에서 프레임 선택 및 다중 형식 처리에 대한 내용을 참조하십시오.
문서 작성기 파이프라인 간소화
LEADTOOLS 검색 가능한 PDF 생성에는 엔진에서 DocumentWriter 인스턴스를 구성하고, 출력 유형 및 오버레이 설정과 함께 PdfDocumentOptions 객체를 생성하고, SetOptions()를 통해 옵션을 적용한 다음, 포맷 enum과 함께 document.Save()을 호출해야 합니다. 각 단계는 별개의 객체이며 별도의 API 호출입니다.
LEADTOOLS OCR 접근 방식:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
public class LeadtoolsDocumentWriterService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var document = _engine.DocumentManager.CreateDocument();
foreach (var imagePath in imagePaths)
{
using var image = _codecs.Load(imagePath);
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
}
// DocumentWriter configuration — four properties to set before save
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true, // Image layer visible, text layer searchable
Linearized = false,
Title = "Searchable Output"
};
// Apply options to the engine's writer instance
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// Save with format enum — the format must match the options set above
document.Save(outputPath, DocumentFormat.Pdf, null);
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
var pdfInfo = _codecs.GetInformation(inputPdfPath, true);
using var document = _engine.DocumentManager.CreateDocument();
for (int i = 1; i <= pdfInfo.TotalPages; i++)
{
using var pageImage = _codecs.Load(inputPdfPath, 0,
CodecsLoadByteOrder.BgrOrGray, i, i);
var page = document.Pages.AddPage(pageImage, null);
page.Recognize(null);
}
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true,
Title = Path.GetFileNameWithoutExtension(inputPdfPath)
};
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
document.Save(outputPath, DocumentFormat.Pdf, null);
}
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
public class LeadtoolsDocumentWriterService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var document = _engine.DocumentManager.CreateDocument();
foreach (var imagePath in imagePaths)
{
using var image = _codecs.Load(imagePath);
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
}
// DocumentWriter configuration — four properties to set before save
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true, // Image layer visible, text layer searchable
Linearized = false,
Title = "Searchable Output"
};
// Apply options to the engine's writer instance
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// Save with format enum — the format must match the options set above
document.Save(outputPath, DocumentFormat.Pdf, null);
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
var pdfInfo = _codecs.GetInformation(inputPdfPath, true);
using var document = _engine.DocumentManager.CreateDocument();
for (int i = 1; i <= pdfInfo.TotalPages; i++)
{
using var pageImage = _codecs.Load(inputPdfPath, 0,
CodecsLoadByteOrder.BgrOrGray, i, i);
var page = document.Pages.AddPage(pageImage, null);
page.Recognize(null);
}
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true,
Title = Path.GetFileNameWithoutExtension(inputPdfPath)
};
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
document.Save(outputPath, DocumentFormat.Pdf, null);
}
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports Leadtools.Forms.DocumentWriters
Public Class LeadtoolsDocumentWriterService
Private ReadOnly _engine As IOcrEngine
Private ReadOnly _codecs As RasterCodecs
Public Sub CreateSearchablePdfFromImages(imagePaths As String(), outputPath As String)
Using document = _engine.DocumentManager.CreateDocument()
For Each imagePath In imagePaths
Using image = _codecs.Load(imagePath)
Dim page = document.Pages.AddPage(image, Nothing)
page.Recognize(Nothing)
End Using
Next
' DocumentWriter configuration — four properties to set before save
Dim pdfOptions As New PdfDocumentOptions With {
.DocumentType = PdfDocumentType.Pdf,
.ImageOverText = True, ' Image layer visible, text layer searchable
.Linearized = False,
.Title = "Searchable Output"
}
' Apply options to the engine's writer instance
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)
' Save with format enum — the format must match the options set above
document.Save(outputPath, DocumentFormat.Pdf, Nothing)
End Using
End Sub
Public Sub CreateSearchablePdfFromPdf(inputPdfPath As String, outputPath As String)
Dim pdfInfo = _codecs.GetInformation(inputPdfPath, True)
Using document = _engine.DocumentManager.CreateDocument()
For i As Integer = 1 To pdfInfo.TotalPages
Using pageImage = _codecs.Load(inputPdfPath, 0, CodecsLoadByteOrder.BgrOrGray, i, i)
Dim page = document.Pages.AddPage(pageImage, Nothing)
page.Recognize(Nothing)
End Using
Next
Dim pdfOptions As New PdfDocumentOptions With {
.DocumentType = PdfDocumentType.Pdf,
.ImageOverText = True,
.Title = Path.GetFileNameWithoutExtension(inputPdfPath)
}
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)
document.Save(outputPath, DocumentFormat.Pdf, Nothing)
End Using
End Sub
End Class
IronOCR 접근 방식:
using IronOcr;
public class SearchablePdfService
{
private readonly IronTesseract _ocr = new IronTesseract();
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var input = new OcrInput();
foreach (var imagePath in imagePaths)
input.LoadImage(imagePath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath); // DocumentWriter pipeline: gone
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath);
}
// Get bytes directly — useful for streaming responses in ASP.NET
public byte[] CreateSearchablePdfBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
return _ocr.Read(input).SaveAsSearchablePdfBytes();
}
}
using IronOcr;
public class SearchablePdfService
{
private readonly IronTesseract _ocr = new IronTesseract();
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var input = new OcrInput();
foreach (var imagePath in imagePaths)
input.LoadImage(imagePath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath); // DocumentWriter pipeline: gone
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath);
}
// Get bytes directly — useful for streaming responses in ASP.NET
public byte[] CreateSearchablePdfBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
return _ocr.Read(input).SaveAsSearchablePdfBytes();
}
}
Imports IronOcr
Public Class SearchablePdfService
Private ReadOnly _ocr As New IronTesseract()
Public Sub CreateSearchablePdfFromImages(imagePaths As String(), outputPath As String)
Using input As New OcrInput()
For Each imagePath In imagePaths
input.LoadImage(imagePath)
Next
Dim result = _ocr.Read(input)
result.SaveAsSearchablePdf(outputPath) ' DocumentWriter pipeline: gone
End Using
End Sub
Public Sub CreateSearchablePdfFromPdf(inputPdfPath As String, outputPath As String)
Using input As New OcrInput()
input.LoadPdf(inputPdfPath)
Dim result = _ocr.Read(input)
result.SaveAsSearchablePdf(outputPath)
End Using
End Sub
' Get bytes directly — useful for streaming responses in ASP.NET
Public Function CreateSearchablePdfBytes(inputPdfPath As String) As Byte()
Using input As New OcrInput()
input.LoadPdf(inputPdfPath)
Return _ocr.Read(input).SaveAsSearchablePdfBytes()
End Using
End Function
End Class
SaveAsSearchablePdf()는 전체 PdfDocumentOptions + SetOptions() + document.Save() 체인을 대체합니다. 이미지가 텍스트 위에 겹쳐지는 레이어 동작은 자동으로 이루어집니다. 검색 가능한 PDF 출력에 대한 전체 문서는 검색 가능한 PDF 사용 방법 가이드에서 출력 옵션을 다루고 , 검색 가능한 PDF 예제에서 ASP.NET 응답 스트림과의 통합을 보여줍니다.
다중 영역 필드 추출 마이그레이션
LEADTOOLS 영역 기반 OCR은 LeadRect 경계, OcrZoneType 및 OcrZoneCharacterFilters 속성과 함께 OcrZone 객체를 사용합니다. 여러 영역을 한 페이지에 추가하고, 한 번의 page.Recognize() 호출에서 인식한 후, 영역 인덱스를 통해 추출합니다. 영역 인덱스는 삽입 순서와 일치하므로 추출 루프는 해당 순서를 유지해야 합니다.
LEADTOOLS OCR 접근 방식:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsFormFieldExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
// Invoice field extraction using named zones
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
using var image = _codecs.Load(invoicePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
// Must clear auto-detected zones before adding custom ones
page.Zones.Clear();
// Zone definitions — index order matters for extraction
var zoneDefinitions = new[]
{
new { Name = "InvoiceNumber", X = 450, Y = 80, W = 200, H = 30 },
new { Name = "InvoiceDate", X = 450, Y = 115, W = 200, H = 30 },
new { Name = "VendorName", X = 50, Y = 150, W = 300, H = 40 },
new { Name = "TotalAmount", X = 450, Y = 600, W = 200, H = 30 }
};
foreach (var def in zoneDefinitions)
{
var zone = new OcrZone
{
Bounds = new LeadRect(def.X, def.Y, def.W, def.H),
ZoneType = OcrZoneType.Text,
CharacterFilters = OcrZoneCharacterFilters.None,
RecognitionModule = OcrZoneRecognitionModule.Auto
};
page.Zones.Add(zone);
}
page.Recognize(null);
// Extract by index — must match insertion order exactly
return new InvoiceFields
{
InvoiceNumber = page.Zones[0].Text?.Trim(),
InvoiceDate = page.Zones[1].Text?.Trim(),
VendorName = page.Zones[2].Text?.Trim(),
TotalAmount = page.Zones[3].Text?.Trim()
};
}
}
public class InvoiceFields
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public string TotalAmount { get; set; }
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsFormFieldExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
// Invoice field extraction using named zones
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
using var image = _codecs.Load(invoicePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
// Must clear auto-detected zones before adding custom ones
page.Zones.Clear();
// Zone definitions — index order matters for extraction
var zoneDefinitions = new[]
{
new { Name = "InvoiceNumber", X = 450, Y = 80, W = 200, H = 30 },
new { Name = "InvoiceDate", X = 450, Y = 115, W = 200, H = 30 },
new { Name = "VendorName", X = 50, Y = 150, W = 300, H = 40 },
new { Name = "TotalAmount", X = 450, Y = 600, W = 200, H = 30 }
};
foreach (var def in zoneDefinitions)
{
var zone = new OcrZone
{
Bounds = new LeadRect(def.X, def.Y, def.W, def.H),
ZoneType = OcrZoneType.Text,
CharacterFilters = OcrZoneCharacterFilters.None,
RecognitionModule = OcrZoneRecognitionModule.Auto
};
page.Zones.Add(zone);
}
page.Recognize(null);
// Extract by index — must match insertion order exactly
return new InvoiceFields
{
InvoiceNumber = page.Zones[0].Text?.Trim(),
InvoiceDate = page.Zones[1].Text?.Trim(),
VendorName = page.Zones[2].Text?.Trim(),
TotalAmount = page.Zones[3].Text?.Trim()
};
}
}
public class InvoiceFields
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public string TotalAmount { get; set; }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Public Class LeadtoolsFormFieldExtractor
Private ReadOnly _engine As IOcrEngine
Private ReadOnly _codecs As RasterCodecs
' Invoice field extraction using named zones
Public Function ExtractInvoiceFields(invoicePath As String) As InvoiceFields
Using image = _codecs.Load(invoicePath)
Using document = _engine.DocumentManager.CreateDocument()
Dim page = document.Pages.AddPage(image, Nothing)
' Must clear auto-detected zones before adding custom ones
page.Zones.Clear()
' Zone definitions — index order matters for extraction
Dim zoneDefinitions = New() {
New With {.Name = "InvoiceNumber", .X = 450, .Y = 80, .W = 200, .H = 30},
New With {.Name = "InvoiceDate", .X = 450, .Y = 115, .W = 200, .H = 30},
New With {.Name = "VendorName", .X = 50, .Y = 150, .W = 300, .H = 40},
New With {.Name = "TotalAmount", .X = 450, .Y = 600, .W = 200, .H = 30}
}
For Each def In zoneDefinitions
Dim zone = New OcrZone With {
.Bounds = New LeadRect(def.X, def.Y, def.W, def.H),
.ZoneType = OcrZoneType.Text,
.CharacterFilters = OcrZoneCharacterFilters.None,
.RecognitionModule = OcrZoneRecognitionModule.Auto
}
page.Zones.Add(zone)
Next
page.Recognize(Nothing)
' Extract by index — must match insertion order exactly
Return New InvoiceFields With {
.InvoiceNumber = page.Zones(0).Text?.Trim(),
.InvoiceDate = page.Zones(1).Text?.Trim(),
.VendorName = page.Zones(2).Text?.Trim(),
.TotalAmount = page.Zones(3).Text?.Trim()
}
End Using
End Using
End Function
End Class
Public Class InvoiceFields
Public Property InvoiceNumber As String
Public Property InvoiceDate As String
Public Property VendorName As String
Public Property TotalAmount As String
End Class
IronOCR 접근 방식:
using IronOcr;
public class FormFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
// Each field gets its own CropRectangle-scoped Read() call
// No zone index management, no zone ordering dependency
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
return new InvoiceFields
{
InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
};
}
private string ReadRegion(string imagePath, int x, int y, int width, int height)
{
using var input = new OcrInput();
input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
return _ocr.Read(input).Text.Trim();
}
// Batch: extract the same field from many invoices in parallel
public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
{
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
Parallel.ForEach(invoicePaths, invoicePath =>
{
using var input = new OcrInput();
input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
});
return new Dictionary<string, string>(results);
}
}
using IronOcr;
public class FormFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
// Each field gets its own CropRectangle-scoped Read() call
// No zone index management, no zone ordering dependency
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
return new InvoiceFields
{
InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
};
}
private string ReadRegion(string imagePath, int x, int y, int width, int height)
{
using var input = new OcrInput();
input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
return _ocr.Read(input).Text.Trim();
}
// Batch: extract the same field from many invoices in parallel
public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
{
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
Parallel.ForEach(invoicePaths, invoicePath =>
{
using var input = new OcrInput();
input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
});
return new Dictionary<string, string>(results);
}
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Public Class FormFieldExtractor
Private ReadOnly _ocr As New IronTesseract()
' Each field gets its own CropRectangle-scoped Read() call
' No zone index management, no zone ordering dependency
Public Function ExtractInvoiceFields(invoicePath As String) As InvoiceFields
Return New InvoiceFields With {
.InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
.InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
.VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
.TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
}
End Function
Private Function ReadRegion(imagePath As String, x As Integer, y As Integer, width As Integer, height As Integer) As String
Using input As New OcrInput()
input.LoadImage(imagePath, New CropRectangle(x, y, width, height))
Return _ocr.Read(input).Text.Trim()
End Using
End Function
' Batch: extract the same field from many invoices in parallel
Public Function ExtractInvoiceNumbersBatch(invoicePaths As String()) As Dictionary(Of String, String)
Dim results As New ConcurrentDictionary(Of String, String)()
Parallel.ForEach(invoicePaths, Sub(invoicePath)
Using input As New OcrInput()
input.LoadImage(invoicePath, New CropRectangle(450, 80, 200, 30))
results(invoicePath) = New IronTesseract().Read(input).Text.Trim()
End Using
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
End Class
CropRectangle를 LoadImage()에 직접 전달하여 OcrZone 설정을 전부 대체합니다. 추적할 영역 인덱스가 없고, page.Zones.Clear() 호출이 필요 없으며, 인식 상태 확인도 필요하지 않습니다. 영역 기반 OCR 가이드는 단일 영역 및 다중 영역 추출 패턴을 다룹니다. 송장 필드 추출에 대한 전체 튜토리얼은 송장 OCR 튜토리얼을 참조하세요.
단어 좌표를 이용한 구조화된 데이터 추출
LEADTOOLS의 구조화된 출력은 페이지 및 영역 수준에서 작동합니다. 경계 상자 좌표로 단어 수준의 데이터를 얻으려면, 개발자는 인식된 영역 내에서 OcrWord 객체에 접근합니다. API를 사용하려면 인식 후 영역 컬렉션을 처리하고 영역별 단어 목록을 반복해야 합니다.
LEADTOOLS OCR 접근 방식:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsStructuredExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var wordLocations = new List<WordLocation>();
using var image = _codecs.Load(imagePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
// Access words through the zone collection
foreach (OcrZone zone in page.Zones)
{
foreach (OcrWord word in zone.Words)
{
wordLocations.Add(new WordLocation
{
Text = word.Value,
X = word.Bounds.X,
Y = word.Bounds.Y,
Width = word.Bounds.Width,
Height = word.Bounds.Height,
Confidence = word.Confidence
});
}
}
return wordLocations;
}
}
public class WordLocation
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Confidence { get; set; }
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsStructuredExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var wordLocations = new List<WordLocation>();
using var image = _codecs.Load(imagePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
// Access words through the zone collection
foreach (OcrZone zone in page.Zones)
{
foreach (OcrWord word in zone.Words)
{
wordLocations.Add(new WordLocation
{
Text = word.Value,
X = word.Bounds.X,
Y = word.Bounds.Y,
Width = word.Bounds.Width,
Height = word.Bounds.Height,
Confidence = word.Confidence
});
}
}
return wordLocations;
}
}
public class WordLocation
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Confidence { get; set; }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Public Class LeadtoolsStructuredExtractor
Private ReadOnly _engine As IOcrEngine
Private ReadOnly _codecs As RasterCodecs
Public Function ExtractWordsWithLocations(imagePath As String) As List(Of WordLocation)
Dim wordLocations As New List(Of WordLocation)()
Using image = _codecs.Load(imagePath)
Using document = _engine.DocumentManager.CreateDocument()
Dim page = document.Pages.AddPage(image, Nothing)
page.Recognize(Nothing)
' Access words through the zone collection
For Each zone As OcrZone In page.Zones
For Each word As OcrWord In zone.Words
wordLocations.Add(New WordLocation With {
.Text = word.Value,
.X = word.Bounds.X,
.Y = word.Bounds.Y,
.Width = word.Bounds.Width,
.Height = word.Bounds.Height,
.Confidence = word.Confidence
})
Next
Next
End Using
End Using
Return wordLocations
End Function
End Class
Public Class WordLocation
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 Integer
End Class
IronOCR 접근 방식:
using IronOcr;
public class StructuredExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
return result.Pages
.SelectMany(page => page.Paragraphs)
.SelectMany(para => para.Lines)
.SelectMany(line => line.Words)
.Select(word => new WordLocation
{
Text = word.Text,
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height,
Confidence = (int)word.Confidence
})
.ToList();
}
// Paragraph-level extraction with position data
public void PrintDocumentStructure(string imagePath)
{
var result = _ocr.Read(imagePath);
Console.WriteLine($"Document confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}:");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
}
using IronOcr;
public class StructuredExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
return result.Pages
.SelectMany(page => page.Paragraphs)
.SelectMany(para => para.Lines)
.SelectMany(line => line.Words)
.Select(word => new WordLocation
{
Text = word.Text,
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height,
Confidence = (int)word.Confidence
})
.ToList();
}
// Paragraph-level extraction with position data
public void PrintDocumentStructure(string imagePath)
{
var result = _ocr.Read(imagePath);
Console.WriteLine($"Document confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}:");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
}
Imports IronOcr
Public Class StructuredExtractor
Private ReadOnly _ocr As New IronTesseract()
Public Function ExtractWordsWithLocations(imagePath As String) As List(Of WordLocation)
Dim result = _ocr.Read(imagePath)
' Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
Return result.Pages _
.SelectMany(Function(page) page.Paragraphs) _
.SelectMany(Function(para) para.Lines) _
.SelectMany(Function(line) line.Words) _
.Select(Function(word) New WordLocation With {
.Text = word.Text,
.X = word.X,
.Y = word.Y,
.Width = word.Width,
.Height = word.Height,
.Confidence = CInt(word.Confidence)
}) _
.ToList()
End Function
' Paragraph-level extraction with position data
Public Sub PrintDocumentStructure(imagePath As String)
Dim result = _ocr.Read(imagePath)
Console.WriteLine($"Document confidence: {result.Confidence}%")
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}:")
For Each paragraph In page.Paragraphs
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):")
Console.WriteLine($" {paragraph.Text}")
Next
Next
End Sub
End Class
IronOCR의 결과 계층 구조는 Pages에서 시작하여 Paragraphs, Lines, Words 및 Characters으로 내려갑니다. 각 수준은 X, Y, Width, Height, Text 및 Confidence를 노출합니다. LEADTOOLS의 영역 기반 액세스 패턴이 사라집니다. 워드 데이터에 접근하기 위해 영역을 반복할 필요가 없습니다. 읽기 결과 사용 설명서는 좌표 접근 패턴을 포함한 전체 출력 모델을 다룹니다. OcrResult API 참조 문서에는 결과 계층 구조의 모든 속성이 설명되어 있습니다.
리드툴즈 OCR API와IronOCR매핑 참조
| 리드툴즈 OCR | IronOCR |
|---|---|
RasterSupport.SetLicense(licPath, keyContent) |
IronOcr.License.LicenseKey = "key" |
new RasterCodecs() |
필요하지 않음 |
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) |
new IronTesseract() |
engine.Startup(codecs, null, null, runtimePath) |
필요하지 않음 |
engine.IsStarted |
필수 사항이 아닙니다 (항상 준비되어 있습니다). |
engine.Shutdown() |
필요하지 않음 |
engine.Dispose() |
필요하지 않음 |
_codecs.Load(imagePath) |
input.LoadImage(imagePath) |
_codecs.Load(path, 0, BgrOrGray, page, page) |
input.LoadPdf(path) 또는 input.LoadImageFrames(path) |
_codecs.GetInformation(path, true).TotalPages |
필요 없음 — 자동 |
engine.DocumentManager.CreateDocument() |
필요하지 않음 |
document.Pages.AddPage(image, null) |
input.LoadImage(imagePath) |
page.Recognize(null) |
ocr.Read(input) (인식은 Read()의 일부) |
page.GetText(-1) |
result.Text |
page.RecognizeStatus |
result.Confidence (백분율) |
OcrZone { Bounds = new LeadRect(x, y, w, h) } |
new CropRectangle(x, y, w, h) |
page.Zones.Clear() |
필요하지 않음 |
page.Zones.Add(zone) |
input.LoadImage(path, cropRect) |
zone.Words / word.Bounds |
result.Pages[n].Words / word.X, word.Y |
DeskewCommand().Run(image) |
input.Deskew() |
DespeckleCommand().Run(image) |
input.DeNoise() |
AutoBinarizeCommand().Run(image) |
input.Binarize() |
ContrastBrightnessCommand().Run(image) |
input.Contrast() |
new PdfDocumentOptions { ImageOverText = true } |
SaveAsSearchablePdf()에 의해 자동으로 처리됩니다. |
engine.DocumentWriterInstance.SetOptions(format, opts) |
필요하지 않음 |
document.Save(path, DocumentFormat.Pdf, null) |
result.SaveAsSearchablePdf(path) |
_codecs.Options.Pdf.Load.Password = password |
input.LoadPdf(path, Password: password) |
RasterSupport.IsLocked(RasterSupportType.Document) |
IronOcr.License.IsLicensed |
일반적인 마이그레이션 문제와 해결책
문제 1: 라이선스 파일 경로 확인 실패
LEADTOOLS: RasterSupport.SetLicense()은 작업 디렉토리를 기준으로 .LIC 및 .LIC.KEY 파일 경로를 해결하며, 이는 bin/Debug, bin/Release, Docker 컨테이너 및 IIS 애플리케이션 풀 간에 다릅니다. 일반적인 실패 모드는 개발에서 작동하지만, 작업 디렉토리가 변경되어 생산에서 "License file not found"를 던지는 경로입니다.
해결책: 두 라이선스 파일과 SetLicense() 호출을 완전히 삭제하십시오. 환경 변수에서 읽어오는 단일 문자열 할당으로 대체하세요.
// Remove this:
// RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Replace with this:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set");
// Remove this:
// RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Replace with this:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set");
' Remove this:
' RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath))
' Replace with this:
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE"), Throw New InvalidOperationException("IRONOCR_LICENSE environment variable not set"))
문자열 키는 모든 환경에서 동일하게 동작합니다. 데이터베이스 연결 문자열에 이미 사용 중인 동일한 비밀 관리자에 저장하십시오.
문제 2: 엔진 시동 실패 예외
LEADTOOLS: engine.Startup() 완료 전에 또는 engine.Shutdown() 호출 후에 (예: 종료 중 사용하는 이전 경합 조건에서) 인식 메서드를 호출하면 "엔진이 시작되지 않았습니다."라는 메시지와 함께 InvalidOperationException를 던집니다. 장기 서비스를 제공하는 클래스는 IsStarted 검사를 통해 이를 방지해야 합니다.
해결책: IronTesseract는 시작 호출이 필요 없으며 시작/중지 상태가 없습니다. IsStarted 보호자와 전체 수명 주기 서비스 클래스를 삭제할 수 있습니다:
// Remove the guard:
// if (!_engine.IsStarted)
// throw new InvalidOperationException("Engine not started");
// IronTesseract is always ready — just call Read()
var result = _ocr.Read(imagePath);
// Remove the guard:
// if (!_engine.IsStarted)
// throw new InvalidOperationException("Engine not started");
// IronTesseract is always ready — just call Read()
var result = _ocr.Read(imagePath);
문제 3: 일괄 처리 시 래스터 이미지 메모리 누적 문제
LEADTOOLS: PDF 페이지 또는 이미지 파일을 반복하는 배치 처리는 루프에서 RasterImage 인스턴스를 생성합니다. 예외가 using 블록 종료 전에 발생하거나, 호출이 누락된 수동 폐기 패턴으로 인해 RasterImage를 처리하지 못하는 코드 경로가 발생하면, 해제되지 않은 이미지가 메모리에 축적됩니다. LEADTOOLS 생산 코드는 일반적으로 배치 사이에 보상 메커니즘으로 GC.Collect() / GC.WaitForPendingFinalizers() 호출을 포함합니다.
해결책: 모든 GC.Collect() 호출을 제거하십시오. OcrInput은IronOCR파이프라인에서 유일한 IDisposable이며, 표준 using 블록을 사용하여 각 배치 작업에 범위가 지정됩니다:
// Remove this pattern:
// GC.Collect();
// GC.WaitForPendingFinalizers();
// Replace with standard using scope:
foreach (var filePath in filePaths)
{
using var input = new OcrInput();
input.LoadImage(filePath);
var text = _ocr.Read(input).Text;
// input disposed here — no accumulation
}
// Remove this pattern:
// GC.Collect();
// GC.WaitForPendingFinalizers();
// Replace with standard using scope:
foreach (var filePath in filePaths)
{
using var input = new OcrInput();
input.LoadImage(filePath);
var text = _ocr.Read(input).Text;
// input disposed here — no accumulation
}
' Remove this pattern:
' GC.Collect()
' GC.WaitForPendingFinalizers()
' Replace with standard using scope:
For Each filePath In filePaths
Using input As New OcrInput()
input.LoadImage(filePath)
Dim text = _ocr.Read(input).Text
' input disposed here — no accumulation
End Using
Next
메모리 최적화에 대한 추가적인 지침은 메모리 할당 감소 관련 블로그 게시물을 참조하세요.
문제 4: DocumentWriterInstance 옵션이 호출 간에 유지됨
LEADTOOLS: engine.DocumentWriterInstance.SetOptions()는 공유 엔진 작성자 인스턴스의 상태를 수정합니다. 어떤 코드 경로가 PdfDocumentOptions를 DocumentType = PdfDocumentType.PdfA로 설정하고 후속 호출이 document.Save()를 호출하기 전에 해당 옵션을 재설정하지 않으면, 이전 호출의 출력 형식이 유지됩니다. 이는 공유 엔진 인스턴스에 상태를 유지하는 부작용입니다.
해결책:IronOCR공유되는 기록자 상태가 없습니다. 각 SaveAsSearchablePdf() 호출은 독립적입니다.
// Remove the options setup:
// var pdfOptions = new PdfDocumentOptions { ... };
// _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// document.Save(outputPath, DocumentFormat.Pdf, null);
// Replace with:
result.SaveAsSearchablePdf(outputPath);
// Remove the options setup:
// var pdfOptions = new PdfDocumentOptions { ... };
// _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// document.Save(outputPath, DocumentFormat.Pdf, null);
// Replace with:
result.SaveAsSearchablePdf(outputPath);
' Remove the options setup:
' Dim pdfOptions As New PdfDocumentOptions With { ... }
' _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)
' document.Save(outputPath, DocumentFormat.Pdf, Nothing)
' Replace with:
result.SaveAsSearchablePdf(outputPath)
각 호출은 이미지 오버레이와 검색 가능한 텍스트 레이어가 포함된 표준 PDF 출력을 생성합니다. 통화 간 재설정할 공유 옵션 상태가 없습니다.
문제 5: 잘못된 번들 - 런타임 시 모듈 누락
LEADTOOLS: 문서 이미징 SDK를 구매한 팀은 Leadtools.Pdf 유형을 사용할 수 없거나, 암호화된 PDF 페이지가 RasterException 및 RasterExceptionCode.FeatureNotSupported를 가진 예외를 던지는 것을 발견할 수 있습니다. 이 문제는 구매한 패키지에 PDF 모듈이 포함되어 있지 않은 경우에 발생하며, 프로덕션 환경에서 런타임 시에만 오류가 나타납니다.
해결책:IronOCR모든 라이선스 등급에 모든 기능을 포함합니다. 별도의 PDF 모듈, 암호화된 문서를 위한 애드온, 또는 더 높은 정확도의 엔진을 위한 부수적인 벤더는 없습니다. 단일 IronOcr 패키지를 설치한 후에는 추가 구매 없이 전체 기능 세트가 사용할 수 있습니다:
dotnet add package IronOcr
문제 6: 재정렬 후 영역 인덱스 불일치
LEADTOOLS: 영역 추출은 삽입 순서에 추출 논리를 연결하는 포지셔널 인덱싱 — page.Zones[0].Text, page.Zones[1].Text — 을 사용합니다. 변경된 양식 레이아웃에 맞춰 영역 정의 순서를 재정렬하면 이후의 모든 인덱스가 이동하여 추출이 자동으로 중단됩니다.
해결책: IronOCR는 필드별 CropRectangle와 함께 명명된 변수를 사용합니다. 각 필드는 독립적으로 범위가 지정되므로 필드 정의 순서를 변경해도 추출에는 영향을 미치지 않습니다.
// Each field is independent — reorder freely without breaking extraction
var invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30);
var invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30);
var vendorName = ReadRegion(imagePath, 50, 150, 300, 40);
var totalAmount = ReadRegion(imagePath, 450, 600, 200, 30);
// Each field is independent — reorder freely without breaking extraction
var invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30);
var invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30);
var vendorName = ReadRegion(imagePath, 50, 150, 300, 40);
var totalAmount = ReadRegion(imagePath, 450, 600, 200, 30);
' Each field is independent — reorder freely without breaking extraction
Dim invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30)
Dim invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30)
Dim vendorName = ReadRegion(imagePath, 50, 150, 300, 40)
Dim totalAmount = ReadRegion(imagePath, 450, 600, 200, 30)
리드툴즈 OCR 마이그레이션 체크리스트
사전 마이그레이션
변경 작업을 수행하기 전에 코드베이스를 감사하여 LEADTOOLS 사용 부분을 모두 확인하십시오.
# Find all LEADTOOLS namespace imports
grep -rn "using Leadtools" --include="*.cs" .
# Find engine lifecycle calls
grep -rn "OcrEngineManager\|\.Startup(\|\.Shutdown()" --include="*.cs" .
# Find license file references
grep -rn "SetLicense\|LEADTOOLS\.LIC\|\.LIC\.KEY" --include="*.cs" .
# Find RasterCodecs usage
grep -rn "RasterCodecs\|_codecs\.Load\|GetInformation" --include="*.cs" .
# Find DocumentWriter usage
grep -rn "DocumentWriterInstance\|PdfDocumentOptions\|DocumentFormat\." --include="*.cs" .
# Find zone-based OCR
grep -rn "OcrZone\|page\.Zones\|LeadRect\|ZoneType" --include="*.cs" .
# Find GC workarounds to remove
grep -rn "GC\.Collect\|WaitForPendingFinalizers" --include="*.cs" .
# Find all LEADTOOLS namespace imports
grep -rn "using Leadtools" --include="*.cs" .
# Find engine lifecycle calls
grep -rn "OcrEngineManager\|\.Startup(\|\.Shutdown()" --include="*.cs" .
# Find license file references
grep -rn "SetLicense\|LEADTOOLS\.LIC\|\.LIC\.KEY" --include="*.cs" .
# Find RasterCodecs usage
grep -rn "RasterCodecs\|_codecs\.Load\|GetInformation" --include="*.cs" .
# Find DocumentWriter usage
grep -rn "DocumentWriterInstance\|PdfDocumentOptions\|DocumentFormat\." --include="*.cs" .
# Find zone-based OCR
grep -rn "OcrZone\|page\.Zones\|LeadRect\|ZoneType" --include="*.cs" .
# Find GC workarounds to remove
grep -rn "GC\.Collect\|WaitForPendingFinalizers" --include="*.cs" .
마이그레이션 후 검증 과정에서 동일한IronOCR기능이 테스트되도록 구매한 LEADTOOLS 번들(OCR 모듈, PDF 모듈 및 엔진 유형(LEAD, Tesseract 또는 OmniPage))을 기록해 두십시오.
코드 마이그레이션
- 모든 LEADTOOLS NuGet 패키지를 제거하십시오:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf IronOcrNuGet Install-Package- 모든 LEADTOOLS
using지시어를using IronOcr;로 교체하십시오 - 프로젝트 및 배포 아티팩트에서
.LIC및.LIC.KEY파일 제거 - 애플리케이션 시작 시
RasterSupport.SetLicense(licPath, keyContent)를IronOcr.License.LicenseKey = "key"로 교체하십시오 - 전적으로
IOcrEngine및RasterCodecs수명 관리를 위해 존재하는IDisposable서비스 클래스를 모두 삭제하십시오 OcrEngineManager.CreateEngine()+engine.Startup()를new IronTesseract()으로 교체하십시오_codecs.Load(imagePath)를using var input = new OcrInput()블록 내의input.LoadImage(imagePath)로 교체하십시오- 다중 프레임 TIFF 페이지 루프를
input.LoadImageFrames(tiffPath)로 교체하십시오 - PDF 페이지 반복 루프를
input.LoadPdf(pdfPath)로 교체하십시오 document.Pages.AddPage()+page.Recognize(null)+page.GetText(-1)를ocr.Read(input).Text로 교체하십시오OcrZone+page.Zones.Add()패턴을input.LoadImage(path, new CropRectangle(x, y, w, h))로 교체하십시오PdfDocumentOptions+DocumentWriterInstance.SetOptions()+document.Save()를result.SaveAsSearchablePdf(path)로 교체하십시오OcrInput인스턴스에서input.Deskew(),input.DeNoise(),input.Binarize()로 전처리 명령 클래스를 (DeskewCommand,DespeckleCommand,AutoBinarizeCommand) 교체하십시오- LEADTOOLS 메모리 관리를 보상하기 위해 추가된 모든
GC.Collect()/GC.WaitForPendingFinalizers()호출 제거
마이그레이션 이후
- 대표적인 이미지 및 PDF 샘플에서 인식된 텍스트 출력이 LEADTOOLS 출력과 일치하는지 확인합니다.
result.Confidence을 사용하여 신뢰도 점수가 예상 범위 내에 있는지 확인하십시오- 다중 프레임 TIFF 처리 테스트에서 LEADTOOLS 프레임 반복 루프와 동일한 페이지 수가 생성되는지 확인합니다.
- 검색 가능한 PDF 출력물이 PDF 리더(Adobe Acrobat 또는 이와 동등한 프로그램)에서 텍스트 검색이 가능한지 확인합니다.
- 생산 송장 또는 양식의 정상 필드 값과 비교하여 영역 기반 필드 추출을 테스트합니다.
- 별도의
Leadtools.Pdf모듈 없이 비밀번호로 보호된 PDF 복호화가 작동하는지 확인하십시오 - 모든
GC.Collect()를 먼저 제거한 후 부하 상태에서 배치 처리를 실행하고 메모리 성장이 없음을 확인하십시오 - 라이선스 파일 없이 Docker 및 CI/CD 배포를 테스트하고, 환경 변수에서 "라이선스 키" 문자열이 올바르게 해석되는지 확인합니다.
Parallel.ForEach와 함께 병렬 처리를 테스트하여 스레드 안전성을 확인하십시오- 구조화된 데이터 추출 (
result.Pages,page.Paragraphs,page.Words)이 올바른 좌표를 반환하는지 확인하십시오
IronOCR로 마이그레이션할 때의 주요 이점
배포는 상태 없는 것이 됩니다. LEADTOOLS .LIC 및 .LIC.KEY 파일은 모든 배포 환경에서 지니고 있어야 하는 아티팩트입니다. 라이선스 데이터를 이미지 기록에 노출하는 컨테이너는 라이선스 정보를 이미지에 포함시킵니다. 이를 장착하는 컨테이너는 부피 조정이 필요합니다.IronOCR로 마이그레이션한 후에는 라이선스가 환경 변수에 문자열로 저장됩니다. 배포 아티팩트는 표준 NuGet 참조입니다. 파일도 없고, 경로도 없고, 마운트 전략도 없습니다. Docker 배포 가이드 와 Azure 배포 가이드는 컨테이너화된 환경을 위한 전체 설정 과정을 보여줍니다.
네 개의 패키지가 하나로 됩니다. 마이그레이션은 Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, 옵션으로 Leadtools.Pdf를 단일 IronOcr 참조로 줄입니다. 전처리, 네이티브 PDF 입력, 검색 가능한 PDF 출력, 바코드 판독, 구조화된 데이터 추출, 125개 이상의 언어 지원 등 모든 기능이 하나의 패키지에 포함되어 있습니다. 기능 구성은 어떤 패키지를 구매했는지에 따라 달라지지 않습니다.
배치 처리는 수동 메모리 관리를 제거합니다. LEADTOOLS 배치 코드는 다중 문서 실행 중에 메모리 누적을 방지하기 위한 방어적인 GC.Collect() 호출 및 명시적인 RasterImage 폐기를 포함합니다. IronOCR의 OcrInput은 using 블록과 범위가 지정되어 자동으로 정리를 처리합니다. 스레드 안전한 병렬 처리는 Parallel.ForEach로 수행됩니다—스레드당 하나의 IronTesseract 인스턴스—싱크로나이제이션 코드 없이 멀티코어 쓰루풋을 제공합니다. 생산 처리량 조정을 위한 속도 최적화 가이드를 참조하십시오.
예측 가능한 총비용. LEADTOOLS는 5인 개발자 팀 기준으로 첫 해 예상 비용이 15,000달러에서 40,000달러이며, 업데이트를 받으려면 라이선스 비용의 약 20~25%에 해당하는 연간 유지 보수 비용이 필요합니다.IronOCR Professional 2,999달러이며, 10명의 개발자와 10개의 배포 위치를 지원하는 일회성 영구 구매 상품입니다. 1년간의 업데이트가 포함되어 있습니다. 업데이트 기간 이후 계속 사용하시는 경우 추가 비용은 발생하지 않습니다. IronOCR 라이선스 페이지에는 영업 상담 없이 모든 등급별 가격이 직접 게시되어 있습니다.
영역 설정 없는 구조화된 데이터입니다. 마이그레이션 후, 단어, 라인, 및 문단 수준의 데이터가 OcrResult에서 직접 경계 상자 좌표와 함께 사용 가능합니다—영역 정의가 필요하지 않습니다. 다섯 단계의 계층 구조 (Pages, Paragraphs, Lines, Words, Characters) 각각이 위치 좌표와 요소별 신뢰도 점수를 표시합니다. 구조화된 데이터 추출을 위해 LEADTOOLS 영역 구성이 필요했던 애플리케이션은 더 간단한 API를 통해 더욱 풍부한 데이터를 얻을 수 있습니다. OCR 결과 기능 페이지에는 전체 출력 모델이 요약되어 있습니다.
자주 묻는 질문
LEADTOOLS OCR에서 IronOCR로 마이그레이션해야 하는 이유는 무엇인가요?
일반적인 동인으로는 COM 상호 운용의 복잡성 제거, 파일 기반 라이선스 관리 대체, 페이지당 과금 방지, Docker/컨테이너 배포 활성화, 표준 .NET 툴링과 통합되는 NuGet 네이티브 워크플로 채택 등이 있습니다.
LEADTOOLS OCR에서 IronOCR로 마이그레이션할 때 주요 코드 변경 사항은 무엇인가요?
리드툴즈 OCR 초기화 시퀀스를 IronTesseract 인스턴스화로 대체하고, COM 수명 주기 관리(명시적 생성/로드/폐쇄 패턴)를 제거하며, 결과 속성 이름을 업데이트합니다. 그 결과 상용구 줄이 현저히 줄어듭니다.
마이그레이션을 시작하려면 IronOCR을 어떻게 설치하나요?
패키지 관리자 콘솔에서 '설치-패키지 IronOcr'을 실행하거나 CLI에서 '닷넷 추가 패키지 IronOcr'을 실행하세요. 언어 팩은 별도의 패키지입니다: 예를 들어 프랑스어의 경우 '닷넷 추가 패키지 IronOcr.Languages.French'를 실행합니다.
IronOCR은 표준 비즈니스 문서에 대한 LEADTOOLS OCR의 OCR 정확도와 일치하나요?
IronOCR은 송장, 계약서, 영수증, 타이핑된 양식 등 표준 비즈니스 콘텐츠에 대해 높은 정확도를 달성합니다. 이미지 전처리 필터(데스큐, 노이즈 제거, 대비 향상)는 품질이 저하된 입력에 대한 인식률을 더욱 향상시킵니다.
IronOCR은 LEADTOOLS OCR이 별도로 설치하는 언어 데이터를 어떻게 처리하나요?
IronOCR의 언어 데이터는 NuGet 패키지로 배포됩니다. '닷넷 추가 패키지 IronOcr.Languages.German'은 독일어 지원을 설치합니다. 수동 파일 배치나 디렉터리 경로는 필요하지 않습니다.
LEADTOOLS OCR에서 IronOCR로 마이그레이션하려면 배포 인프라를 변경해야 하나요?
IronOCR은 리드툴즈 OCR보다 인프라 변경이 덜 필요합니다. SDK 바이너리 경로, 라이선스 파일 배치 또는 라이선스 서버 구성이 필요하지 않습니다. NuGet 패키지에는 완전한 OCR 엔진이 포함되어 있으며 라이선스 키는 애플리케이션 코드에 설정된 문자열입니다.
마이그레이션 후 IronOCR 라이선싱은 어떻게 구성하나요?
애플리케이션 시작 코드에 IronOcr.License.LicenseKey = "YOUR-KEY"를 할당하세요. Docker 또는 Kubernetes에서 키를 환경 변수로 저장하고 시작 시 키를 읽습니다. 트래픽을 수락하기 전에 License.IsValidLicense를 사용하여 유효성을 검사하세요.
IronOCR이 리드툴즈 OCR과 동일한 방식으로 PDF를 처리할 수 있나요?
예. IronOCR은 원본 및 스캔한 PDF를 모두 읽습니다. IronTesseract를 인스턴스화하고, 입력이 PDF 경로 또는 OcrPdfInput인 경우 ocr.Read(input)를 호출하고, OcrResult 페이지를 반복하면 됩니다. 별도의 PDF 렌더링 파이프라인이 필요하지 않습니다.
IronOCR은 대량 처리에서 스레딩을 어떻게 처리하나요?
IronTesseract는 스레드별로 인스턴스화해도 안전합니다. Parallel.ForEach 또는 Task 풀에서 스레드당 하나의 인스턴스를 스핀업하고, OCR을 동시에 실행하고, 완료되면 각 인스턴스를 폐기하면 됩니다. 전역 상태나 잠금이 필요하지 않습니다.
IronOCR은 텍스트 추출 후 어떤 출력 형식을 지원하나요?
IronOCR은 텍스트, 단어 좌표, 신뢰도 점수, 페이지 구조를 포함한 구조화된 결과를 반환합니다. 내보내기 옵션에는 일반 텍스트, 검색 가능한 PDF, 다운스트림 처리를 위한 구조화된 결과 개체가 포함됩니다.
IronOCR 가격이 워크로드 확장을 위해 LEADTOOLS OCR보다 더 예측 가능한가요?
IronOCR은 페이지당 또는 볼륨당 요금이 없는 정액제 영구 라이선스를 사용합니다. 10,000페이지를 처리하든 1,000만 페이지를 처리하든 라이선스 비용은 일정하게 유지됩니다. 볼륨 및 팀 라이선스 옵션은 IronOCR 가격 페이지에서 확인할 수 있습니다.
LEADTOOLS OCR에서 IronOCR로 마이그레이션한 후 기존 테스트는 어떻게 되나요?
추출된 텍스트 콘텐츠에 대해 어설트하는 테스트는 마이그레이션 후에도 계속 통과해야 합니다. API 호출 패턴 또는 COM 개체 수명 주기의 유효성을 검사하는 테스트는 IronOCR의 더 간단한 초기화 및 결과 모델을 반영하도록 업데이트해야 합니다.

