บทความเทคนิค

OCR จีนกับหลายภาษาใน HotPDF ผ่าน RapidOCR

HotPDF ทำ OCR ภาษาจีนและหลายภาษาใน Delphi ผ่าน adapter ฝั่ง RapidOCR DLL native: THPDFRapidOCRDLLOptions.ForLanguage แมป tag ภาษาอย่าง 'zh-CN', 'zh-TW', 'ru' หรือ 'ar' ไปยัง model ฝั่ง recognition กับ character dictionary ที่จับคู่กัน และ THotPDF.ApplyLoadedOCRTextLayer เปลี่ยนบรรทัดที่จดจำได้ให้เป็น text layer Unicode มองไม่เห็นที่ค้นหาได้บนหน้า PDF ที่สแกน

ทำดีโมอักษรลาตินให้รันได้เป็นส่วนง่าย ความพังที่น่าสนใจเริ่มเมื่อคุณสลับไปจีนตัวเต็มหรือรัสเซียแล้ว output กลายเป็นตัวอักษรมั่วที่มั่นใจสุดขีดและมีรูปร่างดูดี หรือเมื่อทุกบรรทัดเงียบ ๆ เสียอักขระตัวสุดท้ายไป หรือเมื่อหน้าอาหรับกลับมาพร้อม text box เรียงผิดลำดับ ทั้งหมดนี้ไม่ raise exception เองสักอย่าง preset ภาษาที่เพิ่มใน HotPDF v2.775.0 มีอยู่เพื่อปิดช่องว่างพวกนี้เป็นหลัก และกับดักสี่ตัวด้านล่างควรค่าแก่การเข้าใจแม้คุณจะไม่แตะโค้ด native เลย เพราะแต่ละตัวอธิบายอาการที่ไม่งั้นคุณต้องไล่กันเป็นวัน

ForLanguage เลือก model กับ dictionary อย่างไร

THPDFRapidOCRDLLOptions.ForLanguage resolve tag ไปยังหนึ่งในเก้าโปรไฟล์ แล้วคืน options ที่ชี้ไปที่ <profile>/recognition.onnx กับ <profile>/dictionary.txt ใต้ไดเรกทอรี model ของคุณ ขณะที่คง detector ที่ใช้ร่วมกัน, angle classifier แบบเลือกได้ และค่าเริ่มต้นของ thread, pixel กับ timeout จาก THPDFRapidOCRDLLOptions.Default เมธอดจะแปลง tag เป็นตัวพิมพ์เล็ก เปลี่ยน underscore เป็น hyphen และตัดช่องว่างรอบ ๆ 'zh_TW', 'ZH-tw' กับ ' zh-tw ' จึงตกลงที่โปรไฟล์เดียวกัน alias เป็นรายการชัดเจน ไม่ใช่การ match ด้วย prefix 'zh-Hant-TW' ถูกยอมรับเพราะอยู่ในรายการ ขณะที่เวอร์ชันภูมิภาคมั่ว ๆ ที่ไม่อยู่ในรายการ raise EArgumentException ก่อน model ตัวใดจะถูกโหลด

การ resolve โปรไฟล์ของ ForLanguage ใน THPDFRapidOCRDLLOptions ของ HotPDF: tag อย่าง zh_TW, ZH-tw กับ zh-TW ถูก normalize แล้ว match กับเก้าโปรไฟล์ที่อยู่ในรายการ แต่ละโปรไฟล์ตรึงคู่ model กับ dictionary ของ recognition ที่ถูกเซ็ตพร้อมกันเสมอ ส่วน tag ที่ไม่ในรายการ raise EArgumentException ก่อน model ตัวใดโหลด
tag หนึ่งตัวเลือกคู่ model กับ dictionary ที่ถูกตรึงไว้หนึ่งคู่ detector, classifier กับงบประมาณยังใช้ร่วมกัน และ tag ที่ไม่รู้จัก fail ไว ก่อนโหลดอะไรทั้งสิ้น
โปรไฟล์ภาษาตัวอย่าง tagmodel ที่ตรึงไว้
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

