影片

從 AWS Textract 遷移到 IronOCR

Kannaopat Udonpant
Kannapat Udonpant
Updated: 2026年6月20日

本指南為需要本地文件處理的.NET開發人員提供了從AWS Textract遷移到IronOCR的全過程,而無需依賴雲端。 它涵蓋了憑證拆解、異步管道刪除、S3消除以及將每個Textract操作轉移到其IronOCR等效項所需的具體程式碼替換。

為何從AWS Textract遷移

AWS Textract是一種功能強大的托管服務,但其架構帶來的成本——財務和操作——會隨著您的文件工作負載的增長而增加。 構建嚴肅文件處理管道的團隊最終會遇到所有這些摩擦點。

AWS憑證基礎設施複合化每次部署。運行Textract程式碼的每個環境都需要AWS憑證:IAM使用者或角色、存取密鑰和密碼、區域配置,以及 - 對於PDF處理 - 需要S3儲存桶權限。這在處理第一頁之前有五個不同的配置步驟。 生產部署需要憑證輪換策略、將其安全注入Docker容器或Kubernetes插件中,以及當IAM策略偏離導致靜默失敗時監控AccessDeniedException。 IronOCR要求一個字串:啟動時設置一次的授權鑰匙。

每頁價格無上限。AnalyzeDocument - 這是基礎費率的十倍。 表單每頁增加0.05美元。 每月處理100,000頁表格提取的混合文件工作流程每年需要花費18,000美元,該數字每年1月份重新計算。 IronOCR專業版授權費用為2,999美元一次性。 之後,無論數量多少,每頁成本均為零。

異步PDF管道是一個維護責任。通過Textract處理任何多頁PDF需要五個獨立階段:S3上傳、StartDocumentTextDetection、輪詢迴圈、分頁結果檢索和S3清理。每個階段都是一個獨立的故障模式,需要其自己的錯誤處理、重試策略和超時管理。 將此管道投入生產的團隊通常報告稱,花在維護上的時間比建設此管道的時間更多。 IronOCR用兩行程式碼讀取PDF。

無網路存取意味著無Textract。處於隔離網路片段中的Docker容器、內部部署的伺服器、氣隙研究環境以及對外流量有限制的工業系統都有一個共同特徵:Textract不可用。 IronOCR作為NuGet包安裝,並且在初始包下載後不需要任何網路調用即可操作。

每次調用皆會從您的基礎設施中留出資料。使用Textract的每次OCR操作都會將文件內容傳輸到亞馬遜的伺服器。 對於處理PHI的醫療組織、處理CUI的國防承包商、處理特權通信的法律團隊以及處理支付卡圖像的金融機構來說,這不是一個配置選項——而是一個完全禁止Textract在受管制環境中使用的架構限制。 IronOCR在您的硬體上處理。 沒有需要禁用的雲模式; 本地處理是唯一的模式。

速率限制限制批次吞吐量。預設StartDocumentTextDetection TPS限制為每秒5個請求。 處理數百個文件的批處理作業需要ProvisionedThroughputExceededException的指數回退和速率恢復計時器。 請求TPS增加需要正式的AWS支持案例,且無保證結果。 IronOCR的處理速度僅由本地CPU允許的速度決定——一台16核心的伺服器可同時處理16個文件,不需要服務級別談判。

根本問題

每次Textract部署開始之前,必須先經過這一儀式——才能開始處理單頁:

// Textract: five environment variables, an IAM role, and an S3 bucket
// — all required before the first OCR call

// Environment must have:
//   AWS_ACCESS_KEY_ID=AKIA...
//   AWS_SECRET_ACCESS_KEY=...
//   AWS_DEFAULT_REGION=us-east-1
//   IAM role: textract:DetectDocumentText, textract:AnalyzeDocument
//   IAM role: s3:PutObject, s3:DeleteObject (for any PDF processing)
//   S3 bucket: my-textract-staging-bucket (with lifecycle policy to prevent cost accumulation)

public class TextractOcrService
{
    private readonly AmazonTextractClient _textractClient;
    private readonly AmazonS3Client _s3Client; // required for PDFs

    public TextractOcrService()
    {
        // Fails with AmazonClientException if credentials not found in chain
        _textractClient = new AmazonTextractClient(Amazon.RegionEndpoint.USEast1);
        _s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
    }
}

IronOCR用單個分配替換了整個憑證鏈:

// IronOCR: one line, one string, done
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Or from environment (no IAM, no rotation, no S3)
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");

IronOCR對比AWS Textract:功能比較

下表列出了在遷移決策中最相關的維度上比較兩個程式庫的能力。

