HotPDF doet Chinese en meertalige OCR in Delphi via zijn native RapidOCR-DLL-adapter: THPDFRapidOCRDLLOptions.ForLanguage mapt een taaltag zoals 'zh-CN', 'zh-TW', 'ru' of 'ar' op een matchend recognitiemodel en karakterwoordenboek, en THotPDF.ApplyLoadedOCRTextLayer maakt van de herkende regels een onzichtbare, doorzoekbare Unicode-tekstlaag op gescande PDF-pagina's
Een demo in het Latijnse schrift aan de praat krijgen is het makkelijke deel. De interessante falingen beginnen zodra u omschakelt naar Traditioneel Chinees of Russisch en de uitvoer verandert in zelfverzekerd, netjes gevormde onzin, of zodra elke regel stilletjes zijn laatste karakter verliest, of zodra een Arabische pagina terugkomt met zijn tekstboxen in de verkeerde volgorde. Geen daarvan geeft uit zichzelf een exception. De taalpresets toegevoegd in HotPDF v2.775.0 bestaan vooral om die gaten te dichten, en de vier valkuilen hieronder zijn het begrijpen waard ook als u de native code nooit aanraakt, want elk verklaart een symptoom waar u anders een dag achteraan kunt jagen
Hoe kiest ForLanguage een model en woordenboek?
THPDFRapidOCRDLLOptions.ForLanguage resolvet een tag naar een van negen profielen en geeft opties terug die wijzen naar <profile>/recognition.onnx en <profile>/dictionary.txt onder uw modelmap, terwijl de gedeelde detector, de optionele hoekclassificator en de thread-, pixel- en timeout-defaults uit THPDFRapidOCRDLLOptions.Default blijven staan. De methode zet de tag om naar kleine letters, verandert underscores in koppeltekens en knipt witruimte aan de randen af, dus 'zh_TW', 'ZH-tw' en ' zh-tw ' komen allemaal op hetzelfde profiel uit. Aliassen zijn een expliciete lijst in plaats van een prefixmatch: 'zh-Hant-TW' wordt geaccepteerd omdat hij op de lijst staat, terwijl een willekeurige regionale variant die er niet op staat een EArgumentException geeft voordat er een model wordt geladen
| Profiel | Talen | Voorbeeldtags | Vastgepind model |
|---|---|---|---|
ch | Vereenvoudigd Chinees en Engels | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Traditioneel Chinees | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Engels | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Frans, Duits, Spaans, Portugees, Italiaans, Nederlands, Turks | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japans | ja, ja-JP, jpn | PP-OCRv4 |
korean | Koreaans | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Russisch, Oekraïens, Bulgaars, Wit-Russisch | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arabisch, Perzisch, Urdu | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindi, Marathi, Nepalees | hi, mr, ne | PP-OCRv4 |
De adapter zelf downloadt nooit iets. U voorziet de bestanden één keer met de meegeleverde helper, bijvoorbeeld tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (of -Language All voor alle negen profielen), en de helper legt een gedeelde detector en classifier neer op de root-bestandsnamen die Default verwacht. Daarna wordt een scan in Vereenvoudigd Chinees doorzoekbaar met een paar regels code. De engine-bedrading is dezelfde IHPDFOCREngine-naad die wordt beschreven in het artikel over de in-process RapidOCR-DLL en zijn ABI-grens, dus dit artikel blijft bij de talen
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, gedeelde detector en 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
// een lege paginalijst betekent elke pagina; pagina's die al tekst hebben worden overgeslagen
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;
Twee details in die uitvoer verdienen een notitie. De native pijplijn geeft één resultaat per gedetecteerde tekstregel, niet per woord, dus AcceptedWordCount telt hier regels, en MinimumConfidence wordt vergeleken met de gemiddelde karakterconfidence van de hele regel: een regel met gemiddeld 0.45 wordt als geheel laten vallen. UniqueScalarCount meldt hoeveel verschillende Unicode-scalars de tekstlaag in zijn font en ToUnicode-tabel moest mappen, een nuttige sanity check dat CJK-tekst er echt aankwam in plaats van een handjevol Latijnse fallbacks. Houd de engine-interface in leven over documenten heen, want modelinitialisatie gebeurt in de factory en is de dure stap
Waarom levert alleen het recognitiemodel wisselen rommel op?
Een CTC-recognitiemodel geeft nooit karakters, alleen klasse-indexen, en het woordenboek is het enige dat index 1.204 in een glyph verandert. Wissel ch/recognition.onnx om voor cyrillic/recognition.onnx maar houd het Chinese woordenboek aan, en het model zal vrolijk geldige Cyrilische indexen uitstoten die het oude woordenboek vertaalt in willekeurige Han-karakters. Het resultaat oogt als tekst, haalt UTF-8-validatie en is doorzoekbaar voor precies niets. Daarom zet ForLanguage RecognitionModel en CharacterDictionary altijd samen, en waarom handgebouwde opties er nooit één zonder de ander mogen veranderen
De voor de hand liggende veiligheidscontrole, het woordenboekformaat vergelijken met de breedte van de modeluitvoer, is nodig maar niet voldoende. Twee woordenboeken kunnen evenveel entries hebben in een andere volgorde, en een off-by-one in de volgorde schuift elk karakter één codepunt op. HotPDF controleert daarom in twee stappen zodra de factory het model initialiseert. Ten eerste moet het klasseaantal van de uitvoer gelijk zijn aan de woordenboekentries plus twee. Ten tweede, als het ONNX-bestand een character-metadatalijst insluit, wordt elke woordenboekentry er in volgorde mee vergeleken, en een mismatch laat de initialisatie falen met EInvalidOperation en een native diagnostiek in plaats van later geloofwaardige rommel te produceren
De "plus twee" komt uit de klasselayout. Klasse 0 is de CTC blank, klassen 1 tot N zijn de woordenboekregels in bestandsvolgorde, en de laatste klasse is een spatie. Sommige woordenboeken voeren bovendien hun eigen space-entry, en die regel moet exact blijven zoals hij is. Dit is de plek waar een goedbedoelde Trim echte schade aanricht: hij maakt van een entry met één spatie een lege string en verschuift of breekt de tabel. De enige veilige normalisatie is het verwijderen van een afsluitende carriage return, dus een woordenboek opgeslagen met CRLF-regeleinden laadt correct, terwijl een UTF-8 byte order mark, een lege regel of een entry met een tab wordt geweigerd. De schets hieronder toont de layout in Pascal; het is toelichtende code, geen HotPDF-API
// Alleen ter illustratie: de klassentabel die een CTC-recognizer verwacht
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); // regeleinde aan het eind van het bestand
SetLength(Result, Last + 3);
Result[0] := ''; // klasse 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: alleen de CR laten vallen
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // nooit Trim: ' ' is een klasse
end;
Result[Last + 2] := ' '; // laatste klasse: spatie
// Length(Result) moet gelijk zijn aan het klasseaantal van de modeluitvoer
end;
Wat doet greedy CTC-decodering eigenlijk?
Greedy CTC-decodering pikt bij elke tijdstap de hoogst scorende klasse, klapt opeenvolgende herhalingen samen tot één karakter en laat de blank-klasse vallen; de blank is wat echt verdubbelde letters laat overleven. Een recognitiemodel bekijkt een tekstregel als een reeks smalle verticale plakjes, en voor elk plakje, of tijdstap, geeft het een kans voor elke klasse. Een regel met AA中 zou de argmax-reeks A A blank A 中 space kunnen opleveren. De eerste twee A-stappen samenvouwen geeft één A, de blank scheidt hem van de volgende A, en het resultaat is AA中 met de afsluitende spatie intact. Zonder de blank-regel zouden book en bok ononderscheidbaar zijn
Omdat de decoder maar een dozijn regels is, gaan de grenzen makkelijk mis, en de falingen zijn stil. Stopt de binnenste argmax-lus één klasse te vroeg, dan kan de spatieklasse nooit winnen en komt elke regel terug zonder woordspatiëring, wat zinszoeken op Engelse en Latijnse pagina's vernielt. Stopt de buitenste lus één tijdstap te vroeg, dan verdwijnt het laatste karakter van elke regel, wat bij een korte regel een derde van de tekst kan zijn. En wordt de herhalingsvang niet door een blank gereset, dan klappen verdubbelde karakters zoals ll of Chinese reduplicaties zoals 谢谢 samen tot één. De HotPDF-decoder neemt de laatste klasse en de laatste tijdstap mee, houdt door blanks gescheiden herhalingen aan, en weigert bovendien scores die niet eindig zijn of buiten 0 tot 1 vallen, en elk klasseaantal dat niet met het woordenboek matcht. Hier is dezelfde logica als Pascal-illustratie
// Alleen ter illustratie: greedy CTC-decodering met correcte grenzen.
// Scores bevat Steps * Classes kansen, één rij per tijdstap
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; // klasse 0 is de CTC blank
for Step := 0 to Steps - 1 do // neem de laatste tijdstap mee
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // neem de laatste klasse mee (spatie)
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; // een blank reset de herhalingsvang
end;
end;
Greedy decodering is niet de meest accurate CTC-strategie die bestaat; beam search met een taalmodel kan enkele twijfelachtigee plakjes redden. Voor gedrukte documenten op 300 DPI is het greedy-resultaat meestal alles wat het model te bieden heeft, en de decoder is niet de plek om modelzwaktes te compenseren. Het Latijnse PP-OCRv3-model kan bijvoorbeeld ñ als n lezen zelfs op nette invoer. HotPDF plakt dat niet dicht met post-processing karaktervervangingen, want een substitutietabel die het Spaans fixt breekt iets anders, en een fout karakter in een doorzoekbare laag is erger dan een eerlijke miss
Hoe ordent HotPDF tekstregels, inclusief rechts-naar-links Arabisch?
HotPDF sorteert gedetecteerde tekstboxen van boven naar beneden, groepeert boxen in een rij zodra ze verticaal met ten minste de helft van de kleinere boxhoogte overlappen, en ordent elke rij van links naar rechts, of van rechts naar links zodra RightToLeft is ingeschakeld; de karakters binnen elke herkende regel worden nooit omgekeerd. De groepering telt omdat een detector vaak één visuele regel in meerdere boxen splitst, bijvoorbeeld een label en een waarde gescheiden door een brede opening, en een pure sortering op bovencoördinaat zou ze met de naburige regel interleaven zodra hun bovenkanten een pixel of twee verschillen
Het Arabische preset zet RightToLeft := True, wat de DLL vertelt de boxen in elke rij op hun rechterrand te ordenen, van de rechtermarge naar binnen. Dat is het hele effect. De tekst die het model voor een regel teruggeeft staat al in Unicode-logische volgorde, de volgorde waarin een Arabische lezer leest en typt, en dat is ook de volgorde waarin PDF-tekstextractie en zoeken verwachten. De string mechanisch omdraaien zodat hij er in een debugger "goed uitziet" breekt zoeken, kopiëren en plakken en screenreaders. Bidirectionele weergave en glyph-vormgeving zijn het werk van de viewer
Eén engine bedient één taalprofiel. Er is geen automatische schriftdetectie, dus een document dat schriften mengt heeft één engine per profiel nodig, toegepast op de pagina's die hem gebruiken. Omdat ApplyLoadedOCRTextLayer een expliciete paginalijst neemt en elke aanroep als eigen alles-of-niets-transactie wegschrijft, is dat eenvoudig
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// geeft EArgumentException bij een onbekende tag, voordat er een model laadt
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // ruimte voor A3-pagina's op 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-profiel
Arabic := CreateRapidEngine('ar-SA'); // arabic-profiel, 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;
De MaxPixels-regel staat er met reden. De DLL-opties defaulten op 16.777.216 pixels per verzoek, wat A4 en US Letter op 300 DPI ruim dekt, maar een A3-pagina op 300 DPI is zo'n 3508 bij 4961 pixels, ruwweg 17,4 miljoen, en het verzoek wordt geweigerd als over budget. Verhoog MaxPixels (het plafond is 67.108.864) of verlaag THPDFOCRTextLayerOptions.DPI voor grote formaten. Rechts-naar-links-ordenen gebruikt de optionele HPDFRapidOCRSetReadingDirection-export van ABI-versie 1; de adapter eist hem alleen zodra RightToLeft is gezet, dus een oudere DLL blijft links-naar-rechts-talen bedienen en faalt bij het aanmaken van de engine met een EArgumentException die de ontbrekende export noemt voor Arabisch
Waarom laden nieuwere OCR-modellen niet?
De HotPDF RapidOCR-DLL linkt een statische ONNX Runtime 1.14, die modellen opgeslagen met ONNX IR-versie 10 niet kan lezen, en nieuwere exports zoals PP-OCRv5-modellen kunnen een nieuwere runtime dan dat vereisen; zo'n model faalt bij het aanmaken van de engine met een native diagnostiek. Die beperking is de reden dat de taalpakketten vastzitten aan specifieke PP-OCRv3- en PP-OCRv4-recognizer- en woordenboekparen in plaats van "latest", en waarom de tabel hierboven de twee generaties mengt: elk vastgepind paar is er een dat onder die runtime laadt en verifieert
De installer handhaaft het paren. Elk bestand in zijn manifest voert een SHA256-hash, een bestaand bestand met een andere hash stopt de installatie in plaats van overschreven te worden, en elke download landt onder een tijdelijke naam en schuift pas op zijn plek zodra zijn hash matcht. Dat beschermt tegen de stille variant van het woordenboekprobleem: iemand legt handmatig een nieuwere recognition.onnx in een profielmap, het klasseaantal blijkt toevallig te matchen, en niets faalt totdat een klant meldt dat zoeken de woorden niet vindt die hij duidelijk ziet. Op runtime blijft de adapter offline en haalt nooit een ontbrekend model op. De recognizer valideert ook de modelvorm bij het laden, en accepteert NCHW-invoer met een vaste hoogte van 32 of 48 pixels of een dynamische hoogte, die hij op 48 draait
Heeft u een schrift nodig dat geen van de negen profielen dekt, dan kunt u RecognitionModel en CharacterDictionary nog steeds naar eigen bestanden wijzen. Dezelfde controles gelden, en dat is het punt: een verkeerd paar faalt bij initialisatie, niet in het archief van uw klant. Voor pagina's waar geen enkel RapidOCR-profiel past, koppelt de Tesseract-adapter voor doorzoekbare PDF in op dezelfde ApplyLoadedOCRTextLayer-aanroep, en voor machinaal bedrukte ASCII-formulieren heeft de ingebouwde template-matching-OCR-engine helemaal geen modellen nodig
Snelnaslag: meertalige RapidOCR-checklist
- Maak opties met
THPDFRapidOCRDLLOptions.ForLanguageen zieEArgumentExceptionals een niet-ondersteunde tag, niet als een runtimefout - Verander
RecognitionModelenCharacterDictionarysamen, nooit één alleen; gelijke klasseaantallen bewijzen geen gelijke karaktervolgorde - Houd woordenboeken als UTF-8 zonder BOM, trim nooit entries, en verwacht dat het model N + 2 klassen heeft: blank, N entries, spatie
- Een custom CTC-decoder moet de laatste klasse en de laatste tijdstap dekken en herhalingen die door een blank gescheiden worden aan te houden
- Gebruik één engine per taalprofiel en geef expliciete paginalijsten door voor documenten met gemengde schriften
RightToLeftverandert alleen de boxvolgorde; herkende tekst blijft in Unicode-logische volgorde- Installeer modellen met
Install-RapidOCRModels.ps1zodat SHA256-pins het model-woordenboek-paar vasthouden; zetUseAngleClassifier := Falseals u met-SkipClassifierhebt geïnstalleerd - Verhoog
MaxPixelsboven de default van 16.777.216 voordat u A3 of groter op 300 DPI draait
De RapidOCR-taalpresets, de native DLL-adapter en de OCR-tekstlaag-pijplijn maken deel uit van de HotPDF Delphi PDF Component voor Delphi, C++Builder en Windows FPC/Lazarus, te beginnen met v2.775.0 voor de meertalige profielen