ตัว adapter เองไม่เคยดาวน์โหลดอะไรเลย คุณจัดหาไฟล์ครั้งเดียวด้วย helper ที่แถมมา อย่าง tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (หรือ -Language All สำหรับทั้งเก้าโปรไฟล์) และ helper จะวาง detector กับ classifier ที่ใช้ร่วมกันไว้ที่ชื่อไฟล์รากที่ Default คาดหวัง หลังจากนั้นสแกนจีนตัวย่อกลายเป็นของที่ค้นหาได้ในไม่กี่บรรทัด ท่อประปาของ engine เป็น seam IHPDFOCREngine ชุดเดิมที่เล่าไว้ในบทความเรื่อง RapidOCR DLL แบบ in-process กับขอบเขต 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 กับ classifier ใช้ร่วมกัน
  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
    // รายการหน้าว่างหมายถึงทุกหน้า หน้าที่มี text อยู่แล้วถูกข้าม
    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;

รายละเอียดสองจุดใน output นั้นควรได้รับบันทึก pipeline ฝั่ง native คืนผลหนึ่งรายการต่อบรรทัดข้อความที่ detect เจอ ไม่ใช่ต่อคำ AcceptedWordCount จึงนับบรรทัดตรงนี้ และ MinimumConfidence ถูกเทียบกับความเชื่อมั่นเฉลี่ยของอักขระทั้งบรรทัด บรรทัดที่เฉลี่ย 0.45 จะถูกทิ้งเป็นชุดเดียว UniqueScalarCount รายงานว่า text layer ต้องแมปอักขระ Unicode ที่ต่างกันกี่ตัวเข้า font กับตาราง ToUnicode ของมัน ซึ่งเป็นเช็คสุขภาพจิตที่ดีว่า CJK จริง ๆ มาถึง ไม่ใช่ fallback ลาตินกองเล็ก ๆ คง interface ของ engine ไว้ข้ามเอกสาร เพราะการ initialize model เกิดใน factory และเป็นขั้นที่แพงที่สุด

ทำไมสลับแค่ model ฝั่ง recognition แล้วได้ตัวอักษรมั่ว

model แบบ CTC ฝั่ง recognition ไม่เคย output อักขระตรง ๆ มัน output แต่ index ของคลาส และ dictionary คือสิ่งเดียวที่เปลี่ยน index 1,204 ให้กลายเป็น glyph สลับ ch/recognition.onnx เป็น cyrillic/recognition.onnx แต่ยังใช้ dictionary จีนต่อ model จะยิง index ซีริลลิกที่ถูกต้องออกมาอย่างสบายใจ แล้ว dictionary ตัวเก่าแปลงมันเป็นอักษรฮันจื้อมั่ว ผลลัพธ์ดูเหมือนข้อความ ผ่านการตรวจ UTF-8 และค้นหาได้เป๊ะกับความว่างเปล่า นั่นเหตุผลที่ ForLanguage เซ็ต RecognitionModel กับ CharacterDictionary พร้อมกันเสมอ และ options ที่ปั้นเองไม่ควรเปลี่ยนข้างเดียวเด็ดขาด

เช็คความปลอดภัยที่คิดขึ้นมาง่ายที่สุดคือเทียบขนาดของ dictionary กับความกว้างของ output ของ model จำเป็นแต่ไม่พอ dictionary สองชุดมีจำนวน entry เท่ากันแต่เรียงคนละลำดับได้ และ off-by-one ในลำดับขยับทุกอักขระหลุดไปหนึ่ง code point HotPDF จึงตรวจสองชั้นตอน factory initialize model ขั้นแรก จำนวนคลาสของ output ต้องเท่ากับจำนวน entry ของ dictionary บวกสอง ขั้นที่สอง เมื่อไฟล์ ONNX ฝังรายการ metadata ชื่อ character มา ทุก entry ของ dictionary จะถูกเทียบกับมันทีละลำดับ และความไม่ตรงจะทำให้ initialize fail ด้วย EInvalidOperation พร้อม diagnostic ฝั่ง native แทนที่จะผลิตขยะที่ดูน่าเชื่อไว้ทีหลัง

