Τεχνικό Άρθρο

HotPDF: Κινεζικό και πολύγλωσσο OCR με RapidOCR στο Delphi

Το HotPDF εκτελεί κινεζικό και πολύγλωσσο OCR σε Delphi μέσω του εγγενή adapter του για DLL RapidOCR: το THPDFRapidOCRDLLOptions.ForLanguage αντιστοιχίζει γλωσσική ετικέτα όπως 'zh-CN', 'zh-TW', 'ru' ή 'ar' σε ταιριασμένο model αναγνώρισης και character dictionary, και το THotPDF.ApplyLoadedOCRTextLayer γυρνά τις αναγνωρισμένες γραμμές σε αόρατο, αναζητήσιμο text layer Unicode πάνω σε σαρωμένες σελίδες PDF

Το να δουλέψει ένα demo λατινικής γραφής είναι το εύκολο κομμάτι. Οι ενδιαφέρουσες αποτυχίες ξεκινούν όταν αλλάξετε σε Παραδοσιακά Κινεζικά ή Ρωσικά και η έξοδος γίνεται σίγουρο, καλογραμμένο αλαμπουρνέζικο, ή όταν κάθε γραμμή χάνει αθόρυβα τον τελευταίο της χαρακτήρα, ή όταν μια αραβική σελίδα επιστρέφει με τα text boxes της σε λάθος σειρά. Κανένα από αυτά δεν πετά exception από μόνο του. Τα γλωσσικά presets που πρόσθεσε το HotPDF v2.775.0 υπάρχουν ως επί το πλείστον για να κλείσουν εκείνα τα κενά, και οι τέσσερις παγίδες παρακάτω αξίζει να τις κατανοήσετε ακόμα κι αν δεν αγγίξετε ποτέ τον εγγενή κώδικα, επειδή καθεμιά εξηγεί ένα σύμπτωμα που αλλιώς θα κυνηγούσατε μια ολόκληρη μέρα

Πώς διαλέγει το ForLanguage model και dictionary;

Το THPDFRapidOCRDLLOptions.ForLanguage λύνει μια ετικέτα σε ένα από εννέα προφίλ και επιστρέφει options που δείχνουν σε <profile>/recognition.onnx και <profile>/dictionary.txt κάτω από τον κατάλογο models σας, κρατώντας τον κοινό detector, τον προαιρετικό classifier γωνίας, και τις προεπιλογές νημάτων, pixels και timeout από το THPDFRapidOCRDLLOptions.Default. Η μέθοδος μικραίνει το tag, γυρνά underscores σε hyphens και κόβει περιβάλλοντα κενά, οπότε 'zh_TW', 'ZH-tw' και ' zh-tw ' προσγειώνονται όλα στο ίδιο προφίλ. Τα aliases είναι ρητή λίστα και όχι prefix match: το 'zh-Hant-TW' γίνεται δεκτό επειδή απαριθμείται, ενώ αυθαίρετη περιφερειακή παραλλαγή που δεν απαριθμείται πετά EArgumentException πριν φορτώσει οποιοδήποτε model

