HotPDF vykonáva čínsku a multijazyčnú OCR v Delphi cez svoj natívny adaptér RapidOCR DLL: THPDFRapidOCRDLLOptions.ForLanguage mapuje jazykovú značku ako 'zh-CN', 'zh-TW', 'ru' alebo 'ar' na zodpovedajúci rozpoznávací model a znakový slovník a THotPDF.ApplyLoadedOCRTextLayer mení rozpoznané línie na neviditeľnú, prehľadávateľnú Unicode textovú vrstvu na naskenovaných PDF stranách
Rozbehať demo v latinkovom písme je tá ľahká časť. Zaujímavé zlyhania začínajú, keď prepnete na tradičnú čínštinu alebo ruštinu a výstup sa zmení na sebaistý, dobre utvorený nesmysel, alebo keď každá línia potichu príde o posledný znak, alebo keď arabská strana sa vráti s textovými boxmi v nesprávnom poradí. Žiadne z toho samo o sebe nespadne výnimkou. Jazykové presety pridané v HotPDF v2.775.0 existujú prevažne na zapchanie týchto dier a štyri pasce nižšie stoja za pochopenie aj vtedy, keď sa natívneho kódu nikdy nedotknete, lebo každá z nich vysvetľuje príznak, po ktorom by ste inak behali deň
Ako ForLanguage vyberie model a slovník?
THPDFRapidOCRDLLOptions.ForLanguage vyrieši značku na jeden z deviatich profilov a vráti options ukazujúce na <profile>/recognition.onnx a <profile>/dictionary.txt pod vaším adresárom modelov, pričom ponechá zdieľaný detektor, voliteľný klasifikátor uhla a predvolené vlákien, pixelov aj termínu z THPDFRapidOCRDLLOptions.Default. Metóda prevedie značku na malé písmená, podčiarkovníky zmení na spojovníky a odstrihne okolité biele miesta, takže 'zh_TW', 'ZH-tw' aj ' zh-tw ' pristanú na tom istom profile. Aliasy sú explicitný zoznam, nie porovnanie prefixu: 'zh-Hant-TW' sa prijme, lebo je vypísaný, zatiaľ čo ľubovoľná regionálna varianta, ktorá vypísaná nie je, vyhodí EArgumentException skôr, než sa načíta akýkoľvek model
| Profil | Jazyky | Príklady značiek | Pripnutý model |
|---|---|---|---|
ch | Zjednodušená čínština a angličtina | zh, zh-CN, zh-Hans, chi_sim | PP-OCRv4 |
chinese_cht | Tradičná čínština | zh-TW, zh-HK, zh-Hant, chi_tra | PP-OCRv3 |
en | Angličtina | en, en-US, en-GB, eng | PP-OCRv4 |
latin | Francúzština, nemčina, španielčina, portugalčina, taliančina, holandčina, turečtina | fr, de, es-419, pt-BR, tr | PP-OCRv3 |
japan | Japončina | ja, ja-JP, jpn | PP-OCRv4 |
korean | Kórejčina | ko, ko-KR, kor | PP-OCRv4 |
cyrillic | Ruština, ukrajinčina, bulharčina, bieloruština | ru, ru-RU, uk, bg | PP-OCRv3 |
arabic | Arabčina, perzština, urdčina | ar, ar-SA, fa, ur | PP-OCRv4 |
devanagari | Hindčina, maráthčina, nepálčina | hi, mr, ne | PP-OCRv4 |
Adaptér sám nič nestahuje. Súbory provisionsujete raz s pribaleným pomocníkom, napríklad tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (alebo -Language All pre všetkých deväť profilov) a pomocník položí zdieľaný detektor a klasifikátor na koreňové mená súborov, ktoré očakáva Default. Potom sa nasken zjednodušenej čínštiny stane prehľadávateľným na pár riadkoch. Inštalatérstvo enginu je to isté rozhranie IHPDFOCREngine popísané v článku o in-process RapidOCR DLL a jeho ABI hranici, takže tento ostanú pri jazykoch
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, zdieľaný detektor a klasifikátor
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
// prázdny zoznam strán znamená každá strana; strany, ktoré už text majú, sa preskočia
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;
Dva detaily v tom výstupe si zaslúžia poznámku. Natívna pipeline vracia jeden výsledok na detekovanú textovú líniu, nie na slovo, takže AcceptedWordCount tu počíta línie a MinimumConfidence sa porovnáva s priemernou confidence znakov celej línie: línia priemerujúca 0.45 sa zahodí ako celok. UniqueScalarCount hlási, koľko odlišných Unicode scalarov musela textová vrstva zmapovať do svojho fontu a tabuľky ToUnicode — užitočná sanity kontrola, že CJK text naozaj prišiel, a nie hrsť latinských záloha. Rozhranie enginu držte nažive cez dokumenty, lebo inicializácia modelov sa deje vo factory a je to tá drahá etapa
Prečo prepnutie len rozpoznávacieho modelu vyrobí smetie?
CTC rozpoznávací model nikdy nevypúšťa znaky, len indexy tried a slovník je jediná vec, ktorá zmení index 1 204 na glyf. Vymeňte ch/recognition.onnx za cyrillic/recognition.onnx, ale nechajte čínsky slovník a model bude spokojne emitovať platné cyrilské indexy, ktoré starý slovník preloží na náhodné han znaky. Výsledok vyzerá ako text, prejde UTF-8 validáciou a je prehľadávateľný presne na nič. Preto ForLanguage vždy nastavuje RecognitionModel a CharacterDictionary spolu a preto ručne stavané options nikdy nemajú meniť jedno bez druhého
Zjavná bezpečnostná kontrola, porovnanie veľkosti slovníka so šírkou výstupu modelu, je nutná, ale nie postačujúca. Dva slovníky môžu mať rovnaký počet položiek v inom poradí a odchýlka o jedno v poradí posunie každý znak o jeden code point. HotPDF preto kontroluje v dvoch etapách, keď factory inicializuje model. Najprv sa počet výstupných tried musí rovnať položkám slovníka plus dva. Potom, keď ONNX súbor vnára metadata zoznam character, porovná sa každá položka slovníka s ním v poradí a nezrovnalosť prepasuje inicializáciu s EInvalidOperation a natívnou diagnostikou namiesto neskôr vyrobenia verohodného smetia
To „plus dva“ pochádza z rozloženia tried. Trieda 0 je CTC blank, triedy 1 až N sú riadky slovníka v poradí súboru a finálna trieda je medzera. Niektoré slovníky nesú aj vlastnú položku medzery a ten riadok musí ostať presne taký, aký je. Tu dobre mienený Trim narobí reálnu škodu: zmení položku s jedinou medzerou na prázdny reťazec a posunie alebo pokazí tabuľku. Jediná bezpečná normalizácia je odstránenie koncového carriage return, takže slovník uložený s koncami riadkov CRLF sa načíta korektne, zatiaľ čo UTF-8 byte order mark, prázdny riadok alebo položka obsahujúca tab sa odmietnu. Náčrt nižšie ukazuje rozloženie v Pascali; je to vysvetľujúci kód, nie API HotPDF
// Len ilustrácia: tabuľka tried, ktorú CTC recognizer očakáva
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); // nový riadok na konci súboru
SetLength(Result, Last + 3);
Result[0] := ''; // trieda 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: zhoď len CR
if (Entry = '') or (Pos(#9, Entry) > 0) then
raise EArgumentException.Create('Invalid dictionary entry');
Result[I + 1] := Entry; // nikdy Trim: ' ' je trieda
end;
Result[Last + 2] := ' '; // finálna trieda: medzera
// Length(Result) sa musí rovnať počtu tried výstupu modelu
end;
Čo vlastne robí greedy CTC dekódovanie?
Greedy CTC dekódovanie vyberá najlepšie skórovanú triedu v každom časovom kroku, zvinie po sebe idúce opakovania do jedného znaku a zahodí triedu blank; blank je to, čo dovoľuje skutočne zdvojeným písmenám prežiť. Rozpoznávací model sa pozerá na textovú líniu ako na sekvenciu úzkych vertikálnych rezov a pre každý rez, čiže časový krok, vypúšťa pravdepodobnosť pre každú triedu. Línia obsahujúca AA中 môže vyprodukovať sekvenciu argmax A A blank A 中 space. Zvinutie prvých dvoch krokov A dá jedno A, blank ho oddelí od ďalšieho A a výsledkom je AA中 s koncovou medzerou intact. Bez pravidla blank by book a bok boli nerozoznateľné
Keďže dekodér má len tucet riadkov, ľahko sa pokazia hranice a zlyhania sú tiché. Ak vnútorná slučka argmax skončí jednu triedu skôr, trieda medzery nemôže nikdy vyhrať a každá línia sa vráti bez medzier slov, čo rozmláti frázové hľadanie na anglických a latinských stranách. Ak vonkajšia slučka skončí jeden časový krok skôr, posledný znak každej línie zmizne, čo môže byť pri krátkej línii tretina textu. A ak strážca opakovaní nie je reštartovaný blankom, zdvojené znaky ako ll alebo čínske reduplikácie ako 谢谢 sa zvinú do jedného. Dekodér HotPDF zahrňa poslednú triedu aj posledný časový krok, drží opakovania oddelené blankom a navyše odmietne skóre, ktoré nie sú konečné alebo padajú mimo 0 až 1, a akýkoľvek počet tried nesúhlasiaci so slovníkom. Tu je tá istá logika ako Pascal ilustrácia
// Len ilustrácia: greedy CTC dekódovanie s korektnými hranicami.
// Scores drží Steps * Classes pravdepodobností, jeden riadok na časový krok
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; // trieda 0 je CTC blank
for Step := 0 to Steps - 1 do // zahrňte posledný časový krok
begin
Best := 0;
BestScore := Scores[Step * Classes];
for C := 1 to Classes - 1 do // zahrňte poslednú triedu (medzeru)
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 reštartuje strážcu opakovaní
end;
end;
Greedy dekódovanie nie je najpresnejšia dostupná CTC stratégia; beam search s jazykovým modelom dokáže opraviť niektoré nejednoznačné rezy. Pre tlačené dokumenty pri 300 DPI je greedy výsledok zvyčajne to, čo model ponúknuť môže, a dekodér nie je miesto, kde sa kompenzujú slabosti modelu. Latinský model PP-OCRv3 dokáže napríklad prečítať ñ ako n dokonca aj na čistom vstupe. HotPDF to nezametá post-processingovými náhradami znakov, lebo substitučná tabuľka, ktorá opraví španielčinu, niečo iné pokazí a zlý znak v prehľadávateľnej vrstve je horší než poctivá minule
Ako HotPDF usporadúva textové línie, vrátane sprava-doľava arabských?
HotPDF zotriedi detekované textové boxy zhora nadol, zoskupí boxy do riadka, keď sa vertikálne prekrývajú najmenej o polovicu výšky menšieho boxu a usporiada každý riadok zľava doprava, alebo sprava doľava, keď je zapnuté RightToLeft; znaky vo vnútri každej rozpoznanej línie sa nikdy neprevracajú. Zoskupovanie má význam, lebo detektor často rozdelí jeden vizuálny riadok na viacero boxov, napríklad nálepka a hodnota oddelené širokou medzerou a čisté triedenie podľa hornej súradnice by ich prelúdalo so susedným riadkom zakaždým, keď sa ich horné hrany líšia o pixel či dva
Arabský preset nastaví RightToLeft := True, čo prikáže DLL usporiadať boxy v každom riadku podľa ich pravej hrany, od pravého okraja dovnútra. To je celý efekt. Text, ktorý model vráti pre líniu, je už v Unicode logickom poradí, poradí, v akom ho arabský čitateľ číta a píše, a to je aj poradie, ktoré očakáva extrakcia aj hľadanie PDF textu. Mechanické prevrátenie reťazca, aby „vyzeral správne“ v debugeri, by rozbilo hľadanie, kopírovanie, vkladanie aj screen readery. Obojsmerné zobrazenie a tvarovanie glyfov je práca prehliadača
Jeden engine obsluhuje jeden jazykový profil. Automatická detekcia písma neexistuje, takže dokument miešajúci písma potrebuje jeden engine na profil, aplikovaný na strany, ktoré ho používajú. Keďže ApplyLoadedOCRTextLayer berie explicitný zoznam strán a commitne každé volanie ako vlastnú all-or-nothing transakciu, je to priamočiary
uses
SysUtils, HPDFDoc, HPDFRapidOCRRecognition;
function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
Models: THPDFRapidOCRDLLOptions;
begin
// vyhodí EArgumentException pre neznámu značku, skôr než sa čokoľvek načíta
Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
Models.MaxPixels := 33554432; // priestor pre strany A3 pri 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'); // profil chinese_cht
Arabic := CreateRapidEngine('ar-SA'); // profil 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;
Riadok MaxPixels tam je z dôvodu. Options DLL sa predvolene berú na 16 777 216 pixelov na požiadavku, čo pokrýva A4 aj US Letter pri 300 DPI s rezervou, ale strana A3 pri 300 DPI je zhruba 3508 krát 4961 pixelov, asi 17,4 milióna, a požiadavka sa odmietne ako nad rozpočet. Zvýšte MaxPixels (strop je 67 108 864) alebo znížte THPDFOCRTextLayerOptions.DPI pre veľké formáty. Usporiadanie sprava doľava používa voliteľný export HPDFRapidOCRSetReadingDirection verzie ABI 1; adaptér ho vyžaduje len keď je nastavené RightToLeft, takže staršie DLL naďalej obsluhuje jazyky zľava doprava a zlyhá pri vytvorení enginu s EArgumentException menujúcou chýbajúci export pre arabčinu
Prečo sa novšie OCR modely nevedia načítať?
DLL RapidOCR v HotPDF linkuje statický ONNX Runtime 1.14, ktorý nedokáže čítať modely uložené s ONNX IR verziou 10 a novšie exporty ako modely PP-OCRv5 môžu vyžadovať novší runtime; takýto model zlyhá pri vytvorení enginu s natívnou diagnostikou. To obmedzenie je dôvod, prečo sú jazykové balíčky pripnuté na konkrétne páry recognizer a slovník PP-OCRv3 a PP-OCRv4 namiesto „najnovšieho“ a prečo tabuľka vyššie mieša obe generácie: každý pripnutý pár je taký, čo sa pod tým runtime načíta a overí
Inštalátor pairing vynucuje. Každý súbor v jeho manifeste nesie heš SHA256, existujúci súbor s iným hešom zastaví inštaláciu namiesto prepísania a každé stiahnutie pristane pod dočasným menom a na miesto sa presunie až potom, čo heš sedí. To chráni proti tichej verzii problému so slovníkom: niekto ručne vhodí novšie recognition.onnx do priečinka profilu, počet tried náhodou sedí a nič nezlyhá, kým zákazník nenahlási, že hľadanie nenachádza slová, ktoré zjavne vidí. Počas behu ostanú adaptér offline a nikdy nestiahne chýbajúci model. Recognizer navyše overuje tvar modelu pri načítaní, prijíma NCHW vstup s fixnou výškou 32 alebo 48 pixelov alebo dynamickou výškou, ktorú beží pri 48
Ak potrebujete písmo, ktoré žiadny z deviatich profilov nepokrýva, stále môžete namieriť RecognitionModel a CharacterDictionary na vlastné súbory. Platia tie isté kontroly, v tom je zmysel: nezhodný pár zlyhá pri inicializácii, nie v archíve vášho zákazníka. Pre strany, kde nesedí žiadny profil RapidOCR, adaptér Tesseract pre prehľadávateľné PDF sa zapojí do rovnakého volania ApplyLoadedOCRTextLayer a pre strojovo tlačené ASCII formuláre vstavaný OCR engine s template matchingom nepotrebuje žiadne modely
Rýchla referencia: multijazyčný checklist RapidOCR
- Vytvárajte options cez
THPDFRapidOCRDLLOptions.ForLanguagea berteEArgumentExceptionako nepodporovanú značku, nie runtime poruchu - Meniť
RecognitionModelaCharacterDictionaryspolu, nikdy jedno samotné; rovnaké počty tried nedokazujú rovnaké poradie znakov - Držte slovníky ako UTF-8 bez BOM, nikdy nestrihajte položky a očakávajte, že model má N + 2 tried: blank, N položiek, medzera
- Vlastný CTC dekodér musí pokryť poslednú triedu aj posledný časový krok a držať opakovania oddelené blankom
- Používajte jeden engine na jazykový profil a podávajte explicitné zoznamy strán pre dokumenty miešajúce písma
RightToLeftmení len poradie boxov; rozpoznaný text ostanú v Unicode logickom poradí- Inštalujte modely cez
Install-RapidOCRModels.ps1, aby piny SHA256 držali pairing modelu a slovníka; nastavteUseAngleClassifier := False, ak ste inštalovali s-SkipClassifier - Zvýšte
MaxPixelsnad predvolené 16 777 216 skôr, než pustíte strany A3 a väčšie pri 300 DPI
Jazykové presety RapidOCR, natívny DLL adaptér aj pipeline OCR textovej vrstvy sú súčasťou HotPDF Delphi PDF Component pre Delphi, C++Builder a Windows FPC/Lazarus, od v2.775.0 pre multijazyčné profily