"บวกสอง" มาจาก layout ของคลาส คลาส 0 คือ CTC blank คลาส 1 ถึง N คือบรรทัดของ dictionary ตามลำดับไฟล์ และคลาสสุดท้ายคือช่องว่าง dictionary บางชุดยังพา entry ของช่องว่างของตัวเองมาด้วย และบรรทัดนั้นต้องถูกเก็บไว้ตรงตามที่เป็น จุดนี้แหละที่ Trim ที่มีเจตนาดีทำร้ายงานจริง มันเปลี่ยน entry ที่เป็นช่องว่างตัวเดียวให้เป็น string ว่าง แล้วขยับหรือทำตารางพัง การ normalize ที่ปลอดภัยมีอย่างเดียวคือถอด carriage return ต่อท้ายออก dictionary ที่ save ด้วยการจบบรรทัดแบบ CRLF จึงโหลดได้ถูกต้อง ขณะที่ BOM ของ UTF-8, บรรทัดว่าง หรือ entry ที่มี tab จะถูกปฏิเสธ sketch ด้านล่างโชว์ layout นี้ใน Pascal มันเป็นโค้ดเพื่ออธิบาย ไม่ใช่ API ของ HotPDF

layout ตารางคลาส CTC ของ HotPDF สำหรับ dictionary ของ RapidOCR: คลาส 0 คือ blank คลาส 1 ถึง N คือบรรทัดของ dictionary ตามลำดับไฟล์โดยคง entry ช่องว่างที่โดดเดี่ยวไว้ และคลาสสุดท้ายคือช่องว่าง รวมเป็น N บวก 2 คลาสของ output ที่ factory ตรวจกับ model รวมถึง metadata ด้วย
dictionary คือสิ่งเดียวที่เปลี่ยน index คลาสให้เป็นอักขระ ขนาด ลำดับ และ entry ช่องว่างของมันจึงถูกตรวจก่อนหน้าสักหน้าจะถูกจดจำ
// เพื่อประกอบคำอธิบาย: ตารางคลาสที่ CTC recognizer คาดหวัง
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);                                   // newline ตอนท้ายไฟล์
  SetLength(Result, Last + 3);
  Result[0] := '';                               // class 0: CTC blank
  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] := ' ';                       // คลาสสุดท้าย: space
  // Length(Result) ต้องเท่ากับจำนวนคลาสของ output ของ model
end;

greedy CTC decoding ทำอะไรกันแน่

greedy CTC decoding หยิบคลาสที่ได้คะแนนสูงสุดที่ทุก time step ยุบการซ้ำติดกันให้เหลืออักขระเดียว แล้วทิ้งคลาส blank ไป ตัว blank แหละที่ทำให้อักษรที่ซ้ำจริง ๆ รอดชีวิต model ฝั่ง recognition มองบรรทัดข้อความเป็นลำดับของแถบแนวตั้งแคบ ๆ และสำหรับแต่ละแถบ หรือ time step มัน output ความน่าจะเป็นให้ทุกคลาส บรรทัดที่มี AA中 อาจผลิตลำดับ argmax เป็น A A blank A 中 space การยุบ time step ของ A สองตัวแรกให้เหลือ A เดียว blank คั่นมันออกจาก A ตัวถัดไป ผลลัพธ์คือ AA中 พร้อมช่องว่างท้ายบรรทัดที่ยังคงอยู่ ถ้าไม่มีกฎ blank book กับ bok จะแยกกันไม่ได้เลย

การเดิน GreedyCTCDecode ของ HotPDF: หก time step โหวตคลาส argmax เป็น A, A, blank, A, อักษรฮันและ space การซ้ำติดกันถูกยุบ blank รีเซ็ต guard กันซ้ำจึงอักษรที่ซ้ำจริงรอดชีวิต และบั๊กขอบสามแบบที่เงียบ ๆ ทิ้งช่องว่างระหว่างคำ อักขระสุดท้าย หรืออักษรที่ซ้ำไป
decoder เป็นโค้ดสิบกว่าบรรทัดและทุกขอบสำคัญ: ใส่คลาสสุดท้ายด้วย ใส่ time step สุดท้ายด้วย และปล่อยให้แต่ blank เท่านั้นคั่นการซ้ำ