Λύση προφίλ ForLanguage HotPDF για το THPDFRapidOCRDLLOptions: ετικέτες όπως zh_TW, ZH-tw και zh-TW κανονικοποιούνται και ταιριάζουν απέναντι σε εννέα απαριθμημένα προφίλ, το καθένα καρφωμένο σε model αναγνώρισης και dictionary που ορίζονται πάντα μαζί, ενώ μη απαριθμημένη ετικέτα πετά EArgumentException πριν φορτώσει οποιοδήποτε model
μία ετικέτα διαλέγει ένα καρφωμένο ζεύγος model-dictionary· ο detector, ο classifier και οι προϋπολογισμοί μένουν κοινοί, και άγνωστη ετικέτα αποτυγχάνει γρήγορα αντί να φορτώσει οτιδήποτε
ΠροφίλΓλώσσεςΠαραδείγματα ετικετώνΚαρφωμένο model
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 ο ίδιος δεν κατεβάζει ποτέ τίποτα. Προμηθεύεστε τα αρχεία μία φορά με το δέμα βοηθός, για παράδειγμα tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (ή -Language All για όλα τα εννέα προφίλ), και ο βοηθός τοποθετεί κοινό detector και classifier στα ονόματα αρχείων ρίζας που περιμένει το Default. Μετά από εκεί, μια σάρωση Απλοποιημένων Κινεζικών γίνεται αναζητήσιμη με λίγες γραμμές. Η υδραυλική του engine είναι η ίδια ραφή IHPDFOCREngine που περιγράφεται στο άρθρο για το in-process 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 και 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
    // κενή λίστα σελίδων σημαίνει κάθε σελίδα· σελίδες που έχουν ήδη κείμενο παραλείπονται
    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 αναφέρει πόσα ξεχωριστά Unicode scalars χρειάστηκε να χαρτογραφήσει το text layer στη γραμματοσειρά του και στον πίνακα ToUnicode του, χρήσιμος έλεγχος υγείας ότι CJK κείμενο έφτασε πραγματικά αντί για μια χούφτα Λατινικά fallbacks. Κρατήστε τη διεπαφή engine ζωντανή σε έγγραφα επειδή η αρχικοποίηση models γίνεται στο factory και είναι το ακριβό βήμα

Γιατί η αλλαγή μόνο του model αναγνώρισης παράγει σκουπίδια;

Ένα model αναγνώρισης CTC δεν εξάγει ποτέ χαρακτήρες, μόνο δείκτες κλάσεων, και το dictionary είναι το μοναδικό πράγμα που γυρνά τον δείκτη 1.204 σε glyph. Ανταλλάξτε το ch/recognition.onnx με το cyrillic/recognition.onnx αλλά κρατήστε το κινεζικό dictionary, και το model θα εκπέμψει ευχαρίστως έγκυρους κυριλλικούς δείκτες που το παλιό dictionary μεταφράζει σε τυχαίους χαρακτήρες Han. Το αποτέλεσμα μοιάζει με κείμενο, περνά επικύρωση UTF-8, και είναι αναζητήσιμο για το τίποτα ακριβώς. Γι αυτό το ForLanguage ορίζει πάντα RecognitionModel και CharacterDictionary μαζί, και γι αυτό χειροποίητα options δεν πρέπει ποτέ να αλλάξουν το ένα χωρίς το άλλο

Ο φανερός έλεγχος ασφαλείας, η σύγκριση του μεγέθους dictionary με το πλάτος εξόδου του model, είναι απαραίτητος αλλά όχι επαρκής. Δύο dictionaries μπορούν να έχουν τον ίδιο αριθμό εγγραφών σε διαφορετική σειρά, και μια απόκλιση κατά ένα στη σειρά μετατοπίζει κάθε χαρακτήρα κατά ένα code point. Το HotPDF ελέγχει λοιπόν σε δύο στάδια όταν το factory αρχικοποιεί το model. Πρώτον, το πλήθος κλάσεων εξόδου πρέπει να ισούται με τις εγγραφές dictionary συν δύο. Δεύτερον, όταν το αρχείο ONNX ενσωματώνει λίστα μεταδεδομένων character, κάθε εγγραφή dictionary συγκρίνεται μαζί της με τη σειρά, και ασυμφωνία αποτυγχάνει την αρχικοποίηση με EInvalidOperation και εγγενές διαγνωστικό αντί να παράγει πειστικά σκουπίδια αργότερα

