A HotPDF in-process RapidOCR-ral teszi kereshetővé a szkenelt PDF oldalakat a HPDFCreateRapidOCRDLLOCREngine-en át, egy v2.774.0-ban érkezett factoryn, ami betölti a HotPDFRapidOCR.dll-t, memóriában tartja az ONNX detektáló, szög-osztályozó és felismerő modelleket, és visszaad egy IHPDFOCREngine-t. Ezt az enginet adod át a THotPDF.ApplyLoadedOCRTextLayer-nek, ami renderel minden oldalt, CPU inferenciát futtat Python vagy gyerekfolyamat nélkül, és elkötelez egy láthatatlan Unicode szövegréteget
A motiváció az oldalankénti költség. A korábban szállított RapidOCR process adaptér, a HPDFCreateRapidOCREngine, minden Recognize hívásra indít egy Python workert, és az a worker beimportálja a runtime-ját és betölti az ONNX modelljeit, mielőtt egyetlen pixelt megnézne. Egy 500 oldalas archívumnál ez az indítási adó 500-szor ismétlődik, a deployment pedig azt jelenti, hogy egy Python környezetet szállítasz a Delphi executable mellé. A natív DLL a modelleket egyszer tölti be, amikor létrehozod az enginet, és a deployment összehúzódik a DLL-re, a modellfájljaira és egy karakterdictionaryre. Ammit cserébe feladsz, az a lehetőség, hogy agyonlőj egy beragadt felismerőt, és az adaptér mérnöki munkájának nagy része arról szól, hogy ezzel becsületesen együtt éljen
Hogyan teszel kereshetővé egy szkenelt PDF-et a RapidOCR DLL-lel?
Egy kereshető PDF létrehozása a natív RapidOCR DLL-lel egy factory hívást és ugyanazt az ApplyLoadedOCRTextLayer hívást veszi igénybe, amit minden HotPDF OCR engine használ. A factory a HPDFRapidOCRRecognition unitban lakik, és mohón validál: a DLL-nek és a modellkönyvtárnak léteznie kell, minden modell- és dictionary fájl feloldódni köteles, az ABI verziónak 1-nek kell lennie, és minden szükséges exportnak jelen kell lennie, mielőtt bármely modell inicializálódna. A konfigurációs hibák EArgumentException-t dobnak; a be nem tölthető modell EInvalidOperation-t dob a DLL által írt diagnosztikai szöveggel
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// A modellek itt töltődnek be, felismerési határidőn kívül.
// A THPDFRapidOCRDLLOptions.Default relatív modelnevei a
// modellkönyvtárhoz képest oldódnak fel.
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
// az üres oldallista minden oldalt jelent; a már szöveggel bíró oldalak kimaradnak
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 a ch_PP-OCRv3_det_infer.onnx-t, a ch_PP-OCRv3_rec_infer.onnx-t, a ch_ppocr_mobile_v2.0_cls_infer.onnx-t és a ppocr_keys_v1.txt-t nevezi meg, egy CPU threaddel, 16 777 216 pixeles bemeneti limitkel és 60 000 ms felismerési határidővel. v2.775.0 óta a THPDFRapidOCRDLLOptions.ForLanguage illeszkedő felismerő modellt és dictionaryt cserél be hagyományos kínaihoz, oroszhoz, japánhoz, arabhoz és további profilokhoz; hogy miért kell a modellnek és a dictionarynek együtt váltania, azt a RapidOCR többnyelvű modelljei és CTC dictionaryk a HotPDF-ben cikk taglalja. Az engine magát RapidOCR (native DLL)-ként jelenti Info.EngineName-ben, ami a naplókat egyértelműen tartja a külső Tesseract OCR process adaptér és a beépített template-matching OCR engine mellett
Miért beszél csak int32_t-t és UTF-8 bájtokat a C ABI?
A HotPDFRapidOCR.dll ABI-ja csak fix szélességű egészeket, nyers pointereket és explicit bájthosszokat használ, mert a Delphi, a C++Builder és a Free Pascal semmiben nem osztozik az MSVC-vel a C hívási konvención túl. Egy std::string, egy std::vector vagy egy C++ kivétel olyan layouttal és unwinding modellel bír, ami egy kompilátorhoz és egy runtime libraryhez tartozik. Ha valamelyik átlépi a határt, a hiba romlott stack vagy rossz alloktor által felszabadított heap blokk, nem pedig tiszta hibaüzenet
Az ABI 1-es verziója ezért rövid szabálylistát követ. Minden export cdecl, és int32_t státuszt ad vissza, ahol az 1 a siker, a 0 a bukás. Minden elbukható függvény átvesz egy hívó tulajdonú diagnosztikai puffert és annak kapacitását bájtban; a DLL NUL-lal lezárt, méretre csonkolt UTF-8 üzenetet ír, az adaptér pedig kemény terminátorral, a saját 4 096 bájtos pufferének utolsó bájtjában dekódolja. Minden export testét try veszi körül, catch (const std::exception &)-tal és catch (...)-tal egyaránt, így egy ONNX Runtime hiba, egy OpenCV assertió vagy érvénytelen dictionary 0 státuszt és szöveget eredményez, sosem kivételt, ami a Pascal kódba szökne
| Export | Szerep | Mikor oldja fel az adaptér |
|---|---|---|
HPDFRapidOCRAbiVersion | 1-et ad; bármely más érték elutasítva | Először, minden más előtt |
HPDFRapidOCRCreate | Betölti a detektálót, az opcionális osztályozót, a felismerő modelleket és a dictionaryt | A factoryban |
HPDFRapidOCRRecognize | Egy bitmapet futtat, és szövegsoronként egy callbacket bocsát ki | A factoryban |
HPDFRapidOCRDestroy | Felszabadítja a modellpéldányt | A factoryban |
HPDFRapidOCRSetReadingDirection | Opcionális jobbról-balra sorsorrend, v2.775.0-ban érkezett | Csak ha a RightToLeft be van állítva |
Az opcionális export szándékosan lustán oldódik fel: egy v2.774.0-s DLL, amiből hiányzik, továbbra is kiszolgálja a balról jobbra kéréseket. A DLL LoadLibraryEx-sel töltődik, olyan keresőflaggekkel, amik lefedik a DLL saját mappáját meg az alapértelmezett biztonságos könyvtárakat, így az ONNX Runtime vagy OpenCV függőségek, amik a HotPDFRapidOCR.dll mellé kerülnek, PATH piszkálása nélkül megtalálódnak. A modell- és dictionary elérési utak UTF-8-ként utaznak, és a DLL szigorú módban MultiByteToWideChar-ral alakítja őket, mielőtt széles karakteres API-okon át nyitná a fájlokat, így egy kínai vagy cirill usernév alatti modellkönyvtár működik, ahelyett hogy bájtonként butasággá szélesedne
Egy szabály a buildben él, nem a headerben. A DLL statikusan linkeli az ONNX Runtime-ot és az OpenCV-t, és az alapértelmezett CMake konfiguráció a statikus release CRT-t használja (/MT). A /MD-re kompilált statikus libraryk, amik egy /MT DLL-be keverednek, legjobb esetben linkhibákat, legrosszabb esetben két független heapet adnak, így a beszállított libraryknak arra a CRT módra kell illeszkedniük, amit a DLL használ
Mi történik egy TBitmap és egy szövegsor között?
A HotPDF átad a DLL-nek a renderelt oldal egy független, felülről-lefelé BGR pillanatképét, a DLL pedig szövegsoronként egy callbacket ad vissza kölcsönzött UTF-8 szöveggel, amit az adaptérnek a visszatérés előtt le kell másolnia
Delphin az adaptér az oldal bitmapjét egy privát TBitmap-hoz rendeli, pf24bit-re kényszeríti, és negatív biHeight-dal, GetDIBits-szel olvassa a sorokat, ami négybájtos igazításra kiegészített, felülről-lefelé sorokat ad; azt a stride-ot explicit adja át. FPC-n CreateIntfImage-en át olvas, mert az LCL scanline írásai frissíthetik a nyers képet a GDI handle frissítése nélkül. A hívó bitmapjét soha nem módosítja, és a pixelbudgetet (MaxPixels, alapértelmezésben 16 777 216, konfigurálható 67 108 864-ig) és a dimenziónkénti 32 767 pixeles limitet még a pillanatkép-puffer lefoglalása előtt ellenőrzi
A DLL-en belül a pillanatképet 50 fehér pixellel kiegészítik, a szövegterületek 1 024 pixeles maximális oldallal detektálódnak, a boxok vízszintes sorokba rendeződnek, és minden kivágás opcionálisan elfordul a szögosztályozóval, mielőtt felismerésre kerülne. Minden szövegsor aztán egy callbacken megy át, ami egy const char*-ot, egy bájtszámot, az eredeti kép pixeleiben mért egész boxot és az átlagos karakterkonfidenciát kap. A szövegpointer csak a callback idején érvényes, így az adaptér azonnal lemásolja, és szigorú abban, mit fogad el:
- Az UTF-8
MB_ERR_INVALID_CHARS-szel dekódolódik; egy rosszul formált szekvencia az oldalt buktatja, ahelyett hogy helyettesítő karaktereket gyártana egy kereshető rétegben - A C0 és C1 kontrollkarakterek elutasítva, és a csak whitespace sorok kimaradnak
- A boxnak a bitmapen belül kell lennie, a konfidenciának pedig véges, 0-tól 1-ig terjedő értéknek
- A szöveg a kérés
MaxTextCodeUnits-ába számolódik, hívásenként 1 048 576 UTF-16 egységes kemény plafonnal, és a kiegészítő síkbeli karakterek két egységbe kerülnek - A callbacken belüli bármely Pascal kivétel ott elkapódik, tárolódik, és 0-s visszatéréssé alakul, ami arra készteti a DLL-t, hogy megálljon és hibát jelentsen; a tárolt üzenet lesz aztán a diagnosztika
Két következmény számít hangoláskor. Először is a kimenet egysége a sor, nem a szó: minden sor egy MaxWords helyet fogyaszt, az Info.AcceptedWordCount és az Info.DroppedWordCount sorokat számol, és a keresési kiemelés a sor boxját fogja át. Másodszor a MinimumConfidence (alapértéken 0.5) a sor átlagos karakterkonfidenciájával vetődik össze, így egy sor, amiben húsz tiszta karakter mellett egy olvashatatlan van, általában túléli. A DLL nem szállít alapot, így a szövegréteg-pipeline a boxból becsül egyet. Egy üres oldal nulla sorral sikerrel jár, és bármely hiba törli a részleges eredményeket, így a többoldalas elkötelezés all-or-nothing marad
Modelltulajdonlás és thread safety
Minden RapidOCR DLL engine pontosan egy modellpéldányt birtokol a teljes élettartama alatt, és az arra az enginere érkező Recognize hívások critical sectionnel sorosítódnak. Az IHPDFOCREngine interfész birtoklása tartja melegen a modelleket, így a batch munka helyes mintája az, hogy egyszer hozod létre az enginet, és dokumentumok közt újrahasznosítod
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; // álló szkenek: nincs betöltve osztályozó modell
Models.Threads := 4; // 1..64, a logikai processzorszámnál levágva
Models.TimeoutMilliseconds := 120000; // Recognize hívásonként, kooperatív
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; // az utolsó referencia elengedve: a modellek elpusztulnak, aztán a DLL kikerül
A Threads érték az ONNX sessionenkénti intra-op és inter-op thread számokat is beállítja, és a DLL az aktív processzorszámra vágja le. Két thread, ami egy enginet oszt meg, nem fut párhuzamosan; a második a zárra vár. Az a várakozás nem vak EnterCriticalSection: az adaptér 25 ms-enként TryEnterCriticalSection-t hív, és a kísérletek közt nézi a cancellation tokent és a határidőt, így egy sorban álló kérés még mindig megszakítható vagy timeoutolhat. Ha valódi párhuzamosságra van szükséged, workerenként hozz létre egy enginet, és vállald, hogy minden engine a saját modellmásolatát tartja a memóriában
A lebontás sorrendjét az engine destruktora rögzíti: a HPDFRapidOCRDestroy előbb felszabadítja a modellpéldányt, aztán a FreeLibrary kipakolja a DLL-t. A natív oldalon a modellinicializálás hasonlóan gondos: amikor a felismerő modell bukik el, miután a detektor és az osztályozó sessionjei már felépültek, azok a sessionek felszabadulnak, mielőtt a hibát jelentenék, és a dictionary osztályszámát már inicializáláskor ellenőrzik a modell kimenetével szemben, nem az első oldalon
Miért nem lőhető le egy natív OCR hívás az inferencia közben?
Egy natív RapidOCR hívás az inferencia közben azért nem lőhető le, mert a te threadeden fut, a te folyamatodban, egy ONNX Runtime session közepén, ami nem fogad megszakítást. A HotPDF DLL adaptérjában a cancellation ezért kooperatív: a DLL detektálás előtt és után, osztályozás után és minden felismert sor után abort callbacket hív, és az első checkpointnál megáll, ahol a callback 0-t ad. Egy már elindult ONNX Run előbb lefut
Az alternatívák rosszabbak a várakozásnál. A TerminateThread a CRT heap zárat, az ONNX Runtime thread poolját és bármely OpenCV állapotot ott hagyná, amilyenben épp van, megmérgezve a folyamat többi részét. A FreeLibrary egy futó hívás közben olyan kódot pakolna ki, ami a stacken van. Egyik sem tehető biztonságossá, így az adaptér sosem próbálja őket. A TimeoutMilliseconds-beli határidő ezért kooperatív határidő, és egy lejárt határidő engine-hibaként jelentkezik timeout diagnózissal, a megszakított token pedig otlsCancelled-ként:
// A tokent a hívó hozza létre, és megosztja a UI threaddel,
// ami Token.Cancel-t hív, amikor a user megnyomja a Stopot
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
case Info.Status of
otlsCancelled:
// a következő szakasz- vagy sorhatárnál tér vissza; a dokumentum változatlan
Writeln('Cancelled');
otlsEngineError:
// kooperatív határidő lejáratot és natív diagnosztikát is tartalmaz
Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded:
Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
Ez a központi kompromisszum a HotPDF process adaptérjei és az in-process DLL között, és egyik fél sem nyer minden sorban:
- Indítási költség: a Tesseract és Python RapidOCR adaptérek minden oldalra folyamatot indítanak és modelleket töltenek; a DLL engineenként egyszer tölti a modelleket
- Megállítás: egy gyerekfolyamat egy csapásra lelőhető, és a Python worker kill-on-close Job Objectben fut, így az egész folyamatfája vele megy; a DLL csak szakasz- és sorhatárokon tud megállni
- Hibabeszabályozás: egy crash a
tesseract.exe-ben egy oldalt buktat; egy access violation a DLL-en belül a te folyamatodat viszi magával - Deployment: a process adaptéreknek telepített program vagy Python környezet kell; a DLL-nek önmaga, a modelljei és a dictionaryje, az alkalmazás bitségségére illesztve
- Memória: a process adaptérek mindenkit elengednek, amikor a gyerek kilép; egy DLL engine a modelljeit az utolsó interfészreferencia elengedéséig tartja bent
Egy interaktív desktop alkalmazásnak, ami egyszerre egy oldalt OCR-z, általában a DLL válaszideje nyer. Egy szervernek, ami éjjel-nappal nem megbízható szkeneket nyel, megéri a process határ indítási költsége
A HotPDFRapidOCR.dll buildelése és deployolása
A HotPDFRapidOCR.dll a Native/RapidOCR C++ forrásaiból épül MSVC-vel, C++17-gyel, Windows SDK-val és CMake 3.20-mal vagy újabbal, egy segédszkripttel, ami átveszi a natív hálózati forrásokat, az ONNX Runtime és OpenCV könyvtárakat meg egy Win32 vagy Win64 platformot. Építsd mindkettőt, ha mindkettőt szállítod, mert egy 32 bites Delphi alkalmazás nem tud 64 bites DLL-t betölteni, és a beszállított statikus libraryknek is a célarchitektúrára meg a CRT módra kell illeszkedniük
A modelloldalnak megvannak a maga kompatibilitási határai. A detektor egy DB szövegdetektor; a felismerő NCHW layoutú, 32 vagy 48 rögzített bemeneti magasságú CTC modelleket fogad, dinamikus magasságú modellekre pedig a 48-at használja. A becsomagolt statikus ONNX Runtime nem tud az újabb IR verzióval mentett modelleket betölteni, így a friss PP-OCRv5 exportok inicializálásnál buknak el diagnosztikával, részleges betöltés helyett. A dictionarynek BOM nélküli UTF-8-nak kell lennie, pontosan a modell karakterrendjében, osztályszámának a modell kimenetéhez kell illeszkednie; a CRLF sorszégek elfogadottak. A felismerés offline: a DLL soha nem tölt le hiányzó modellt
Gyorsreferencia
- Factory:
HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options])aHPDFRapidOCRRecognition-ban, v2.774.0 óta elérhető Delphiben, C++Builderben és Windows FPC/Lazarus buildekben - Tartsd életben a visszaadott
IHPDFOCREngine-t oldalak és dokumentumok át; az elengedése elpusztítja a modelleket és kipakolja a DLL-t - Egy engine egyszerre egy felismerést futtat; párhuzamos workerekhez több enginet hozz létre, és budgetezz memóriát minden modellmásolatra
- A kimenet szövegsoronként egy bejegyzés átlagos karakterkonfidenciával, szűrve a
THPDFOCRTextLayerOptions.MinimumConfidence-dal - A cancellation és a
TimeoutMillisecondskooperatív; egy futó ONNX Run mindig befejeződik - Illeszd a DLL bitségségét az alkalmazáshoz, a statikus ONNX Runtime és OpenCV libraryk CRT módját a DLL-hez
- Válassz nyelvi profilt engineenként a
THPDFRapidOCRDLLOptions.ForLanguage-dal (v2.775.0); egy engine önmagától nem detektál nyelveket
A natív RapidOCR adaptér, a process alapú OCR adaptérek, a rájuk etető oldalrenderer és a láthatatlan Unicode szövegréteg-író együtt szállítanak a HotPDF-ben, egy natív VCL PDF componentban Delphihez és C++Builderhez. Ha a dokumentumfeldolgozó vagy archiváló alkalmazásodnak kereshető kimenetre van szüksége Python runtime nélkül a célgépen, a HotPDF Delphi PDF component a teljes pipeline-t adja, amiből csak a DLL és a modelljei maradnak deployolnivalók