decoder มีแค่สิบกว่าบรรทัด การได้ขอบผิดจึงง่าย และความพังก็เงียบ ถ้า loop argmax ด้านในหยุดก่อนคลาสสุดท้ายหนึ่งอัน คลาส space จะไม่มีวันชนะ และทุกบรรทัดจะกลับมาโดยไม่มีช่องว่างระหว่างคำ ซึ่งทำลายการค้นวลีบนหน้าอังกฤษกับลาติน ถ้า loop นอกหยุดก่อน time step สุดท้ายหนึ่งอัน อักขระสุดท้ายของทุกบรรทัดหายไป กับบรรทัดสั้น ๆ นั่นคือครึ่งหนึ่งถึงสามหน่วยของข้อความทั้งชุด และถ้า repeat guard ไม่ถูกรีเซ็ตด้วย blank อักษรซ้ำอย่าง ll หรือการซ้ำพยางค์จีนอย่าง 谢谢 จะยุบเหลือตัวเดียว decoder ของ HotPDF ใส่คลาสสุดท้ายกับ time step สุดท้ายครบ คงการซ้ำที่ถูกคั่นด้วย blank และเพิ่มการปฏิเสธคะแนนที่ไม่ใช่ค่าจำกัดหรือตกนอก 0 ถึง 1 กับจำนวนคลาสใด ๆ ที่ไม่ตรงกับ dictionary นี่คือตรรกะเดียวกันในรูปตัวอย่าง Pascal

// เพื่อประกอบคำอธิบาย: greedy CTC decoding ที่ขอบถูกต้อง
// Scores เก็บความน่าจะเป็น Steps * Classes ค่า แถวละหนึ่ง time step
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;                            // class 0 คือ CTC blank
  for Step := 0 to Steps - 1 do             // รวม time step สุดท้ายด้วย
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // รวมคลาสสุดท้ายด้วย (space)
      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;                       // blank รีเซ็ต guard กันซ้ำ
  end;
end;

greedy decoding ไม่ใช่กลยุทธ์ CTC ที่แม่นที่สุดที่มี beam search คู่กับ language model แก้ slice ที่กำกวมบางตัวได้ กับเอกสารพิมพ์ที่ 300 DPI ผลแบบ greedy มักคือทุกอย่างที่ model มีให้ และ decoder ไม่ใช่ที่ที่ควรชดเชยความอ่อนแอของ model model ลาตินของ PP-OCRv3 อ่าน ñ เป็น n ได้แม้บน input ที่สะอาด HotPDF ไม่ปิดตาข้ามเรื่องนี้ด้วยการแทนที่อักขระตอน post-processing เพราะตารางแทนที่ที่แก้ภาษาสเปนได้จะทำอย่างอื่นพัง และอักขระผิดใน searchable layer แย่กว่าการพลาดอย่างซื่อสัตย์

HotPDF เรียงบรรทัดข้อความอย่างไร รวมถึงอาหรับขวาไปซ้าย

HotPDF เรียง text box ที่ detect ได้จากบนลงล่าง จับกลุ่ม box เป็นแถวเมื่อมันทับแนวตั้งกันอย่างน้อยครึ่งหนึ่งของความสูง box ที่เตี้ยกว่า แล้วเรียงแต่ละแถวซ้ายไปขวา หรือขวาไปซ้ายเมื่อเปิด RightToLeft อักขระข้างในแต่ละบรรทัดที่จดจำได้ไม่เคยถูกกลับด้าน การจับกลุ่มสำคัญเพราะ detector มักผลิตแถวที่มองเห็นแนวเดียวออกมาหลายกล่อง เช่น ป้ายกำกับกับค่าที่ถูกคั่นด้วยช่องว่างกว้าง การเรียงด้วยพิกัดบนแบบโง่ ๆ จะสอดพวกมันแทรกกับแถวเพื่อนบ้านเมื่อพิกัดบนต่างกันเพียงหนึ่งสอง pixel