功能AWS TextractIronOCR
處理位置Amazon雲(強制性)本地/僅限內部部署
需要互聯網總是需要絕不需要
憑證設置IAM使用者/角色 + 可選的S3儲存桶單一授權密鑰字串
多頁PDF需要S3階段 + 異步工作直接同步input.LoadPdf()
密碼保護的PDF不支持input.LoadPdf(path, Password: "x")
多框架TIFF不支持input.LoadImageFrames("multi.tiff")
需要異步輪詢是的(適用於所有PDF)否——預設為同步
速率限制預設5 TPS(StartDocumentTextDetection)無——僅CPU綁定
** 每頁成本**每頁 $0.0015–$0.065$0在購買授權後
自動預處理內部,無法配置Deskew, DeNoise, Contrast, Binarize, Sharpen, Scale
可搜尋的 PDF 輸出不可用result.SaveAsSearchablePdf()
hOCR輸出不可用result.SaveAsHocrFile()
條形碼讀取不可用ocr.Configuration.ReadBarCodes = true
基於區域的OCR通過AnalyzeDocument + 块關係CropRectangle(x, y, width, height)
結構化結果平整BlockType過濾打字Words
支持的語言以英語為主(可選其他)通過NuGet獲得125+語言包
多語言同步不支持OcrLanguage.French + OcrLanguage.German
氣隙部署無法實現全面支持
不與BAA的HIPAA無法實現無外部處理器——僅限內部部署
Docker部署需要注入的AWS憑證無憑證——純粹的NuGet包
跨平台AWS管理(僅限Linux)Windows,Linux,macOS,Docker,Azure,AWS EC2
授權模式每頁按次計費(永久消耗)永久一次性($999 / $1,499 / $2,999)

快速入門:AWS Textract到IronOCR遷移

步驟1:替換NuGet包

移除AWS SDK包:

dotnet remove package AWSSDK.Textract
dotnet remove package AWSSDK.S3
dotnet remove package AWSSDK.Core
SHELL

從NuGet安裝IronOCR:

> dotnet add package IronOcr

步驟2:更新命名空間

用單個IronOCR名稱空間替換所有AWS名稱空間導入:

// Before (AWS Textract)
using Amazon.Textract;
using Amazon.Textract.Model;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Runtime;

// After (IronOCR)
using IronOcr;

步驟3:初始化許可證

在應用程式啟動時新增授權初始化。移除所有AWS憑證環境變數設置:

// Remove from startup:
//   AWS_ACCESS_KEY_ID environment variable
//   AWS_SECRET_ACCESS_KEY environment variable
//   AWS_DEFAULT_REGION environment variable
//   ~/.aws/credentials file entries
//   IAM role bindings on the execution context

// Add instead:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

程式碼遷移範例

替換AmazonTextractClient構造函式和IAM設置

最明顯的遷移更改是客戶端初始化。 Textract需要依賴注入的客戶端,具有底層憑證解析——意味著客戶端及其所有依賴項必須在每個部署環境中預先配置。 任何配置錯誤直到第一次OCR調用拋出AmazonClientException之前都是靜默失敗。

AWS Textract方法:

// Requires: NuGet AWSSDK.Textract, AWSSDK.S3
// Requires: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION in environment
// Requires: IAM policy with textract:* and s3:PutObject, s3:DeleteObject

using Amazon.S3;
using Amazon.Textract;

public class DocumentOcrService
{
    private readonly AmazonTextractClient _textractClient;
    private readonly AmazonS3Client _s3Client;
    private readonly string _stagingBucket;

    public DocumentOcrService(IConfiguration config)
    {
        // Credential chain: environment → ~/.aws/credentials → EC2 instance profile
        // Fails if none found — runtime exception, not compile-time
        _textractClient = new AmazonTextractClient(Amazon.RegionEndpoint.USEast1);
        _s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
        _stagingBucket = config["Textract:StagingBucket"]; // bucket must exist and have permissions
    }

    public async Task<string> ReadImageAsync(string imagePath)
    {
        var bytes = File.ReadAllBytes(imagePath);
        var response = await _textractClient.DetectDocumentTextAsync(
            new DetectDocumentTextRequest
            {
                Document = new Document { Bytes = new MemoryStream(bytes) }
            });

        return string.Join("\n", response.Blocks
            .Where(b => b.BlockType == BlockType.LINE)
            .Select(b => b.Text));
    }
}

IronOCR方法:

// Requires: NuGet IronOcr
// Requires: one license key string — nothing else

using IronOcr;

public class DocumentOcrService
{
    private readonly IronTesseract _ocr;

    public DocumentOcrService()
    {
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; // or set at Program.cs startup
        _ocr = new IronTesseract();
    }

    public string ReadImage(string imagePath)
    {
        // No credential chain, no region, no bucket — just read
        return _ocr.Read(imagePath).Text;
    }
}

移除了整個AWS憑證基礎設施——IAM角色、環境變數、~/.aws/credentials文件、S3儲存桶生命周期策略和區域配置。 DocumentOcrService構造函式從3個依賴的注入點(具有運行時故障模式)變為單個授權分配。 有關部署模式的詳細資訊,請參見 IronTesseract設置指南。

用TIFF多框架處理替換StartDocumentTextDetection

Textract的異步工作API(StartDocumentTextDetection)是任何多頁文件所必需的。 文件數字化管道中的常見模式是將掃描文件作為多框架的TIFF接收——每掃描頁一幀。 Textract不能直接接受TIFF輸入; 文件必須轉換為PDF並上傳到S3才能開始工作。 IronOCR本地讀取多框架TIFF。

AWS Textract方法:

// Textract cannot accept TIFF directly — requires PDF conversion + S3 + async job
// This shows the conversion overhead plus the minimum async pipeline

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<string> ProcessScannedTiffAsync(string tiffPath, string s3Bucket)
{
    // Step 0: Convert TIFF to PDF first (Textract does not accept TIFF for async jobs)
    // Requires a separate PDF conversion library — not shown here
    var pdfPath = Path.ChangeExtension(tiffPath, ".pdf");
    ConvertTiffToPdf(tiffPath, pdfPath); // external dependency required

    // Step 1: Upload converted PDF to S3
    var s3Key = $"staging/{Guid.NewGuid()}.pdf";
    using (var stream = File.OpenRead(pdfPath))
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = s3Bucket,
            Key = s3Key,
            InputStream = stream
        });
    }

    try
    {
        // Step 2: Start async detection job
        var startResp = await _textractClient.StartDocumentTextDetectionAsync(
            new StartDocumentTextDetectionRequest
            {
                DocumentLocation = new DocumentLocation
                {
                    S3Object = new S3Object { Bucket = s3Bucket, Name = s3Key }
                }
            });

        // Step 3: Poll every 5 seconds — can block for 30–120 seconds on large files
        GetDocumentTextDetectionResponse pollResp;
        do
        {
            await Task.Delay(5000);
            pollResp = await _textractClient.GetDocumentTextDetectionAsync(
                new GetDocumentTextDetectionRequest { JobId = startResp.JobId });
        } while (pollResp.JobStatus == JobStatus.IN_PROGRESS);

        if (pollResp.JobStatus != JobStatus.SUCCEEDED)
            throw new Exception($"Job failed: {pollResp.StatusMessage}");

        // Step 4: Collect paginated results
        var text = new StringBuilder();
        string token = null;
        do
        {
            var page = await _textractClient.GetDocumentTextDetectionAsync(
                new GetDocumentTextDetectionRequest
                {
                    JobId = startResp.JobId,
                    NextToken = token
                });
            foreach (var block in page.Blocks.Where(b => b.BlockType == BlockType.LINE))
                text.AppendLine(block.Text);
            token = page.NextToken;
        } while (token != null);

        return text.ToString();
    }
    finally
    {
        // Step 5: Clean up S3 — orphaned objects accumulate storage costs
        await _s3Client.DeleteObjectAsync(s3Bucket, s3Key);
        File.Delete(pdfPath); // clean up intermediate PDF
    }
}

IronOCR方法:

//IronOCRreads multi-frame TIFF directly — no conversion, no S3, no polling

using IronOcr;

public string ProcessScannedTiff(string tiffPath)
{
    using var input = new OcrInput();
    input.LoadImageFrames(tiffPath); // reads all frames natively

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

    // Access per-page results if needed
    foreach (var page in result.Pages)
        Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words detected");

    return result.Text;
}

TIFF到PDF的轉換步驟、S3上傳、異步輪詢迴圈、分頁結果累積和S3清理都被消除。 IronOCR實現直接讀取幀,並通過result.Pages提供每頁存取,而無需任何中間格式轉換。 TIFF和GIF輸入指南涵蓋需要僅選擇某些幀時的幀選擇。

用可搜尋的PDF輸出替換S3階段

Textract的一個常見用例是將掃描的PDF檔案轉換為可搜尋的形式。 Textract的方法需要將每個PDF上傳到S3,運行異步工作,收集提取的文字,然後使用單獨的PDF庫將該文字嵌入為可搜尋層。 IronOCR僅需一次調用即可執行掃描並生成可搜尋的PDF。

AWS Textract方法:

// Textract extracts text but cannot produce searchable PDFs
// Requires: S3 upload + async job + separate PDF library to embed the text layer

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<string> ExtractTextForSearchableLayerAsync(
    string scannedPdfPath,
    string s3Bucket)
{
    // Must upload to S3 — cannot pass PDF bytes directly for async jobs
    var key = $"ocr-input/{Guid.NewGuid()}.pdf";

    await using (var fs = File.OpenRead(scannedPdfPath))
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = s3Bucket,
            Key = key,
            InputStream = fs,
            ContentType = "application/pdf"
        });
    }

    string jobId;
    try
    {
        var startResp = await _textractClient.StartDocumentTextDetectionAsync(
            new StartDocumentTextDetectionRequest
            {
                DocumentLocation = new DocumentLocation
                {
                    S3Object = new S3Object { Bucket = s3Bucket, Name = key }
                }
            });
        jobId = startResp.JobId;
    }
    catch
    {
        await _s3Client.DeleteObjectAsync(s3Bucket, key);
        throw;
    }

    // Poll — minimum 5s wait; typical 15–60s for a 10-page PDF
    GetDocumentTextDetectionResponse pollResp;
    int pollAttempts = 0;
    const int maxAttempts = 120; // 10 minute timeout

    do
    {
        await Task.Delay(5000);
        pollResp = await _textractClient.GetDocumentTextDetectionAsync(
            new GetDocumentTextDetectionRequest { JobId = jobId });

        if (++pollAttempts >= maxAttempts)
            throw new TimeoutException("Textract job timed out after 10 minutes");

    } while (pollResp.JobStatus == JobStatus.IN_PROGRESS);

    await _s3Client.DeleteObjectAsync(s3Bucket, key); // cleanup regardless of outcome

    if (pollResp.JobStatus != JobStatus.SUCCEEDED)
        throw new Exception($"Textract job status: {pollResp.JobStatus} — {pollResp.StatusMessage}");

    // Return extracted text — caller must use a separate library to produce searchable PDF
    return string.Join("\n", pollResp.Blocks
        .Where(b => b.BlockType == BlockType.LINE)
        .Select(b => b.Text));

    // Caller still needs: iTextSharp, PdfSharp, or similar to embed text layer
}