Το "συν δύο" προέρχεται από τη διάταξη κλάσεων. Η κλάση 0 είναι το CTC blank, οι κλάσεις 1 έως N είναι οι γραμμές dictionary σε σειρά αρχείου, και η τελική κλάση είναι κενό. Μερικά dictionaries κουβαλάνε επίσης δική τους εγγραφή κενού, και εκείνη η γραμμή πρέπει να κρατηθεί ακριβώς όπως είναι. Εδώ ένα καλόβουλο Trim κάνει πραγματική ζημιά: γυρνά εγγραφή ενός μόνο κενού σε κενή συμβολοσειρά και μετατοπίζει ή σπάει τον πίνακα. Η μοναδική κανονικοποίηση που είναι ασφαλής είναι η αφαίρεση trailing carriage return, ώστε dictionary αποθηκευμένο με line endings CRLF να φορτώνει σωστά, ενώ BOM UTF-8, κενή γραμμή, ή εγγραφή που περιέχει tab απορρίπτεται. Το σκίτσο παρακάτω δείχνει τη διάταξη σε Pascal· είναι επεξηγηματικός κώδικας, όχι API του HotPDF

Διάταξη πίνακα κλάσεων CTC HotPDF για dictionaries RapidOCR: η κλάση 0 είναι το blank, οι κλάσεις 1 έως N είναι οι γραμμές dictionary σε σειρά αρχείου με όποια μοναχική εγγραφή κενού κρατημένη, και η τελική κλάση είναι κενό, δίνοντας N συν 2 κλάσεις εξόδου που το factory επαληθεύει απέναντι στο model, μεταδεδομένα συμπεριλαμβανομένα
το dictionary είναι το μόνο πράγμα που γυρνά δείκτες κλάσεων σε χαρακτήρες, οπότε το μέγεθος, η σειρά και η εγγραφή κενού του επαληθεύονται πριν αναγνωριστεί έστω μία σελίδα
// Μόνο για επίδειξη: ο πίνακας κλάσεων που περιμένει recognizer 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);                                   // newline στο τέλος του αρχείου
  SetLength(Result, Last + 3);
  Result[0] := '';                               // κλάση 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] := ' ';                       // τελική κλάση: κενό
  // Το Length(Result) πρέπει να ισούται με το πλήθος κλάσεων εξόδου του model
end;

Τι κάνει στην πραγματικότητα η πεινασμένη αποκωδικοποίηση CTC;

Η πεινασμένη αποκωδικοποίηση CTC διαλέγει την υψηλότερα βαθμολογημένη κλάση σε κάθε βήμα χρόνου, καταρρέει διαδοχικές επαναλήψεις σε έναν χαρακτήρα, και πετά την κλάση blank· το blank είναι ό,τι επιτρέπει σε γνήσια διπλασιασμένα γράμματα να επιβιώσουν. Ένα model αναγνώρισης κοιτάζει μια γραμμή κειμένου ως αλληλουχία στενών κάθετων τομών, και για κάθε τομή, ή βήμα χρόνου, εξάγει πιθανότητα για κάθε κλάση. Μια γραμμή που περιέχει AA中 μπορεί να παράγει αλληλουχία argmax A A blank A 中 space. Η κατάρρευση των δύο πρώτων βημάτων A δίνει ένα A, το blank το χωρίζει από το επόμενο A, και το αποτέλεσμα είναι AA中 με το trailing space ανέπαφο. Χωρίς τον κανόνα blank, το book και το bok θα ήταν αδιαχώριστα

Περιήγηση GreedyCTCDecode HotPDF: έξι βήματα χρόνου ψηφίζουν κλάσεις argmax A, A, blank, A, χαρακτήρας Han και κενό, διαδοχικές επαναλήψεις καταρρέουν, το blank μηδενίζει τον φρουρό επανάληψης ώστε γνήσια διπλασιασμένο γράμμα να επιβιώνει, και τρία bugs ορίων πετούν αθόρυβα κενά λέξεων, τον τελευταίο χαρακτήρα, ή διπλασιασμένους χαρακτήρες
ο decoder είναι μια ντουζίνα γραμμές και κάθε όριο μετράει: συμπεριλάβετε την τελευταία κλάση, συμπεριλάβετε το τελευταίο βήμα, και αφήστε μόνο ένα blank να χωρίζει επαναλήψεις

