مقاله فنی

‏OCR چینی و چندزبانه با RapidOCR در HotPDF و Delphi

‏HotPDF ‏OCR چینی و چندزبانه را در Delphi از طریق آداپتور DLL بومی یعنی RapidOCR انجام می‌دهد: ‏THPDFRapidOCRDLLOptions.ForLanguage یک تگ زبانی مثل 'zh-CN' و 'zh-TW' و 'ru' یا 'ar' را به یک مدل بازشناسی و dictionary نویسه‌های منطبق نگاشت می‌کند و THotPDF.ApplyLoadedOCRTextLayer خط‌های بازشناسی‌شده را به یک لایهٔ متنی Unicode نامرئی و قابل‌جست‌وجو روی صفحات PDF اسکن‌شده تبدیل می‌کند

راه انداختن یک دمو با خط لاتین بخش آسان است. خرابی‌های جالب وقتی شروع می‌شوند که به چینی سنتی یا روسی سوییچ کنی و خروجی به یک آشفتگی خوش‌فرم و مطمئن تبدیل شود، یا وقتی هر خط بی‌سروصدا آخرین نویسه‌اش را گم می‌کند، یا وقتی یک صفحهٔ عربی با جعبه‌های متنی‌اش در ترتیب اشتباه برمی‌گردد. هیچ‌کدام از این‌ها به خودی خود exception بالا نمی‌دهند. پیش‌تنظیم‌های زبانی اضافه‌شده در HotPDF v2.775.0 عمدتاً برای بستن همین شکاف‌ها هستند و چهار تلهٔ پایین حتی اگر هرگز کد بومی را لمس نکنی ارزش فهمیدن دارند، چون هر کدام علائمی را توضیح می‌دهد که وگرنه ممکن بود یک روز دنبالش بدوی

‏ForLanguage چطور یک مدل و dictionary انتخاب می‌کند؟

THPDFRapidOCRDLLOptions.ForLanguage یک تگ را به یکی از نه پروفایل حل می‌کند و آپشن‌هایی برمی‌گرداند که به <profile>/recognition.onnx و <profile>/dictionary.txt زیر پوشهٔ مدل تو اشاره می‌کنند، در حالی که detector مشترک و دسته‌بند زاویهٔ اختیاری و پیش‌فرض‌های thread و پیکسل و timeout از THPDFRapidOCRDLLOptions.Default نگه داشته می‌شوند. متد تگ را کوچک‌حرف می‌کند، زیرخط‌ها را به خط تیره تبدیل می‌کند و فاصله‌های دورش را می‌تراشد، پس 'zh_TW' و 'ZH-tw' و ' zh-tw ' همه روی یک پروفایل فرود می‌آیند. نام‌های مستعار یک فهرست صریح‌اند نه تطبیق پیشوند: ‏'zh-Hant-TW' پذیرفته می‌شود چون در فهرست است، در حالی که یک واریانت منطقه‌ای دلخواه که در فهرست نیست قبل از load شدن هر مدلی ‏EArgumentException بالا می‌دهد