IronOCR方法:

//IronOCRreads the PDF and produces a searchable PDF output directly
// No S3, no polling, no external PDF library needed

using IronOcr;

public void ConvertToSearchablePdf(string scannedPdfPath, string outputPath)
{
    var ocr = new IronTesseract();

    using var input = new OcrInput();
    input.LoadPdf(scannedPdfPath);

    var result = ocr.Read(input);

    // Embed extracted text as searchable layer in one call
    result.SaveAsSearchablePdf(outputPath);

    Console.WriteLine($"Pages processed: {result.Pages.Length}");
    Console.WriteLine($"Overall confidence: {result.Confidence:F1}%");
}

輪詢超時邏輯、S3上傳和清理模式以及外部PDF庫依賴都消失了。 SaveAsSearchablePdf直接將識別的文字嵌入輸出文件中,無需第二個庫,每個掃描頁都能進行全文檢索。 可搜尋PDF指南涵蓋頁面選擇、壓縮選項和元資料嵌入。 要了解PDF OCR管道的更廣泛背景,請參閱PDF輸入指南。

用結構化頁面結果替換AnalyzeDocument塊圖遍歷

Textract的RelationshipType.CHILD ID陣列連接。 重構文件的邏輯結構需要遍歷這個關係圖。 IronOCR返回一個有型別的層次結構:頁、段落、行、詞,每一個都有座標存取。

AWS Textract方法:

// AnalyzeDocument with TABLES returns blocks that must be graph-traversed
// to determine which words belong to which paragraphs

using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<List<DocumentSection>> ExtractDocumentStructureAsync(string imagePath)
{
    var bytes = File.ReadAllBytes(imagePath);

    var response = await _textractClient.AnalyzeDocumentAsync(
        new AnalyzeDocumentRequest
        {
            Document = new Document { Bytes = new MemoryStream(bytes) },
            FeatureTypes = new List<string> { "TABLES", "FORMS" }
            // Note: AnalyzeDocument (TABLES) costs $0.015/page — 10x DetectDocumentText
        });

    var sections = new List<DocumentSection>();

    // Filter LINE blocks for paragraph reconstruction
    // LINE blocks are not grouped into paragraphs — must infer from Y proximity
    var lineBlocks = response.Blocks
        .Where(b => b.BlockType == BlockType.LINE)
        .OrderBy(b => b.Geometry?.BoundingBox?.Top ?? 0)
        .ToList();

    // Group lines into pseudo-paragraphs by vertical gap heuristic
    var currentParagraph = new List<Block>();
    float? lastBottom = null;
    const float paragraphGapThreshold = 0.02f; // 2% of page height

    foreach (var line in lineBlocks)
    {
        var top = line.Geometry?.BoundingBox?.Top ?? 0;
        var height = line.Geometry?.BoundingBox?.Height ?? 0;

        if (lastBottom.HasValue && (top - lastBottom.Value) > paragraphGapThreshold)
        {
            if (currentParagraph.Any())
            {
                sections.Add(new DocumentSection
                {
                    Text = string.Join(" ", currentParagraph.Select(b => b.Text)),
                    LineCount = currentParagraph.Count,
                    // Confidence: average of all LINE block confidence values
                    Confidence = (float)currentParagraph.Average(b => b.Confidence ?? 0)
                });
                currentParagraph.Clear();
            }
        }

        currentParagraph.Add(line);
        lastBottom = top + height;
    }

    if (currentParagraph.Any())
        sections.Add(new DocumentSection
        {
            Text = string.Join(" ", currentParagraph.Select(b => b.Text)),
            LineCount = currentParagraph.Count,
            Confidence = (float)currentParagraph.Average(b => b.Confidence ?? 0)
        });

    return sections;
}

public class DocumentSection
{
    public string Text { get; set; }
    public int LineCount { get; set; }
    public float Confidence { get; set; }
}

IronOCR方法:

//IronOCRreturns a typed hierarchy — paragraphs are first-class objects
// No block graph, no heuristic gap detection, no confidence averaging

using IronOcr;

public List<DocumentSection> ExtractDocumentStructure(string imagePath)
{
    var ocr = new IronTesseract();
    var result = ocr.Read(imagePath);

    var sections = new List<DocumentSection>();

    foreach (var page in result.Pages)
    {
        foreach (var paragraph in page.Paragraphs)
        {
            sections.Add(new DocumentSection
            {
                Text = paragraph.Text,
                LineCount = paragraph.Lines.Length,
                // Coordinate access: exact pixel position on the page
                LocationX = paragraph.X,
                LocationY = paragraph.Y,
                Width = paragraph.Width,
                Height = paragraph.Height,
                Confidence = paragraph.Confidence
            });
        }
    }

    return sections;
}