Επειδή ο decoder είναι μόνο μια ντουζίνα γραμμές, είναι εύκολο να πάρεις λάθος τα όρια, και οι αποτυχίες είναι αθόρυβες. Αν ο εσωτερικός βρόχος argmax σταματήσει μία κλάση νωρίτερα, η κλάση κενού δεν μπορεί ποτέ να κερδίσει και κάθε γραμμή επιστρέφει χωρίς κενά λέξεων, που γκρεμίζει αναζήτηση φράσεων σε σελίδες Αγγλικών και Λατινικών. Αν ο εξωτερικός βρόχος σταματήσει ένα βήμα χρόνου νωρίτερα, ο τελευταίος χαρακτήρας κάθε γραμμής εξαφανίζεται, που για σύντομη γραμμή μπορεί να είναι το ένα τρίτο του κειμένου. Και αν ο φρουρός επανάληψης δεν μηδενίζεται από blank, διπλασιασμένοι χαρακτήρες όπως ll ή κινεζικές επαναλήψεις όπως 谢谢 καταρρέουν σε έναν. Ο decoder του HotPDF συμπεριλαμβάνει την τελευταία κλάση και το τελευταίο βήμα χρόνου, κρατά επαναλήψεις χωρισμένες από blank, και απορρίπτει επιπλέον βαθμολογίες που δεν είναι πεπερασμένες ή πέφτουν έξω από 0 έως 1, και οποιοδήποτε πλήθος κλάσεων που δεν ταιριάζει το dictionary. Ορίστε η ίδια λογική ως Pascal επίδειξη

// Μόνο για επίδειξη: πεινασμένη αποκωδικοποίηση 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 blank
  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;                       // ένα blank μηδενίζει τον φρουρό επανάληψης
  end;
end;

Η πεινασμένη αποκωδικοποίηση δεν είναι η πιο ακριβής διαθέσιμη στρατηγική CTC· beam search με γλωσσικό model μπορεί να διορθώσει μερικές αμφίβολες τομές. Για τυπωμένα έγγραφα στα 300 DPI το πεινασμένο αποτέλεσμα είναι συνήθως ό,τι έχει να προσφέρει το model, και ο decoder δεν είναι το μέρος για να αναπληρώσετε αδυναμίες model. Το λατινικό model PP-OCRv3, για παράδειγμα, μπορεί να διαβάσει το ñ ως n ακόμα και σε καθαρή είσοδο. Το HotPDF δεν το κουκουλώνει με post-processing αντικαταστάσεις χαρακτήρων, επειδή πίνακας υποκατάστασης που διορθώνει Ισπανικά σπάει κάτι άλλο, και λάθος χαρακτήρας σε αναζητήσιμο layer είναι χειρότερος από τίμια αστοχία

Πώς ταξινομεί το HotPDF γραμμές κειμένου, συμπεριλαμβανομένου αραβικού από δεξιά προς αριστερά;

Το HotPDF ταξινομεί ανιχνευμένα text boxes από πάνω προς τα κάτω, ομαδοποιεί boxes σε γραμμή όταν αλληλοκαλύπτονται κατακόρυφα κατά τουλάχιστον το μισό του ύψους του μικρότερου box, και ταξινομεί κάθε γραμμή αριστερά προς δεξιά, ή δεξιά προς αριστερά όταν το RightToLeft είναι ενεργό· οι χαρακτήρες μέσα σε κάθε αναγνωρισμένη γραμμή δεν αντιστρέφονται ποτέ. Η ομαδοποίηση μετράει επειδή ένας detector συχνά σπάει μία οπτική γραμμή σε αρκετά boxes, για παράδειγμα ετικέτα και τιμή χωρισμένες από φαρδύ κενό, και καθαρή ταξινόμηση κατά πάνω συντεταγμένη θα τις πλέκουσε με τη γειτονική γραμμή όποτε τα πάνω τους διαφέρουν κατά ένα δύο pixels

