フッターコンテンツにスキップ
ビデオ

Migrating from Charlesw Tesseract to IronOCR

MindeeからIronOCRへの移行により、文書処理が外部のクラウドサーバーから自社のインフラストラクチャに移行されるため、ページごとの課金、サードパーティへのデータ送信、および実行時のネットワーク可用性への依存が解消されます。 以下のセクションでは、パッケージの置き換え、名前空間の更新、および請求書ゾーンの抽出、複数ページの領収書PDFの処理、財務文書の前処理、検索可能なPDFアーカイブという4つの実用的なコード変更前後のシナリオについて説明します。

Mindeeから移行する理由

Mindeeは、文書を送信すると構造化されたJSONフィールドを受け取るという、現実的な問題を巧みに解決します。 財務書類を外部APIに送信することが許容され、かつ送信量が中程度にとどまるチームにとっては、迅速な結果が得られます。 移行のトリガーは通常、いくつかの条件のうちの1つが変化したときに発生します。

金融データがコンプライアンスの境界を越えます。 ミンディー の ParseAsync<InvoiceV4> はドキュメント全体を外部サーバーにアップロードします。 IBANコード、ルーティング番号、ベンダーEIN、および明細項目は、公共のインターネットを経由して送信され、お客様の管理下にないハードウェアで処理されます。 SOC 2 Type II認証は、Mindeeがセキュリティ対策を遵守していることを証明するものです。 それは、あなたの文書をセキュリティ境界内に保持しません。 コンプライアンス審査、新規顧客契約、またはデータ所在地の規制において、第三者によるクラウド処理が制限される場合、Mindeeのアーキテクチャは、抽出精度に関わらず、不適格とみなされる。

ページあたりの料金は、利用状況に応じて変動します。スタータープランでは、月額49ドルで月間1,000ページまで利用できます。 プロプランは月額499ドルで、月間5,000ページまで利用できます。 複数ページのPDFファイルは、すべてのページをカウントします。 開発実行回数は割り当て量にカウントされます。 月に4,000件の請求書を処理するチームの場合、月額499ドル(年間5,988ドル)を支払うことになり、この金額は毎年リセットされ、処理量の増加に応じて上昇します。 月間24,000ページという利用量であれば、Enterprise価格の交渉となります。 IronOCRの永久ライセンス(Professional版)は1,499ドルで、文書の容量に制限がなく、Mindee Proと比較して4ヶ月以内に費用を回収できます。

非同期ポーリングはスタック全体に伝播します。Mindeeのすべての操作はネットワークリクエストであるため、非同期です。この必須の非同期チェーンは、コントローラー、サービスレイヤー、ワーカークラスを通して上位に伝播します。 ネットワークが利用できない場合、またはMindeeのサービスに障害が発生した場合、連鎖的に障害が発生し、ローカルでの代替手段がなくなります。 IronOCR は同期処理を行い、ローカル CPU 作業を実行し、Task.Run を使用して非同期インタフェースが設計の一貫性のために必要な場合にラップします。

事前作成済みの API カタログには限界があります。 ミンディー は InvoiceV4, ReceiptV5, PassportV1 をカバーしており、他のドキュメントタイプの小さなカタログがあります。そのリスト外のドキュメントタイプには、エンタープライズプランのカスタムモデルトレーニングが必要であり、サンプル収集、ラベリング、トレーニング時間、API が利用可能になるまでのリードタイムが発生します。 貴社が必要とする新しい文書の種類ごとに、個別のベンダーとの契約が必要となります。 IronOCR は同じ IronTesseract().Read() コールを使用してどのドキュメントタイプも処理できます。 新しい文書タイプを追加するということは、抽出パターンを作成するということであり、研修契約を交渉するということではない。

APIキー管理により運用上の制約が緩和されます。Mindeeの認証情報は、サーバー側で安全に保管し、定期的に更新し、外部への漏洩から保護する必要があります。 ネットワーク送信ルールでは、MindeeエンドポイントへのHTTPS送信を許可する必要があります。 再試行ロジックは HttpRequestExceptionMindeeException を処理する必要があります。これは避けられないネットワーク障害です。 Mindeeを削除すると、その運用上のあらゆる側面が失われます。認証情報も、送信ルールも、ネットワークI/Oを囲む再試行ラッパーもなくなります。

エアギャップ環境および制限された環境は対象外です。政府請負業者、防衛関連下請け業者、厳格なインターネット発信制限のある医療ネットワーク、および信頼性の高いインターネット接続のない施設に導入された産業システムは、設計どおりにMindeeを使用することはできません。 IronOCRは、アウトバウンドアクセスなしのDockerコンテナ、送信制限付きのAzure Functions、および完全にエアギャップされた環境のいずれにおいても、全く同じように動作します。 実行時にクラウドへの依存はありません。デプロイメントティアの詳細については、 IronOCRのライセンスページをご覧ください。

基本的な問題

Mindeeが処理するすべての財務文書は、あなたが制御できないネットワーク境界を越えます。

// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to ミンディー cloud
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to ミンディー cloud
Imports System.Threading.Tasks

' Mindee: invoice with bank details leaves your infrastructure on this line
Dim response = Await _client.ParseAsync(Of InvoiceV4)(New LocalInputSource("invoice.pdf"))
' IBAN, routing number, EIN, line items — all transmitted to ミンディー cloud
$vbLabelText   $csharpLabel
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
' IronOCR: same invoice, same result, zero data transmission
Dim result = New IronTesseract().Read("invoice.pdf")
' All processing local — bank details never leave your machine
$vbLabelText   $csharpLabel

IronOCRとMindee:機能比較

以下の表は、移行の意思決定を左右するアーキテクチャと機能の違いをまとめたものです。

フィーチャー ミンディー IronOCR
処理場所 Mindeeクラウドサーバー ローカルマシン / インフラストラクチャ
実行時にインターネット接続が必要 いつも 一度もない
データ伝送 通話ごとに完全なドキュメントがアップロードされます None
文書ごとの費用 はい(ページ単位、プラン別) いいえ(永久ライセンス)
開始価格 月額49ドル(1,000ページ) $999 ワンタイム (Lite)
オフライン/エアギャップ対応 サポートされていません フルサポート
オンプレミス展開 不可 唯一の選択肢
請求書の解析 事前作成済みの構造化 API (InvoiceV4) OCR + 領域ベースまたは正規表現による抽出
レシート解析 事前作成済みの構造化 API (ReceiptV5) OCR + 抽出パターン
任意の文書タイプ カスタムトレーニングなしではサポートされません 完全サポート、あらゆる文書
カスタムモデルトレーニング Enterprise向けプラン+リードタイム 不要
API呼び出しパターン 非同期処理は必須です(ネットワークI/O)。 同期(非同期はオプション)
律速段階 プラン依存 None
PDF入力 はい はい(ネイティブ、変換なし)
複数ページPDFの処理 はい はい
検索可能なPDF出力 なし はい (result.SaveAsSearchablePdf())
画像前処理 露出していない 傾き補正、ノイズ除去、コントラスト調整、二値化、シャープ化、スケール調整
領域ベースの抽出 応答としてフィールド座標 入力時に CropRectangle
125以上の言語に対応 制限的 はい(バンドル版、tessdata管理なし)
バーコード読み取り なし はい(OCRと同時)
構造化された単語座標 なし はい(ページ数、段落数、行数、単語数)
スレッドセーフ 該当なし(呼び出しごとのネットワークI/O) 内蔵
クロスプラットフォーム クラウド(プラットフォーム非依存) Windows、Linux、macOS、Docker、Azure、AWS
.NET互換性 .NET Standard 2.0以降 .NET Framework 4.6.2以降、 .NET 5/6/7/8/9
商業サポート はい はい

クイックスタート:MindeeからIronOCRへの移行

ステップ 1: NuGet パッケージを置き換える

Mindeeパッケージを削除してください。

dotnet remove package Mindee
dotnet remove package Mindee
SHELL

NuGetからIronOCRをインストールしてください。

dotnet add package IronOcr

ステップ 2: 名前空間の更新

Mindee名前空間ブロックをIronOCR名前空間に置き換えてください。

// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;

// After (IronOCR)
using IronOcr;
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;

// After (IronOCR)
using IronOcr;
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports Mindee.Product.Receipt
Imports Mindee.Product.FinancialDocument

Imports IronOcr
$vbLabelText   $csharpLabel

ステップ 3: ライセンスの初期化

アプリケーションの起動時(最初のOCR呼び出し前)にライセンスキーを追加してください。

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