public class DocumentSection
{
    public string Text { get; set; }
    public int LineCount { get; set; }
    public int LocationX { get; set; }
    public int LocationY { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public double Confidence { get; set; }
}

段落、行和單詞作為.Confidence屬性的標記集合直接存取。 無BoundingBox歸一化,無間隙閾值啟發式方法,無塊型別過濾。 閱讀結果指南介紹了完整的OcrResult層次結構,包括字元級座標存取。 對於依賴於這一結構的發票和收據工作流程,請參閱發票OCR教程。

用無約束的並行執行替換速率限制的批處理

批量圖像處理是Textract的TPS限制產生的最明顯的運營成本。DetectDocumentText同步API受制於速率限制; 每秒發送超過5個請求會產生ProvisionedThroughputExceededException。 正確的批次處理需要SemaphoreSlim的限流、回退重試邏輯及對飛行中請求的仔細核算。 IronOCR沒有速率限制——吞吐量僅由可用CPU核心數量限制。

AWS Textract方法:

// Batch processing requires SemaphoreSlim throttle + retry on rate limit exceeded

using Amazon.Textract;
using Amazon.Textract.Model;
using System.Collections.Concurrent;

public async Task<Dictionary<string, string>> ProcessImageBatchAsync(
    IReadOnlyList<string> imagePaths,
    IProgress<(int Completed, int Total)> progress = null)
{
    var results = new ConcurrentDictionary<string, string>();
    var semaphore = new SemaphoreSlim(5); // 5 concurrent — Textract default TPS limit
    var tasks = new List<Task>();
    int completed = 0;

    foreach (var path in imagePaths)
    {
        await semaphore.WaitAsync();

        tasks.Add(Task.Run(async () =>
        {
            try
            {
                string text = null;
                int retryCount = 0;

                while (text == null && retryCount < 3)
                {
                    try
                    {
                        var bytes = await File.ReadAllBytesAsync(path);
                        var response = await _textractClient.DetectDocumentTextAsync(
                            new DetectDocumentTextRequest
                            {
                                Document = new Document { Bytes = new MemoryStream(bytes) }
                            });

                        text = string.Join("\n", response.Blocks
                            .Where(b => b.BlockType == BlockType.LINE)
                            .Select(b => b.Text));
                    }
                    catch (AmazonTextractException ex)
                        when (ex.ErrorCode == "ProvisionedThroughputExceededException")
                    {
                        // Exponential backoff on rate limit — blocks the entire thread
                        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
                        retryCount++;
                    }
                }

                results[path] = text ?? string.Empty;
                var done = Interlocked.Increment(ref completed);
                progress?.Report((done, imagePaths.Count));
            }
            finally
            {
                semaphore.Release();
            }
        }));
    }

    await Task.WhenAll(tasks);
    return results.ToDictionary(x => x.Key, x => x.Value);
}

IronOCR方法:

// No rate limits, no semaphore, no retry logic — Parallel.ForEach saturates all cores

using IronOcr;
using System.Collections.Concurrent;

public Dictionary<string, string> ProcessImageBatch(
    IReadOnlyList<string> imagePaths,
    IProgress<(int Completed, int Total)> progress = null)
{
    var ocr = new IronTesseract();
    var results = new ConcurrentDictionary<string, string>();
    int completed = 0;

    Parallel.ForEach(imagePaths, path =>
    {
        var result = ocr.Read(path);
        results[path] = result.Text;

        var done = Interlocked.Increment(ref completed);
        progress?.Report((done, imagePaths.Count));
    });

    return results.ToDictionary(x => x.Key, x => x.Value);
}

ProvisionedThroughputExceededException捕獲塊都被移除。 IronTesseract是執行緒安全的,單個實例共享跨執行緒處理整個並行負載,無需鎖定。 在一臺8核機器上,這可以同時處理8個文件,無需配置; 在32核上,同時處理32個。 多執行緒範例展示了完整的模式,速度優化指南涵蓋以吞吐量為重點的部署的引擎配置調優。

用基於區域的CropRectangle OCR替換AnalyzeDocument表單提取

Textract的KEY_VALUE_SET塊。 重構表單字段鍵值對需要按WORD塊以組合文字。 對於字段位置已知的固定格式表單,IronOCR的CropRectangle直接提取每個字段,無需遍歷關係圖——並且成本為每頁零。

AWS Textract方法:

// FORMS mode costs $0.05/page — the most expensive Textract tier
// Relationship graph traversal required to reconstruct key-value pairs

using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<Dictionary<string, string>> ExtractInvoiceFieldsAsync(string imagePath)
{
    var bytes = File.ReadAllBytes(imagePath);

    // $0.05/page for FORMS analysis
    var response = await _textractClient.AnalyzeDocumentAsync(
        new AnalyzeDocumentRequest
        {
            Document = new Document { Bytes = new MemoryStream(bytes) },
            FeatureTypes = new List<string> { "FORMS" }
        });

    var allBlocks = response.Blocks;
    var fields = new Dictionary<string, string>();

    // Find KEY blocks only
    var keyBlocks = allBlocks.Where(b =>
        b.BlockType == BlockType.KEY_VALUE_SET &&
        b.EntityTypes?.Contains("KEY") == true);

    foreach (var keyBlock in keyBlocks)
    {
        // Assemble key text from CHILD WORD blocks
        var keyText = GetTextFromBlock(allBlocks, keyBlock);

        // Find associated VALUE block via VALUE relationship
        var valueBlockId = keyBlock.Relationships?
            .FirstOrDefault(r => r.Type == RelationshipType.VALUE)?
            .Ids.FirstOrDefault();

        if (valueBlockId == null) continue;

        var valueBlock = allBlocks.FirstOrDefault(b => b.Id == valueBlockId);
        if (valueBlock == null) continue;

        var valueText = GetTextFromBlock(allBlocks, valueBlock);
        fields[keyText.TrimEnd(':')] = valueText;
    }

    return fields;
}

private string GetTextFromBlock(IList<Block> allBlocks, Block block)
{
    if (block.Relationships == null) return string.Empty;

    var childWordIds = block.Relationships
        .Where(r => r.Type == RelationshipType.CHILD)
        .SelectMany(r => r.Ids);

    return string.Join(" ", allBlocks
        .Where(b => b.BlockType == BlockType.WORD && childWordIds.Contains(b.Id))
        .Select(b => b.Text));
}

IronOCR方法:

// CropRectangle extracts each field zone directly — no relationship graph
// Works for any fixed-format form where field positions are known

using IronOcr;

// Define the field zones for your form template once
private static readonly Dictionary<string, CropRectangle> InvoiceFieldZones =
    new Dictionary<string, CropRectangle>
    {
        { "InvoiceNumber", new CropRectangle(450, 80,  300, 35) },
        { "InvoiceDate",   new CropRectangle(450, 120, 300, 35) },
        { "VendorName",    new CropRectangle(50,  160, 400, 35) },
        { "TotalAmount",   new CropRectangle(450, 680, 200, 35) },
        { "DueDate",       new CropRectangle(450, 720, 200, 35) }
    };

public Dictionary<string, string> ExtractInvoiceFields(string imagePath)
{
    var ocr = new IronTesseract();
    var fields = new Dictionary<string, string>();

    foreach (var zone in InvoiceFieldZones)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath, zone.Value); // load only the defined region

