HotPDF čini skenirane PDF stranice pretraživim in-process RapidOCR-om kroz HPDFCreateRapidOCRDLLOCREngine, fabriku dodatu u v2.774.0 koja učitava HotPDFRapidOCR.dll, drži ONNX modele detekcije, klasifikacije ugla i prepoznavanja rezidentne u memoriji, i vraća IHPDFOCREngine. Taj engine predajete THotPDF.ApplyLoadedOCRTextLayer, koji renderuje svaku stranicu, izvodi CPU inferenciju bez Python-a ili podprocesa, i uveruje nevidljivi Unicode tekstualni sloj
Motivacija je cena po stranici. RapidOCR procesni adapter isporučen ranije, HPDFCreateRapidOCREngine, pokreće Python radnika za svaki poziv Recognize, i taj radnik uvozi svoj runtime i učitava svoje ONNX modele pre nego što pročita ijedan piksel. Na arhivu od 500 stranica taj porez na start ponavlja se 500 puta, i raspoređivanje znači isporučiti Python okruženje pored Delphi izvršnog. Nativni DLL učitava modele jednom, kad stvorite engine, i raspoređivanje se skupi na DLL, njegove fajlove modela i rečnik karaktera. Ono što ustupate zauzvrat je sposobnost da ubijete zaglavljeni prepoznavač, i većina inženjeringa u ovom adapteru je o tome da živite s tim pošteno
Kako učiniti skenirani PDF pretraživim RapidOCR DLL-om?
Pravljenje pretraživog PDF-a sa nativnim RapidOCR DLL-om traži jedan poziv fabrike i isti poziv ApplyLoadedOCRTextLayer koji svaki HotPDF OCR engine koristi. Fabrika živi u HPDFRapidOCRRecognition jedinici i validira rano: DLL i direktorijum modela moraju postojati, svaki fajl modela i rečnika mora se razrešiti, ABI verzija mora biti 1, i svi potrebni izvozi moraju biti prisutni pre nego što se bilo koji model inicijalizuje. Greške konfiguracije podižu EArgumentException; model koji ne uspe da se učita podiže EInvalidOperation noseći dijagnostički tekst koji je DLL napisao
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Modeli se učitavaju ovde, van bilo kog roka prepoznavanja.
// Relativna imena modela u THPDFRapidOCRDLLOptions.Default razrešavaju se
// prema direktorijumu modela.
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
// prazna lista stranica znači svaku stranicu; stranice koje već imaju tekst preskaču se
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 imenuje ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx i ppocr_keys_v1.txt, sa jednom CPU niti, ograničenjem unosa od 16.777.216 piksela i rokom prepoznavanja od 60.000 ms. Od v2.775.0, THPDFRapidOCRDLLOptions.ForLanguage umeće odgovarajući model prepoznavanja i rečnik za tradicionalni kineski, ruski, japanski, arapski i druge profile; zašto se model i rečnik moraju menjati zajedno pokriveno je u RapidOCR višejezičnim modelima i CTC rečnicima u HotPDF-u. Engine prijavljuje sebe kao RapidOCR (native DLL) u Info.EngineName, što drži logove nedvosmislenim pored eksternog Tesseract OCR procesnog adaptera i ugrađenog OCR engine-a sa poklapanjem šablona
Zašto C ABI govori samo int32_t i UTF-8 bajtovima?
HotPDFRapidOCR.dll ABI koristi samo cele brojeve fiksne širine, sirove pokazivače i eksplicitne dužine u bajtovima jer se Delphi, C++Builder i Free Pascal ni u čemu ne dele sa MSVC osim C pozivne konvencije. std::string, std::vector ili C++ izuzetak ima raspored i model odmotavanja koji pripadaju jednom kompajleru i jednoj runtime biblioteci. Pustite bilo koji od njih preko granice i kvar je oštećen stek ili blok hipa oslobođen pogrešnim alokatorom, a ne čista greška
ABI verzija 1 zato prati kratak spisak pravila. Svaki izvoz je cdecl i vraća int32_t status, gde 1 znači uspeh a 0 neuspeh. Svaka funkcija koja može pasti prima dijagnostički bafer u vlasništvu pozivaoca i njegov kapacitet u bajtovima; DLL piše NUL-završenu UTF-8 poruku odsečenu da stane, i adapter je dekodira sa tvrdim terminatorom u poslednjem bajtu sopstvenog bafera od 4.096 bajtova. Svako telo izvoza umotano je u try sa i catch (const std::exception &) i catch (...), pa ONNX Runtime greška, OpenCV tvrđenje ili nevažeći rečnik postaje status 0 plus tekst, nikada izuzetak koji beži u Pascal kod
| Izvoz | Uloga | Kad ga adapter razrešava |
|---|---|---|
HPDFRapidOCRAbiVersion | Vraća 1; bilo koja druga vrednost se odbija | Prvo, pre bilo čega drugog |
HPDFRapidOCRCreate | Učitava modele detekcije, opcionu klasifikaciju, prepoznavanja i rečnik | U fabrici |
HPDFRapidOCRRecognize | Izvodi jednu bitmapu i emituje jedan callback po tekstualnoj liniji | U fabrici |
HPDFRapidOCRDestroy | Oslobađa instancu modela | U fabrici |
HPDFRapidOCRSetReadingDirection | Opcioni redosled redova desno-u-levo, dodato u v2.775.0 | Samo kad je RightToLeft postavljen |
Opcioni izvoz razrešava se lenjo namerno: v2.774.0 DLL kojem nedostaje i dalje servira zahteve s-levo-u-desno. DLL se učitava sa LoadLibraryEx sa zastavicama pretrage koje pokrivaju sopstveni folder DLL-a plus podrazumevane bezbedne direktorijume, pa se ONNX Runtime ili OpenCV zavisnosti postavljene pored HotPDFRapidOCR.dll nalaze bez diranja PATH. Putanje modela i rečnika putuju kao UTF-8 i DLL ih pretvara sa MultiByteToWideChar u strogom režimu pre otvaranja fajlova kroz wide-character API-je, pa direktorijum modela ispod kineskog ili ćiriličnog korisničkog imena radi umesto da se proširuje bajt po bajt u bešmilje
Jedno pravilo živi u gradnji, a ne u header-u. DLL statički vezuje ONNX Runtime i OpenCV, i podrazumevana CMake konfiguracija koristi statički release CRT (/MT). Statičke biblioteke kompajlirane protiv /MD umešane u /MT DLL proizvode greške vezivanja u najboljem slučaju i dva nezavisna hipa u najgorem, pa obezbeđene biblioteke moraju odgovarati CRT režimu koji DLL koristi
Šta se dešava između TBitmap-a i tekstualne linije?
HotPDF uručuje DLL-u nezavisni top-down BGR snimak renderovane stranice, a DLL vraća jedan callback po prepoznatoj tekstualnoj liniji sa pozajmljenim UTF-8 tekstom koji adapter mora iskopirati pre vraćanja
Na Delphi-ju adapter dodeljuje bitmapu stranice privatnom TBitmap-u, nameće pf24bit, i čita redove sa GetDIBits koristeći negativan biHeight, što daje top-down redove podložene na poravnanje od četiri bajta; taj stride predaje se eksplicitno. Na FPC-u čita kroz CreateIntfImage, jer LCL scanline upisi mogu ažurirati sirovu sliku bez osvežavanja GDI handle-a. Bitmapa pozivaoca nikada se ne menja, i piksel budžet (MaxPixels, 16.777.216 podrazumevano i podesivo do 67.108.864) i ograničenje od 32.767 piksela po dimenziji proveravaju se pre nego što se bafer snimka alocira
Unutar DLL-a snimak se podlaže sa 50 belih piksela, tekstualni regioni detektuju se sa maksimalnom stranom od 1.024 piksela, kutije se uređuju u horizontalne redove, i svaki isečak opciono se rotira klasifikatorom ugla pre prepoznavanja. Svaka tekstualna linija zatim prolazi kroz callback koji prima const char*, broj bajtova, celobrojnu kutiju u pikselima originalne slike i srednje poverenje karaktera. Tekstualni pokazivač važi samo tokom callback-a, pa ga adapter odmah kopira, i strog je u tome šta prima:
- UTF-8 se dekodira sa
MB_ERR_INVALID_CHARS; deformisana sekvenca pada stranicu umesto da proizvede zamenske karaktere u pretraživom sloju - C0 i C1 kontrolni karakteri se odbijaju, i linije samo sa praznim prostorom preskaču se
- Kutija mora ležati unutar bitmape i poverenje mora biti konačna vrednost od 0 do 1
- Tekst se broji protiv
MaxTextCodeUnitszahteva sa tvrdim plafonom od 1.048.576 UTF-16 jedinica po pozivu, i karakteri dopunske ravni koštaju dve jedinice - Svaki Pascal izuzetak unutar callback-a hvata se tamo, čuva, i pretvara u povratak 0, što natera DLL da stane i prijavi neuspeh; sačuvana poruka zatim postaje dijagnostika
Dve posledice su bitne za ugađanje. Prvo, jedinica izlaza je linija, ne reč: svaka linija troši jedno MaxWords mesto, Info.AcceptedWordCount i Info.DroppedWordCount broje linije, i isticanje u pretrazi pokriva kutiju linije. Drugo, MinimumConfidence (0.5 podrazumevano) poredi se sa srednjim poverenjem karaktera linije, pa linija sa jednim nečitljivim karakterom među dvadeset čistih obično preživi. DLL ne isporučuje baznu liniju, pa je cevovod tekstualnog sloja procenjuje iz kutije. Prazna stranica uspeva sa nula linija, i svaki neuspeh briše delimične rezultate pa višestranično uveravanje ostaje sve-ili-ništa
Vlasništvo modela i sigurnost niti
Svaki RapidOCR DLL engine poseduje tačno jednu instancu modela za ceo svoj vek, i pozivi Recognize na tom engine-u serijalizuju se kritičnom sekcijom. Držanje IHPDFOCREngine interfejsa je ono što drži tople modele, pa je pravi obrazac za serijski rad stvoriti engine jednom i koristiti ga preko dokumenata
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; // uspravni skenovi: nijedan klasifikator model se ne učitava
Models.Threads := 4; // 1..64, stezano na broj logičkih procesora
Models.TimeoutMilliseconds := 120000; // po Recognize pozivu, kooperativno
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; // poslednja referenca otpuštena: modeli uništeni, pa se DLL istovara
Vrednost Threads postavlja i intra-op i inter-op broj niti svake ONNX sesije, i DLL je steže na broj aktivnih procesora. Dve niti koje dele jedan engine ne rade paralelno; druga čeka bravu. To čekanje nije slepo EnterCriticalSection: adapter zove TryEnterCriticalSection svakih 25 ms i proverava token otkazivanja i rok između pokušaja, pa redom stavljen zahtev može se i dalje otkazati ili isteknuti. Ako treba prava paralelnost, stvorite jedan engine po radniku i prihvatite da svaki engine drži sopstvenu kopiju modela u memoriji
Redosled rastavljanja fiksira destruktor engine-a: HPDFRapidOCRDestroy oslobađa instancu modela prvo, pa FreeLibrary istovara DLL. Na nativnoj strani, inicijalizacija modela jednako je pažljiva; kad model prepoznavanja padne posle što su sesije detektora i klasifikatora već sagrađene, te sesije se otpuštaju pre nego što se greška prijavi, i broj klasa rečnika proverava se protiv izlaza modela tokom inicijalizacije, a ne na prvoj stranici
Zašto nativni OCR poziv ne može biti ubijen usred inferencije?
Nativni RapidOCR poziv ne može biti ubijen usred inferencije jer radi na vašoj niti, unutar vašeg procesa, usred ONNX Runtime sesije koja ne prima prekid. Otkazivanje u HotPDF DLL adapteru je zato kooperativno: DLL zove abort callback pre i posle detekcije, posle klasifikacije, i posle svake prepoznate linije, i staje na prvoj kontrolnoj tački gde callback vrati 0. Jedan ONNX Run koji je počeo završiće se prvo
Alternative su gore od čekanja. TerminateThread ostavio bi bravu CRT hipa, ONNX Runtime bazen niti i bilo koje OpenCV stanje u onom stanju u kojem su se zatekli, trujući ostatak procesa. FreeLibrary dok se poziv još izvršava istovara kod koji je na steku. Nijedno ne može učiniti sigurnim, pa adapter nikada ne pokušava. Rok u TimeoutMilliseconds je zato kooperativni rok, i istekli rok pokazuje se kao greška engine-a sa dijagnostikom isteka, dok otkazani token pokazuje se kao otlsCancelled:
// Token stvara pozivalac i deli sa UI niti,
// koja zove Token.Cancel kad korisnik pritisne Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
case Info.Status of
otlsCancelled:
// vraćeno na sledećoj granici faze ili linije; dokument nepromenjen
Writeln('Cancelled');
otlsEngineError:
// uključuje istek kooperativnog roka i nativnu dijagnostiku
Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded:
Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
Ovo je srž prilagođavanja između procesnih adaptera HotPDF-a i in-process DLL-a, i nijedna strana ne pobeđuje u svakom redu:
- Cena starta: Tesseract i Python RapidOCR adapteri pokreću proces i učitavaju modele za svaku stranicu; DLL učitava modele jednom po engine-u
- Zaustavljanje: podproces se može prekinuti odmah, i Python radnik radi unutar kill-on-close Job Object-a pa mu celo drvo procesa ide s njim; DLL može stati samo na granicama faza i linija
- Sadržanje kvarova: pad u
tesseract.exepada jednu stranicu; access violation unutar DLL-a povlači vaš proces nizbrdo - Raspoređivanje: procesni adapteri trebaju instaliran program ili Python okruženje; DLL treba sebe, svoje modele i svoj rečnik, usklađene sa bitnošću aplikacije
- Memorija: procesni adapteri oslobađaju sve kad dete izađe; DLL engine drži modele rezidentne dok se poslednja referenca interfejsa ne otpusti
Za interaktivnu desktop aplikaciju koja radi OCR stranicu po stranicu, brzina odziva DLL-a obično pobeđuje. Za server koji guta nepouzdane skenove danonoćno, procesna granica vredi svoju cenu starta
Gradnja i raspoređivanje HotPDFRapidOCR.dll
HotPDFRapidOCR.dll gradi se iz C++ izvora u Native/RapidOCR sa MSVC, C++17, Windows SDK i CMake 3.20 ili novijim, pomoćnim skriptom koja prima nativne network izvore, ONNX Runtime i OpenCV direktorijume plus platformu Win32 ili Win64. Gradite oba ako isporučujete oba, jer 32-bitna Delphi aplikacija ne može učitati 64-bitni DLL, i statičke biblioteke koje obezbedite moraju odgovarati ciljnoj arhitekturi kao i CRT režimu
Strana modela ima sopstvene granice kompatibilnosti. Detektor je DB text detektor; prepoznavač prima CTC modele u NCHW rasporedu sa fiksnom visinom unosa 32 ili 48, i koristi 48 za modele sa dinamičkom visinom. Zbijeni statički ONNX Runtime ne može učitati modele sačuvane novijom IR verzijom, pa skorašnji PP-OCRv5 izvozi padaju na inicijalizaciji sa dijagnostikom umesto da se učitaju delimično. Rečnik mora biti UTF-8 bez BOM-a, tačno u redosledu karaktera modela, i njegov broj klasa mora odgovarati izlazu modela; CRLF krajevi linija primaju se. Prepoznavanje je oflajn: DLL nikada ne preuzima nedostajući model
Brzi pregled
- Fabrika:
HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options])uHPDFRapidOCRRecognition, dostupna od v2.774.0 u Delphi, C++Builder i Windows FPC/Lazarus verzijama - Držite vraćeni
IHPDFOCREngineživim preko stranica i dokumenata; otpuštanje uništava modele i istovara DLL - Jedan engine izvodi jedno prepoznavanje istovremeno; stvorite više engine-a za paralelne radnike i budžetirajte memoriju za svaku kopiju modela
- Izlaz je jedan unos po tekstualnoj liniji sa srednjim poverenjem karaktera, filtriran sa
THPDFOCRTextLayerOptions.MinimumConfidence - Otkazivanje i
TimeoutMillisecondssu kooperativni; ONNX rad u toku uvek se dovrši - Uskladite bitnost DLL-a sa aplikacijom i CRT režim statičkih ONNX Runtime i OpenCV biblioteka sa DLL-om
- Birajte jezički profil po engine-u sa
THPDFRapidOCRDLLOptions.ForLanguage(v2.775.0); jedan engine sam ne detektuje jezike
Nativni RapidOCR adapter, procesni OCR adapteri, renderer stranica koji ih hrani i pisac nevidljivog Unicode tekstualnog sloja isporučuju se zajedno u HotPDF-u, nativnoj VCL PDF komponenti za Delphi i C++Builder. Ako vaša aplikacija za hvatanje ili arhiviranje dokumenata treba pretraživ izlaz bez Python runtime-a na ciljnoj mašini, HotPDF Delphi PDF komponenta isporučuje ceo cevovod sa samo DLL-om i njegovim modelima za raspoređivanje