preset ของอาหรับเซ็ต RightToLeft := True ซึ่งบอก DLL ให้เรียง box ในแต่ละแถวตามขอบขวา จากระยะขอบขวาเข้าหาใน นั่นคือผลทั้งหมด ข้อความที่ model คืนให้บรรทัดหนึ่งอยู่ในลำดับ Unicode แบบ logical แล้ว คือลำดับที่คนอ่านอาหรับอ่านและพิมพ์ ซึ่งก็คือลำดับที่การ extract text และการค้นหาของ PDF คาดหวัง การกลับ string ด้วยเครื่องจักรเพื่อให้ "ดูถูก" ใน debugger จะทำ search, copy กับ paste และ screen reader พัง การแสดงแบบ bidirectional กับการจัดรูป glyph เป็นหน้าที่ของ viewer

engine หนึ่งตัวเสิร์ฟโปรไฟล์ภาษาหนึ่งโปรไฟล์ ไม่มีการ detect script อัตโนมัติ เอกสารที่ผสม script จึงต้องใช้ engine หนึ่งตัวต่อโปรไฟล์ โดยใช้กับหน้าที่ใช้มัน เพราะ ApplyLoadedOCRTextLayer รับรายการหน้าแบบแจ้งชัดและ commit แต่ละ call เป็น transaction all-or-nothing ของตัวเอง เรื่องนี้จึงตรงไปตรงมา

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // raise EArgumentException กับ tag ที่ไม่รู้จัก ก่อน model ตัวใดโหลด
  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 อยู่ตรงนั้นด้วยเหตุผล options ของ DLL เริ่มต้นที่ 16,777,216 pixel ต่อ request ซึ่งรองรับ A4 กับ US Letter ที่ 300 DPI สบาย ๆ แต่หน้า A3 ที่ 300 DPI คือราว 3508 คูณ 4961 pixel หรือราว 17.4 ล้าน และ request จะถูกปฏิเสธในฐานะเกินงบ ให้เพิ่ม MaxPixels (เพดานคือ 67,108,864) หรือลด THPDFOCRTextLayerOptions.DPI สำหรับขนาดกระดาษใหญ่ การเรียงขวาไปซ้ายใช้ export HPDFRapidOCRSetReadingDirection แบบเสริมของ ABI version 1 adapter ต้องการมันเฉพาะเมื่อเซ็ต RightToLeft DLL รุ่นเก่าจึงยังเสิร์ฟภาษาซ้ายไปขวาได้ และล้มตอนสร้าง engine ด้วย EArgumentException ที่ชี้ชื่อ export ที่หายไปสำหรับอาหรับ

ทำไม model OCR รุ่นใหม่ถึงโหลดไม่ขึ้น

HotPDF RapidOCR DLL ลิงก์ ONNX Runtime 1.14 แบบ static ซึ่งอ่าน model ที่ save ด้วย ONNX IR version 10 ไม่ได้ และ model export ใหม่อย่าง PP-OCRv5 อาจต้องการ runtime ใหม่กว่านั้น model แบบนี้ fail ตอนสร้าง engine พร้อม diagnostic ฝั่ง native ข้อจำกัดนี้เองเป็นเหตุผลที่ language pack ถูกตรึงไว้กับคู่ recognizer กับ dictionary ของ PP-OCRv3 กับ PP-OCRv4 ตัวใดตัวหนึ่ง ไม่ใช่ "ล่าสุด" และเป็นเหตุผลที่ตารางข้างบนผสมสองรุ่นของมัน คู่ที่ถูกตรึงทุกคู่คือคู่ที่โหลดและตรวจผ่านภายใต้ runtime ตัวนั้น