コード移行の例

文書ゾーンを使用した請求書フィールドの抽出

Mindeeは、サーバー側のモデルが請求書番号、日付、合計金額が通常表示される場所を把握しているため、既知の場所に事前に解析されたフィールドを返します。 IronOCR の同等の機能では、ドキュメントの特定のゾーンを読み取り、Mindee の抽出の空間認識を一致させつつ、ドキュメントを送信せずに CropRectangle を使用します。

Mindeeのアプローチ:

using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeZoneExtractor
{
    private readonly MindeeClient _client;

    public MindeeZoneExtractor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<(string invoiceNumber, string supplierName, decimal? total)>
        ExtractKeyFieldsAsync(string filePath)
    {
        // Document transmitted to ミンディー — spatial field detection happens server-side
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // Fields arrive pre-parsed; no zone configuration required on your end
        return (
            prediction.InvoiceNumber?.Value,
            prediction.SupplierName?.Value,
            prediction.TotalAmount?.Value
        );
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeZoneExtractor
{
    private readonly MindeeClient _client;

    public MindeeZoneExtractor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<(string invoiceNumber, string supplierName, decimal? total)>
        ExtractKeyFieldsAsync(string filePath)
    {
        // Document transmitted to ミンディー — spatial field detection happens server-side
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // Fields arrive pre-parsed; no zone configuration required on your end
        return (
            prediction.InvoiceNumber?.Value,
            prediction.SupplierName?.Value,
            prediction.TotalAmount?.Value
        );
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks

Public Class MindeeZoneExtractor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ExtractKeyFieldsAsync(filePath As String) As Task(Of (invoiceNumber As String, supplierName As String, total As Decimal?))
        ' Document transmitted to ミンディー — spatial field detection happens server-side
        Dim inputSource = New LocalInputSource(filePath)
        Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        ' Fields arrive pre-parsed; no zone configuration required on your end
        Return (
            prediction.InvoiceNumber?.Value,
            prediction.SupplierName?.Value,
            prediction.TotalAmount?.Value
        )
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCRのアプローチ:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrZoneExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public (string invoiceNumber, string supplierName, string total)
        ExtractKeyFields(string filePath)
    {
        // Zone 1: supplier name — top 12% of document, full width
        var headerRegion = new CropRectangle(0, 0, 850, 120);
        using var headerInput = new OcrInput();
        headerInput.LoadImage(filePath, headerRegion);
        var supplierResult = _ocr.Read(headerInput);

        // Zone 2: invoice number and date — upper-right quadrant
        var metaRegion = new CropRectangle(450, 80, 400, 100);
        using var metaInput = new OcrInput();
        metaInput.LoadImage(filePath, metaRegion);
        var metaResult = _ocr.Read(metaInput);

        // Zone 3: totals block — bottom-right 20%
        var totalsRegion = new CropRectangle(500, 800, 350, 200);
        using var totalsInput = new OcrInput();
        totalsInput.LoadImage(filePath, totalsRegion);
        var totalsResult = _ocr.Read(totalsInput);

        var invoiceNumber = Regex.Match(
            metaResult.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            totalsResult.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        // Supplier name is the first substantial text line in the header zone
        var supplierName = supplierResult.Text
            .Split('\n')
            .FirstOrDefault(l => l.Trim().Length > 4)
            ?.Trim();

        return (invoiceNumber, supplierName, total);
    }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrZoneExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public (string invoiceNumber, string supplierName, string total)
        ExtractKeyFields(string filePath)
    {
        // Zone 1: supplier name — top 12% of document, full width
        var headerRegion = new CropRectangle(0, 0, 850, 120);
        using var headerInput = new OcrInput();
        headerInput.LoadImage(filePath, headerRegion);
        var supplierResult = _ocr.Read(headerInput);

        // Zone 2: invoice number and date — upper-right quadrant
        var metaRegion = new CropRectangle(450, 80, 400, 100);
        using var metaInput = new OcrInput();
        metaInput.LoadImage(filePath, metaRegion);
        var metaResult = _ocr.Read(metaInput);

        // Zone 3: totals block — bottom-right 20%
        var totalsRegion = new CropRectangle(500, 800, 350, 200);
        using var totalsInput = new OcrInput();
        totalsInput.LoadImage(filePath, totalsRegion);
        var totalsResult = _ocr.Read(totalsInput);

        var invoiceNumber = Regex.Match(
            metaResult.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            totalsResult.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        // Supplier name is the first substantial text line in the header zone
        var supplierName = supplierResult.Text
            .Split('\n')
            .FirstOrDefault(l => l.Trim().Length > 4)
            ?.Trim();

        return (invoiceNumber, supplierName, total);
    }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrZoneExtractor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ExtractKeyFields(filePath As String) As (invoiceNumber As String, supplierName As String, total As String)
        ' Zone 1: supplier name — top 12% of document, full width
        Dim headerRegion As New CropRectangle(0, 0, 850, 120)
        Using headerInput As New OcrInput()
            headerInput.LoadImage(filePath, headerRegion)
            Dim supplierResult = _ocr.Read(headerInput)

            ' Zone 2: invoice number and date — upper-right quadrant
            Dim metaRegion As New CropRectangle(450, 80, 400, 100)
            Using metaInput As New OcrInput()
                metaInput.LoadImage(filePath, metaRegion)
                Dim metaResult = _ocr.Read(metaInput)

                ' Zone 3: totals block — bottom-right 20%
                Dim totalsRegion As New CropRectangle(500, 800, 350, 200)
                Using totalsInput As New OcrInput()
                    totalsInput.LoadImage(filePath, totalsRegion)
                    Dim totalsResult = _ocr.Read(totalsInput)

                    Dim invoiceNumber = Regex.Match(metaResult.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
                    Dim total = Regex.Match(totalsResult.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)", RegexOptions.IgnoreCase).Groups(1).Value

                    ' Supplier name is the first substantial text line in the header zone
                    Dim supplierName = supplierResult.Text.Split(vbLf).FirstOrDefault(Function(l) l.Trim().Length > 4)?.Trim()

                    Return (invoiceNumber, supplierName, total)
                End Using
            End Using
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

ゾーンベースOCRは、Mindeeの空間フィールド検出の意図と一致しています。つまり、ページ全体に正規表現を適用するのではなく、文書の特定の領域を読み取るということです。 CropRectangle(x, y, width, height) はピクセル座標を取るので、調整には代表的なサンプル請求書と比較する必要があります。地域別 OCR ガイド は座標の測定とゾーン重複戦略をカバーしています。 レイアウトが変動する請求書には、result.Text 上での完全ページの正規表現とゾーン抽出を組み合わせることで、最も信頼性の高い結果が得られます。

複数ページにわたる経費報告書(PDF形式)の処理

MindeeはPDFファイルを受け取り、各ページを個別の文書単位として処理し、ページごとに構造化された結果を返します。 IronOCR はネイティブに OcrInput.LoadPdf() を通じて複数ページの PDF を読み取り、単一の呼び出しですべてのページを処理し、ページごとのコンテンツと単語の座標を含む result.Pages を返します。

Mindeeのアプローチ:

using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;

public class MindeeExpenseReportProcessor
{
    private readonly MindeeClient _client;

    public MindeeExpenseReportProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
    {
        var summaries = new List<ExpenseSummary>();

        // ミンディー processes per-document; each page of a PDF is a separate upload
        // For a 10-page expense report: 10 API calls, 10 pages billed
        var inputSource = new LocalInputSource(pdfPath);
        var response    = await _client.ParseAsync<ReceiptV5>(inputSource);

        var prediction = response.Document.Inference.Prediction;

        summaries.Add(new ExpenseSummary
        {
            MerchantName  = prediction.SupplierName?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value ?? 0m,
            Category      = prediction.Category?.Value
        });

        return summaries;
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;

public class MindeeExpenseReportProcessor
{
    private readonly MindeeClient _client;

    public MindeeExpenseReportProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
    {
        var summaries = new List<ExpenseSummary>();

        // ミンディー processes per-document; each page of a PDF is a separate upload
        // For a 10-page expense report: 10 API calls, 10 pages billed
        var inputSource = new LocalInputSource(pdfPath);
        var response    = await _client.ParseAsync<ReceiptV5>(inputSource);

        var prediction = response.Document.Inference.Prediction;

        summaries.Add(new ExpenseSummary
        {
            MerchantName  = prediction.SupplierName?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value ?? 0m,
            Category      = prediction.Category?.Value
        });

        return summaries;
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Receipt
Imports System.Threading.Tasks

Public Class MindeeExpenseReportProcessor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ProcessExpenseReportAsync(pdfPath As String) As Task(Of List(Of ExpenseSummary))
        Dim summaries As New List(Of ExpenseSummary)()

        ' ミンディー processes per-document; each page of a PDF is a separate upload
        ' For a 10-page expense report: 10 API calls, 10 pages billed
        Dim inputSource As New LocalInputSource(pdfPath)
        Dim response = Await _client.ParseAsync(Of ReceiptV5)(inputSource)

        Dim prediction = response.Document.Inference.Prediction

        summaries.Add(New ExpenseSummary With {
            .MerchantName = prediction.SupplierName?.Value,
            .Date = prediction.Date?.Value?.ToString(),
            .TotalAmount = If(prediction.TotalAmount?.Value, 0D),
            .Category = prediction.Category?.Value
        })

        Return summaries
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCRのアプローチ:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrExpenseReportProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
    {
        // Load entire multi-page PDF in one call — no per-page upload
        using var input = new OcrInput();
        input.LoadPdf(pdfPath);    // all pages loaded locally

        var result    = _ocr.Read(input);
        var summaries = new List<ExpenseSummary>();

        // Each page maps to one receipt in the expense report
        foreach (var page in result.Pages)
        {
            var pageText = page.Text;

            // Skip pages without receipt content
            if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
                continue;

            var merchantName = page.Lines
                .FirstOrDefault(l => l.Text.Trim().Length > 4)
                ?.Text.Trim();

            var totalMatch = Regex.Match(
                pageText,
                @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
                RegexOptions.IgnoreCase);

            var dateMatch = Regex.Match(
                pageText,
                @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");

            var categoryMatch = Regex.Match(
                pageText,
                @"(?:Category|Dept|Department)\s*:?\s*(.+)",
                RegexOptions.IgnoreCase);

            summaries.Add(new ExpenseSummary
            {
                PageNumber   = page.PageNumber,
                MerchantName = merchantName,
                Date         = dateMatch.Success ? dateMatch.Value : null,
                TotalAmount  = totalMatch.Success
                    ? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
                    : 0m,
                Category     = categoryMatch.Success
                    ? categoryMatch.Groups[1].Value.Trim()
                    : null,
                Confidence   = page.Words.Any()
                    ? page.Words.Average(w => (double)w.Confidence)
                    : 0
            });
        }

        return summaries;
    }
}

public class ExpenseSummary
{
    public int    PageNumber   { get; set; }
    public string MerchantName { get; set; }
    public string Date         { get; set; }
    public decimal TotalAmount { get; set; }
    public string Category     { get; set; }
    public double Confidence   { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrExpenseReportProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
    {
        // Load entire multi-page PDF in one call — no per-page upload
        using var input = new OcrInput();
        input.LoadPdf(pdfPath);    // all pages loaded locally

        var result    = _ocr.Read(input);
        var summaries = new List<ExpenseSummary>();

        // Each page maps to one receipt in the expense report
        foreach (var page in result.Pages)
        {
            var pageText = page.Text;

            // Skip pages without receipt content
            if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
                continue;

            var merchantName = page.Lines
                .FirstOrDefault(l => l.Text.Trim().Length > 4)
                ?.Text.Trim();

            var totalMatch = Regex.Match(
                pageText,
                @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
                RegexOptions.IgnoreCase);

            var dateMatch = Regex.Match(
                pageText,
                @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");

            var categoryMatch = Regex.Match(
                pageText,
                @"(?:Category|Dept|Department)\s*:?\s*(.+)",
                RegexOptions.IgnoreCase);

            summaries.Add(new ExpenseSummary
            {
                PageNumber   = page.PageNumber,
                MerchantName = merchantName,
                Date         = dateMatch.Success ? dateMatch.Value : null,
                TotalAmount  = totalMatch.Success
                    ? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
                    : 0m,
                Category     = categoryMatch.Success
                    ? categoryMatch.Groups[1].Value.Trim()
                    : null,
                Confidence   = page.Words.Any()
                    ? page.Words.Average(w => (double)w.Confidence)
                    : 0
            });
        }

        return summaries;
    }
}

public class ExpenseSummary
{
    public int    PageNumber   { get; set; }
    public string MerchantName { get; set; }
    public string Date         { get; set; }
    public decimal TotalAmount { get; set; }
    public string Category     { get; set; }
    public double Confidence   { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrExpenseReportProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessExpenseReport(pdfPath As String) As List(Of ExpenseSummary)
        ' Load entire multi-page PDF in one call — no per-page upload
        Using input As New OcrInput()
            input.LoadPdf(pdfPath) ' all pages loaded locally

            Dim result = _ocr.Read(input)
            Dim summaries As New List(Of ExpenseSummary)()

            ' Each page maps to one receipt in the expense report
            For Each page In result.Pages
                Dim pageText = page.Text

                ' Skip pages without receipt content
                If Not pageText.Contains("Total", StringComparison.OrdinalIgnoreCase) Then
                    Continue For
                End If

                Dim merchantName = page.Lines _
                    .FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
                    ?.Text.Trim()

                Dim totalMatch = Regex.Match(
                    pageText,
                    "(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
                    RegexOptions.IgnoreCase)

                Dim dateMatch = Regex.Match(
                    pageText,
                    "\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b")

                Dim categoryMatch = Regex.Match(
                    pageText,
                    "(?:Category|Dept|Department)\s*:?\s*(.+)",
                    RegexOptions.IgnoreCase)

                summaries.Add(New ExpenseSummary With {
                    .PageNumber = page.PageNumber,
                    .MerchantName = merchantName,
                    .Date = If(dateMatch.Success, dateMatch.Value, Nothing),
                    .TotalAmount = If(totalMatch.Success, Decimal.Parse(totalMatch.Groups(1).Value.Replace(",", "")), 0D),
                    .Category = If(categoryMatch.Success, categoryMatch.Groups(1).Value.Trim(), Nothing),
                    .Confidence = If(page.Words.Any(), page.Words.Average(Function(w) CDbl(w.Confidence)), 0)
                })
            Next

            Return summaries
        End Using
    End Function
End Class

Public Class ExpenseSummary
    Public Property PageNumber As Integer
    Public Property MerchantName As String
    Public Property Date As String
    Public Property TotalAmount As Decimal
    Public Property Category As String
    Public Property Confidence As Double
End Class
$vbLabelText   $csharpLabel

Mindeeを使用して10ページの経費報告書PDFを処理すると、10回のAPI呼び出しと10ページの課金対象ページが生成されます。 IronOCRは、ページごとのコストやページごとのネットワーク往復通信なしに、同じファイルを1回のローカル呼び出しで処理します。 result.Pages コレクションは、ページごとのテキスト、単語レベルのバウンディングボックス、レイアウトを認識するためのライン位置を提供します。 パスワードで保護されたPDFファイルの読み込みとページ範囲の選択については、 PDF入力ガイドを参照してください。また、単語座標の操作については、構造化された読み取り結果ガイドを参照してください。

スキャン済み請求書の前処理パイプライン

Mindeeのクラウド処理では、フィールド抽出を実行する前に独自の画像正規化処理が適用されます。 その正規化の品質は不透明です。正規化を制御することも、どのような前処理が実行されたかを把握することもできません。 スキャンした請求書に歪み、影、またはコントラストの低さがある場合、Mindeeは信頼度スコアを低く表示し、提出前にスキャンを改善する仕組みはありません。 IronOCRは前処理パイプラインを明示的に公開しているため、認識を実行する前に、特定の文書に必要な修正を正確に適用できます。

Mindeeのアプローチ:

using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeScanProcessor
{
    private readonly MindeeClient _client;

    public MindeeScanProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
    {
        // ミンディー applies internal normalization — no developer control over preprocessing
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new ScanResult
        {
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Total         = prediction.TotalAmount?.Value,
            // Per-field confidence: only proxy for scan quality
            InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
            TotalConfidence         = prediction.TotalAmount?.Confidence
        };
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeScanProcessor
{
    private readonly MindeeClient _client;

    public MindeeScanProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
    {
        // ミンディー applies internal normalization — no developer control over preprocessing
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new ScanResult
        {
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Total         = prediction.TotalAmount?.Value,
            // Per-field confidence: only proxy for scan quality
            InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
            TotalConfidence         = prediction.TotalAmount?.Confidence
        };
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks

Public Class MindeeScanProcessor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ProcessScannedInvoiceAsync(scanPath As String) As Task(Of ScanResult)
        ' ミンディー applies internal normalization — no developer control over preprocessing
        Dim inputSource = New LocalInputSource(scanPath)
        Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        Return New ScanResult With {
            .InvoiceNumber = prediction.InvoiceNumber?.Value,
            .Total = prediction.TotalAmount?.Value,
            ' Per-field confidence: only proxy for scan quality
            .InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
            .TotalConfidence = prediction.TotalAmount?.Confidence
        }
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCRのアプローチ:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrScanProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ScanResult ProcessScannedInvoice(string scanPath)
    {
        // First pass: read without preprocessing
        var quickResult = _ocr.Read(scanPath);

        OcrResult result;

        if (quickResult.Confidence < 75)
        {
            // Low confidence — apply full preprocessing pipeline
            using var input = new OcrInput();
            input.LoadImage(scanPath);
            input.Deskew();       // correct page rotation up to ±45 degrees
            input.DeNoise();      // remove scanner artifacts and speckle
            input.Contrast();     // normalize contrast for faded or overexposed scans
            input.Sharpen();      // sharpen blurred text edges

            result = _ocr.Read(input);
        }
        else
        {
            result = quickResult;
        }

        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            result.Text,
            @"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase).Groups[1].Value;

        return new ScanResult
        {
            InvoiceNumber    = invoiceNumber,
            Total            = total,
            DocumentConfidence = result.Confidence,
            PreprocessingApplied = quickResult.Confidence < 75
        };
    }
}

public class ScanResult
{
    public string InvoiceNumber        { get; set; }
    public string Total                { get; set; }
    public double DocumentConfidence   { get; set; }
    public bool   PreprocessingApplied { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrScanProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ScanResult ProcessScannedInvoice(string scanPath)
    {
        // First pass: read without preprocessing
        var quickResult = _ocr.Read(scanPath);

        OcrResult result;

        if (quickResult.Confidence < 75)
        {
            // Low confidence — apply full preprocessing pipeline
            using var input = new OcrInput();
            input.LoadImage(scanPath);
            input.Deskew();       // correct page rotation up to ±45 degrees
            input.DeNoise();      // remove scanner artifacts and speckle
            input.Contrast();     // normalize contrast for faded or overexposed scans
            input.Sharpen();      // sharpen blurred text edges

            result = _ocr.Read(input);
        }
        else
        {
            result = quickResult;
        }

        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            result.Text,
            @"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase).Groups[1].Value;

        return new ScanResult
        {
            InvoiceNumber    = invoiceNumber,
            Total            = total,
            DocumentConfidence = result.Confidence,
            PreprocessingApplied = quickResult.Confidence < 75
        };
    }
}

public class ScanResult
{
    public string InvoiceNumber        { get; set; }
    public string Total                { get; set; }
    public double DocumentConfidence   { get; set; }
    public bool   PreprocessingApplied { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrScanProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessScannedInvoice(scanPath As String) As ScanResult
        ' First pass: read without preprocessing
        Dim quickResult = _ocr.Read(scanPath)

        Dim result As OcrResult

        If quickResult.Confidence < 75 Then
            ' Low confidence — apply full preprocessing pipeline
            Using input As New OcrInput()
                input.LoadImage(scanPath)
                input.Deskew()       ' correct page rotation up to ±45 degrees
                input.DeNoise()      ' remove scanner artifacts and speckle
                input.Contrast()     ' normalize contrast for faded or overexposed scans
                input.Sharpen()      ' sharpen blurred text edges

                result = _ocr.Read(input)
            End Using
        Else
            result = quickResult
        End If

        Dim invoiceNumber = Regex.Match(
            result.Text,
            "Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups(1).Value

        Dim total = Regex.Match(
            result.Text,
            "(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase).Groups(1).Value

        Return New ScanResult With {
            .InvoiceNumber = invoiceNumber,
            .Total = total,
            .DocumentConfidence = result.Confidence,
            .PreprocessingApplied = quickResult.Confidence < 75
        }
    End Function
End Class

Public Class ScanResult
    Public Property InvoiceNumber As String
    Public Property Total As String
    Public Property DocumentConfidence As Double
    Public Property PreprocessingApplied As Boolean
End Class
$vbLabelText   $csharpLabel

2段階のパターン(まず高速読み取りを行い、信頼度の低いデータに対して前処理を行う)により、クリーンなスキャンデータに対する完全な前処理のオーバーヘッドを回避できます。 アカウント支払いで一般的なスキャン品質のシナリオ(わずかに歪んだフラットベッドスキャン、ファクスのアーティファクト、コピーされた請求書)には、Deskew()DeNoise() が一緒になって認識精度のほとんどを回復します。 画像品質補正ガイドには、利用可能なすべてのフィルターが記載されており、さまざまなスキャン欠陥に対してどの組み合わせが最適かに関するガイダンスが提供されています。 画像方向補正ガイドでは、上下逆さまにスキャンされた文書の自動回転について説明しています。

財務文書の検索可能なPDFアーカイブ

MindeeはPDF出力を生成しません。 そのアーキテクチャは入力専用です。ドキュメントを送信すると、JSONフィールドが返されます。 スキャンした請求書を検索可能な形式でアーカイブするチームは、そのパイプラインをMindeeによる抽出とは別に維持する必要があります。 IronOCRは、フィールド抽出に使用した認識パスから直接検索可能なPDFを生成します。追加のライブラリや2段階の処理は不要です。

Mindeeのアプローチ:

using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeArchiveProcessor
{
    private readonly MindeeClient _client;

    public MindeeArchiveProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
    {
        // Extract fields via ミンディー (document transmitted to cloud)
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // ミンディー returns no PDF output — searchable PDF requires a separate library
        // (e.g., iTextSharp, PdfSharp, or a separate OCR library)
        // The scan at scanPath is the only PDF available for archiving;
        // it remains image-only with no embedded text layer
        Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
        Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeArchiveProcessor
{
    private readonly MindeeClient _client;

    public MindeeArchiveProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
    {
        // Extract fields via ミンディー (document transmitted to cloud)
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // ミンディー returns no PDF output — searchable PDF requires a separate library
        // (e.g., iTextSharp, PdfSharp, or a separate OCR library)
        // The scan at scanPath is the only PDF available for archiving;
        // it remains image-only with no embedded text layer
        Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
        Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks

Public Class MindeeArchiveProcessor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ArchiveInvoiceAsync(scanPath As String, archivePath As String) As Task
        ' Extract fields via ミンディー (document transmitted to cloud)
        Dim inputSource = New LocalInputSource(scanPath)
        Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        ' ミンディー returns no PDF output — searchable PDF requires a separate library
        ' (e.g., iTextSharp, PdfSharp, or a separate OCR library)
        ' The scan at scanPath is the only PDF available for archiving;
        ' it remains image-only with no embedded text layer
        Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.")
        Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}")
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCRのアプローチ:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrArchiveProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
    {
        // Apply preprocessing before recognition and archiving
        using var input = new OcrInput();
        input.LoadPdf(scanPath);   // works with both PDF scans and image files
        input.Deskew();
        input.DeNoise();

        var result = _ocr.Read(input);

        // Extract fields from the same recognition pass
        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var vendor = result.Pages.FirstOrDefault()?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();

        var totalMatch = Regex.Match(
            result.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase);

        // Write searchable PDF to archive in one call — no separate library needed
        result.SaveAsSearchablePdf(archiveOutputPath);

        return new ArchiveResult
        {
            InvoiceNumber    = invoiceNumber,
            VendorName       = vendor,
            Total            = totalMatch.Success ? totalMatch.Groups[1].Value : null,
            ArchivePath      = archiveOutputPath,
            DocumentConfidence = result.Confidence,
            PageCount        = result.Pages.Count()
        };
    }
}

public class ArchiveResult
{
    public string InvoiceNumber      { get; set; }
    public string VendorName         { get; set; }
    public string Total              { get; set; }
    public string ArchivePath        { get; set; }
    public double DocumentConfidence { get; set; }
    public int    PageCount          { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrArchiveProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
    {
        // Apply preprocessing before recognition and archiving
        using var input = new OcrInput();
        input.LoadPdf(scanPath);   // works with both PDF scans and image files
        input.Deskew();
        input.DeNoise();

        var result = _ocr.Read(input);

        // Extract fields from the same recognition pass
        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var vendor = result.Pages.FirstOrDefault()?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();

        var totalMatch = Regex.Match(
            result.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase);

        // Write searchable PDF to archive in one call — no separate library needed
        result.SaveAsSearchablePdf(archiveOutputPath);

        return new ArchiveResult
        {
            InvoiceNumber    = invoiceNumber,
            VendorName       = vendor,
            Total            = totalMatch.Success ? totalMatch.Groups[1].Value : null,
            ArchivePath      = archiveOutputPath,
            DocumentConfidence = result.Confidence,
            PageCount        = result.Pages.Count()
        };
    }
}

public class ArchiveResult
{
    public string InvoiceNumber      { get; set; }
    public string VendorName         { get; set; }
    public string Total              { get; set; }
    public string ArchivePath        { get; set; }
    public double DocumentConfidence { get; set; }
    public int    PageCount          { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Imports System.Linq

Public Class IronOcrArchiveProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ArchiveInvoice(scanPath As String, archiveOutputPath As String) As ArchiveResult
        ' Apply preprocessing before recognition and archiving
        Using input As New OcrInput()
            input.LoadPdf(scanPath) ' works with both PDF scans and image files
            input.Deskew()
            input.DeNoise()

            Dim result = _ocr.Read(input)

            ' Extract fields from the same recognition pass
            Dim invoiceNumber = Regex.Match(result.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value

            Dim vendor = result.Pages.FirstOrDefault()?.Lines.FirstOrDefault(Function(l) l.Text.Trim().Length > 4)?.Text.Trim()

            Dim totalMatch = Regex.Match(result.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})", RegexOptions.IgnoreCase)

            ' Write searchable PDF to archive in one call — no separate library needed
            result.SaveAsSearchablePdf(archiveOutputPath)

            Return New ArchiveResult With {
                .InvoiceNumber = invoiceNumber,
                .VendorName = vendor,
                .Total = If(totalMatch.Success, totalMatch.Groups(1).Value, Nothing),
                .ArchivePath = archiveOutputPath,
                .DocumentConfidence = result.Confidence,
                .PageCount = result.Pages.Count()
            }
        End Using
    End Function
End Class

Public Class ArchiveResult
    Public Property InvoiceNumber As String
    Public Property VendorName As String
    Public Property Total As String
    Public Property ArchivePath As String
    Public Property DocumentConfidence As Double
    Public Property PageCount As Integer
End Class
$vbLabelText   $csharpLabel

result.SaveAsSearchablePdf() は出力 PDF に認識されたテキストレイヤーを直接埋め込み、任意の PDF ビューアーや企業コンテンツ管理システムで各単語を選択可能かつ検索可能にします。 フィールド抽出とアーカイブは同じパスで実行されます --- 単一の _ocr.Read(input) 呼び出しでパターンマッチング用の result.Text とアーカイブ用の result.SaveAsSearchablePdf() の両方を提供します。サーチャブル PDF ガイド は HOCR エクスポートを含む出力オプションをカバーし、PDF OCR ユースケースページ はドキュメントアーカイブ用パイプラインのための生産展開パターンをカバーしています。

FinancialDocumentV1カスタムエンドポイントの削除

Mindee の FinancialDocumentV1 API は請求書とレシートの両方をドキュメントタイプを自動検出して処理します。 これを利用するチームは、分岐ロジックを排除できる反面、クラウドへの依存度が高まるという代償を払うことになる。つまり、ドキュメントの種類に関係なく、すべてのドキュメントがアップロードされる。 IronOCRに置き換えると、単語レベルの位置情報データを使用して文書構造を検出し、クラウドへの呼び出しなしに抽出ロジックをルーティングします。

Mindeeのアプローチ:

using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;

public class MindeeFinancialRouter
{
    private readonly MindeeClient _client;

    public MindeeFinancialRouter(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
    {
        // All financial documents uploaded for auto-detection and parsing
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new FinancialSummary
        {
            DocumentType  = prediction.DocumentType?.Value,  // "INVOICE" or "RECEIPT"
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value,
            SupplierName  = prediction.SupplierName?.Value,
            CustomerName  = prediction.CustomerName?.Value
        };
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;

public class MindeeFinancialRouter
{
    private readonly MindeeClient _client;

    public MindeeFinancialRouter(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
    {
        // All financial documents uploaded for auto-detection and parsing
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new FinancialSummary
        {
            DocumentType  = prediction.DocumentType?.Value,  // "INVOICE" or "RECEIPT"
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value,
            SupplierName  = prediction.SupplierName?.Value,
            CustomerName  = prediction.CustomerName?.Value
        };
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.FinancialDocument
Imports System.Threading.Tasks

Public Class MindeeFinancialRouter
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ProcessFinancialDocumentAsync(filePath As String) As Task(Of FinancialSummary)
        ' All financial documents uploaded for auto-detection and parsing
        Dim inputSource = New LocalInputSource(filePath)
        Dim response = Await _client.ParseAsync(Of FinancialDocumentV1)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        Return New FinancialSummary With {
            .DocumentType = prediction.DocumentType?.Value,  ' "INVOICE" or "RECEIPT"
            .InvoiceNumber = prediction.InvoiceNumber?.Value,
            .Date = prediction.Date?.Value?.ToString(),
            .TotalAmount = prediction.TotalAmount?.Value,
            .SupplierName = prediction.SupplierName?.Value,
            .CustomerName = prediction.CustomerName?.Value
        }
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCRのアプローチ:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrFinancialRouter
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public FinancialSummary ProcessFinancialDocument(string filePath)
    {
        var result = _ocr.Read(filePath);
        var text   = result.Text;

        // Detect document type using structural keywords from word data
        var isInvoice = DetectInvoice(result);

        if (isInvoice)
        {
            return new FinancialSummary
            {
                DocumentType  = "INVOICE",
                InvoiceNumber = Regex.Match(text,
                    @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                Date          = Regex.Match(text,
                    @"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                TotalAmount   = ParseAmount(text,
                    @"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName  = ExtractTopLine(result),
                CustomerName  = Regex.Match(text,
                    @"Bill\s+To\s*:?\s*\n?(.+)",
                    RegexOptions.IgnoreCase).Groups[1].Value.Trim()
            };
        }
        else
        {
            // Receipt structure: merchant at top, no "Bill To" section
            return new FinancialSummary
            {
                DocumentType = "RECEIPT",
                Date         = Regex.Match(text,
                    @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
                TotalAmount  = ParseAmount(text,
                    @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName = ExtractTopLine(result)
            };
        }
    }

    private bool DetectInvoice(OcrResult result)
    {
        // Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
        var text = result.Text;
        var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
        return invoiceSignals.Any(s =>
            text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
    }

    private string ExtractTopLine(OcrResult result)
    {
        return result.Pages
            .FirstOrDefault()
            ?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();
    }

    private decimal? ParseAmount(string text, string pattern)
    {
        var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
        if (match.Success &&
            decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
            return val;
        return null;
    }
}

public class FinancialSummary
{
    public string   DocumentType  { get; set; }
    public string   InvoiceNumber { get; set; }
    public string   Date          { get; set; }
    public decimal? TotalAmount   { get; set; }
    public string   SupplierName  { get; set; }
    public string   CustomerName  { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrFinancialRouter
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public FinancialSummary ProcessFinancialDocument(string filePath)
    {
        var result = _ocr.Read(filePath);
        var text   = result.Text;

        // Detect document type using structural keywords from word data
        var isInvoice = DetectInvoice(result);

        if (isInvoice)
        {
            return new FinancialSummary
            {
                DocumentType  = "INVOICE",
                InvoiceNumber = Regex.Match(text,
                    @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                Date          = Regex.Match(text,
                    @"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                TotalAmount   = ParseAmount(text,
                    @"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName  = ExtractTopLine(result),
                CustomerName  = Regex.Match(text,
                    @"Bill\s+To\s*:?\s*\n?(.+)",
                    RegexOptions.IgnoreCase).Groups[1].Value.Trim()
            };
        }
        else
        {
            // Receipt structure: merchant at top, no "Bill To" section
            return new FinancialSummary
            {
                DocumentType = "RECEIPT",
                Date         = Regex.Match(text,
                    @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
                TotalAmount  = ParseAmount(text,
                    @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName = ExtractTopLine(result)
            };
        }
    }

    private bool DetectInvoice(OcrResult result)
    {
        // Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
        var text = result.Text;
        var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
        return invoiceSignals.Any(s =>
            text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
    }

    private string ExtractTopLine(OcrResult result)
    {
        return result.Pages
            .FirstOrDefault()
            ?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();
    }

    private decimal? ParseAmount(string text, string pattern)
    {
        var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
        if (match.Success &&
            decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
            return val;
        return null;
    }
}

public class FinancialSummary
{
    public string   DocumentType  { get; set; }
    public string   InvoiceNumber { get; set; }
    public string   Date          { get; set; }
    public decimal? TotalAmount   { get; set; }
    public string   SupplierName  { get; set; }
    public string   CustomerName  { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrFinancialRouter
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessFinancialDocument(filePath As String) As FinancialSummary
        Dim result = _ocr.Read(filePath)
        Dim text = result.Text

        ' Detect document type using structural keywords from word data
        Dim isInvoice = DetectInvoice(result)

        If isInvoice Then
            Return New FinancialSummary With {
                .DocumentType = "INVOICE",
                .InvoiceNumber = Regex.Match(text,
                    "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
                    RegexOptions.IgnoreCase).Groups(1).Value,
                .Date = Regex.Match(text,
                    "(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
                    RegexOptions.IgnoreCase).Groups(1).Value,
                .TotalAmount = ParseAmount(text,
                    "Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
                .SupplierName = ExtractTopLine(result),
                .CustomerName = Regex.Match(text,
                    "Bill\s+To\s*:?\s*\n?(.+)",
                    RegexOptions.IgnoreCase).Groups(1).Value.Trim()
            }
        Else
            ' Receipt structure: merchant at top, no "Bill To" section
            Return New FinancialSummary With {
                .DocumentType = "RECEIPT",
                .Date = Regex.Match(text,
                    "\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
                .TotalAmount = ParseAmount(text,
                    "(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
                .SupplierName = ExtractTopLine(result)
            }
        End If
    End Function

    Private Function DetectInvoice(result As OcrResult) As Boolean
        ' Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
        Dim text = result.Text
        Dim invoiceSignals = {"Invoice", "Bill To", "Due Date", "Purchase Order"}
        Return invoiceSignals.Any(Function(s) text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)
    End Function

    Private Function ExtractTopLine(result As OcrResult) As String
        Return result.Pages _
            .FirstOrDefault() _
            ?.Lines _
            .FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
            ?.Text.Trim()
    End Function

    Private Function ParseAmount(text As String, pattern As String) As Decimal?
        Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
        If match.Success AndAlso
            Decimal.TryParse(match.Groups(1).Value.Replace(",", ""), val) Then
            Return val
        End If
        Return Nothing
    End Function
End Class

Public Class FinancialSummary
    Public Property DocumentType As String
    Public Property InvoiceNumber As String
    Public Property Date As String
    Public Property TotalAmount As Decimal?
    Public Property SupplierName As String
    Public Property CustomerName As String
End Class
$vbLabelText   $csharpLabel

ドキュメントタイプ検出ロジックは、Mindee のサーバー側の DocumentType フィールドを result.Text に対する単純なキーワードスキャンで置き換えます。 ほとんどの財務書類において、"請求書"または"請求先"という表記があれば、それが請求書であることは明確に識別できます。 不在はレシートと識別します。result.Pages の単語レベルポジションデータにより、エッジケースを含むドキュメントセットがある場合に、より高度なレイアウトベースの検出が可能になります。 結果の読み取りガイドは、Words, Lines, Paragraphs 及びその境界座標を含む完全なオブジェクトモデルを説明しています。

ミンディー APIからIronOCRへのマッピングリファレンス

ミンディー IronOCR相当値
new MindeeClient(apiKey) new IronTesseract() — API キーは不要
new LocalInputSource(filePath) new OcrInput() + input.LoadImage(filePath) もしくは ocr.Read(filePath)
_client.ParseAsync<InvoiceV4>(input) _ocr.Read(filePath) + 正規表現抽出 (result.Text 上)
_client.ParseAsync<ReceiptV5>(input) _ocr.Read(filePath) + レシート抽出パターン
_client.ParseAsync<PassportV1>(input) _ocr.Read(filePath) + MRZ ゾーン抽出 (CropRectangle 経由)
_client.ParseAsync<FinancialDocumentV1>(input) _ocr.Read(filePath) + ドキュメントタイプ検出 (result.Text 上)
response.Document.Inference.Prediction result.Text, result.Pages, result.Lines, result.Words
prediction.InvoiceNumber?.Value Regex.Match(result.Text, @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)")
prediction.SupplierName?.Value 最初のページのトップライン (result.Pages[0].Lines.First() 経由)
prediction.TotalAmount?.Value Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})")
prediction.LineItems result.Lines フィルタリング (項目毎の正規表現パターンにより)
prediction.SupplierPaymentDetails[].Iban Regex.Match(result.Text, @"IBAN\s*:?\s*([\w\s]+)")
prediction.InvoiceDate?.Value Regex.Match(result.Text, @"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{4})")
field.Confidence result.Confidence (ドキュメントレベル) または word.Confidence (単語ごと)
catch (MindeeException ex) 同等のものはありません — 認識時にネットワークエラーは発生しません
await Task.Delay(100) (レート制限) 必須ではありません - レート制限はありません
input.LoadPdf(path) ocrInput.LoadPdf(path) — 同じ操作、完全にローカル
設定内のAPIキー ライセンスキー (via IronOcr.License.LicenseKey = "...") — 回転は不要

一般的な移行の問題と解決策

問題1:切り抜き矩形の座標がドキュメントのレイアウトと一致しない

Mindee:空間フィールドの座標はサーバー側で処理されます。Mindeeのモデルは、開発者による座標設定なしに、さまざまな請求書レイアウトに対応します。

解決策:ベンダーから入手した文書の代表的なサンプルを用いて、ゾーン座標を測定してください。 任意の画像エディタでサンプル請求書を開いてピクセル寸法を読み取り、それに応じて CropRectangle(x, y, width, height) を定義します。 レイアウトが可変な請求書の場合は、まず文書全体を読み、ワード位置データを使用してゾーンを動的に特定してください。

var result  = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;

// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
    w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));

if (totalLabel != null)
{
    var valueRegion = new CropRectangle(
        totalLabel.X + totalLabel.Width + 5,
        totalLabel.Y - 5,
        200,
        totalLabel.Height + 10);

    using var valueInput = new OcrInput();
    valueInput.LoadImage("invoice.jpg", valueRegion);
    var valueResult = _ocr.Read(valueInput);
    Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
var result  = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;

// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
    w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));

if (totalLabel != null)
{
    var valueRegion = new CropRectangle(
        totalLabel.X + totalLabel.Width + 5,
        totalLabel.Y - 5,
        200,
        totalLabel.Height + 10);

    using var valueInput = new OcrInput();
    valueInput.LoadImage("invoice.jpg", valueRegion);
    var valueResult = _ocr.Read(valueInput);
    Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
Imports System
Imports System.Linq

Dim result = _ocr.Read("invoice.jpg")
Dim allWords = result.Pages.First().Words

' Find the word "Total" and read the region 50px to its right
Dim totalLabel = allWords.FirstOrDefault(Function(w) w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase))

If totalLabel IsNot Nothing Then
    Dim valueRegion = New CropRectangle(totalLabel.X + totalLabel.Width + 5, totalLabel.Y - 5, 200, totalLabel.Height + 10)

    Using valueInput As New OcrInput()
        valueInput.LoadImage("invoice.jpg", valueRegion)
        Dim valueResult = _ocr.Read(valueInput)
        Console.WriteLine($"Total: {valueResult.Text.Trim()}")
    End Using
End If
$vbLabelText   $csharpLabel

領域ベースのOCRの例では、この動的なゾーンパターンを完全な動作例として示しています。

問題2:Mindeeを削除すると非同期呼び出しサイトが壊れる

Mindee: MindeeのAPIインターフェース全体は、ネットワークI/Oであるため非同期です。 コントローラーとサービスメソッドは、await _client.ParseAsync<t>() をベースにしており、呼び出しチェーン全体で非同期です。

解決策: IronOCR の Read() メソッドは同期しています。 既存の非同期呼び出しサイトは、同期呼び出しを Task.Run でラッピングすることで即座に動作します。

// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
    return await Task.Run(() =>
    {
        var result = new IronTesseract().Read(filePath);
        return Regex.Match(result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;
    });
}
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
    return await Task.Run(() =>
    {
        var result = new IronTesseract().Read(filePath);
        return Regex.Match(result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;
    });
}
Imports System.Text.RegularExpressions
Imports System.Threading.Tasks

' Existing async signature preserved for callers
Public Async Function ExtractInvoiceNumberAsync(filePath As String) As Task(Of String)
    Return Await Task.Run(Function()
                              Dim result = New IronTesseract().Read(filePath)
                              Return Regex.Match(result.Text, 
                                                 "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", 
                                                 RegexOptions.IgnoreCase).Groups(1).Value
                          End Function)
End Function
$vbLabelText   $csharpLabel

高スループットの ASP.NET シナリオのために、Task.Run ラッパーとネイティブ非同期パスの間を選択する前に、非同期 OCR ガイドを確認してください。

問題3:低品質スキャンでは抽出精度が低下する

Mindee: Mindeeの前処理は内部で自動的に行われます。 スキャン品質の問題は、再提出前に開発者による介入ができない場合、フィールド信頼度スコアを低下させます。

解決策:認識前にIronOCRの前処理パイプラインを適用してください。 アカウント支払いのワークフローで見られる標準的なフラットベッドスキャン欠陥に対して、Deskew()DeNoise() の組み合わせでほとんどの精度を回復します:

using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();    // for faded or low-contrast thermal paper receipts
input.Binarize();    // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();    // for faded or low-contrast thermal paper receipts
input.Binarize();    // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
Imports IronOcr

Using input As New OcrInput()
    input.LoadImage("faded-invoice.jpg")
    input.Deskew()
    input.DeNoise()
    input.Contrast() ' for faded or low-contrast thermal paper receipts
    input.Binarize() ' convert to clean black-and-white before recognition
    Dim result = New IronTesseract().Read(input)
End Using
$vbLabelText   $csharpLabel

深刻に劣化したスキャンの場合、input.DeepCleanBackgroundNoise() はより重い背景除去を適用します。 画像品質補正ガイドにはしきい値が記載されており、フィルターウィザードは、特定の文書サンプルに対してどのフィルターが精度を向上させるかを特定するのに役立ちます。

問題4:APIキーとネットワーク出力設定が削除されました

Mindee: アプリ設定に保存された API キー、api.mindee.net に対するアウトバウンド HTTPS を許可するネットワーク外部行動規則、HttpRequestException をラッピングする再試行ロジック、全てが必要なインフラです。

解決策: 3つすべてをまとめて取り除きます。 ミンディー APIキーのエントリが設定から削除されました。 ネットワーク送信ルールは取り消されました。 try/catch (HttpRequestException) ブロックは削除されます。 残りの認証情報は、起動時に一度だけ設定されるIronOCRライセンスキーのみです。

// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// なし rotation schedule, no secure storage beyond standard config secret management
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// なし rotation schedule, no secure storage beyond standard config secret management
$vbLabelText   $csharpLabel

第5号:複数ページPDFのページ数に応じた課金

Mindee: ミンディー にアップロードされた 12 ページのベンダー ステートメントは、月次割り当てに対して 12 ページを消費します。 プロプランの超過料金では、月間上限に近い場合、その1つの文書につき1.20ドルの追加料金が発生します。

解決策: IronOCRは複数ページのPDFをページごとのコストなしで処理します。ドキュメント全体を読み込み、ページを順に処理します。

using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf");   // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);

foreach (var page in result.Pages)
    Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf");   // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);

foreach (var page in result.Pages)
    Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
Imports IronOcr

Using input As New OcrInput()
    input.LoadPdf("vendor-statement.pdf") ' 12 pages — no billing unit increment
    Dim result = New IronTesseract().Read(input)

    For Each page In result.Pages
        Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized")
    Next
End Using
$vbLabelText   $csharpLabel

問題6:FinancialDocumentV1の依存関係を単一のパターンで置き換えることはできません

Mindee: FinancialDocumentV1 はドキュメントタイプを自動的に検出し、呼び出しコードがドキュメントタイプで分岐することなくフィールドを抽出します。

解決策:キーワードの存在を利用した軽量な検出器を構築する。 財務書類は、請求書("請求書"、"請求先"、"支払期日"のいずれかを含む)と領収書("領収書"、"ありがとうございます"のいずれかを含むか、請求書であることを示す表示がない)に明確に区分されます。 2つの抽出方法と3行検出器を組み合わせることで、整形式文書における精度を損なうことなく、Mindee API呼び出しを置き換えることができます。

var result  = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
    ? ExtractInvoiceFields(result)
    : ExtractReceiptFields(result);
var result  = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
    ? ExtractInvoiceFields(result)
    : ExtractReceiptFields(result);
Dim result = _ocr.Read(filePath)
Dim summary = If(result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0, ExtractInvoiceFields(result), ExtractReceiptFields(result))
$vbLabelText   $csharpLabel

Mindee移住チェックリスト

移行前

コードベース全体におけるMindeeの使用状況を監査する:

grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
SHELL

在庫調査結果:

  • 一意の呼び出しサイトを ParseAsync<t> を使ってカウントする
  • 使用されているドキュメントタイプを記録する (InvoiceV4, ReceiptV5, PassportV1, FinancialDocumentV1) Mindee呼び出しから伝播するすべての非同期メソッドチェーンを特定します。
  • 設定ファイルとシークレット保管庫内のAPIキー参照箇所を特定する Mindeeネットワーク呼び出しをラップする再試行ロジックを特定します。 Mindeeエンドポイントへの送信トラフィックを許可するネットワーク出力ルールを特定する

コードの移行

  1. プロジェクトファイルから Mindee NuGet パッケージを削除する
  2. dotnet add package IronOcr を実行して IronOCR をインストールする
  3. アプリケーションの起動時に IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" を追加する
  4. すべての using Mindee, using Mindee.Input, using Mindee.Product.* ディレクティブを削除する
  5. MindeeClient コンストラクター注入を IronTesseract インスタンス化または DI 登録に置き換える
  6. ParseAsync<InvoiceV4> 呼び出しを _ocr.Read(filePath) と請求書抽出方法に置き換える
  7. ParseAsync<ReceiptV5> 呼び出しを _ocr.Read(filePath) とレシート抽出方法に置き換える
  8. ParseAsync<PassportV1> 呼び出しを CropRectangle を使用したゾーンベースの OCR で MRZ 領域に置き換える
  9. ParseAsync<FinancialDocumentV1> 呼び出しをドキュメントタイプ検出器 + ルーティングパターンに置き換える
  10. すべての await Task.Delay(...) レート制限遵守ラインを削除する
  11. すべての catch (MindeeException) および catch (HttpRequestException) ブロックを OCR パスから削除する
  12. スキャンされたドキュメントパスに対して OcrInput プリプロセス呼び出しを追加する (Deskew, DeNoise, Contrast)
  13. 任意のドキュメントアーカイブパスに result.SaveAsSearchablePdf(archivePath) を追加する
  14. ミンディー APIキーをすべての設定ファイルとシークレット保管庫から削除する
  15. ネットワークモックを使用せずに同期的に実行されるように統合テストを更新する

移行後

  • 各文書タイプを網羅する20~30件の文書サンプルセットを用いて、抽出精度を検証する。
  • 代表的なクリーンスキャンで result.Confidence 値が 80 以上であることを確認します。 以下の場合は、前処理を適用してください。
  • 歪んだおよび劣化したスキャンサンプルに対してプリプロセスパイプライン (Deskew, DeNoise) をテストします
  • 複数ページの PDF が正しい result.Pages.Count とページごとのコンテンツを生成することを確認します。
  • result.SaveAsSearchablePdf() 出力が Adobe Acrobat およびシステム PDF ビューアで検索可能であることを確認します
  • ベンダーセットのドキュメントレイアウトの変動に対してすべてのゾーンベースの抽出 (CropRectangle) をテストします
  • Task.Delay 呼び出しなくバッチ処理を実行し、スレッドの問題がないことを確認します
  • アプリケーションがインターネットへの外部アクセスなしで正しく起動および実行されることを確認します。
  • 各エントリポイントで最初の _ocr.Read() 呼び出しの前に、ライセンスキーの初期化が実行されることを確認します。
  • 設定ファイル、環境変数、またはシークレットにMindee APIキーへの参照が残っていないことを確認してください。

IronOCRへの移行の主なメリット

財務書類に関する完全なデータ主権を確保します。移行後、ベンダーのIBANコード、ルーティング番号、顧客のEIN、明細項目は、お客様のインフラストラクチャから外部に送信されることはありません。 コンプライアンスの適用範囲は、貴社組織のみを対象とします。 第三者AIベンダーとの処理契約は、監査証跡から削除されます。 GLBA、CMMC、FedRAMP、またはデータ所在地の要件に基づいて業務を行うチームは、外部クラウドへの送信に必要なコンプライアンス審査を受けることなく、財務文書を処理できます。

ボリュームに関係なく予測可能なコスト。 $999 の IronOCR Lite ライセンスでは、1 人の開発者が無制限のドキュメント処理を行うことが可能です。 月間500件の請求書から月間50,000件の請求書に規模を拡大しても、追加費用は一切かかりません。 年末処理の急増、バッチ修正実行、開発テストサイクルは、請求カウンターを増加させません。 月間5,000ページを処理するチームの場合、3年間の総所有コストは約17,964ドル(Mindee Pro)から1,499ドル~2,999ドル(IronOCR永久ライセンス)に減少します。 料金プランの詳細については、 IronOCRの製品ページをご覧ください。

抽出ロジックは永久に所有できます。IronOCRの抽出パターンは、リポジトリ内のプレーンなC#コードです。 これらは、外部ベンダーのリリーススケジュールやAPIのバージョン管理の決定とは無関係に、バージョン管理され、レビュー可能で、テスト可能で、デプロイ可能です。 ミンディー が InvoiceV5 をリリースし、InvoiceV4 を非推奨にするとき、それはベンダーが課す移行期限です。 抽出パターンを維持するとき、それは git commit の改善です。

実環境でのスキャン品質を確保するための前処理管理。買掛金処理部門は、数十社、数百社ものベンダーから請求書を受け取りますが、それぞれのベンダーは異なる機器と設定でスキャンされています。 IronOCR のプリプロセスパイプライン — Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — は、それぞれのスキャンカテゴリで発見される特定の欠陥に対処します。 感熱レシートの写真は、フラットベッドスキャナーで複数ページをスキャンした請求書とは異なる欠陥を抱えているため、それぞれの処理経路に適したフィルターを適用する必要があります。 Mindeeのプリプロセスは目に見えず、設定変更もできません。 フィルタ一覧の詳細は、前処理機能のページをご覧ください。

ワンステップでの検索可能な PDF アーカイブ。 IronOCR で処理されたすべての請求書は result.SaveAsSearchablePdf() を使って検索可能な PDF としてアーカイブできます。 認識されたテキストレイヤーは出力ファイルに埋め込まれるため、文書管理システム、SharePointライブラリ、およびEnterpriseコンテンツリポジトリにおいて、すべての単語を全文検索することが可能になります。 MindeeはPDF出力機能を一切提供していません。 検索可能なアーカイブを実現するには、従来は別のライブラリ、別の処理工程、および追加のメンテナンスが必要だった。 移行後、抽出とアーカイブは _ocr.Read(input) 呼び出しに統合されます。

アーキテクチャの変更なしに、あらゆる環境にデプロイ可能。IronOCRは、同じNuGetパッケージと初期化手順を使用して、Windows、Linux、macOS、Docker、Azure App Service、AWS Lambda、Google Cloud Runにデプロイできます。 エアギャップ環境、工場現場システム、政府のセキュリティ施設はすべて、変更を加えることなく機能します。 Docker導入ガイドAzureガイド、およびAWSガイドでは、各プラットフォームの環境固有の設定について説明しています。

ご注意Adobe Acrobat、Mindee、PDFSharp、Tesseract、およびiTextは、それぞれの所有者の登録商標です。 このサイトはAdobe Inc.、Google、Mindee、empira Software GmbH、またはiText Groupと提携、承認、または支援されていません。すべての製品名、ロゴ、およびブランドはそれぞれの所有者の財産です。 比較は情報提供のみを目的としており、執筆時点で公開されている情報を反映しています。

よくある質問

なぜMindee OCR APIからIronOCRに移行する必要があるのですか?

一般的な推進要因には、COM相互接続の複雑さの解消、ファイルベースのライセンス管理の置き換え、ページ単位の課金の回避、Docker/コンテナデプロイの有効化、標準的な.NETツールと統合するNuGetネイティブワークフローの採用などがあります。

Mindee OCR APIからIronOCRに移行する際の主なコード変更は何ですか?

Mindeeの初期化シーケンスをIronTesseractのインスタンス化に置き換え、COMのライフサイクル管理(明示的なCreate/Load/Closeパターン)を削除し、結果のプロパティ名を更新します。その結果、定型的な行が大幅に減りました。

IronOCRをインストールして移行を開始するにはどうすればいいですか?

パッケージマネージャーコンソールで'Install-Package IronOcr'を実行するか、CLIで'dotnet add package IronOcr'を実行してください。言語パックは別のパッケージです:例えばフランス語の場合は'dotnet add package IronOcr.Languages.French'となります。

IronOCRは標準的なビジネス文書のMindee OCR APIのOCR精度に匹敵しますか?

IronOCRは、請求書、契約書、領収書、入力フォームなどの標準的なビジネスコンテンツで高い精度を達成しています。画像前処理フィルター(傾き補正、ノイズ除去、コントラスト強調)により、劣化した入力の認識をさらに向上させます。

IronOCR は Mindee OCR API が別途インストールする言語データをどのように扱うのですか?

IronOCRの言語データはNuGetパッケージとして配布されます。'dotnet add package IronOcr.Languages.German'でドイツ語のサポートがインストールされます。手動でのファイル配置やディレクトリパスは必要ありません。

Mindee OCR APIからIronOCRに移行する場合、導入インフラの変更が必要ですか?

IronOCRはMindee OCR APIよりもインフラストラクチャの変更が少なくて済みます。SDKのバイナリパス、ライセンスファイルの配置、ライセンスサーバーの設定は必要ありません。NuGetパッケージには完全なOCRエンジンが含まれており、ライセンスキーはアプリケーションコードに設定された文字列です。

移行後のIronOCRライセンスの設定方法は?

アプリケーションの起動コードでIronOcr.License.LicenseKey = "YOUR-KEY "を割り当てます。DockerまたはKubernetesでは、キーを環境変数として保存し、スタートアップで読み込む。License.IsValidLicenseを使用して、トラフィックを受け入れる前にバリデーションを行う。

IronOCRはMindeeと同じようにPDFを処理できますか?

IronOCRはネイティブPDFもスキャンしたPDFも読み取ります。IronTesseractをインスタンス化し、ocr.Read(input)を呼び出し、入力はPDFパスまたはOcrPdfInputであり、OcrResultページを反復します。別のPDFレンダリングパイプラインは必要ありません。

IronOCRはどのように大量処理のスレッディングを処理するのですか?

IronTesseractはスレッド毎にインスタンス化しても安全です。Parallel.ForEachまたはタスクプールでスレッドごとにインスタンスを1つスピンアップし、OCRを同時に実行し、完了したら各インスタンスを破棄します。グローバルステートやロックは必要ありません。

IronOCRはテキスト抽出後、どのような出力形式をサポートしていますか?

IronOCRはテキスト、単語座標、信頼度スコア、ページ構造を含む構造化された結果を返します。エクスポートオプションには、プレーンテキスト、検索可能なPDF、下流処理用の構造化結果オブジェクトが含まれます。

IronOCRの価格はMindee OCR APIよりもワークロードのスケーリングが予測できますか?

IronOCRは、定額制の永久ライセンスを採用しており、ページ単位やボリューム単位の料金はかかりません。10,000ページでも1,000万ページでも、ライセンスコストは一定です。ボリュームライセンスとチームライセンスのオプションはIronOCRの価格ページをご覧ください。

Mindee OCR APIからIronOCRに移行した後、既存のテストはどうなりますか?

抽出されたテキストコンテンツをアサートするテストは、移行後もパスし続ける必要があります。APIコールパターンやCOMオブジェクトのライフサイクルを検証するテストは、IronOCRのシンプルな初期化と結果モデルを反映するように更新する必要があります。

Kannaopat Udonpant
ソフトウェアエンジニア
ソフトウェアエンジニアになる前に、Kannapatは北海道大学で環境資源の博士号を修了しました。博士号を追求する間に、彼はバイオプロダクションエンジニアリング学科の一部である車両ロボティクスラボラトリーのメンバーになりました。2022年には、C#のスキルを活用してIron Softwareのエンジニアリングチームに参加し、IronPDFに注力しています。Kannapatは、IronPDFの多くのコードを執筆している開発者から直接学んでいるため、この仕事を大切にしています。同僚から学びながら、Iron Softwareでの働く社会的側面も楽しんでいます。コードやドキュメントを書いていない時は、KannapatはPS5でゲームをしたり、『The Last of Us』を再視聴したりしていることが多いです。

アイアンサポートチーム

私たちは週5日、24時間オンラインで対応しています。
チャット
メール
電話してね