        var result = ocr.Read(input);
        fields[zone.Key] = result.Text.Trim();
    }

    return fields;
}

GetTextFromBlock助手都被消除。 每個字段精確地讀取包含它的像素區域——不多不少。 基於區域的OCR指南涵蓋CropRectangle座標以及如何從模板測量定義區域。 對於特定於發票的工作流程,發票OCR部落格文章和收據掃描教程展示了完整的字段提取管道。

##AWS TextractAPI到IronOCR映射參考

AWS TextractIronOCR 等效
AmazonTextractClientIronTesseract
AmazonS3Client不需要
DetectDocumentTextRequestinput.LoadImage()
DetectDocumentTextResponseOcrResult
AnalyzeDocumentRequest(表格/表單)CropRectangle
StartDocumentTextDetectionRequestinput.LoadPdf()——同步,無需啟動工作
GetDocumentTextDetectionRequest不適用——結果是即時的
Document.Bytesinput.LoadImage(stream)
S3Object(文件階段)文件路徑或流——無需階段
BlockType.LINE)result.Lines(型別化集合)
BlockType.WORD)result.Words(型別化集合)
BlockType.TABLE)result.Words按坐標接近性分組
BlockType.KEY_VALUE_SET)CropRectangle領域提取每個字段
Block.Confidenceword.Confidence / result.Confidence
Block.Geometry.BoundingBoxword.X, word.Y, word.Width, word.Height
RelationshipType.CHILD遍歷直接屬性存取型別化的結果物件
JobStatus.IN_PROGRESS不適用 - 無異步狀態
JobStatus.SUCCEEDED不適用 - 同步返回
response.NextToken(分頁)不適用 - 結果不分頁
ProvisionedThroughputExceededException不適用 - 無TPS限制
AccessDeniedException不適用——無憑證鏈
input.LoadImageFrames()多框架TIFF直接支持(無Textract等效品)
result.SaveAsSearchablePdf()可搜尋的PDF輸出(無Textract等效品)

常見的遷移問題与解決方案

問題1:新部署環境中憑證配置錯誤

AWS Textract: ~/.aws/credentials、EC2實例配置文件、ECS任務角色。 如果在新的Docker容器或CI環境中,這些都沒有配置,客戶端會在沒有錯誤的情況下構造,但每次API調用都會拋出AmazonClientException: No credentials specified。 失敗在運行時之前是不可見的。

解決方案:完全移除憑證鏈。 從更易於配置且不帶有IAM權限管理的環境變數設置IronOCR授權鑰匙:

// Single environment variable — no IAM, no rotation, no chain resolution
var key = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
if (string.IsNullOrEmpty(key))
    throw new InvalidOperationException("IRONOCR_LICENSE environment variable is not set");

IronOcr.License.LicenseKey = key;

問題2:整個程式碼庫中的異步調用站點

AWS Textract:由於整個SDK僅支持異步(async Task<t>。 遷移到IronOCR的同步API需要在每個站點做出決定。

解決方案:對於背景處理服務和控制器操作,IronOCR的同步調用在背景執行緒上是安全的。 僅在現有調用鏈必須保持異步時包裹在Task.Run中:

// If the call site must remain async (e.g., an ASP.NET controller action):
public async Task<IActionResult> ProcessUpload(IFormFile file)
{
    using var ms = new MemoryStream();
    await file.CopyToAsync(ms);

    // Offload synchronous OCR to thread pool — keeps controller non-blocking
    var text = await Task.Run(() =>
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(ms.ToArray());
        return ocr.Read(input).Text;
    });

    return Ok(text);
}