حل پروفایل ForLanguage در HotPDF برای THPDFRapidOCRDLLOptions: تگ‌هایی مثل zh_TW و ZH-tw و zh-TW نرمال و با نه پروفایل فهرست‌شده تطبیق می‌شوند، هر کدام یک مدل بازشناسی و dictionary را که همیشه با هم ست می‌شوند پین می‌کنند، و تگ فهرست‌نشده قبل از load شدن هر مدلی EArgumentException بالا می‌دهد
یک تگ یک جفت مدل-dictionary پین‌شده را انتخاب می‌کند؛ ‏detector و دسته‌بند و بودجه‌ها مشترک می‌مانند و یک تگ ناشناس سریع شکست می‌خورد به‌جای اینکه چیزی load کند
پروفایلزبان‌هاتگ‌های مثالمدل پین‌شده
chچینی ساده‌شده و انگلیسی‏zh، ‏zh-CN، ‏zh-Hans، ‏chi_simPP-OCRv4
chinese_chtچینی سنتی‏zh-TW، ‏zh-HK، ‏zh-Hant، ‏chi_traPP-OCRv3
enانگلیسی‏en، ‏en-US، ‏en-GB، ‏engPP-OCRv4
latinفرانسوی، آلمانی، اسپانیایی، پرتغالی، ایتالیایی، هلندی، ترکی‏fr، ‏de، ‏es-419، ‏pt-BR، ‏trPP-OCRv3
japanژاپنی‏ja، ‏ja-JP، ‏jpnPP-OCRv4
koreanکره‌ای‏ko، ‏ko-KR، ‏korPP-OCRv4
cyrillicروسی، اوکراینی، بلغاری، بلاروسی‏ru، ‏ru-RU، ‏uk، ‏bgPP-OCRv3
arabicعربی، فارسی، اردو‏ar، ‏ar-SA، ‏fa، ‏urPP-OCRv4
devanagariهندی، مراتی، نپالی‏hi، ‏mr، ‏nePP-OCRv4

آداپتور خودش هرگز چیزی دانلود نمی‌کند. تو فایل‌ها را یک بار با هلپر همراه‌شده فراهم می‌کنی، مثلاً tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (یا -Language All برای هر نه پروفایل)، و هلپر یک detector و دسته‌بند مشترک در نام فایل‌های ریشه‌ای که Default انتظار دارد می‌گذارد. بعد از آن یک اسکن چینی ساده‌شده با چند خط قابل‌جست‌وجو می‌شود. لوله‌کشی موتور همان درز IHPDFOCREngine است که در مقالهٔ DLL درون‌فرایندی RapidOCR و مرز ABI اش توضیح داده شده، پس این یکی روی زبان‌ها متمرکز می‌ماند

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeChineseScanSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Models: THPDFRapidOCRDLLOptions;
  Layer: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // ‏ch/recognition.onnx + ‏ch/dictionary.txt، ‏detector و دسته‌بند مشترک
  Models := THPDFRapidOCRDLLOptions.ForLanguage('zh-CN');
  Engine := HPDFCreateRapidOCRDLLOCREngine(
    'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
  Doc := THotPDF.Create(nil);
  try
    Doc.AutoLaunch := False;
    if Doc.LoadFromFile(SourceFile) < 1 then
      raise Exception.Create('Cannot load ' + SourceFile);
    Layer := THPDFOCRTextLayerOptions.Default;  // ‏300 DPI، MinimumConfidence برابر 0.5
    // یک فهرست صفحهٔ خالی یعنی همهٔ صفحات؛ صفحاتی که از قبل متن دارند skip می‌شوند
    if not Doc.ApplyLoadedOCRTextLayer([], Engine, Layer, Info) then
      raise Exception.Create(string(Info.Diagnostic));
    Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
      ' lines, ', Info.UniqueScalarCount, ' distinct characters');
    Doc.SaveLoadedDocument(TargetFile);
  finally
    Doc.Free;
  end;
end;

دو جزئیات در آن خروجی لایق یک نکته‌اند. خط لولهٔ بومی به‌ازای هر خط متن تشخیص‌داده‌شده یک نتیجه برمی‌گرداند نه به‌ازای هر کلمه، پس AcceptedWordCount اینجا خط می‌شمارد و MinimumConfidence با میانگین اطمینان نویسه‌های کل خط مقایسه می‌شود: خطی با میانگین 0.45 به‌عنوان یک واحد انداخته می‌شود. ‏UniqueScalarCount گزارش می‌کند لایهٔ متن چند scalar یونیکد متمایز را مجبور شده به فونت و جدول ToUnicode اش نگاشت کند، یک چک عقلانی مفید که متن CJK واقعاً رسیده نه یک مشت fallback لاتین. interface موتور را در طول اسناد زنده نگه دار، چون مقداردهی مدل داخل factory اتفاق می‌افتد و همان گام پرهزینه است

چرا عوض کردن فقط مدل بازشناسی آشغال تولید می‌کند؟

