HotPDF robí naskenované PDF strany prehľadávateľnými cez in-process RapidOCR pomocou HPDFCreateRapidOCRDLLOCREngine, factory pridanej vo v2.774.0, ktorá načíta HotPDFRapidOCR.dll, drží ONNX modely detekcie, klasifikácie uhla a rozpoznávania rezidentné v pamäti a vráti IHPDFOCREngine. Ten engine podáte do THotPDF.ApplyLoadedOCRTextLayer, ktorý vyrenderuje každú stranu, spustí CPU inferenciu bez Pythonu a child procesu a commitne neviditeľnú Unicode textovú vrstvu
Motiváciou je cena za stranu. Adaptér procesu RapidOCR dodaný skôr, HPDFCreateRapidOCREngine, štartuje Python workera pre každé volanie Recognize a ten worker si importuje runtime a načíta ONNX modely skôr, než prečíta jediný pixel. Na 500-stranovom archíve sa tá štartovacia daň opakuje 500-krát a nasadenie znamená dopraviť Python prostredie vedľa Delphi spustiteľného súboru. Natívne DLL načíta modely raz, pri vytvorení enginu a nasadenie sa zmenší na DLL, jeho modelové súbory a znakový slovník. Za to sa vzdávate možnosti zastreliť zaseknutý recognizer a väčšina inžinierstva v tomto adaptéri je o tom, ako s tým žiť poctivo
Ako sprístupníte naskenované PDF pomocou RapidOCR DLL?
Vytvorenie prehľadávateľného PDF s natívnym RapidOCR DLL je jedno factory volanie a to isté volanie ApplyLoadedOCRTextLayer, ktoré používa každý OCR engine HotPDF. Factory býva v jednotke HPDFRapidOCRRecognition a validuje záhorlivé: DLL aj adresár modelov musia existovať, každý model aj slovníkový súbor sa musia vyriešiť, verzia ABI musí byť 1 a všetky povinné exporty musia byť prítomné skôr, než sa inicializuje akýkoľvek model. Chyby konfigurácie vyhodia EArgumentException; model, ktorý sa nenačíta, vyhodí EInvalidOperation nesúcu diagnostický text, ktorý DLL zapísal
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Modely sa načítajú tu, mimo akéhokoľvek termínu rozpoznávania.
// Relatívne mená modelov v THPDFRapidOCRDLLOptions.Default sa riešia
// voči adresáru modelov.
Engine := HPDFCreateRapidOCRDLLOCREngine(
'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models');
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if Doc.LoadFromFile(SourceFile) < 1 then
raise Exception.Create('Cannot load ' + SourceFile);
Options := 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, Options, Info) then
raise Exception.Create(string(Info.Diagnostic));
Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
' lines accepted, ', Info.DroppedWordCount, ' dropped');
Doc.SaveLoadedDocument(TargetFile);
finally
Doc.Free;
end;
end;
THPDFRapidOCRDLLOptions.Default menuje ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx a ppocr_keys_v1.txt, s jedným CPU vláknom, limitom vstupu 16 777 216 pixelov a termínom rozpoznávania 60 000 ms. Od v2.775.0 THPDFRapidOCRDLLOptions.ForLanguage vkladá zodpovedajúci rozpoznávací model a slovník pre tradičnú čínštinu, ruštinu, japončinu, arabčinu a ďalšie profily; prečo sa model a slovník musia meniť spolu, pokrýva multijazyčné modely RapidOCR a CTC slovníky v HotPDF. Engine sa hlási ako RapidOCR (native DLL) v Info.EngineName, čo drží logy jednoznačné vedľa externého adaptéra procesu Tesseract OCR a vstavaného OCR enginu s template matchingom
Prečo C ABI hovorí len int32_t a UTF-8 bajty?
ABI HotPDFRapidOCR.dll používa len celé čísla s pevnou šírkou, holé pointery a explicitné bajtové dĺžky, lebo Delphi, C++Builder a Free Pascal nezdieľajú s MSVC nič okrem C konvencie volania. std::string, std::vector alebo C++ výnimka má rozloženie a unwinding model patriaci jednému kompilátoru a jednej runtime knižnici. Nechajte ktorýkoľvek z nich prekročiť hranicu a zlyhaním je korumpovaný stack alebo heap blok uvoľnený nesprávnym alokátorom, nie čistá chyba
Verzia ABI 1 preto drží krátky zoznam pravidiel. Každý export je cdecl a vracia status int32_t, kde 1 znamená úspech a 0 zlyhanie. Každá funkcia, ktorá môže zlyhať, berie diagnostický buffer vlastnený volajúcim a jeho kapacitu v bajtoch; DLL zapisuje NUL-ukončenú UTF-8 správu skrátenú na veľkosť a adaptér ju dekóduje s tvrdým terminatorom v poslednom bajte vlastného 4 096-bajtového bufferu. Telo každého exportu je zabalené do try s oboma catch (const std::exception &) aj catch (...), takže chyba ONNX Runtime, OpenCV asercia alebo neplatný slovník sa stane statusom 0 plus textom, nikdy výnimkou utekajúcou do Pascal kódu
| Export | Rola | Kedy ho adaptér vyrieši |
|---|---|---|
HPDFRapidOCRAbiVersion | Vracia 1; akákoľvek iná hodnota sa odmietne | Prvý, pred všetkým ostatným |
HPDFRapidOCRCreate | Načíta modely detekcie, voliteľnej klasifikácie a rozpoznávania plus slovník | Vo factory |
HPDFRapidOCRRecognize | Spustí jednu bitmapu a emituje jeden callback na textovú líniu | Vo factory |
HPDFRapidOCRDestroy | Uvoľní inštanciu modelov | Vo factory |
HPDFRapidOCRSetReadingDirection | Voliteľné poradie riadkov sprava doľava, pridané vo v2.775.0 | Len keď je nastavené RightToLeft |
Voliteľný export sa rieši lazily zámerne: DLL z v2.774.0, ktorému chýba, stále obsluhuje požiadavky zľava doprava. DLL sa načítava cez LoadLibraryEx s vyhľadávacími príznakmi pokrývajúcimi vlastný priečinok DLL plus predvolené bezpečné adresáre, takže závislosti ONNX Runtime alebo OpenCV položené vedľa HotPDFRapidOCR.dll sa nájdu bez siahania na PATH. Cesty modelov aj slovníka cestujú ako UTF-8 a DLL ich konvertuje cez MultiByteToWideChar v prísnom režime skôr, než otvára súbory cez wide-character API, takže adresár modelov pod čínskym alebo cyrilským používateľským menom funguje namiesto bajt po bajte rozšírenia do nesmyslov
Jedno pravidlo býva v zostavení, nie v hlavičke. DLL staticky linkuje ONNX Runtime a OpenCV a predvolená konfigurácia CMake používa statický release CRT (/MT). Statické knižnice kompilované proti /MD zmiešané do DLL s /MT dajú v lepšom prípade link chyby a v horšom dva nezávislé heapy, takže provisionsované knižnice musia sedieť s tým CRT režimom, ktorý DLL používa
Čo sa deje medzi TBitmap a textovou líniou?
HotPDF podá DLL nezávislý top-down BGR snapshot vyrenderovanej strany a DLL podá späť jeden callback na rozpoznanú textovú líniu s požičaným UTF-8 textom, ktorý musí adaptér skopírovať skôr, než sa vráti
V Delphi priradí adaptér bitmapu strany do súkromného TBitmap, vynúti pf24bit a číta riadky cez GetDIBits so záporným biHeight, čo dáva top-down riadky vypĺňané na štvorbajtovú vyrovnanie; ten stride sa podá explicitne. Vo FPC číta cez CreateIntfImage, lebo scanline zápisy LCL dokážu aktualizovať holý obraz bez obnovenia GDI handle. Bitmapa volajúceho sa nikdy nemodifikuje a pixelový rozpočet (MaxPixels, predvolene 16 777 216 a konfigurovateľný do 67 108 864) aj limit 32 767 pixelov na dimenziu sa kontrolujú skôr, než sa alokuje snapshot buffer
Vnútri DLL sa snapshot vypĺňa 50 bielymi pixelmi, textové regióny sa detekujú s maximálnou stranou 1 024 pixelov, boxy sa usporiadajú do horizontálnych riadkov a každý orez sa pred rozpoznávaním voliteľne otočí podľa klasifikátora uhla. Každá textová línia potom prechádza callbackom, ktorý prijíma const char*, počet bajtov, celočíselný box v pixeloch pôvodného obrazu a priemernú confidence znakov. Textový pointer je platný len počas callbacku, takže ho adaptér kopíruje okamžite a je prísny v tom, čo prijíma:
- UTF-8 sa dekóduje s
MB_ERR_INVALID_CHARS; deformovaná sekvencia prepasuje stranu namiesto vyrobenia náhradných znakov v prehľadávateľnej vrstve - RIadiace znaky C0 a C1 sa odmietajú a línie obsahujúce len biele miesta sa preskočia
- Box musí ležať vnútri bitmapy a confidence musí byť konečná hodnota od 0 do 1
- Text sa počíta proti
MaxTextCodeUnitspožiadavky s tvrdým stropom 1 048 576 UTF-16 jednotiek na volanie a znaky doplnkovej roviny stoja dve jednotky - Každá Pascal výnimka vo vnútri callbacku sa tam chytí, uloží a zmení na návrat 0, čo prinúti DLL zastaviť sa a nahlásiť zlyhanie; uložená správa sa potom stane diagnostikou
Pre ladenie majú význam dva dôsledky. Po prvé, jednotka výstupu je línia, nie slovo: každá línia spotrebuje jeden slot MaxWords, Info.AcceptedWordCount aj Info.DroppedWordCount počítajú línie a search highlighting prebieha cez box línie. Po druhé, MinimumConfidence (predvolene 0.5) sa porovnáva s priemernou confidence znakov línie, takže línia s jednym nečitateľným znakom medzi dvadsiatimi čistými zvyčajne prežije. DLL nepodáva baseline, takže text-layer pipeline si ju odhadne z boxu. Prázdna strana uspeje s nulou línií a každé zlyhanie vyčistí čiastočné výsledky, takže viacstranový commit ostanú all-or-nothing
Vlastníctvo modelov a thread safety
Každý engine RapidOCR DLL vlastní po celý život presne jednu inštanciu modelov a volania Recognize na tom engine sa serializujú critical section. Držanie rozhrania IHPDFOCREngine je to, čo drží modely teplé, takže správny vzor pre dávkovú prácu je vytvoriť engine raz a znovu ho používať cez dokumenty
procedure OcrBatch(const Files: TStrings; const OutputDir: string);
var
Models: THPDFRapidOCRDLLOptions;
Engine: IHPDFOCREngine;
Doc: THotPDF;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
I: Integer;
begin
Models := THPDFRapidOCRDLLOptions.Default;
Models.UseAngleClassifier := False; // vzpriamené skeny: klasifikátor sa nenačíta
Models.Threads := 4; // 1..64, kapované na počet logických procesorov
Models.TimeoutMilliseconds := 120000; // na volanie Recognize, kooperatívne
Engine := HPDFCreateRapidOCRDLLOCREngine(
'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
Options := THPDFOCRTextLayerOptions.Default;
for I := 0 to Files.Count - 1 do
begin
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if (Doc.LoadFromFile(Files[I]) > 0) and
Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
Doc.SaveLoadedDocument(IncludeTrailingPathDelimiter(OutputDir) +
ExtractFileName(Files[I]))
else
Writeln(Files[I], ': ', string(Info.Diagnostic));
finally
Doc.Free;
end;
end;
end; // uvoľnená posledná referencia: modely sa zničia, potom sa unloaduje DLL
Hodnota Threads nastavuje počet intra-op aj inter-op vlákien každej ONNX session a DLL ju kapuje na počet aktívnych procesorov. Dve vlákna zdieľajúce jeden engine nebežia paralelne; druhé čaká na zámok. To čakanie nie je hluché EnterCriticalSection: adaptér volá TryEnterCriticalSection každých 25 ms a medzi pokusmi kontroluje cancellation token aj termín, takže frontovaná požiadavka sa stále dá zrušiť alebo vyčerpať. Ak potrebujete skutočnú paralelnosť, vytvorte jeden engine na workera a zmierite sa s tým, že každý engine drží v pamäti vlastnú kópiu modelov
Poradie rozoberania je fixované deštruktorom enginu: HPDFRapidOCRDestroy uvoľní najprv inštanciu modelov a potom FreeLibrary unloaduje DLL. Na natívnej strane je inicializácia modelov rovnako starostlivá; keď zlyhá rozpoznávací model potom, čo sessiony detektora a klasifikátora už stáli, tieto sessiony sa uvoľnia skôr, než sa nahlási chyba, a počet tried slovníka sa kontroluje voči výstupu modelu počas inicializácie, nie pri prvej strane
Prečo sa natívne OCR volanie nedá zabiť počas inferencie?
Natívne volanie RapidOCR sa nedá zabiť počas inferencie preto, lebo beží na vašom vlákne, vo vnútri vášho procesu, uprostred ONNX Runtime session, ktorá neprijíma prerušenie. Zrušenie v DLL adaptéri HotPDF je preto kooperatívne: DLL volá abort callback pred detekciou a po nej, po klasifikácii a po každej rozpoznanej línii a zastaví sa na prvom kontrolnom bode, kde callback vráti 0. Jedno už začaté ONNX Run sa najprv doplní
Alternatívy sú horšie než čakanie. TerminateThread by nechal heap zámok CRT, thread pool ONNX Runtime a akýkoľvek stav OpenCV v stave, v akom sa práve nachádzali, a otrávil zvyšok procesu. FreeLibrary počas bežiaceho volania unloaduje kód, ktorý je na stacku. Ani jedno sa nedá spraviť bezpečne, takže sa ich adaptér nikdy nepokúsi. Termín v TimeoutMilliseconds je preto kooperatívny termín a prekročený termín sa ukáže ako chyba enginu s diagnostikou vyčerpaného času, zatiaľ čo zrušený token sa ukáže ako otlsCancelled:
// Token vytvára volajúci a zdieľa ho s UI vláknom,
// ktoré volá Token.Cancel, keď používateľ stlačí Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
case Info.Status of
otlsCancelled:
// vrátené na ďalšej hranici fázy alebo línie; dokument nezmenený
Writeln('Cancelled');
otlsEngineError:
// vrátane kooperatívneho prekročenia termínu a natívnych diagnostík
Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded:
Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
Toto je jadrový kompromis medzi procesovými adaptérmi HotPDF a in-process DLL a ani jedna strana nevyhráva v každom riadku:
- Štartovacia cena: adaptéry Tesseract a Python RapidOCR púšťajú proces a načítavajú modely pre každú stranu; DLL načíta modely raz na engine
- Zastavenie: child proces sa dá zastreliť rovno a Python worker beží vo vnútri Job Objectu s kill-on-close, takže odíde celý jeho procesový strom; DLL sa dokáže zastaviť len na hraniciach fáz a línií
- Obsah faultov: pád vo
tesseract.exeprepasuje jednu stranu; access violation vo vnútri DLL zoberie váš proces nadol - Nasadenie: procesové adaptére potrebujú nainštalovaný program alebo Python prostredie; DLL potrebuje seba, svoje modely a svoj slovník, sediace s bitness aplikácie
- Pamäť: procesové adaptére uvoľnia všetko, keď child skončí; engine DLL drží modely rezidentné, kým sa neuvolní posledná referencia rozhrania
Pre interaktívnu desktopovú aplikáciu, ktorá OCR-uje stranu po strane, zvyčajne vyhráva odozva DLL. Pre server, ktorý nonstop požiera nedôveryhodné skeny, hranica procesu stojí za svoju štartovaciu cenu
Stavba a nasadenie HotPDFRapidOCR.dll
HotPDFRapidOCR.dll sa stavia z C++ zdrojov v Native/RapidOCR s MSVC, C++17, Windows SDK a CMake 3.20 alebo novším, pomocou pomocného skriptu, ktorý berie natívne sieťové zdroje, adresáre ONNX Runtime a OpenCV plus platformu Win32 alebo Win64. Stavte obe, ak dopravujete obe, lebo 32-bitová Delphi aplikácia nedokáže načítať 64-bitové DLL a statické knižnice, ktoré provisionsujete, musia sedieť aj s cieľovou architektúrou, aj s CRT režimom
Strana modelov má vlastné kompatibilné limity. Detektor je DB text detektor; recognizer prijíma CTC modely v NCHW rozložení s fixnou výškou vstupu 32 alebo 48 a používa 48 pre modely s dynamickou výškou. Pribalený statický ONNX Runtime nedokáže načítať modely uložené s novšou IR verziou, takže nedávne exporty PP-OCRv5 zlyhajú inicializáciu s diagnostikou namiesto čiastočného načítania. Slovník musí byť UTF-8 bez BOM, v presnom poradí znakov modelu a jeho počet tried musí sedieť s výstupom modelu; CRLF konce riadkov sa prijímajú. Rozpoznávanie je offline: DLL nikdy nestiahne chýbajúci model
Rýchla referencia
- Factory:
HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options])vHPDFRapidOCRRecognition, dostupná od v2.774.0 v zostaveniach Delphi, C++Builder aj Windows FPC/Lazarus - Držte vrátené
IHPDFOCREnginenažive cez strany aj dokumenty; jeho uvoľnenie zničí modely a unloaduje DLL - Jeden engine beží na jednom rozpoznávaní naraz; vytvorte viacero enginov pre paralelných workerov a rozpočítajte pamäť na každú kópiu modelov
- Výstup je jedna položka na textovú líniu s priemernou confidence znakov, filtrovaná cez
THPDFOCRTextLayerOptions.MinimumConfidence - Zrušenie aj
TimeoutMillisecondssú kooperatívne; rozbehnuté ONNX run sa vždy doplní - Slaďte bitness DLL s aplikáciou a CRT režim statických knižníc ONNX Runtime a OpenCV s DLL
- Vyberte jazykový profil na engine cez
THPDFRapidOCRDLLOptions.ForLanguage(v2.775.0); jeden engine sám jazyky nedetekuje
Natívny adaptér RapidOCR, procesové OCR adaptéry, page renderer, ktorý ich kŕmi, aj zapisovač neviditeľnej Unicode textovej vrstvy dodáva HotPDF, natívny VCL PDF komponent pre Delphi a C++Builder. Ak vaša aplikácia na zber alebo archiváciu dokumentov potrebuje prehľadávateľný výstup bez Python runtime na cieľovom stroji, HotPDF Delphi PDF component podá celú pipeline a na nasadenie ostane len DLL a jeho modely