對於已在非UI執行緒上運行的批處理和背景服務,可同步調用Task.Run無益,卻增加了負擔。 異步OCR指南涵蓋推薦的模式。

問題3:分散在程式碼中的S3儲存桶清理邏輯

AWS Textract:任何為Textract處理上傳到S3的程式碼在作業完成後也必須從S3中刪除。 該清理經常存在於finally塊中,有時在錯誤路徑上被省略,導致孤立的物件積累儲存成本。 在遷移過程中,S3清理程式碼很容易被忽視,因為它與OCR在概念上不相關。

解決方案:整個S3上傳和清理模式在IronOCR中沒有等效品。 完全刪除所有try/finally塊。 IronOCR直接從本地路徑或流中讀取:

// Before: upload → job → poll → extract → cleanup (50+ lines)
// After: two lines, no cleanup required

using var input = new OcrInput();
input.LoadPdf(localPdfPath);
var text = new IronTesseract().Read(input).Text;

完成遷移後,停用S3階段儲存桶。 PDF輸入指南和流輸入指南涵蓋了替代S3分階文件存取的每一個輸入模式。

問題4:塊圖遍歷程式碼無直接等效品

AWS Textract:遍歷Block.Relationships以重構表格單元、表單字段或段落分組的程式碼是遷移時間最耗的程式碼。 沒有一對一的替代品,因為IronOCR返回型別化集合,而不是關係圖。

** 解決方案:**每個遍歷模式替換為合適的型別化屬性。 關係ID查找變為直接的屬性存取:

// Before: filter by BlockType + traverse relationship IDs
var paragraphText = string.Join(" ", allBlocks
    .Where(b => b.BlockType == BlockType.WORD &&
                childIds.Contains(b.Id))
    .Select(b => b.Text));

// After: direct paragraph text (no filtering, no relationship lookup)
foreach (var page in result.Pages)
    foreach (var para in page.Paragraphs)
        Console.WriteLine(para.Text); // already assembled

對於依賴於result.Words上的詞座標分組。 表格讀取指南涵蓋了基於坐標的方法。

問題5:ProvisionedThroughputExceededException捕獲塊

AWS Textract:穩健的批處理程式碼捕獲ErrorCode == "ProvisionedThroughputExceededException"實現回退重試。 此錯誤程式碼是Textract特有的概念,沒有IronOCR的等效品。

解決方案:刪除所有ProvisionedThroughputExceededException捕獲塊。 IronOCR無TPS限制。 用Parallel.ForEach替換整個限流批處理模式:

// Delete: SemaphoreSlim, retry loops, ProvisionedThroughputExceededException handlers
// Replace with:
Parallel.ForEach(imagePaths, path =>
{
    var result = new IronTesseract().Read(path);
    results[path] = result.Text;
});

問題6:硬編碼於客戶端構造中的區域配置

AWS Textract:new AmazonTextractClient(Amazon.RegionEndpoint.USEast1)硬編碼區域。 多區域部署需要多個客戶端實例或動態區域選擇。 當Textract在新區域中增加支持時,必須更新程式碼。

解決方案:IronOCR無區域概念。 移除所有Amazon.RegionEndpoint引用。 IronTesseract構造不需要網路配置——處理是本地的。 對於AWS基礎架構上的多區域部署,其中每個區域運行其自己的計算,每個IronOCR實例在該計算邊界內本地處理文件,無跨區域調用。

AWS Textract遷移清單

遷移前

審核程式碼庫中所有Textract和S3 SDK的使用情況:

grep -rn "Amazon.Textract\|AmazonTextractClient\|DetectDocumentText" --include="*.cs" .
grep -rn "StartDocumentTextDetection\|GetDocumentTextDetection\|JobStatus" --include="*.cs" .
grep -rn "AmazonS3Client\|PutObjectRequest\|DeleteObjectAsync" --include="*.cs" .
grep -rn "BlockType\|RelationshipType\|KEY_VALUE_SET" --include="*.cs" .
grep -rn "ProvisionedThroughputExceededException\|AmazonTextractException" --include="*.cs" .
grep -rn "AWSSDK\|Amazon.RegionEndpoint" --include="*.csproj" .
SHELL

在編寫任何替代程式碼之前清點以下內容:

  • 計算所有包含AmazonTextractClient的文件數量——每個都是替換目標
  • 列出所有FORMS或兩者
  • 確認所有異步輪詢迴圈(do { await Task.Delay } while JobStatus == IN_PROGRESS)
  • 找到所有S3清理finally塊——這些是批量刪除的,不需替換
  • 計算所有ProvisionedThroughputExceededException捕獲塊數量——被刪除而不需替換
  • 記錄所有BlockType.KEY_VALUE_SET遍歷邏輯——每個需要以結構化輸出的對應物替換

程式碼遷移

