從Veryfi遷移到IronOCR
這份指南為 .NET 開發人員提供如何用 IronOCR,一個當地 OCR 程式庫,取代Veryfi的雲端文件處理 API 的方法。 它涵蓋了包裹交換、命名空間清理,以及四個完整程式碼遷移的例子,專注於Veryfi最常見的模式:客戶端初始化、基於區域的欄位提取、以結構化資料進行消費分類和 webhook 替換。 不需要閱讀比較文章。
為何從Veryfi遷移
財務文件通過Veryfi的管道向一個方向流動:從您的基礎設施到他們的基礎設施。 這一架構事實使得大多數遷移成為可能。 以下是推動團隊轉換的具體痛點。
每次文件呼叫都會將敏感的財務資料傳輸到第三方伺服器。 收據帶有卡片的最後四位數字和供應商關係。 發票包含銀行賬戶號、路由號和供應商納稅識別號。 銀行對賬單包含完整的交易歷史。 有了 Veryfi,每一個 ProcessDocumentAsync 呼叫都會將這些字節上傳至 api.veryfi.com,在Veryfi的基礎設施上處理,然後返回 JSON。 一旦 HTTP 請求發送,您對資料的控制就結束了。
需要四個憑據,並且需要在每個環境中保持同步。 VeryfiClient 需要 username 和 apiKey - 四個需要儲存在配置中的獨立秘密、按計劃旋轉、注入 CI/CD 管道並進行暴露審計。 單一的憑據洩露會破壞整個應用程式中所有文件的身份驗證。IronOCR需要一個授權金鑰字串。
每文件的定價是無上限的。 每張收據約為 $0.05-0.15,發票為 $0.10-0.25,銀行對賬單為 $0.15-0.30。在每月 50,000 份文件的情況下,這在按流量計費中每月是 $5,000-15,000,第二年或第三年沒有減少。IronOCR的 Professional 授權為 $2,399,可無限期地覆蓋無限數量的文件——與每月 $5,000 的Veryfi花費相比,並不超過三週的時間。
API 僅支持異步,因為基礎工作是遠程的。 ProcessDocumentAsync 不是異步的,因為處理計算時間很長; 它是異步的,因為文件必須傳送到伺服器,在其他請求後排隊,完成推理,並通過網路返回響應。 延遲是非確定性的。 HTTP 429 負載限制需要重試邏輯。 HTTP 402 支付失敗會完全終止批量處理。Veryfi的基礎設施上的 HTTP 500 錯誤會損壞您的工作流程。
Veryfi 的文件範圍在消費文件邊界結束。 訓練的模型可靠地返回組織欄位以供收據、發票、支票、銀行對賬單、W-2和名片使用。 在該列表之外——一般商業文件、合同、醫療記錄、運輸文件、自定義內部表單——結果會降低或需要付費定制的模型訓練。 在消費自動化中採用Veryfi的組織通常會在 6-12 個月內發現,其他團隊需要 OCR 來處理Veryfi未經設計去處理的文件。
Veryfi 的專有 JSON 架構將所有提取邏輯綁定到單一供應商。 任何讀取 response.BankAccount?.RoutingNumber 或 response.LineItems 的程式碼僅適用於 Veryfi。 更換供應商——或切換到本地 OCR——意味著需要從頭重寫所有提取邏輯。
根本問題
// Veryfi: financial data leaves your infrastructure on every call
var client = new VeryfiClient(clientId, clientSecret, username, apiKey); // 4 secrets
var bytes = File.ReadAllBytes("invoice-with-routing-number.pdf");
var response = await client.ProcessDocumentAsync(bytes); // bank details transmitted
var routingNumber = response.BankAccount?.RoutingNumber; // arrived viaVeryficloud' Veryfi: financial data leaves your infrastructure on every call
Dim client As New VeryfiClient(clientId, clientSecret, username, apiKey) ' 4 secrets
Dim bytes = File.ReadAllBytes("invoice-with-routing-number.pdf")
Dim response = Await client.ProcessDocumentAsync(bytes) ' bank details transmitted
Dim routingNumber = response.BankAccount?.RoutingNumber ' arrived viaVeryficloud// IronOCR: routing numbers never leave your server
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; // 1 key
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadPdf("invoice-with-routing-number.pdf"); // processed locally
var result = ocr.Read(input);
var routingNumber = Regex.Match(result.Text, @"Routing\s*#?\s*:?\s*(\d{9})").Groups[1].Value;IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" ' 1 key
Dim ocr = New IronTesseract()
Using input = New OcrInput()
input.LoadPdf("invoice-with-routing-number.pdf") ' processed locally
Dim result = ocr.Read(input)
Dim routingNumber = Regex.Match(result.Text, "Routing\s*#?\s*:?\s*(\d{9})").Groups(1).Value
End UsingIronOCR與 Veryfi:功能比較
下表將產品的能力進行對映,以支援技術評估。
| 功能 | Veryfi | IronOCR |
|---|---|---|
| 處理位置 | Veryfi雲伺服器 | 您自己的基礎設施 |
| 部署模型 | 僅雲API | 內部部署、Docker、Azure、AWS、Linux |
| 脫機支持 | 不是 | 是 |
| 需要網際網路 | 是的(每個文件) | 不是 |
| 資料離開基礎設施 | 是的(每次通話) | 絕不需要 |
| 相容 HIPAA 而不需要 BAA | 不是 | 是 |
| 氣隙環境支持 | 無法實現 | 全面支持 |
| 價格模型 | 每文件 ($0.05-0.30) | 永久授權 ($999–$2,399) |
| 需要憑據 | 4 (clientId, clientSecret, username, apiKey) | 1個授權金鑰 |
| 同步 API | 否 (僅支持異步) | 是 |
| 速率限制 | 是 (HTTP 429) | None |
| 文件範圍 | 收據、發票、支票、銀行對賬單、W-2、名片 | 任何文件型別 |
| 自定義文件型別 | 需要付費模型訓練 | 任何布局通過正則表達式/模式提取 |
| PDF輸入 | 是 (字節上傳) | 是的(本地,內生) |
| 可搜尋的PDF輸出 | 不是 | 是 (result.SaveAsSearchablePdf()) |
| 基於區域的OCR | 不是 | 是 (CropRectangle) |
| 條碼識別 | 不是 | 是 (相同的 OCR 通過) |
| 結構化結果存取 | 預解析的 JSON 欄位 | 頁面、段落、行、單詞帶座標 |
| 信心評估 | 每欄 (專有) | 每字和整體 (result.Confidence) |
| 125+語言支持 | 有限 | 是 (NuGet 語言包) |
| 執行緒安全的並行處理 | HTTP 並發限制適用 | 完整 (每執行緒一個 IronTesseract) |
| 無需模擬的單元測試 | 需要 HTTP 模擬 | 直接本地測試 |
快速開始:Veryfi 到IronOCR遷移
步驟1:替換NuGet包
移除VeryfiSDK:
dotnet remove package Veryfi
從NuGet安裝IronOCR:
步驟2:更新命名空間
將Veryfi的命名空間替換為IronOCR的命名空間:
// Before (Veryfi)
using Veryfi;
using Veryfi.Models;
// After (IronOCR)
using IronOcr;
using System.Text.RegularExpressions;' Before (Veryfi)
Imports Veryfi
Imports Veryfi.Models
' After (IronOCR)
Imports IronOcr
Imports System.Text.RegularExpressions步驟3:初始化許可證
在應用程式啟動時新增此行,在任何 OCR 呼叫之前:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"程式碼遷移範例
文件處理客戶端替換
Veryfi 的服務構建在 VeryfiClient 的構造函式注入之上。 四憑據構造函式是依賴注入的自然接口,但它創造了四個需要被管理和旋轉的秘密。 用IronOCR替換這個將憑據統合為一個授權金鑰,並將處理引擎的實例化移至服務類中。
Veryfi 方法:
using Veryfi;
using Microsoft.Extensions.Configuration;
public class ExpenseDocumentService
{
private readonly VeryfiClient _client;
// Four credentials injected — four secrets to manage, store, rotate
public ExpenseDocumentService(IConfiguration config)
{
_client = new VeryfiClient(
config["Veryfi:ClientId"], // secret 1
config["Veryfi:ClientSecret"], // secret 2
config["Veryfi:Username"], // secret 3
config["Veryfi:ApiKey"] // secret 4
);
}
public async Task<string> GetVendorNameAsync(string documentPath)
{
var bytes = File.ReadAllBytes(documentPath);
// Document uploaded toVeryfion this call
var response = await _client.ProcessDocumentAsync(bytes);
return response.Vendor?.Name;
}
public async Task<decimal?> GetTotalAsync(string documentPath)
{
var bytes = File.ReadAllBytes(documentPath);
var response = await _client.ProcessDocumentAsync(bytes);
return response.Total;
}
}Imports Veryfi
Imports Microsoft.Extensions.Configuration
Public Class ExpenseDocumentService
Private ReadOnly _client As VeryfiClient
' Four credentials injected — four secrets to manage, store, rotate
Public Sub New(config As IConfiguration)
_client = New VeryfiClient(config("Veryfi:ClientId"), ' secret 1
config("Veryfi:ClientSecret"), ' secret 2
config("Veryfi:Username"), ' secret 3
config("Veryfi:ApiKey")) ' secret 4
End Sub
Public Async Function GetVendorNameAsync(documentPath As String) As Task(Of String)
Dim bytes = File.ReadAllBytes(documentPath)
' Document uploaded toVeryfion this call
Dim response = Await _client.ProcessDocumentAsync(bytes)
Return response.Vendor?.Name
End Function
Public Async Function GetTotalAsync(documentPath As String) As Task(Of Decimal?)
Dim bytes = File.ReadAllBytes(documentPath)
Dim response = Await _client.ProcessDocumentAsync(bytes)
Return response.Total
End Function
End ClassIronOCR方法:
using IronOcr;
using System.Text.RegularExpressions;
public class ExpenseDocumentService
{
private readonly IronTesseract _ocr;
// One license key — set once at startup, not per-instance
public ExpenseDocumentService()
{
_ocr = new IronTesseract();
}
public string GetVendorName(string documentPath)
{
// All processing local — document bytes never leave this server
var result = _ocr.Read(documentPath);
// Vendor is typically the first non-whitespace line on a receipt
return result.Pages[0].Paragraphs
.OrderBy(p => p.Y)
.Select(p => p.Text.Trim())
.FirstOrDefault(t => t.Length > 3);
}
public decimal? GetTotal(string documentPath)
{
var result = _ocr.Read(documentPath);
var match = Regex.Match(result.Text,
@"(?:Total|Grand Total|Amount Due):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
return match.Success
? decimal.Parse(match.Groups[1].Value.Replace(",", ""))
: (decimal?)null;
}
}Imports IronOcr
Imports System.Text.RegularExpressions
Public Class ExpenseDocumentService
Private ReadOnly _ocr As IronTesseract
' One license key — set once at startup, not per-instance
Public Sub New()
_ocr = New IronTesseract()
End Sub
Public Function GetVendorName(documentPath As String) As String
' All processing local — document bytes never leave this server
Dim result = _ocr.Read(documentPath)
' Vendor is typically the first non-whitespace line on a receipt
Return result.Pages(0).Paragraphs _
.OrderBy(Function(p) p.Y) _
.Select(Function(p) p.Text.Trim()) _
.FirstOrDefault(Function(t) t.Length > 3)
End Function
Public Function GetTotal(documentPath As String) As Decimal?
Dim result = _ocr.Read(documentPath)
Dim match = Regex.Match(result.Text, "(?:Total|Grand Total|Amount Due):?\s*\$?\s*([\d,]+\.\d{2})", RegexOptions.IgnoreCase)
Return If(match.Success, Decimal.Parse(match.Groups(1).Value.Replace(",", "")), CType(Nothing, Decimal?))
End Function
End Class構造函式的更改消除了每個環境中的四個配置條目:appsettings.json,Docker 秘密,Azure 金鑰保管庫引用和 CI/CD 管道變數。 IronTesseract 實例可在相同執行緒上的多個呼叫中重用。 請參考 IronTesseract 安裝指南 以瞭解 ASP.NET Core 依賴注入容器中的單例註冊模式。
基於區域的 OCR 收據欄位提取
Veryfi 通過在整個文件圖像上運行其訓練的機器學習模型並返回預結構化的 JSON 響應來提取收據欄位。IronOCR的等效方法是使用 CropRectangle 的區域基 OCR,它針對收據圖像的特定區域——供應商的標頭區域,總計的腳註區域——而不是運行整頁傳遞並在輸出中搜尋模式。 這對於已知的佈局來說更快,當關注區域定義良好時,更加準確。
Veryfi 方法:
using Veryfi;
public class ReceiptFieldExtractor
{
private readonly VeryfiClient _client;
public ReceiptFieldExtractor(VeryfiClient client)
{
_client = client;
}
public async Task<(string Vendor, decimal? Total, decimal? Tax)>
ExtractReceiptFieldsAsync(string imagePath)
{
var bytes = File.ReadAllBytes(imagePath);
// Full document uploaded — Veryfi's ML returns structured fields
var response = await _client.ProcessDocumentAsync(bytes);
return (
Vendor: response.Vendor?.Name,
Total: response.Total,
Tax: response.Tax
);
}
}Imports Veryfi
Public Class ReceiptFieldExtractor
Private ReadOnly _client As VeryfiClient
Public Sub New(client As VeryfiClient)
_client = client
End Sub
Public Async Function ExtractReceiptFieldsAsync(imagePath As String) As Task(Of (Vendor As String, Total As Decimal?, Tax As Decimal?))
Dim bytes = File.ReadAllBytes(imagePath)
' Full document uploaded — Veryfi's ML returns structured fields
Dim response = Await _client.ProcessDocumentAsync(bytes)
Return (Vendor:=response.Vendor?.Name,
Total:=response.Total,
Tax:=response.Tax)
End Function
End ClassIronOCR方法:
using IronOcr;
using System.Text.RegularExpressions;
public class ReceiptFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string Vendor, decimal? Total, decimal? Tax)
ExtractReceiptFields(string imagePath)
{
// Region 1: Header zone — vendor name typically in top 15% of receipt
var headerRegion = new CropRectangle(0, 0, 800, 150);
using var headerInput = new OcrInput();
headerInput.LoadImage(imagePath, headerRegion);
headerInput.Deskew();
var headerResult = _ocr.Read(headerInput);
// Region 2: Footer zone — totals typically in bottom 20% of receipt
var footerRegion = new CropRectangle(0, 650, 800, 200);
using var footerInput = new OcrInput();
footerInput.LoadImage(imagePath, footerRegion);
footerInput.DeNoise();
var footerResult = _ocr.Read(footerInput);
var vendor = headerResult.Pages[0].Paragraphs
.OrderBy(p => p.Y)
.Select(p => p.Text.Trim())
.FirstOrDefault(t => t.Length > 3);
var footerText = footerResult.Text;
var totalMatch = Regex.Match(footerText,
@"(?:Total|Grand Total):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var taxMatch = Regex.Match(footerText,
@"(?:Tax|Sales Tax|VAT):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
return (
Vendor: vendor,
Total: totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: (decimal?)null,
Tax: taxMatch.Success
? decimal.Parse(taxMatch.Groups[1].Value.Replace(",", ""))
: (decimal?)null
);
}
}Imports IronOcr
Imports System.Text.RegularExpressions
Public Class ReceiptFieldExtractor
Private ReadOnly _ocr As New IronTesseract()
Public Function ExtractReceiptFields(imagePath As String) As (Vendor As String, Total As Decimal?, Tax As Decimal?)
' Region 1: Header zone — vendor name typically in top 15% of receipt
Dim headerRegion = New CropRectangle(0, 0, 800, 150)
Using headerInput = New OcrInput()
headerInput.LoadImage(imagePath, headerRegion)
headerInput.Deskew()
Dim headerResult = _ocr.Read(headerInput)
' Region 2: Footer zone — totals typically in bottom 20% of receipt
Dim footerRegion = New CropRectangle(0, 650, 800, 200)
Using footerInput = New OcrInput()
footerInput.LoadImage(imagePath, footerRegion)
footerInput.DeNoise()
Dim footerResult = _ocr.Read(footerInput)
Dim vendor = headerResult.Pages(0).Paragraphs _
.OrderBy(Function(p) p.Y) _
.Select(Function(p) p.Text.Trim()) _
.FirstOrDefault(Function(t) t.Length > 3)
Dim footerText = footerResult.Text
Dim totalMatch = Regex.Match(footerText,
"(?:Total|Grand Total):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase)
Dim taxMatch = Regex.Match(footerText,
"(?:Tax|Sales Tax|VAT):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase)
Return (
Vendor := vendor,
Total := If(totalMatch.Success, Decimal.Parse(totalMatch.Groups(1).Value.Replace(",", "")), CType(Nothing, Decimal?)),
Tax := If(taxMatch.Success, Decimal.Parse(taxMatch.Groups(1).Value.Replace(",", "")), CType(Nothing, Decimal?))
)
End Using
End Using
End Function
End ClassCropRectangle 以像素為單位接收 (x, y, width, height)。 僅處理標頭和腳註區域比整頁閱讀更快,並避免收據主體中的行項目金額的錯誤匹配。 區域基 OCR 指南 涵蓋了針對可變大小文件的坐標測量策略,區域裁剪範例展示了整個模式。
使用結構化段落資料進行消費分類
Veryfi 以預結構化物件陣列的形式返回 response.LineItems,其中 UnitPrice 和 Total 已經被解析。IronOCR提供等效資訊通過 result.Pages[0].Paragraphs 和 result.Lines,它們以 X/Y 坐標暴露每個文字塊。 消費分類邏輯——決定某行項目是膳食、旅行、供應品還是軟體收費——在相同的文字上運行。 區別在於使用 IronOCR,您可以控制、調整和擴展分類邏輯而無需付費進行機器學習重訓練。
Veryfi 方法:
using Veryfi;
public class ExpenseCategorizer
{
private readonly VeryfiClient _client;
public ExpenseCategorizer(VeryfiClient client)
{
_client = client;
}
public async Task<Dictionary<string, decimal>> CategorizeExpensesAsync(string receiptPath)
{
var bytes = File.ReadAllBytes(receiptPath);
var response = await _client.ProcessDocumentAsync(bytes);
var categories = new Dictionary<string, decimal>();
// Line items arrive pre-parsed from Veryfi's ML pipeline
foreach (var item in response.LineItems ?? Enumerable.Empty<dynamic>())
{
var category = response.Category ?? "Uncategorized";
var amount = (decimal)(item.Total ?? 0m);
if (!categories.ContainsKey(category))
categories[category] = 0m;
categories[category] += amount;
}
return categories;
}
}Imports Veryfi
Public Class ExpenseCategorizer
Private ReadOnly _client As VeryfiClient
Public Sub New(client As VeryfiClient)
_client = client
End Sub
Public Async Function CategorizeExpensesAsync(receiptPath As String) As Task(Of Dictionary(Of String, Decimal))
Dim bytes = File.ReadAllBytes(receiptPath)
Dim response = Await _client.ProcessDocumentAsync(bytes)
Dim categories = New Dictionary(Of String, Decimal)()
' Line items arrive pre-parsed from Veryfi's ML pipeline
For Each item In If(response.LineItems, Enumerable.Empty(Of Dynamic)())
Dim category = If(response.Category, "Uncategorized")
Dim amount = CType(If(item.Total, 0D), Decimal)
If Not categories.ContainsKey(category) Then
categories(category) = 0D
End If
categories(category) += amount
Next
Return categories
End Function
End ClassIronOCR方法:
using IronOcr;
using System.Text.RegularExpressions;
public class ExpenseCategorizer
{
private readonly IronTesseract _ocr = new IronTesseract();
// Keyword-based categorization — tune these for your expense policy
private static readonly Dictionary<string, string[]> CategoryKeywords = new()
{
["Meals & Entertainment"] = new[] { "restaurant", "cafe", "coffee", "lunch", "dinner", "food", "bar" },
["Travel"] = new[] { "airline", "hotel", "uber", "lyft", "taxi", "parking", "gas", "fuel" },
["Office Supplies"] = new[] { "staples", "office depot", "paper", "ink", "toner", "supplies" },
["Software & Subscriptions"] = new[] { "adobe", "microsoft", "github", "aws", "azure", "slack" }
};
public Dictionary<string, decimal> CategorizeExpenses(string receiptPath)
{
var result = _ocr.Read(receiptPath);
// Use paragraph coordinates to isolate line items
// Line items typically appear in the middle vertical band of the receipt
var lineItemParagraphs = result.Pages[0].Paragraphs
.Where(p => p.Y > 150 && p.Y < 650) // skip header/footer regions
.OrderBy(p => p.Y)
.ToList();
var categories = new Dictionary<string, decimal>();
var pricePattern = new Regex(@"\$?([\d,]+\.\d{2})$");
var vendorText = result.Text.ToLower();
// Determine top-level category from vendor name
var topCategory = "Uncategorized";
foreach (var (cat, keywords) in CategoryKeywords)
{
if (keywords.Any(kw => vendorText.Contains(kw)))
{
topCategory = cat;
break;
}
}
// Extract individual line item amounts
foreach (var para in lineItemParagraphs)
{
var priceMatch = pricePattern.Match(para.Text.Trim());
if (!priceMatch.Success)
continue;
if (!decimal.TryParse(priceMatch.Groups[1].Value.Replace(",", ""), out var amount))
continue;
// Classify individual items where keywords appear in the description
var itemCategory = topCategory;
var descriptionText = para.Text.ToLower();
foreach (var (cat, keywords) in CategoryKeywords)
{
if (keywords.Any(kw => descriptionText.Contains(kw)))
{
itemCategory = cat;
break;
}
}
if (!categories.ContainsKey(itemCategory))
categories[itemCategory] = 0m;
categories[itemCategory] += amount;
}
return categories;
}
}Imports IronOcr
Imports System.Text.RegularExpressions
Public Class ExpenseCategorizer
Private ReadOnly _ocr As New IronTesseract()
' Keyword-based categorization — tune these for your expense policy
Private Shared ReadOnly CategoryKeywords As New Dictionary(Of String, String()) From {
{"Meals & Entertainment", New String() {"restaurant", "cafe", "coffee", "lunch", "dinner", "food", "bar"}},
{"Travel", New String() {"airline", "hotel", "uber", "lyft", "taxi", "parking", "gas", "fuel"}},
{"Office Supplies", New String() {"staples", "office depot", "paper", "ink", "toner", "supplies"}},
{"Software & Subscriptions", New String() {"adobe", "microsoft", "github", "aws", "azure", "slack"}}
}
Public Function CategorizeExpenses(receiptPath As String) As Dictionary(Of String, Decimal)
Dim result = _ocr.Read(receiptPath)
' Use paragraph coordinates to isolate line items
' Line items typically appear in the middle vertical band of the receipt
Dim lineItemParagraphs = result.Pages(0).Paragraphs _
.Where(Function(p) p.Y > 150 AndAlso p.Y < 650) ' skip header/footer regions
.OrderBy(Function(p) p.Y).ToList()
Dim categories = New Dictionary(Of String, Decimal)()
Dim pricePattern = New Regex("\$?([\d,]+\.\d{2})$")
Dim vendorText = result.Text.ToLower()
' Determine top-level category from vendor name
Dim topCategory = "Uncategorized"
For Each kvp In CategoryKeywords
Dim cat = kvp.Key
Dim keywords = kvp.Value
If keywords.Any(Function(kw) vendorText.Contains(kw)) Then
topCategory = cat
Exit For
End If
Next
' Extract individual line item amounts
For Each para In lineItemParagraphs
Dim priceMatch = pricePattern.Match(para.Text.Trim())
If Not priceMatch.Success Then
Continue For
End If
Dim amount As Decimal
If Not Decimal.TryParse(priceMatch.Groups(1).Value.Replace(",", ""), amount) Then
Continue For
End If
' Classify individual items where keywords appear in the description
Dim itemCategory = topCategory
Dim descriptionText = para.Text.ToLower()
For Each kvp In CategoryKeywords
Dim cat = kvp.Key
Dim keywords = kvp.Value
If keywords.Any(Function(kw) descriptionText.Contains(kw)) Then
itemCategory = cat
Exit For
End If
Next
If Not categories.ContainsKey(itemCategory) Then
categories(itemCategory) = 0D
End If
categories(itemCategory) += amount
Next
Return categories
End Function
End ClassParagraphs 集合提供了每個文字塊的 Y 坐標,使其能夠輕鬆隔離出現在標準收據佈局中的行項目所在的垂直區域。 結構化資料存取指南 解釋了 Words 和 Characters 的完整層次結構及其坐標屬性。 對於掃描質量差的收據——皺巴巴的紙張,低對比度的熱轉印列印——圖像質量校正指南 涵蓋了能夠在分類邏輯運行之前提升準確度的預處理過濾器。
Webhook 消除和同步批次替換
在高文件量的情況下,Veryfi 建議使用基於 webhook 的通知,而不是輪詢。 該模式需要一個公開可存取的 HTTPS 端點、一個 webhook 秘密進行簽名驗證、一個隊列以保存結果直到 webhook 觸發以及丟失交付的重試邏輯。 這對於最終僅是雲端 OCR 相對於本地處理緩慢的事實的權宜之計來說,已是顯著的基礎設施。IronOCR以同步方式處理。 沒有需要通過 webhook 連結的異步間隙。
Veryfi 方法:
using Veryfi;
using Microsoft.AspNetCore.Mvc;
//Veryfiwebhook receiver — required for high-volume reliable processing
[ApiController]
[Route("webhooks")]
public class VeryfiWebhookController : ControllerBase
{
private readonly IDocumentResultQueue _queue;
public VeryfiWebhookController(IDocumentResultQueue queue)
{
_queue = queue;
}
[HttpPost("veryfi")]
public IActionResult ReceiveWebhook([FromBody] VeryfiWebhookPayload payload,
[FromHeader(Name = "X-Veryfi-Token")] string token)
{
// Validate webhook signature — prevents spoofed payloads
if (!IsValidSignature(token, payload))
return Unauthorized();
// Enqueue result for async downstream consumption
_queue.Enqueue(new DocumentResult
{
DocumentId = payload.Id,
Vendor = payload.Data?.Vendor?.Name,
Total = payload.Data?.Total
});
return Ok();
}
private bool IsValidSignature(string token, VeryfiWebhookPayload payload) =>
// HMAC validation against webhook secret — infrastructure requirement
token == ComputeHmac(payload, Environment.GetEnvironmentVariable("VERYFI_WEBHOOK_SECRET"));
}
// Document batch submission — fire and forget, results arrive via webhook
public class VeryfiDocumentBatchSubmitter
{
private readonly VeryfiClient _client;
public async Task SubmitBatchAsync(string[] documentPaths)
{
foreach (var path in documentPaths)
{
var bytes = File.ReadAllBytes(path);
// Submit — result arrives asynchronously via webhook, not here
await _client.ProcessDocumentAsync(bytes);
}
}
}Imports Veryfi
Imports Microsoft.AspNetCore.Mvc
'Veryfiwebhook receiver — required for high-volume reliable processing
<ApiController>
<Route("webhooks")>
Public Class VeryfiWebhookController
Inherits ControllerBase
Private ReadOnly _queue As IDocumentResultQueue
Public Sub New(queue As IDocumentResultQueue)
_queue = queue
End Sub
<HttpPost("veryfi")>
Public Function ReceiveWebhook(<FromBody> payload As VeryfiWebhookPayload,
<FromHeader(Name:="X-Veryfi-Token")> token As String) As IActionResult
' Validate webhook signature — prevents spoofed payloads
If Not IsValidSignature(token, payload) Then Return Unauthorized()
' Enqueue result for async downstream consumption
_queue.Enqueue(New DocumentResult With {
.DocumentId = payload.Id,
.Vendor = payload.Data?.Vendor?.Name,
.Total = payload.Data?.Total
})
Return Ok()
End Function
Private Function IsValidSignature(token As String, payload As VeryfiWebhookPayload) As Boolean
' HMAC validation against webhook secret — infrastructure requirement
Return token = ComputeHmac(payload, Environment.GetEnvironmentVariable("VERYFI_WEBHOOK_SECRET"))
End Function
End Class
' Document batch submission — fire and forget, results arrive via webhook
Public Class VeryfiDocumentBatchSubmitter
Private ReadOnly _client As VeryfiClient
Public Async Function SubmitBatchAsync(documentPaths As String()) As Task
For Each path As String In documentPaths
Dim bytes = File.ReadAllBytes(path)
' Submit — result arrives asynchronously via webhook, not here
Await _client.ProcessDocumentAsync(bytes)
Next
End Function
End ClassIronOCR方法:
using IronOcr;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;
//不是webhook controller needed — results are synchronous and local
public class DocumentBatchProcessor
{
// IronTesseract is thread-safe when one instance is created per thread
public List<DocumentResult> ProcessBatch(string[] documentPaths)
{
var results = new ConcurrentBag<DocumentResult>();
Parallel.ForEach(documentPaths, documentPath =>
{
// One IronTesseract per thread — thread-safe pattern
var ocr = new IronTesseract();
var result = ocr.Read(documentPath);
results.Add(new DocumentResult
{
FilePath = documentPath,
Vendor = ExtractVendor(result),
Total = ExtractTotal(result.Text),
Confidence = result.Confidence,
// Result is available immediately — no queue, no webhook
ProcessedAt = DateTime.UtcNow
});
});
return results.OrderBy(r => r.FilePath).ToList();
}
private string ExtractVendor(OcrResult result)
{
// Vendor: first substantive paragraph ordered by vertical position
return result.Pages[0].Paragraphs
.OrderBy(p => p.Y)
.Select(p => p.Text.Trim())
.FirstOrDefault(t => !string.IsNullOrWhiteSpace(t) && t.Length > 3);
}
private decimal? ExtractTotal(string text)
{
var match = Regex.Match(text,
@"(?:Total|Grand Total|Amount Due):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
return match.Success
? decimal.Parse(match.Groups[1].Value.Replace(",", ""))
: (decimal?)null;
}
}
public class DocumentResult
{
public string FilePath { get; set; }
public string Vendor { get; set; }
public decimal? Total { get; set; }
public double Confidence { get; set; }
public DateTime ProcessedAt { get; set; }
}Imports IronOcr
Imports System.Text.RegularExpressions
Imports System.Collections.Concurrent
'不是webhook controller needed — results are synchronous and local
Public Class DocumentBatchProcessor
' IronTesseract is thread-safe when one instance is created per thread
Public Function ProcessBatch(documentPaths As String()) As List(Of DocumentResult)
Dim results = New ConcurrentBag(Of DocumentResult)()
Parallel.ForEach(documentPaths, Sub(documentPath)
' One IronTesseract per thread — thread-safe pattern
Dim ocr = New IronTesseract()
Dim result = ocr.Read(documentPath)
results.Add(New DocumentResult With {
.FilePath = documentPath,
.Vendor = ExtractVendor(result),
.Total = ExtractTotal(result.Text),
.Confidence = result.Confidence,
' Result is available immediately — no queue, no webhook
.ProcessedAt = DateTime.UtcNow
})
End Sub)
Return results.OrderBy(Function(r) r.FilePath).ToList()
End Function
Private Function ExtractVendor(result As OcrResult) As String
' Vendor: first substantive paragraph ordered by vertical position
Return result.Pages(0).Paragraphs _
.OrderBy(Function(p) p.Y) _
.Select(Function(p) p.Text.Trim()) _
.FirstOrDefault(Function(t) Not String.IsNullOrWhiteSpace(t) AndAlso t.Length > 3)
End Function
Private Function ExtractTotal(text As String) As Decimal?
Dim match = Regex.Match(text,
"(?:Total|Grand Total|Amount Due):?\s*\$?\s*([\d,]+\.\d{2})",
RegexOptions.IgnoreCase)
Return If(match.Success, Decimal.Parse(match.Groups(1).Value.Replace(",", "")), CType(Nothing, Decimal?))
End Function
End Class
Public Class DocumentResult
Public Property FilePath As String
Public Property Vendor As String
Public Property Total As Decimal?
Public Property Confidence As Double
Public Property ProcessedAt As DateTime
End Class移除 webhook 層次將消除 HTTPS 端點、webhook 秘密輪轉要求、結果隊列、HMAC 驗證邏輯和重試配置。 整個下游管道存在的唯一原因是Veryfi的結果異步來自遠程伺服器。 有了 IronOCR,Parallel.ForEach 取代了所有一切。 多執行緒範例 詳細演示了每執行緒 IronTesseract 模式,異步 OCR 指南 涵蓋了 Task.Run 整合以實現 UI 響應。 速度優化指南 涵蓋了批量工作負載中實例配置以達到最大吞吐量。
VeryfiAPI 到IronOCR對映參考
| Veryfi | IronOCR 等效 |
|---|---|
new VeryfiClient(clientId, clientSecret, username, apiKey) | new IronTesseract() + IronOcr.License.LicenseKey = "key" |
_client.ProcessDocumentAsync(bytes) | ocr.Read(filePath) 或 ocr.Read(ocrInput) |
_client.ProcessDocumentAsync(bytes, categories: new[] { "invoices" }) | input.LoadPdf(path); ocr.Read(input) |
_client.ProcessDocumentAsync(bytes, categories: new[] { "bank_statements" }) | input.LoadPdf(path); ocr.Read(input) |
response.Vendor?.Name | 第一段按 p.Y 排序,來自 result.Pages[0].Paragraphs |
response.Total | Regex.Match(result.Text, @"Total:?\s*\$?([\d,]+\.\d{2})") |
response.Tax | Regex.Match(result.Text, @"Tax:?\s*\$?([\d,]+\.\d{2})") |
response.Date | Regex.Match(result.Text, @"\d{1,2}/\d{1,2}/\d{4}") |
response.LineItems | result.Pages[0].Paragraphs 按 Y 坐標範圍過濾 |
response.InvoiceNumber | Regex.Match(result.Text, @"Invoice\s*#?\s*:?\s*(\w+[-\w]*)") |
response.BankAccount?.AccountNumber | Regex.Match(result.Text, @"Account\s*#?\s*:?\s*(\d{4,})") |
response.BankAccount?.RoutingNumber | Regex.Match(result.Text, @"Routing\s*#?\s*:?\s*(\d{9})") |
response.ConfidenceScore | result.Confidence (整體) 或 word.Confidence (每字) |
response.Payment?.Last4 | Regex.Match(result.Text, @"\*{4}\s*(\d{4})") |
VeryfiApiException (401/402/429/500) | 標準 .NET 異常——當地處理無 HTTP 錯誤程式碼 |
| 上傳前Base64編碼 | 不需要——ocr.Read(filePath) 直接接受文件路徑 |
response.Category | result.Text 自定義關鍵字匹配 |
| Webhook 有效負載反序列化 | 不需要——ocr.Read() 同步返回結果 |
ProcessDocumentAsync 帶重試/退避 | 不需要——當地處理無流量限制 |
常見的遷移問題与解決方案
問題 1:預解析欄位缺失
Veryfi: response.LineItems 作為預訓練機器學習模型中的結構欄位到達。 客戶端無需提取邏輯。
解決方案:為您的應用使用的每個字段編寫正則表達式模式。 遷移工作量通常需要 8-24 小時,這取決於您處理的不同文件佈局的數量。 對於常見的收據和發票模式,發票 OCR 教程 和 收據掃描教程 提供了完整的提取模式實現。
// Map eachVeryfifield to a Regex extraction
private static readonly Dictionary<string, string> FieldPatterns = new()
{
["InvoiceNumber"] = @"Invoice\s*#?\s*:?\s*(\w+[-\w]*)",
["PurchaseOrder"] = @"(?:PO|P\.O\.|Purchase Order)\s*#?\s*:?\s*(\w+)",
["DueDate"] = @"Due\s*(?:Date)?:?\s*(\d{1,2}/\d{1,2}/\d{4})",
["PaymentTerms"] = @"(?:Terms|Net)\s*:?\s*(\w+\s*\d+)"
};
public string ExtractField(string text, string fieldName)
{
if (!FieldPatterns.TryGetValue(fieldName, out var pattern))
return null;
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
return match.Success ? match.Groups[1].Value.Trim() : null;
}' Map eachVeryfifield to a Regex extraction
Private Shared ReadOnly FieldPatterns As New Dictionary(Of String, String) From {
{"InvoiceNumber", "Invoice\s*#?\s*:?\s*(\w+[-\w]*)"},
{"PurchaseOrder", "(?:PO|P\.O\.|Purchase Order)\s*#?\s*:?\s*(\w+)"},
{"DueDate", "Due\s*(?:Date)?:?\s*(\d{1,2}/\d{1,2}/\d{4})"},
{"PaymentTerms", "(?:Terms|Net)\s*:?\s*(\w+\s*\d+)"}
}
Public Function ExtractField(ByVal text As String, ByVal fieldName As String) As String
Dim pattern As String = Nothing
If Not FieldPatterns.TryGetValue(fieldName, pattern) Then
Return Nothing
End If
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
Return If(match.Success, match.Groups(1).Value.Trim(), Nothing)
End Function問題 2:整個程式碼庫的異步方法簽名
Veryfi: ProcessDocumentAsync 在VeryfiSDK 級別是異步的。 團隊通常會將 await 傳播到調用堆疊中的每個調用方法,這意味著服務類、控制器和後臺工作都帶有 async Task<t> 簽名。
解決方案:IronOCR 的 Read() 是同步的。 在過渡期間,現有的 async 方法簽名可以用 Task.Run 包裝來保留。 這避免了程式碼庫中的大規模簽名更改,同時依然消除了雲中依賴。
// Preserve async signature during transition — no codebase-wide refactor needed
public async Task<string> GetVendorNameAsync(string documentPath)
{
return await Task.Run(() =>
{
var result = _ocr.Read(documentPath);
return result.Pages[0].Paragraphs
.OrderBy(p => p.Y)
.Select(p => p.Text.Trim())
.FirstOrDefault(t => t.Length > 3);
});
}Public Async Function GetVendorNameAsync(documentPath As String) As Task(Of String)
Return Await Task.Run(Function()
Dim result = _ocr.Read(documentPath)
Return result.Pages(0).Paragraphs _
.OrderBy(Function(p) p.Y) _
.Select(Function(p) p.Text.Trim()) _
.FirstOrDefault(Function(t) t.Length > 3)
End Function)
End Function問題 3:環境中的憑據配置分散
Veryfi: 四個憑據(Veryfi:ClientId, Veryfi:ClientSecret, Veryfi:Username, Veryfi:ApiKey)出現在 appsettings.json、Docker Compose 文件中的環境變數塊、GitHub Actions 秘密、Azure 金鑰保管庫引用和 CI/CD 管道配置中。
解決方案:搜索並從每個環境中刪除所有四個憑據條目。 新增一個 IRONOCR_LICENSE_KEY 環境變數。 在啟動時載入它。
# Find allVeryficredential references
grep -r "Veryfi:ClientId\|Veryfi:ClientSecret\|Veryfi:Username\|Veryfi:ApiKey" \
--include="*.json" --include="*.yml" --include="*.yaml" --include="*.env" .
// Load from environment at startup
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")
?? throw new InvalidOperationException("IRONOCR_LICENSE_KEY not set");' Load from environment at startup
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY"), Throw New InvalidOperationException("IRONOCR_LICENSE_KEY not set"))問題 4:之前不可見的掃描質量問題
Veryfi:雲處理在機器學習推理運行之前包含伺服器端圖像增強。 低質量的收據掃描——皺巴巴的紙張、褪色的熱轉印列印、傾斜的手機照片——在字段提取之前被無聲地糾正。
解決方案:明確地應用IronOCR的預處理管道。 DeNoise() 和 Contrast() 涵蓋了大多數真實世界收據掃描質量問題。
using var input = new OcrInput();
input.LoadImage("receipt-phone-photo.jpg");
input.Deskew(); // correct rotation from angled phone capture
input.DeNoise(); // remove compression artifacts
input.Contrast(); // improve faded thermal print
input.Sharpen(); // recover edge detail
var result = _ocr.Read(input);Using input As New OcrInput()
input.LoadImage("receipt-phone-photo.jpg")
input.Deskew() ' correct rotation from angled phone capture
input.DeNoise() ' remove compression artifacts
input.Contrast() ' improve faded thermal print
input.Sharpen() ' recover edge detail
Dim result = _ocr.Read(input)
End Using圖像質量校正指南 和 圖像過濾器教程 涵蓋了針對特定掃描退化模式應用哪種過濾器。
問題 5:大批量處理吞吐量
Veryfi:流量限制限制文件提交速度。 HTTP 429 回應需要指數退避邏輯。 吞吐量受制於Veryfi每計劃的流量限制,而不是您的硬體。
解決方案:IronOCR 僅受 CPU 核心限制。 使用 Parallel.ForEach,每執行緒一個 IronTesseract 實例。 在具有8核的伺服器上,吞吐量大致隨著核心數量線性擴展。
// One IronTesseract per thread — do not share instances across threads
Parallel.ForEach(
documentPaths,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
path =>
{
var ocr = new IronTesseract();
var result = ocr.Read(path);
SaveResult(path, result.Text, result.Confidence);
});Parallel.ForEach(
documentPaths,
New ParallelOptions With { .MaxDegreeOfParallelism = Environment.ProcessorCount },
Sub(path)
Dim ocr As New IronTesseract()
Dim result = ocr.Read(path)
SaveResult(path, result.Text, result.Confidence)
End Sub)問題 6:專有的 JSON 架構鎖定在 Veryfi
Veryfi:所有提取程式碼從Veryfi的回應架構中讀取:response.BankAccount?.RoutingNumber。 這一程式碼僅與Veryfi的 SDK 一起運作。VeryfiAPI 更新中的任何字段名稱更改都會損壞應用程式程式碼。
解決方案:IronOCR 提取使用標準 .NET System.Text.RegularExpressions.Regex 來結合純文字。 這些模式是可移植的,可測試的而無需任何 SDK 模擬,可由您控制。 單元測試在無需網路連接的情況下運行。
// Extraction logic that is fully portable and unit-testable
[Fact]
public void ExtractsRoutingNumberFromInvoiceText()
{
const string sampleText = "Routing Number: 021000021\nAccount: 1234567890";
var match = Regex.Match(sampleText, @"Routing\s*(?:Number)?:?\s*(\d{9})",
RegexOptions.IgnoreCase);
Assert.True(match.Success);
Assert.Equal("021000021", match.Groups[1].Value);
}<Fact>
Public Sub ExtractsRoutingNumberFromInvoiceText()
Const sampleText As String = "Routing Number: 021000021" & vbLf & "Account: 1234567890"
Dim match = Regex.Match(sampleText, "Routing\s*(?:Number)?:?\s*(\d{9})", RegexOptions.IgnoreCase)
Assert.True(match.Success)
Assert.Equal("021000021", match.Groups(1).Value)
End SubVeryfi遷移清單
遷移前
在接觸任何程式碼之前審計程式碼庫,以清點所有Veryfi的使用情況:
# Find allVeryfiusing statements
grep -rn "using Veryfi" --include="*.cs" .
# Find all VeryfiClient instantiations
grep -rn "VeryfiClient\|ProcessDocumentAsync" --include="*.cs" .
# Find allVeryfiresponse field accesses
grep -rn "response\.Vendor\|response\.Total\|response\.LineItems\|response\.BankAccount" --include="*.cs" .
# Find all credential configuration references
grep -r "Veryfi:ClientId\|Veryfi:ClientSecret\|Veryfi:Username\|Veryfi:ApiKey" \
--include="*.json" --include="*.yml" --include="*.yaml" --include="*.env" .
# Find all webhook-related code
grep -rn "VeryfiWebhook\|X-Veryfi-Token\|webhook" --include="*.cs" .
記錄 ProcessDocumentAsync 調用站點的總數,每個調用站點存取的響應欄位列表,以及包含Veryfi憑據的環境列表。
程式碼遷移
- 從解決方案中的所有項目中刪除
VeryfiNuGet 包。 - 為之前引用
Veryfi的所有項目安裝IronOcrNuGet 包。 - 將
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";新增到應用程式啟動(在任何 OCR 呼叫之前)。 - 用
using IronOcr;替換所有using Veryfi;和using Veryfi.Models;語句。 - 用
IronTesseract字段初始化替換VeryfiClient構造函式注入。 - 從每個
appsettings.*.json和秘密配置文件中刪除所有四個Veryfi憑據條目。 - 將
ProcessDocumentAsync(bytes)呼叫轉換為ocr.Read(filePath)或ocr.Read(ocrInput)。 - 用段落有序文字提取替換
response.Vendor?.Name存取自result.Pages[0].Paragraphs。 - 用正則表達式對
result.Text進行匹配替換response.InvoiceNumber和其他結構化字段存取。 - 用 Y 坐標過濾的
result.Pages[0].Paragraphs迭代替換response.LineItems迭代。 - 刪除 webhook 控制器類,並移除 webhook 端點註冊。
- 從所有環境中刪除 webhook 秘密環境變數。
- 新增
OcrInput帶預處理(Contrast())以用於掃描圖像輸入。 - 用
Parallel.ForEach替換單執行緒順序迴圈,每執行緒一個IronTesseract。 - 在所有環境變數配置和 CI/CD 秘密儲存中新增
IRONOCR_LICENSE_KEY。
遷移後
- 驗證遷移部署後 HTTP 流量記錄中無Veryfi網路呼叫。
- 確認提取的供應商名稱與 20–50 張收據樣本集中的預期值相符。
- 確認提取的總金額以 $0.01 容差匹配相同樣本集中的預期值。
- 驗證每個文件集中的發票編號提取成功。
- 測試批量處理吞吐量與基準Veryfi吞吐量對比,以確認流量限制的消除。
- 在無網路連接的情況下運行整個測試套件,以確認無雲依賴性。
- 確認
result.Confidence分數超過 80% 用於乾淨的文件掃描; 低於 80% 表示應新增預處理步驟。 - 驗證所有四個Veryfi憑據已從每個環境(開發、測試、生產)中移除。
- 確認 webhook 端點返回 404 或從路由表中移除。
- 測試低質量收據掃描的行為(皺巴巴、褪色、傾斜)時,預處理管道處於活動狀態。
遷移至IronOCR的主要好處
當地處理的財務文件是無法在第三方泄露的文件。 遷移後,從發票中提取的銀行賬號、從支票中解析的路由號和從銀行對賬單中讀取的交易記錄都在您的硬體上處理。 無第三方安全事件、次處理器資料存取或Veryfi基礎設施入侵可以曝光從未離開您伺服器的文件。
每文件成本在遷移部署當天降至零。 在每月 50,000 份文件的情況下, $5,000-15,000 的Veryfi月費消失。 一次性購買的IronOCRProfessional 授權為 $2,399,並在第一個月的第一週內收回成本。 在更高的數量下,節省每年不斷累積,而不需要任何批量折扣談判或合同續簽。
處理吞吐量隨硬體擴展,而非供應商的速率限位。 HTTP 429 回應、計劃級別的吞吐量上限、季節性超限費是雲 API 的架構產物。使用 IronOCR,新增 CPU 核心比例增加吞吐量。 10,000 張收據的批量在您的時間表上處理,而不是Veryfi的速率限制時間表。
任何文件型別均可用相同的 API 處理。 當人力資源部門需要處理入職表單時,公司不再需要第二個 OCR 工具,法律需要合同文字提取時,或營運需要運輸文件資料時。 ocr.Read() 處理所有這些。 從影像中讀取文字教程 和 專用文件指南 涵蓋了IronOCR處理的完整文件格式範圍。
提取邏輯成為程式碼庫的第一類部分。 正則表達式模式在源控制中,可在拉取請求中審查,可以不需模擬任何 SDK 的情況下在單元測試中測試,也可以根據生產反饋調整。 當Veryfi的預訓練模型返回錯誤的供應商名稱時,沒有任何調整空間。 當IronOCR的提取模式返回一個錯誤的供應商名稱時,解決方案是用單行正則表達式更改和單元測試修復。IronOCR 授權頁面 涵蓋了各種層級選項,包括每年訂閱計費而不是永久購買的 SaaS 選項,適合對此有興趣的團隊。
部署足跡縮小為一個可在任何地方運行的 NuGet 包。IronOCR安裝為一個包,無外部依賴,無本地二進制管理,無需 tessdata 文件夾配置。 相同的包引用可以在 Windows、Linux、macOS、Docker、Azure App Service 和 AWS Lambda 上解決,無需平台條件程式碼。 查看 Docker 部署指南 和 Linux 部署指南,以便在Veryfi的網路外發需要作為部署障礙的容器化環境中使用。