یک مدل بازشناسی CTC هرگز نویسه خروجی نمی‌دهد، فقط اندیس کلاس، و dictionary تنها چیزی است که اندیس 1,204 را به یک گلیف تبدیل می‌کند. ‏ch/recognition.onnx را با cyrillic/recognition.onnx عوض کن اما dictionary چینی را نگه دار، و مدل با خوشحالی اندیس‌های سیریلیک معتبر تولید می‌کند که dictionary قدیمی به نویسه‌های هان تصادفی ترجمه‌شان می‌کند. نتیجه شبیه متن به نظر می‌رسد، اعتبارسنجی UTF-8 را رد می‌کند، و برای دقیقاً هیچ چیز قابل‌جست‌وجو است. برای همین ForLanguage همیشه ‏RecognitionModel و CharacterDictionary را با هم ست می‌کند، و آپشن‌های دست‌ساز هرگز نباید یکی را بدون دیگری عوض کنند

چک امنیتی واضح، مقایسهٔ اندازهٔ dictionary با عرض خروجی مدل، لازم است اما کافی نیست. دو dictionary می‌توانند تعداد مدخل برابر در ترتیب متفاوت داشته باشند و یک خطای off-by-one در ترتیب هر نویسه را یک code point جابه‌جا می‌کند. برای همین HotPDF موقع مقداردهی مدل توسط factory در دو مرحله چک می‌کند. اول، شمارش کلاس خروجی باید برابر مدخل‌های dictionary به‌علاوهٔ دو باشد. دوم، وقتی فایل ONNX یک فهرست متادیتای character جاسازی کرده، هر مدخل dictionary به‌ترتیب با آن مقایسه می‌شود و ناهمخوانی مقداردهی را با EInvalidOperation و یک تشخیص بومی شکست می‌دهد به‌جای تولید آشفتگی محتمل‌نما بعداً

«به‌علاوهٔ دو» از چیدمان کلاس می‌آید. کلاس 0 بلیک CTC است، کلاس‌های 1 تا N خط‌های dictionary به‌ترتیب فایل‌اند، و کلاس آخر یک فاصله است. بعضی dictionaryها مدخل فاصلهٔ خودشان را هم حمل می‌کنند و آن خط باید دقیقاً همان‌طور که هست نگه داشته شود. اینجا است که یک Trim خیرخواهانه خرابی واقعی می‌کند: یک مدخل تک‌فاصله را به رشتهٔ خالی تبدیل و جدول را جابه‌جا یا می‌شکند. تنها نرمال‌سازی امن حذف یک carriage return انتهایی است، پس dictionary ذخیره‌شده با پایان خط‌های CRLF درست load می‌شود، در حالی که یک علامت ترتیب بایت UTF-8 یا یک خط خالی یا مدخلی حاوی tab رد می‌شود. طرح زیر چیدمان را در Pascal نشان می‌دهد؛ کد توضیحی است، نه یک API در HotPDF

چیدمان جدول کلاس CTC در HotPDF برای dictionaryهای RapidOCR: کلاس 0 بلیک است، کلاس‌های 1 تا N خط‌های dictionary به‌ترتیب فایل با حفظ هر مدخل فاصلهٔ تنها هستند، و کلاس آخر یک فاصله است، که N به‌علاوهٔ 2 کلاس خروجی می‌دهد که factory آن را نسبت به مدل با احتساب متادیتا تأیید می‌کند
dictionary تنها چیزی است که اندیس‌های کلاس را به نویسه تبدیل می‌کند، پس اندازه و ترتیب و مدخل فاصله‌اش قبل از بازشناسی حتی یک صفحه تأیید می‌شوند
// فقط برای توضیح: جدول کلاسی که یک بازشناس CTC انتظار دارد
uses
  SysUtils, IOUtils;

function BuildCTCClassTable(const FileName: string): TArray<string>;
var
  Text, Entry: string;
  Lines: TArray<string>;
  I, Last: Integer;