Το αραβικό preset θέτει RightToLeft := True, που λέει στον DLL να ταξινομεί τα boxes κάθε γραμμής κατά το δεξί τους άκρο, από το δεξί περιθώριο προς τα μέσα. Αυτό είναι όλο το αποτέλεσμα. Το κείμενο που επιστρέφει το model για μια γραμμή είναι ήδη σε Unicode λογική σειρά, τη σειρά που διαβάζει και πληκτρολογεί ένας Αραβόφωνος αναγνώστης, και εκείνη είναι επίσης η σειρά που περιμένουν η εξαγωγή και η αναζήτηση PDF κειμένου. Η μηχανική αντιστροφή της συμβολοσειράς για να "φαίνεται σωστή" σε debugger θα έσπασε αναζήτηση, αντιγραφή και επικόλληση, και screen readers. Η αμφίδρομη προβολή και το glyph shaping είναι δουλειά του viewer

Ένα engine εξυπηρετεί ένα γλωσσικό προφίλ. Δεν υπάρχει αυτόματη ανίχνευση γραφής, οπότε έγγραφο που αναμειγνύει γραφές θέλει ένα engine ανά προφίλ, εφαρμοσμένο στις σελίδες που το χρησιμοποιούν. Επειδή το ApplyLoadedOCRTextLayer παίρνει ρητή λίστα σελίδων και δεσμεύει κάθε κλήση ως δική της all-or-nothing συναλλαγή, είναι ευθύγραμμο

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // πετά EArgumentException για άγνωστη ετικέτα, πριν φορτώσει οποιοδήποτε 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 pixels ανά αίτημα, που καλύπτει A4 και US Letter στα 300 DPI άνετα, αλλά μια σελίδα A3 στα 300 DPI είναι περίπου 3508 επί 4961 pixels, γύρω στα 17,4 εκατομμύρια, και το αίτημα απορρίπτεται ως εκτός προϋπολογισμού. Ανασηκώστε το MaxPixels (το ταβάνι είναι 67.108.864) ή χαμηλώστε το THPDFOCRTextLayerOptions.DPI για μεγάλα formats. Η ταξινόμηση από δεξιά προς αριστερά χρησιμοποιεί το προαιρετικό export HPDFRapidOCRSetReadingDirection της έκδοσης ABI 1· ο adapter το απαιτεί μόνο όταν είναι ορισμένο το RightToLeft, οπότε παλαιότερος DLL εξακολουθεί να σερβίρει γλώσσες αριστερά-προς-δεξιά και αποτυγχάνει στη δημιουργία engine με EArgumentException που κατονομάζει το λείπον export για Αραβικά

Γιατί νεότερα OCR models αποτυγχάνουν να φορτώσουν;

Το DLL RapidOCR του HotPDF συνδέει static ONNX Runtime 1.14, που δεν μπορεί να διαβάσει models αποθηκευμένα με ONNX IR έκδοση 10, και νεότερα exports όπως models PP-OCRv5 μπορεί να απαιτούν νεότερο runtime από εκείνο· τέτοιο model αποτυγχάνει στη δημιουργία engine με εγγενές διαγνωστικό. Εκείνος ο περιορισμός είναι ο λόγος που τα γλωσσικά πακέτα είναι καρφωμένα σε συγκεκριμένα ζεύγη recognizer και dictionary PP-OCRv3 και PP-OCRv4 αντί για "τελευταία", και γιατί ο πίνακας παραπάνω αναμειγνύει τις δύο γενιές: κάθε καρφωμένο ζεύγος είναι ένα που φορτώνει και επαληθεύεται κάτω από εκείνο το runtime"