ตัวติดตั้งบังคับคู่พวกนี้ ไฟล์ทุกตัวใน manifest พา hash SHA256 มาด้วย ไฟล์ที่มีอยู่แล้วซึ่ง hash ไม่ตรงจะหยุดการติดตั้งแทนการถูกเขียนทับ และแต่ละดาวน์โหลดลงถิ่นด้วยชื่อชั่วคราว จะย้ายเข้าที่เมื่อ hash ตรงเท่านั้น สิ่งนี้ป้องกันเวอร์ชันเงียบ ๆ ของปัญหา dictionary ใครสักคนเท่าโยน recognition.onnx ใหม่กว่าลงโฟลเดอร์โปรไฟล์ด้วยมือ จำนวนคลาสบังเอิญตรง และไม่มีอะไร fail จนกว่าลูกค้าจะรายงานว่า search หาคำที่ตาเปล่าเห็นชัด ๆ ไม่เจอ ตอน runtime adapter อยู่แบบ offline และไม่เคยไปหยิบ model ที่หาย ฝั่ง recognizer ยังตรวจ shape ของ model ตอนโหลดด้วย ยอมรับ input NCHW ที่ความสูงตายตัว 32 หรือ 48 pixel หรือความสูงแบบ dynamic ซึ่งมันจะรันที่ 48

ถ้าคุณต้องการ script ที่ทั้งเก้าโปรไฟล์ไม่ครอบคลุม คุณยังชี้ RecognitionModel กับ CharacterDictionary ไปที่ไฟล์ของคุณเองได้ เช็คชุดเดิมมีผลเหมือนกัน ซึ่งก็คือจุดประสงค์ คู่ที่ไม่ตรงกันต้อง fail ตอน initialize ไม่ใช่ตอนอยู่ในคลังเอกสารของลูกค้า สำหรับหน้าที่โปรไฟล์ไหนของ RapidOCR ก็ไม่เข้า adapter ของ Tesseract สำหรับ searchable PDFเสียบเข้า call ApplyLoadedOCRTextLayer ตัวเดิมได้ และกับฟอร์ม ASCII พิมพ์เครื่อง engine OCR จับคู่เทมเพลตในตัวไม่ต้องพึ่ง model ใด ๆ เลย

สรุปด่วน: เช็คลิสต์ RapidOCR หลายภาษา

  • สร้าง options ด้วย THPDFRapidOCRDLLOptions.ForLanguage แล้วถือ EArgumentException เป็น tag ที่ไม่รองรับ ไม่ใช่ความผิดพลาดระดับ runtime
  • เปลี่ยน RecognitionModel กับ CharacterDictionary พร้อมกัน ห้ามทีละอย่างเด็ดขาด จำนวนคลาสเท่ากันไม่พิสูจน์ว่าลำดับอักขระเท่ากัน
  • เก็บ dictionary เป็น UTF-8 ไม่มี BOM ห้าม trim entry และคาดหวังว่า model จะมี N + 2 คลาส: blank, entry N ตัว, space
  • decoder CTC แบบ custom ต้องครอบคลุมคลาสสุดท้ายกับ time step สุดท้าย และคงการซ้ำที่ถูกคั่นด้วย blank
  • ใช้ engine หนึ่งตัวต่อโปรไฟล์ภาษา แล้วส่งรายการหน้าแบบแจ้งชัดสำหรับเอกสารที่ผสม script
  • RightToLeft เปลี่ยนแค่ลำดับ box text ที่จดจำได้ยังอยู่ในลำดับ Unicode แบบ logical
  • ติดตั้ง model ด้วย Install-RapidOCRModels.ps1 เพื่อให้ pin SHA256 คุมคู่ model กับ dictionary ไว้ และเซ็ต UseAngleClassifier := False ถ้าคุณติดตั้งแบบมี -SkipClassifier
  • เพิ่ม MaxPixels เกินค่าเริ่มต้น 16,777,216 ก่อนรันหน้า A3 หรือใหญ่กว่าที่ 300 DPI

preset ภาษาของ RapidOCR, adapter ฝั่ง DLL native และ pipeline ของ OCR text layer เป็นส่วนหนึ่งของHotPDF Delphi PDF Component สำหรับ Delphi, C++Builder และ FPC/Lazarus บน Windows เริ่มจาก v2.775.0 สำหรับโปรไฟล์หลายภาษา