HotPDF dělá naskenované PDF stránky prohledávatelnými in-process RapidOCR přes HPDFCreateRapidOCRDLLOCREngine, továrnu přidanou ve v2.774.0, která nahraje HotPDFRapidOCR.dll, drží ONNX modely detekce, klasifikace úhlu a rozpoznávání rezidentní v paměti a vrací IHPDFOCREngine. Tenhle engine předáte THotPDF.ApplyLoadedOCRTextLayer, která vyrenderuje každou stránku, spustí CPU inferenci bez Pythonu a bez dceřiného procesu a zapíše neviditelnou Unicode textovou vrstvu
Motivací je cena za stránku. Dřívější process adapter RapidOCR HPDFCreateRapidOCREngine startuje pro každé volání Recognize Python workera, který si naimportuje runtime a nahraje ONNX modely dřív, než přečte jediný pixel. U 500stránkového archivu se ta startovací daň opakuje 500krát a nasazení znamená vozit vedle spustitelného souboru Delphi ještě celé Python prostředí. Nativní DLL nahraje modely jednou, při vytvoření engine, a nasazení se smrskne na DLL, jeho modelové soubory a slovník znaků. Co za to obětujete, je možnost zaseknutý recognizer zastřelit, a většina té inženýrské práce v tomhle adapteru je o tom, jak s tím čestně žít
Jak uděláte naskenované PDF prohledávatelné pomocí RapidOCR DLL?
Vytvoření prohledávatelného PDF s nativním RapidOCR DLL stojí jedno volání továrny a totéž volání ApplyLoadedOCRTextLayer, které používá každý HotPDF OCR engine. Továrna bydlí v unitu HPDFRapidOCRRecognition a validuje hned: DLL i adresář modelů musí existovat, každý model i slovníkový soubor se musí najít, ABI verze musí být 1 a všechny povinné exporty musí být přítomné, než se kterýkoli model inicializuje. Chyby konfigurace vyhodí EArgumentException; model, který se nepodaří nahrát, vyhodí EInvalidOperation s diagnostickým textem, který DLL napsal
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Modely se nahrajou tady, mimo jakýkoli rozpoznávací deadline.
// Relativní názvy modelů v THPDFRapidOCRDLLOptions.Default se
// řeší vůči adresáři modelů.
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ázdný seznam stránek znamená všechny stránky; stránky, které už text mají, se přeskakují
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 jmenuje 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áknem, limitem vstupu 16 777 216 pixelů a rozpoznávací deadline 60 000 ms. Od v2.775.0 THPDFRapidOCRDLLOptions.ForLanguage vymění za odpovídající rozpoznávací model a slovník pro tradiční čínštinu, ruštinu, japonštinu, arabštinu a další profily; proč se model a slovník musí měnit spolu popisuje vícejazyčné modely RapidOCR a CTC slovníky v HotPDF. Engine se v Info.EngineName hlásí jako RapidOCR (native DLL), takže logy zůstávají jednoznačné vedle externího Tesseract OCR process adapteru a vestavěného template-matching OCR engine
Proč C ABI komunikuje jen přes int32_t a UTF-8 bajty?
ABI HotPDFRapidOCR.dll používá jen celá čísla pevné šířky, syrové ukazatele a explicitní délky v bajtech, protože Delphi, C++Builder a Free Pascal nesdílejí s MSVC nic kromě C volací konvence. std::string, std::vector nebo C++ výjimka má rozložení a unwinding model, který patří jednomu kompilátoru a jedné runtime knihovně. Pusťte kteréholi z nich přes hranici a selhání bude poškozený stack nebo heap blok uvolněný špatným alokátorem, ne čistá chyba
ABI verze 1 proto dodržuje krátký seznam pravidel. Každý export je cdecl a vrací status int32_t, kde 1 znamená úspěch a 0 selhání. Každá funkce, která může selhat, bere diagnostický buffer vlastněný volajícím a jeho kapacitu v bajtech; DLL zapíše UTF-8 zprávu ukončenou NUL, zkrácenou na dostupné místo, a adapter ji dekóduje s tvrdým terminátorem v posledním bajtu vlastního 4 096bajtového bufferu. Tělo každého exportu je obalené v try s catch (const std::exception &) i catch (...), takže chyba ONNX Runtime, assert OpenCV nebo nevalidní slovník se promění ve status 0 plus text, nikdy ve výjimku utíkající do Pascal kódu
| Export | Role | Kdy ho adapter resolvuje |
|---|---|---|
HPDFRapidOCRAbiVersion | Vrací 1; jakákoli jiná hodnota se odmítne | Nejdřív, před čímkoli jiným |
HPDFRapidOCRCreate | Nahraje modely detekce, volitelné klasifikace a rozpoznávání a slovník | V továrně |
HPDFRapidOCRRecognize | Zpracuje jeden bitmap a vysílá jeden callback na textový řádek | V továrně |
HPDFRapidOCRDestroy | Uvolní instanci modelů | V továrně |
HPDFRapidOCRSetReadingDirection | Volitelné pořadí řádků zprava doleva, přidané ve v2.775.0 | Jen když je nastavené RightToLeft |
Volitelný export se resolvuje lazy záměrně: DLL z v2.774.0, který ho nemá, stále obslouží požadavky zleva doprava. DLL se nahraje přes LoadLibraryEx s vyhledávacími příznaky pokrývajícími vlastní složku DLL plus defaultní bezpečné adresáře, takže závislosti ONNX Runtime nebo OpenCV položené vedle HotPDFRapidOCR.dll se najdou bez sahání na PATH. Cesty modelů a slovníků cestují jako UTF-8 a DLL je převede přes MultiByteToWideChar v přísném režimu, než otevře soubory přes wide-character API, takže adresář modelů pod čínským nebo cyrilským uživatelským jménem funguje místo toho, aby se rozšířil bajt po bajtu do nesmyslů
Jedno pravidlo bydlí v buildu, ne v headeru. DLL linkuje ONNX Runtime a OpenCV staticky a defaultní CMake konfigurace používá static release CRT (/MT). Statické knihovny kompilované proti /MD smíchané do DLL s /MT dají v nejlepším případě link chyby a v nejhorším dva nezávislé heap, takže dopravené knihovny musí odpovídat tomu, který CRT režim DLL používá
Co se děje mezi TBitmap a textovým řádkem?
HotPDF podá DLL nezávislý top-down BGR snapshot vyrenderované stránky a DLL podá zpátky jeden callback na rozpoznaný textový řádek s vypůjčeným UTF-8 textem, který musí adapter zkopírovat, než se vrátí
V Delphi adapter přiřadí bitmap stránky do privátního TBitmap, vynutí pf24bit a čte řádky přes GetDIBits se záporným biHeight, což dá top-down řádky doplněné na čtyřbajtové zarovnání; tenhle stride se předává explicitně. Na FPC čte přes CreateIntfImage, protože zápisy přes LCL scanline můžou aktualizovat syrový obrázek, aniž by osvěžily GDI handle. Bitmap volajícího se nikdy nemění a pixelový rozpočet (MaxPixels, default 16 777 216 a konfigurovatelný až na 67 108 864) i limit 32 767 pixelů na dimenzi se kontrolují, než se alokuje snapshot buffer
Uvnitř DLL se snapshot doplní 50 bílými pixely, textové regiony se detekují s maximální stranou 1 024 pixelů, boxy se seřadí do vodorovných řádků a každý výřez se před rozpoznáváním volitelně otočí podle angle classifieru. Každý textový řádek pak projde callbackem, který dostane const char*, počet bajtů, celočíselný box v pixelech původního obrázku a průměrnou confidence znaků. Textový ukazatel je validní jen během callbacku, takže ho adapter zkopíruje hned, a je přísný v tom, co přijímá:
- UTF-8 se dekóduje s
MB_ERR_INVALID_CHARS; poškozená sekvence failne stránku místo toho, aby v prohledávatelné vrstvě vyrobila náhradní znaky - Řídicí znaky C0 a C1 se odmítají a řádky s pouhými bílými znaky se přeskakují
- Box musí ležet uvnitř bitmapu a confidence musí být konečná hodnota od 0 do 1
- Text se počítá do
MaxTextCodeUnitspožadavku s tvrdým stropem 1 048 576 UTF-16 jednotek na volání a znaky ze supplementary plane stojí dvě jednotky - Jakákoli Pascal výjimka uvnitř callbacku se tam chytne, uloží a promění v návrat 0, což donutí DLL zastavit se a ohlásit selhání; uložená zpráva pak slouží jako diagnostika
Pro ladění jsou důležité dva důsledky. Zaprvé, jednotkou výstupu je řádek, ne slovo: každý řádek spotřebuje jednu pozici MaxWords, Info.AcceptedWordCount a Info.DroppedWordCount počítají řádky a zvýrazňování ve vyhledávání se táhne přes box celého řádku. Zadruhé, MinimumConfidence (default 0.5) se porovnává s průměrnou confidence znaků řádku, takže řádek s jedním nečitelným znakem mezi dvaceti čistými obvykle přežije. DLL nepodává žádnou baseline, takže ji pipeline textové vrstvy odhadne z boxu. Prázdná stránka uspěje s nulou řádků a jakékoli selhání vymaže částečné výsledky, takže zápis více stránek zůstává all-or-nothing
Vlastnictví modelů a thread safety
Každý RapidOCR DLL engine vlastní přesně jednu instanci modelů po celý svůj život a volání Recognize na tomhle engine serializuje kritická sekce. Držením interface IHPDFOCREngine zůstávají modely nahřáté, takže správný vzor pro dávkovou práci je vytvořit engine jednou a používat ho napříč 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; // vzpřímené skeny: žádný model klasifikátoru se nenahrává
Models.Threads := 4; // 1..64, omezeno na počet logických procesorů
Models.TimeoutMilliseconds := 120000; // na jedno volání Recognize, kooperativně
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; // poslední reference uvolněna: modely zničeny, pak se DLL odloaduje
Hodnota Threads nastavuje počty vláken intra-op i inter-op každé ONNX session a DLL ji sevře na počet aktivních procesorů. Dvě vlákna sdílející jeden engine neběží paralelně; druhé čeká na zámek. To čekání není slepé EnterCriticalSection: adapter volá TryEnterCriticalSection každých 25 ms a mezi pokusy kontroluje cancellation token i deadline, takže zařazený požadavek se stále dá zrušit nebo může vypršet. Pokud potřebujete skutečný paralelismus, vytvořte jeden engine na workera a přijměte, že každý engine drží vlastní kopii modelů v paměti
Pořadí rozebrání je dané destruktorem engine: HPDFRapidOCRDestroy uvolní instanci modelů nejdřív a pak FreeLibrary DLL odloaduje. Na nativní straně je inicializace modelů stejně pečlivá; když rozpoznávací model selže poté, co sessiony detektoru a klasifikátoru už byly postavené, tyhle sessiony se uvolní, než se chyba nahlásí, a počet tříd slovníku se kontroluje proti výstupu modelu během inicializace, ne až na první stránce
Proč se nativní OCR volání nedá zabít uprostřed inference?
Nativní volání RapidOCR se nedá zabít uprostřed inference, protože běží na vašem vlákně, uvnitř vašeho procesu, uprostřed ONNX Runtime session, která nepřijímá přerušení. Zrušení v DLL adapteru HotPDF je proto kooperativní: DLL volá abort callback před detekcí a po ní, po klasifikaci a po každém rozpoznaném řádku a zastaví se na prvním checkpointu, kde callback vrátí 0. Jedno rozjeté ONNX Run se nejdřív dotáhne
Alternativy jsou horší než čekání. TerminateThread by nechal heap zámek CRT, thread pool ONNX Runtime a jakýkoli stav OpenCV v tom stavu, ve kterém je právě zastihl, a tím otrávil zbytek procesu. FreeLibrary běžícího volání odloaduje kód, který je na stacku. Ani jedno se nedá udělat bezpečně, takže je adapter nikdy nezkouší. Deadline v TimeoutMilliseconds je tudíž kooperativní deadline; vypršelá deadline se projeví jako chyba engine s diagnostikou vypršení času a zrušený token jako otlsCancelled:
// Token vytvoří volající a sdílí ho s UI vláknem,
// které volá Token.Cancel, když uživatel stiskne Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
case Info.Status of
otlsCancelled:
// vráceno na hranici dalšího kroku nebo řádku; dokument beze změny
Writeln('Cancelled');
otlsEngineError:
// zahrnuje kooperativní vypršení deadline a nativní diagnostiku
Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded:
Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
Tohle je jádro kompromisu mezi process adaptery HotPDF a in-process DLL a ani jedna strana nevyhrává na každé řádce:
- Startovací cena: Tesseract i Python RapidOCR adaptery spustí proces a nahrají modely pro každou stránku; DLL nahraje modely jednou na engine
- Zastavení: dceřiný proces se dá rovnou ukončit a Python worker běží uvnitř Job Object s kill-on-close, takže jeho celý strom procesů odchází s ním; DLL se umí zastavit jen na hranicích kroků a řádků
- Izolace chyb: pád v
tesseract.exezničí jednu stránku; access violation uvnitř DLL strhne váš celý proces - Nasazení: process adaptery potřebují nainstalovaný program nebo Python prostředí; DLL potřebuje sebe, své modely a svůj slovník, v bitnosti odpovídající aplikaci
- Paměť: process adaptery uvolní všechno, když dceřiný proces skončí; DLL engine drží své modely rezidentní, dokud se neuvolní poslední interface reference
Pro interaktivní desktopovou aplikaci, která OCRuje stránku po stránce, obvykle vyhrává odezva DLL. Pro server, který nonstop pohlcuje nedůvěryhodné skeny, stojí process hranice za svou startovací cenu
Build a nasazení HotPDFRapidOCR.dll
HotPDFRapidOCR.dll se builduje z C++ zdrojáků v Native/RapidOCR s MSVC, C++17, Windows SDK a CMake 3.20 a novějším, přes pomocný skript, který bere adresáře nativních síťových zdrojů, ONNX Runtime a OpenCV plus platformu Win32 nebo Win64. Buildujte obě, pokud dodáváte obě, protože 32bitová Delphi aplikace nenahraje 64bitovou DLL a statické knihovny, které dopravíte, musí sedět na cílovou architekturu i na CRT režim
Strana modelů má vlastní kompatibilitní limity. Detektor je DB text detector; recognizer přijímá CTC modely v rozložení NCHW s pevnou výškou vstupu 32 nebo 48 a u modelů s dynamickou výškou používá 48. Přibalený statický ONNX Runtime nenahraje modely uložené s novější IR verzí, takže nedávné exporty PP-OCRv5 selžou při inicializaci s diagnostikou místo částečného nahrání. Slovník musí být UTF-8 bez BOM, v přesném pořadí znaků modelu, a jeho počet tříd musí odpovídat výstupu modelu; konce řádků CRLF se přijímají. Rozpoznávání je offline: DLL nikdy nestáhne chybějící model
Rychlá reference
- Továrna:
HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options])v unituHPDFRapidOCRRecognition, dostupná od v2.774.0 v Delphi, C++Builder a Windows buildech FPC/Lazarus - Udržujte vrácený
IHPDFOCREnginenaživu napříč stránkami a dokumenty; jeho uvolnění zničí modely a odloaduje DLL - Jeden engine spouští jedno rozpoznávání najednou; pro paralelní workery vytvořte víc engineů a pro každou kopii modelů rezervujte paměť
- Výstup je jedna položka na textový řádek s průměrnou confidence znaků, filtrovaná přes
THPDFOCRTextLayerOptions.MinimumConfidence - Zrušení i
TimeoutMillisecondsjsou kooperativní; rozjeté ONNX běžení se vždy dotáhne - Přiřaďte bitnost DLL k aplikaci a CRT režim statických knihoven ONNX Runtime a OpenCV k DLL
- Vyberte jazykový profil pro každý engine přes
THPDFRapidOCRDLLOptions.ForLanguage(v2.775.0); jeden engine sám jazyky nedetekuje
Nativní RapidOCR adapter, process OCR adaptery, renderer stránek, který je zásobuje, i zapisovač neviditelné Unicode textové vrstvy lodí společně v HotPDF, nativní VCL PDF komponentě pro Delphi a C++Builder. Pokud vaše aplikace pro pořizování nebo archivaci dokumentů potřebuje prohledávatelný výstup bez Python runtime na cílovém stroji, HotPDF Delphi PDF komponenta poskytuje celou pipeline a nechá nasadit jen DLL a její modely