1.從所有AWSSDK.Core 2.在每個執行OCR的專案中運行dotnet add package IronOcr 3.在應用程式啟動時新增Program.cs或類似物) 4.移除所有using Amazon.Runtime;語句 5.向以前導入了Textract名稱空間的每個文件新增using IronOcr; 6.用AmazonTextractClient構造調用 7.用直接文件路徑或流讀取替換PutObjectAsync / DeleteObjectAsync調用 8.替換所有var text = ocr.Read(path).Text 9.用StartDocumentTextDetectionAsync和輪詢迴圈 10.用input.LoadImageFrames(tiffPath)替換所有多框架TIFF管道(TIFF → PDF → S3 → 異步) 11.用result.Lines / BlockType.LINE / BlockType.WORD過濾鏈 12.用BlockType.KEY_VALUE_SET關係遍歷 13.刪除所有SemaphoreSlim限流 14.使用共享的Parallel.ForEach 15.移除部署清單和CI管道中的所有AWS憑證環境變數配置 16.在所有處理程式碼遷移並驗證後退役S3分階儲存桶

遷移後

  • 驗證應用程式僅在設置IRONOCR_LICENSE後能夠清潔啟動,且移除了所有AWS環境變數
  • 在之前由Textract處理的相同範例圖像上運行OCR並比較提取的文字以確定準確性
  • 直接處理多頁PDF並驗證所有頁被提取,無需S3或異步輪詢
  • 處理多框架TIFF並驗證頁數與相同文件的Textract結果匹配
  • 用50+圖像集測試批次處理,並確認無遭遇ProvisionedThroughputExceededException等效
  • 在無出站互聯網存取的Docker容器中運行應用程式並確認OCR順利完成
  • 驗證可搜尋的PDF輸出能夠在Adobe Reader中正確打開並能全文搜尋
  • 確認從部署環境中移除AWS_SECRET_ACCESS_KEY不破壞任何東西
  • 將任何表單或發票提取工作流程與Textract的已知輸出進行測試,驗證字段準確性
  • 測量代表性文件集的處理延遲,確認最低5秒輪詢等待已消除

遷移至IronOCR的主要好處

基礎設施整合為單一NuGet包。三個AWS SDK包、一個具有生命周期策略的S3儲存桶、每個部署環境中的IAM角色,以及AWS憑證輪換程式被一個NuGet包所取代。 變更dotnet add package IronOcr。 AWS憑證管理的所有下游影響 - 安全審計、輪換計畫、環境變數的衛生、Docker秘密配置 - 隨之消失。

可預測的總擁有成本。按頁計費模型產生可變的成本,且隨著文件量無限制地增長。 遷移到IronOCR後,無論量度大小,處理每頁的額外成本都為零。 以Textract的AnalyzeDocument率處理每月200,000頁的團隊僅在OCR上每年花費$36,000。 $2,999的IronOCR Enterprise授權在不到31天的避免費用中就能收回成本。 有關完整層級詳細資訊和SaaS再分發條款,請參閱IronOCR授權頁面。

同步處理消除了五階段異步複雜性。S3上傳、工作啟動、輪詢迴圈、分頁結果收集和清理最終塊在Textract PDF管道中代表了五個獨立的故障模式。遷移後,PDF以兩行程式碼讀取。 錯誤處理簡化為ocr.Read()調用周圍的單個try/catch。 輪詢超時邏輯,nextToken分頁累加器——這些概念在IronOCR程式碼庫中不存在。 隨著程式碼的刪除,維護工作負擔成比例地下降。

完整的部署靈活性。Textract要求每次OCR調用均需要存取互聯網。 IronOCR僅在安裝時下載NuGet包需要存取互聯網。之後,它可以在氣隙網路、無出站規則的Docker容器、內部部署的伺服器以及無Textract存取的AWS EC2實例中操作。 相同的二進制文件在Windows Server上的生產中運行,也在Linux容器中運行。 Docker部署指南和Linux部署指南涵蓋以前被阻止使用Textract的容器化環境的具體配置。

架構而非配置實現資料主權。IronOCR沒有需要禁用的網路傳輸模式——本地處理是唯一的模式。 通過IronOCR處理的文件從不離開主機。對於處理PHI的醫療應用、處理CUI的國防應用以及處理支付卡圖像的金融應用,這是一種合規態勢,不需要BAA談判,無需區域資料居住配置,也不需要审核亞馬遜的資料處理政策。 合規範圍是您已經控制的基礎設施邊界。 IronOCR產品頁面提供完成合規審查的團隊的完整功能摘要和部署文件。

可配置的預處理以進行文件質量控制。Textract的內部預處理不可存取或配置。 當掃描文件產生不佳結果時,唯一的訴諸方法是接受輸出或重新掃描。 IronOCR公開完整的預處理管道:DeepCleanBackgroundNoise()。 將因難於掃描的問題主要是由於低信心結果而將文件從Textract轉移至IronOCR的團隊可以直接針對特定缺陷進行處理——旋轉、噪聲、對比度低或解析度不足。 預處理功能頁面和圖像質量校正指南介紹了可用的過濾器及其適宜的用例。

請注意: AWS Textract, PDFSharp, Tesseract, and iText are registered trademarks of their respective owners. 本網站與Amazon Web Services、Google、empira Software GmbH或iText Group無關,也未經其背書或贊助。所有產品名稱、標誌和品牌均為其各自所有者的財產。 比較僅供資訊用途,並反映撰寫時獲得的公開資訊。

相關文章