begin
  Text := TEncoding.UTF8.GetString(TFile.ReadAllBytes(FileName));
  if (Text <> '') and (Text[1] = #$FEFF) then
    raise EArgumentException.Create('Dictionary must be UTF-8 without a BOM');
  Lines := Text.Split([#10]);
  Last := High(Lines);
  if (Last >= 0) and (Lines[Last] = '') then
    Dec(Last);                                   // خط جدید در انتهای فایل
  SetLength(Result, Last + 3);
  Result[0] := '';                               // کلاس 0: بلیک CTC
  for I := 0 to Last do
  begin
    Entry := Lines[I];
    if (Entry <> '') and (Entry[Length(Entry)] = #13) then
      SetLength(Entry, Length(Entry) - 1);       // ‏CRLF: فقط CR حذف می‌شود
    if (Entry = '') or (Pos(#9, Entry) > 0) then
      raise EArgumentException.Create('Invalid dictionary entry');
    Result[I + 1] := Entry;                      // هرگز Trim نکن: ' ' یک کلاس است
  end;
  Result[Last + 2] := ' ';                       // کلاس آخر: فاصله
  // ‏Length(Result) باید با شمارش کلاس خروجی مدل برابر باشد
end;

decode کردن حریصانهٔ CTC در واقع چه می‌کند؟

decode کردن حریصانهٔ CTC در هر گام زمانی کلاس بالاترین امتیاز را برمی‌دارد، تکرارهای متوالی را به یک نویسه فرومی‌ریزد و کلاس بلیک را می‌اندازد؛ بلیک همان چیزی است که اجازه می‌دهد حروف واقعاً دوتایی جان سالم به در ببرند. یک مدل بازشناسی یک خط متن را به‌شکل دنباله‌ای از برش‌های عمودی باریک می‌بیند و برای هر برش، یا گام زمانی، برای هر کلاس یک احتمال خروجی می‌دهد. خطی حاوی AA中 ممکن است توالی argmax یعنی A A blank A 中 space تولید کند. فرو ریختن دو گام اول A یک A می‌دهد، بلیک آن را از A بعدی جدا می‌کند و نتیجه AA中 می‌شود با فاصلهٔ انتهایی سالم. بدون قاعدهٔ بلیک، ‏book و bok قابل‌تفکیک نبودند

گام‌به‌گام GreedyCTCDecode در HotPDF: شش گام زمانی کلاس‌های argmax یعنی A و A و بلیک و A و یک نویسهٔ هان و فاصله را رأی می‌دهند، تکرارهای متوالی فرومی‌ریزند، بلیک نگهبان تکرار را ریست می‌کند تا حرف واقعاً دوتایی جان سالم به در ببرد، و سه باگ مرزی فاصله‌گذاری کلمه یا آخرین نویسه یا نویسه‌های دوتایی را بی‌صدا می‌اندازند
decoder یک دوجن خط است و هر مرزی مهم است: کلاس آخر را بگنجان، گام آخر را بگنجان، و فقط بلیک را بگذار تکرارها را جدا کند

چون decoder فقط یک دوجن خط است، مرزها راحت اشتباه می‌شوند و خرابی‌ها بی‌صدایند. اگر حلقهٔ داخلی argmax یک کلاس مانده به آخر بایستد، کلاس فاصله هرگز نمی‌تواند برنده شود و هر خط بدون فاصله‌گذاری کلمه برمی‌گردد که جست‌وجوی عبارت را روی صفحات انگلیسی و لاتین نابود می‌کند. اگر حلقهٔ بیرونی یک گام زمانی مانده به آخر بایستد، آخرین نویسهٔ هر خط محو می‌شود که برای یک خط کوتاه می‌شود یک‌سوم متن. و اگر نگهبان تکرار توسط بلیک ریست نشود، نویسه‌های دوتایی مثل ll یا تکرارهای چینی مثل 谢谢 یکی می‌شوند. ‏decoder در HotPDF کلاس آخر و گام زمانی آخر را می‌گنجاند، تکرارهای جدا-شده-با-بلیک را نگه می‌دارد، و اضافه بر آن امتیازهایی را که متناهی نیستند یا بیرون 0 تا 1 می‌افتند و هر شمارش کلاسی که با dictionary نخواند رد می‌کند. همین منطق به‌شکل یک توضیح در Pascal

// فقط برای توضیح: decode کردن حریصانهٔ CTC با مرزهای درست.
// ‏Scores احتمالهای Steps * Classes را نگه می‌دارد، یک ردیف به‌ازای هر گام زمانی
function GreedyCTCDecode(const Scores: array of Single;
  Steps, Classes: Integer; const Characters: array of string): string;
var
  Step, C, Best, Previous: Integer;
  BestScore: Single;
begin
  if (Classes < 3) or (Length(Characters) <> Classes) or
    (Length(Scores) <> Steps * Classes) then
    raise EArgumentException.Create('Model output does not match the dictionary');
  Result := '';
  Previous := 0;                            // کلاس 0 بلیک CTC است
  for Step := 0 to Steps - 1 do             // گام زمانی آخر را بگنجان
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // کلاس آخر (فاصله) را بگنجان
      if Scores[Step * Classes + C] > BestScore then
      begin
        Best := C;
        BestScore := Scores[Step * Classes + C];
      end;
    if (Best <> 0) and (Best <> Previous) then
      Result := Result + Characters[Best];
    Previous := Best;                       // بلیک نگهبان تکرار را ریست می‌کند
  end;
end;

decode کردن حریصانه دقیق‌ترین استراتژی CTC موجود نیست؛ beam search با یک مدل زبانی می‌تواند بعضی برش‌های مبهم را درست کند. برای اسناد چاپی در 300 DPI نتیجهٔ حریصانه معمولاً همان چیزی است که مدل ارائه می‌دهد و decoder جای جبران ضعف‌های مدل نیست. مدل لاتین PP-OCRv3 مثلاً حتی روی ورودی تمیز می‌تواند ñ را n بخواند. ‏HotPDF آن را با جایگزینی نویسه‌ها در پس‌پردازش پنهان نمی‌کند، چون یک جدول جایگزینی که اسپانیایی را درست می‌کند یک چیز دیگر را می‌شکند، و یک نویسهٔ اشتباه در یک لایهٔ قابل‌جست‌وجو بدتر از یک از دست دادن صادقانه است

‏HotPDF خط‌های متنی را چطور مرتب می‌کند، از جمله عربی راست‌به‌چپ؟

‏HotPDF جعبه‌های متنی تشخیص‌داده‌شده را بالا به پایین مرتب می‌کند، وقتی جعبه‌ها دست‌کم نصف ارتفاع جعبهٔ کوچک‌تر عمودی هم‌پوشانی دارند آن‌ها را در یک ردیف گروه می‌کند، و هر ردیف را چپ به راست یا وقتی RightToLeft فعال است راست به چپ مرتب می‌کند؛ نویسه‌های داخل هر خط بازشناسی‌شده هرگز معکوس نمی‌شوند. گروه‌بندی مهم است چون detector اغلب یک خط بصری تکی را به چند جعبه می‌شکند، مثلاً یک برچسب و یک مقدار که با فاصلهٔ بزرگی جدا شده‌اند، و یک مرتب‌سازی خالص مبتنی بر مختصات بالایی هر وقت tops شان یک یا دو پیکسل فرق داشته باشد آن‌ها را با خط مجاور درهم‌می‌بافت

پیش‌تنظیم عربی ‏RightToLeft := True را ست می‌کند، که به DLL می‌گوید جعبه‌های هر ردیف را با لبهٔ راستشان، از حاشیهٔ راست به داخل، مرتب کند. کل اثر همین است. متنی که مدل برای یک خط برمی‌گرداند از قبل به‌ترتیب منطقی یونیکد است، ترتیبی که خوانندهٔ عربی می‌خواند و تایپ می‌کند، و همان ترتیبی هم هست که استخراج متن و جست‌وجو در PDF انتظار دارند. معکوس کردن مکانیکی رشته برای اینکه در debugger «درست به نظر برسد» جست‌وجو و کپی و paste و صفحه‌خوان‌ها را می‌شکند. نمایش دوسویه و شکل‌دهی گلیف کار viewer است

یک موتور یک پروفایل زبان را سرو می‌کند. تشخیص خودکار خط وجود ندارد، پس سندی که خطوط را قاطی می‌کند به‌ازای هر پروفایل یک موتور می‌خواهد که روی صفحات استفاده‌کننده از آن اعمال شود. چون ApplyLoadedOCRTextLayer یک فهرست صفحهٔ صریح می‌گیرد و هر فراخوانی را به‌عنوان تراکنش مستقل همه-یا-هیچ خودش ثبت می‌کند، این کار سرراست است

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // برای تگ ناشناس EArgumentException بالا می‌دهد، قبل از load شدن هر مدلی
  Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
  Models.MaxPixels := 33554432;          // جا برای صفحات A3 در 300 DPI
  Result := HPDFCreateRapidOCRDLLOCREngine(
    'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
end;

procedure OCRMixedArchive(Doc: THotPDF);
var
  Chinese, Arabic: IHPDFOCREngine;
  Layer: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  Chinese := CreateRapidEngine('zh-TW');  // پروفایل chinese_cht
  Arabic := CreateRapidEngine('ar-SA');   // پروفایل arabic، ‏RightToLeft = True
  Layer := THPDFOCRTextLayerOptions.Default;
  if not Doc.ApplyLoadedOCRTextLayer([0, 1, 2], Chinese, Layer, Info) then
    raise Exception.Create(string(Info.Diagnostic));
  if not Doc.ApplyLoadedOCRTextLayer([3], Arabic, Layer, Info) then
    raise Exception.Create(string(Info.Diagnostic));
end;

خط MaxPixels به دلیلی آنجاست. آپشن‌های DLL به‌طور پیش‌فرض 16,777,216 پیکسل به‌ازای هر درخواست‌اند که A4 و US Letter در 300 DPI را راحت پوشش می‌دهد، اما یک صفحهٔ A3 در 300 DPI حدوداً 3508 در 4961 پیکسل یعنی تقریباً 17.4 میلیون است و درخواست به‌عنوان بالاتر از بودجه رد می‌شود. برای قالب‌های بزرگ ‏MaxPixels را بالا ببر (سقفش 67,108,864 است) یا ‏THPDFOCRTextLayerOptions.DPI را پایین بیاور. مرتب‌سازی راست‌به‌چپ از export اختیاری یعنی HPDFRapidOCRSetReadingDirection از نسخهٔ ABI یعنی 1 استفاده می‌کند؛ آداپتور فقط وقتی RightToLeft ست شده باشد آن را لازم دارد، پس یک DLL قدیمی‌تر همچنان زبان‌های چپ‌به‌راست را سرو می‌کند و موقع ساخت موتور با یک EArgumentException که export گمشده را برای عربی نام می‌برد شکست می‌خورد

چرا مدل‌های OCR جدیدتر load نمی‌شوند؟

‏DLL یعنی RapidOCR در HotPDF یک ONNX Runtime استاتیک یعنی 1.14 را لینک می‌کند که نمی‌تواند مدل‌های ذخیره‌شده با نسخهٔ 10 از ONNX IR را بخواند و exportهای جدیدتر مثل مدل‌های PP-OCRv5 می‌توانند runtime جدیدتری از آن بخواهند؛ چنین مدلی موقع ساخت موتور با یک تشخیص بومی شکست می‌خورد. همان محدودیت دلیل این است که بسته‌های زبانی به جفت‌های مشخص بازشناس و dictionary یعنی PP-OCRv3 و PP-OCRv4 پین شده‌اند نه «آخرین»، و دلیل مخلوط بودن آن دو نسل در جدول بالا: هر جفت پین‌شده یکی است که زیر آن runtime load و تأیید می‌شود

نصب‌کننده جفت شدن را اعمال می‌کند. هر فایل در manifest اش یک hash یعنی SHA256 دارد، یک فایل موجود با hash متفاوت به‌جای بازنویسی شدن نصب را متوقف می‌کند و هر دانلود زیر یک نام موقت فرود می‌آید و فقط بعد از مطابقت hash اش سر جایش می‌رود. این محافظت در برابر نسخهٔ آرام مشکل dictionary است: کسی با دست یک recognition.onnx جدیدتر داخل یک پوشهٔ پروفایل می‌گذارد، شمارش کلاس‌ها اتفاقاً می‌خواند و هیچ چیز شکست نمی‌خورد تا وقتی مشتری گزارش بدهد جست‌وجو کلماتی را که واضح می‌بیند پیدا نمی‌کند. در زمان اجرا آداپتور آفلاین می‌ماند و هرگز مدلی را که نیست fetch نمی‌کند. بازشناس شکل مدل را هم موقع load اعتبارسنجی می‌کند، ورودی NCHW با ارتفاع ثابت 32 یا 48 پیکسل یا ارتفاع پویا را می‌پذیرد که آن را با 48 اجرا می‌کند

اگر خطی لازم داری که هیچ‌کدام از نه پروفایل پوشش نمی‌دهند، همچنان می‌توانی ‏RecognitionModel و CharacterDictionary را به فایل‌های خودت اشاره بدهی. همان چک‌ها اعمال می‌شوند، که همین هدف است: یک جفت ناهمخوان موقع مقداردهی شکست می‌خورد، نه در آرشیو مشتری‌ات. برای صفحاتی که هیچ‌کدام از پروفایل‌های RapidOCR مناسب نیستند، ‏آداپتور Tesseract برای PDF قابل‌جست‌وجو به همان فراخوانی ApplyLoadedOCRTextLayer وصل می‌شود و برای فرم‌های ASCII چاپ ماشینی ‏موتور OCR تطبیق قالب داخلی اصلاً مدلی نمی‌خواهد

مرجع سریع: چک‌لیست چندزبانهٔ RapidOCR

  • آپشن‌ها را با THPDFRapidOCRDLLOptions.ForLanguage بساز و ‏EArgumentException را یک تگ پشتیبانی‌نشده بگیر نه یک خطای runtime
  • ‏RecognitionModel و CharacterDictionary را با هم عوض کن، هرگز یکی تنها نه؛ شمارش کلاس برابر ثابت نمی‌کند ترتیب نویسه‌ها برابر است
  • dictionaryها را UTF-8 بدون BOM نگه دار، مدخل‌ها را هرگز تراش نده، و انتظار داشته باش مدل N + 2 کلاس داشته باشد: بلیک، ‏N مدخل، فاصله
  • یک decoder سفارشی CTC باید کلاس آخر و گام زمانی آخر را پوشش دهد و تکرارهای جدا-شده-با-بلیک را نگه دارد
  • به‌ازای هر پروفایل زبان یک موتور و برای اسناد چندخطی فهرست صفحهٔ صریح بده
  • RightToLeft فقط ترتیب جعبه‌ها را عوض می‌کند؛ متن بازشناسی‌شده به‌ترتیب منطقی یونیکد می‌ماند
  • مدل‌ها را با Install-RapidOCRModels.ps1 نصب کن تا پین‌های SHA256 جفت مدل و dictionary را نگه دارند؛ اگر با -SkipClassifier نصب کردی ‏UseAngleClassifier := False را ست کن
  • قبل از اجرای صفحات A3 یا بزرگ‌تر در 300 DPI ‏MaxPixels را از پیش‌فرض 16,777,216 بالاتر ببر

پیش‌تنظیم‌های زبانی RapidOCR و آداپتور DLL بومی و خط لولهٔ لایهٔ متنی OCR بخشی از کامپوننت HotPDF Delphi PDF برای Delphi و C++Builder و ‏Windows FPC/Lazarus هستند، از v2.775.0 برای پروفایل‌های چندزبانه