Ο installer επιβάλλει το ζευγάρωμα. Κάθε αρχείο στο manifest του κουβαλά hash SHA256, υπάρχον αρχείο με διαφορετικό hash σταματά την εγκατάσταση αντί να ξαναγραφτεί, και κάθε λήψη προσγειώνεται κάτω από προσωρινό όνομα και μετακινείται στη θέση της μόνο αφού το hash της ταιριάξει. Εκείνο προστατεύει από την αθόρυβη εκδοχή του προβλήματος dictionary: κάποιος ρίχνει νεότερο recognition.onnx σε φάκελο προφίλ στο χέρι, το πλήθος κλάσεων τυχαίνει να ταιριάζει, και τίποτα δεν αποτυγχάνει μέχρι ένας πελάτης να αναφέρει ότι η αναζήτηση δεν βρίσκει λέξεις που βλέπει φανερά. Σε χρόνο εκτέλεσης ο adapter μένει offline και δεν αντλεί ποτέ λείπον model. Ο recognizer επικυρώνει επίσης το σχήμα του model στη φόρτωση, δέχεται είσοδο NCHW με σταθερό ύψος 32 ή 48 pixels ή δυναμικό ύψος, που το τρέχει στα 48

Αν χρειάζεστε γραφή που κανένα από τα εννέα προφίλ δεν καλύπτει, μπορείτε ακόμα να δείξετε τα RecognitionModel και CharacterDictionary στα δικά σας αρχεία. Οι ίδιοι έλεγχοι ισχύουν, που είναι και το νόημα: λάθος ζευγαρωμένο ζεύγος αποτυγχάνει στην αρχικοποίηση, όχι στο αρχείο του πελάτη σας. Για σελίδες όπου κανένα προφίλ RapidOCR δεν ταιριάζει, το adapter Tesseract για αναζητήσιμο PDF μπαίνει στην ίδια κλήση ApplyLoadedOCRTextLayer, και για φόρμες ASCII μηχανόγραφου το built-in engine OCR template-matching δεν θέλει καθόλου models

Σύντομη αναφορά: λίστα ελέγχου multilingual RapidOCR

  • Δημιουργήστε options με THPDFRapidOCRDLLOptions.ForLanguage και μεταχειριστείτε EArgumentException ως μη υποστηριζόμενη ετικέτα, όχι ως σφάλμα runtime
  • Αλλάξτε RecognitionModel και CharacterDictionary μαζί, ποτέ το ένα μόνο· ίσα πλήθη κλάσεων δεν αποδεικνύουν ίση σειρά χαρακτήρων
  • Κρατήστε dictionaries ως UTF-8 χωρίς BOM, μην κάνετε ποτέ trim εγγραφές, και περιμένετε το model να έχει N + 2 κλάσεις: blank, N εγγραφές, κενό
  • Ένας custom decoder CTC πρέπει να καλύπτει την τελευταία κλάση και το τελευταίο βήμα χρόνου και να κρατά επαναλήψεις χωρισμένες από blank
  • Χρησιμοποιήστε ένα engine ανά γλωσσικό προφίλ και περάστε ρητές λίστες σελίδων για έγγραφα μικτών γραφών
  • Το RightToLeft αλλάζει μόνο τη σειρά boxes· το αναγνωρισμένο κείμενο μένει σε Unicode λογική σειρά
  • Εγκαταστήστε models με Install-RapidOCRModels.ps1 ώστε τα pins SHA256 να κρατούν το ζευγάρωμα model και dictionary· θέστε UseAngleClassifier := False αν εγκαταστήσατε με -SkipClassifier
  • Ανασηκώστε το MaxPixels πάνω από την προεπιλογή 16.777.216 πριν τρέξετε σελίδες A3 ή μεγαλύτερες στα 300 DPI

Τα γλωσσικά presets RapidOCR, ο εγγενής adapter DLL και ο αγωγός OCR text layer είναι μέρος του HotPDF Delphi PDF Component για Delphi, C++Builder και Windows FPC/Lazarus, ξεκινώντας από το v2.775.0 για τα πολύγλωσσα προφίλ