Tehnički članak

HotPDF in-process RapidOCR: nativni DLL OCR u Delphiju

HotPDF čini skenirane PDF stranice pretraživima s in-process RapidOCR-om kroz HPDFCreateRapidOCRDLLOCREngine, tvornicu dodanu u v2.774.0 koja učitava HotPDFRapidOCR.dll, drži ONNX detekcijske, klasifikacijske za kut i prepoznavne modele rezidentnima u memoriji i vraća IHPDFOCREngine. Taj engine predajete THotPDF.ApplyLoadedOCRTextLayeru, koji renderira svaku stranicu, izvodi CPU inferenciju bez Pythona ili dječjeg procesa i izvršava nevidljivi Unicode tekstualni sloj

Motivacija je trošak po stranici. RapidOCR procesni adapter isporučen ranije, HPDFCreateRapidOCREngine, pokreće Python workera za svaki poziv Recognize, i taj worker uvozi svoj runtime i učitava ONNX modele prije nego pročita jedan piksel. Na arhivu od 500 stranica taj porez pokretanja ponavlja se 500 puta, a raspoređivanje znači isporučiti Python okruženje uz Delphi izvršnu datoteku. Nativni DLL učitava modele jednom, kad stvorite engine, a raspoređivanje se smanji na DLL, njegove datoteke modela i rječnik znakova. Ono što zauzvrat odustajete jest sposobnost ubijanja zaglavljenog prepoznavatelja, i najveći dio inženjeringa u ovom adapteru jest živjeti s time pošteno

Kako učiniti skenirani PDF pretraživim s RapidOCR DLL-om?

Stvaranje pretraživog PDF-a s nativnim RapidOCR DLL-om traži jedan poziv tvornice i isti poziv ApplyLoadedOCRTextLayer koji svaki HotPDF OCR engine koristi. Tvornica živi u HPDFRapidOCRRecognition jedinici i validira rano: DLL i mapa modela moraju postojati, svaka datoteka modela i rječnika mora se razriješiti, ABI verzija mora biti 1, i svi potrebni izvozi moraju biti prisutni prije nego se bilo koji model inicijalizira. Pogreške konfiguracije dižu EArgumentException; model koji ne uspije učitati diže EInvalidOperation koji nosi dijagnostički tekst koji je DLL napisao

HotPDF RapidOCR DLL redoslijed validacije tvornice za HPDFCreateRapidOCRDLLOCREngine: putanje i datoteke modela moraju postojati, HPDFRapidOCRAbiVersion mora vratiti 1, potrebni izvozi moraju se razriješiti, i HPDFRapidOCRCreate mora inicijalizirati modele, s EArgumentException ili EInvalidOperation dignutim rano prije bilo kojeg prepoznavanja, pri čemu drugi nosi nativni dijagnostički tekst
validacija je namjerno rana: problemi konfiguracije se dižu prije nego se bilo koji model inicijalizira, pa loša putanja ili ABI nikad ne dođe do roka prepoznavanja
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 ovdje, izvan bilo kojeg roka prepoznavanja.
  // Relativna imena modela u THPDFRapidOCRDLLOptions.Default razrješuju se
  // prema mapi 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 se preskaču
    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, s jednom CPU dretvom, ograničenjem ulaza od 16.777.216 piksela i rokom prepoznavanja od 60.000 ms. Od v2.775.0 THPDFRapidOCRDLLOptions.ForLanguage umeće odgovarajući prepoznavni model i rječnik za tradicionalni kineski, ruski, japanski, arapski i druge profile; zašto se model i rječnik moraju mijenjati zajedno obrađeno je u RapidOCR višejezičnim modelima i CTC rječnicima u HotPDF-u. Engine se prijavljuje kao RapidOCR (native DLL) u Info.EngineName, što drži zapise nedvosmislenima uz vanjski Tesseract OCR procesni adapter i ugrađeni OCR engine s podudaranjem predložaka

Zašto C ABI govori samo int32_t i UTF-8 bajtove?

HotPDFRapidOCR.dll ABI koristi samo cijele brojeve fiksne širine, sirove pokazivače i eksplicitne bajtne duljine jer se Delphi, C++Builder i Free Pascal s MSVC-om ne dijele ničim izvan C pozivne konvencije. std::string, std::vector ili C++ iznimka imaju raspored i unwinding model koji pripadaju jednom kompajleru i jednoj runtime biblioteci. Pustite li bilo što od toga prijeći granicu, kvar je oštećeni stog ili blok heapa oslobođen pogrešnim alocatorom, a ne čista pogreška

ABI verzija 1 stoga slijedi kratak popis pravila. Svaki je izvoz cdecl i vraća int32_t status, gdje 1 znači uspjeh, a 0 neuspjeh. Svaka funkcija koja može pasti prima dijagnostički međuspremnik u vlasništvu pozivatelja i njegov kapacitet u bajtovima; DLL piše NUL-om završenu UTF-8 poruku skraćenu da stane, a adapter je dekodira s tvrdim terminatorom u posljednjem bajtu vlastitog međuspremnika od 4.096 bajtova. Svako tijelo izvoza umotano je u try s catch (const std::exception &) i catch (...), pa ONNX Runtime pogreška, OpenCV asercija ili nevažeći rječnik postaje status 0 plus tekst, nikad iznimka koja bježi u Pascal kod

ExportUlogaKad ga adapter razrješuje
HPDFRapidOCRAbiVersionVraća 1; svaka druga vrijednost se odbijaPrvi, prije svega ostalog
HPDFRapidOCRCreateUčitava detekcijske, neobavezne klasifikacijske i prepoznavne modele te rječnikU tvornici
HPDFRapidOCRRecognizeIzvodi jednu bitmapu i emitira jedan callback po tekstualnoj linijiU tvornici
HPDFRapidOCRDestroyOslobađa instancu modelaU tvornici
HPDFRapidOCRSetReadingDirectionNeobavezni redoslijed redaka s desna na lijevo, dodan u v2.775.0Samo kad je postavljen RightToLeft

Neobavezni se izvoz razrješuje lijeno namjerno: v2.774.0 DLL koji ga nema i dalje poslužuje zahtjeve s lijeva na desno. DLL se učitava s LoadLibraryEx s pretraživačkim zastavicama koje pokrivaju vlastitu mapu DLL-a plus zadane sigurne direktorije, pa se ONNX Runtime ili OpenCV ovisnosti postavljene uz HotPDFRapidOCR.dll nalaze bez diranja PATH-a. Putanje modela i rječnika putuju kao UTF-8, a DLL ih pretvara s MultiByteToWideChar u strogom načinu prije otvaranja datoteka kroz wide-character API-je, pa mapa modela pod kineskim ili ćiriličnim korisničkim imenom radi umjesto da se proširi bajt po bajt u besmisao

Jedno pravilo živi u buildu, a ne u zaglavlju. DLL statički povezuje ONNX Runtime i OpenCV, i zadana CMake konfiguracija koristi statički release CRT (/MT). Statičke biblioteke kompilirane protiv /MD pomiješane u /MT DLL proizvode u najboljem slučaju pogreške povezivanja, a u najgorem dva neovisna heapa, pa isporučene biblioteke moraju odgovarati načinu CRT-a koji DLL koristi

Što se događa između TBitmapa i tekstualne linije?

HotPDF predaje DLL-u neovisni top-down BGR snimak renderirane stranice, a DLL vraća jedan callback po prepoznatoj tekstualnoj liniji s posuđenim UTF-8 tekstom koji adapter mora kopirati prije povratka

Na Delphiju adapter dodjeljuje bitmapu stranice privatnom TBitmapu, forsira pf24bit, i čita retke s GetDIBits koristeći negativan biHeight, što daje top-down retke podložene na četverobajtno poravnanje; taj se stride prenosi eksplicitno. Na FPC-u čita kroz CreateIntfImage, jer LCL scanline upisi mogu ažurirati sirovu sliku bez osvježavanja GDI handlea. Pozivateljeva se bitmapa nikad ne mijenja, a pikselni proračun (MaxPixels, 16.777.216 po zadanim vrijednostima i podesivo do 67.108.864) i granica od 32.767 piksela po dimenziji provjeravaju se prije nego se dodijeli međuspremnik snimka

HotPDF RapidOCR DLL pipeline od bitmape do tekstualnog sloja: adapter snima stranicu kao top-down pf24bit BGR, DLL podlaže, detektira, reda i prepoznaje izrezke, isporučuje jedan callback po liniji s posuđenim UTF-8 tekstom, okvirom i pouzdanošću, a adapter validira svaku liniju prije izvršenja tekstualnog sloja
pikseli prelaze ABI jednom kao snimak, linije se vraćaju jedan callback istovremeno, i ništa ne doseže pretraživi sloj dok svaka provjera ne prođe

Unutar DLL-a snimak se podlaže s 50 bijelih piksela, tekstualna se područja detektiraju s maksimalnom stranom od 1.024 piksela, okviri se redaju u horizontalne retke, i svaki se izrezak po potrebi rotira klasifikatorom kuta prije prepoznavanja. Svaka tekstualna linija zatim prolazi kroz callback koji prima const char*, broj bajtova, cijelobrojni okvir u pikselima izvorne slike i srednju pouzdanost znakova. Tekstovni pokazivač valjan je samo tijekom callbacka, pa ga adapter odmah kopira, i strog je u tome što prihvaća:

  • UTF-8 se dekodira s MB_ERR_INVALID_CHARS; neispravan niz obara stranicu umjesto da proizvede znakove zamjene u pretraživom sloju
  • C0 i C1 kontrolni znakovi se odbijaju, a linije samo s razmacima se preskaču
  • Okvir mora ležati unutar bitmape, a pouzdanost mora biti konačna vrijednost od 0 do 1
  • Tekst se broji protiv MaxTextCodeUnits zahtjeva s tvrdim stropom od 1.048.576 UTF-16 jedinica po pozivu, i znakovi dopunskih planova koštaju dvije jedinice
  • Svaka Pascal iznimka unutar callbacka hvata se tamo, sprema i pretvara u povratak 0, što natjerava DLL da stane i prijavi neuspjeh; spremljena poruka tada postaje dijagnostika

Dvije posljedice važne su za štelanje. Prvo, jedinica izlaza je linija, a ne riječ: svaka linija troši jedan MaxWords slot, Info.AcceptedWordCount i Info.DroppedWordCount broje linije, i isticanje pretraživanja prostire se preko okvira linije. Drugo, MinimumConfidence (0.5 po zadanim vrijednostima) uspoređuje se sa srednjom pouzdanošću znakova linije, pa linija s jednim nečitkim znakom među dvadeset čistih obično preživi. DLL ne isporučuje baseline, pa tekstualni sloj procjenjuje jedan iz okvira. Prazna stranica uspijeva s nula linija, i svaki neuspjeh čisti djelomične rezultate pa višestranično izvršenje ostaje sve-ili-ništa

Vlasništvo modela i thread safety

Svaki RapidOCR DLL engine posjeduje točno jednu instancu modela kroz svoj cijeli životni vijek, i pozivi Recognize na tom engineu serijaliziraju se critical sekcijom. Držanje IHPDFOCREngine sučelja ono je što modele drži toplima, pa je pravi obrazac za batch posao stvoriti engine jednom i ponovno ga koristiti kroz dokumente

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 klasifikacijski model se ne učitava
  Models.Threads := 4;                   // 1..64, stezano na broj logičkih procesora
  Models.TimeoutMilliseconds := 120000;  // po Recognize pozivu, suradnički
  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;  // zadnja referenca oslobođena: modeli uništeni, pa se DLL iskrcava

Vrijednost Threads postavlja i intra-op i inter-op broj dretava svake ONNX sesije, i DLL je steže na broj aktivnih procesora. Dvije dretve koje dijele jedan engine ne izvode se paralelno; druga čeka lock. To čekanje nije slijepi EnterCriticalSection: adapter poziva TryEnterCriticalSection svakih 25 ms i između pokušaja provjerava token otkazivanja i rok, pa redom čekajući zahtjev i dalje može biti otkazan ili isteknuti. Ako trebate pravi paralelizam, stvorite jedan engine po workeru i prihvatite da svaki engine drži vlastitu kopiju modela u memoriji

Redoslijed rastavljanja fiksira destruktor enginea: HPDFRapidOCRDestroy najprije oslobađa instancu modela, pa FreeLibrary iskrcava DLL. Na nativnoj strani inicijalizacija modela podjednako je pažljiva; kad prepoznavni model padne nakon što su sesije detektora i klasifikatora već izgrađene, te sesije se oslobađaju prije nego se pogreška prijavi, i broj klasa rječnika provjerava se prema izlazu modela tijekom inicijalizacije, a ne na prvoj stranici

Zašto se nativni OCR poziv ne može ubiti usred inferencije?

Nativni se RapidOCR poziv ne može ubiti usred inferencije jer se izvodi na vašoj dretvi, unutar vašeg procesa, usred ONNX Runtime sesije koja ne prima prekid. Otkazivanje u HotPDF DLL adapteru stoga je suradničko: DLL poziva abort callback prije i poslije detekcije, poslije klasifikacije i poslije svake prepoznate linije, i staje na prvoj kontrolnoj točki gdje callback vrati 0. Jedan ONNX Run koji je počeo dovršit će se prvo

Alternative su gore od čekanja. TerminateThread ostavio bi CRT heap lock, ONNX Runtime thread pool i bilo koje OpenCV stanje u onom stanju u kojem su se slučajno našli, otrovavši ostatak procesa. FreeLibrary dok se poziv još izvodi iskrcava kod koji je na stogu. Nijedno se ne može učiniti sigurnim, pa adapter nikad ne pokušava s njima. Rok u TimeoutMilliseconds posljedično je suradnički rok, i istekli rok izlazi na vidjelo kao engine pogreška s dijagnostikom isteka vremena, dok otkazani token izlazi kao otlsCancelled:

// Token stvara pozivatelj i dijeli ga s UI dretvom,
// koja poziva 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ća se na sljedećoj granici faze ili linije; dokument nepromijenjen
      Writeln('Cancelled');
    otlsEngineError:
      // uključuje istek suradničkog roka i nativnu dijagnostiku
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

To je srž razmjene između HotPDF procesnih adaptera i in-process DLL-a, i nijedna strana ne pobjeđuje u svakom redu:

HotPDF OCR adapter kompromisi: procesni adapteri pokreću workera i učitavaju modele pri svakoj stranici ali se mogu ubiti i sadrže rušenja, dok in-process RapidOCR DLL učitava modele jednom, staje samo na suradničkim kontrolnim točkama, dijeli adresni prostor i raspoređuje se kao DLL sa svojim modelima i rječnikom
birajte po radnom opterećenju: desktop aplikacija stranica-po-stranici ima koristi od toplog DLL-a, dok bi server koji dobavlja nepouzdane skenove non-stop trebao platiti procesni zid
  • Trošak pokretanja: Tesseract i Python RapidOCR adapteri pokreću proces i učitavaju modele za svaku stranicu; DLL učitava modele jednom po engineu
  • Zaustavljanje: dječji se proces može izravno terminirati, i Python worker izvodi se unutar kill-on-close Job Objecta pa cijelo njegovo procesno stablo ide s njim; DLL može stati samo na granicama faza i linija
  • Sadržaj kvarova: rušenje u tesseract.exe obara jednu stranicu; access violation unutar DLL-a obara vaš proces
  • Raspoređivanje: procesni adapteri trebaju instalirani program ili Python okruženje; DLL treba sebe, svoje modele i svoj rječnik, usklađene s bitnessom aplikacije
  • Memorija: procesni adapteri oslobađaju sve kad dijete izađe; DLL engine drži svoje modele rezidentnima dok se zadnja referenca sučelja ne oslobodi

Za interaktivnu desktop aplikaciju koja OCR-a stranicu po stranicu obično pobjeđuje DLL-ova brzina odziva. Za server koji dobavlja nepouzdane skenove non-stop granica procesa vrijedi svoj trošak pokretanja

Gradnja i raspoređivanje HotPDFRapidOCR.dll

HotPDFRapidOCR.dll gradi se iz C++ izvora u Native/RapidOCR s MSVC-om, C++17, Windows SDK-om i CMake 3.20 ili kasnijim, koristeći pomoćnu skriptu koja prima direktorije nativnih mrežnih izvora, ONNX Runtimea i OpenCV-a plus platformu Win32 ili Win64. Gradite oba ako isporučujete oba, jer 32-bitna Delphi aplikacija ne može učitati 64-bitni DLL, a statičke biblioteke koje osigurate moraju odgovarati ciljnoj arhitekturi kao i načinu CRT-a

Strana modela ima vlastite granice kompatibilnosti. Detektor je DB text detector; prepoznavatelj prima CTC modele u NCHW rasporedu s fiksnom ulaznom visinom 32 ili 48, i koristi 48 za modele s dinamičkom visinom. Priloženi statički ONNX Runtime ne može učitati modele spremljene novijom IR verzijom, pa nedavni PP-OCRv5 izvozi padaju u inicijalizaciji s dijagnostikom umjesto da se učitaju djelomično. Rječnik mora biti UTF-8 bez BOM-a, točno u redoslijedu znakova modela, i njegov broj klasa mora odgovarati izlazu modela; CRLF završeci linija prihvaćaju se. Prepoznavanje je offline: DLL nikad ne preuzima model koji nedostaje

Brza referenca

  • Tvornica: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) u HPDFRapidOCRRecognition, dostupna od v2.774.0 u Delphi, C++Builder i Windows FPC/Lazarus verzijama
  • Držite vraćeni IHPDFOCREngine živim kroz stranice i dokumente; oslobađanje ga uništava modele i iskrcava DLL
  • Jedan engine izvodi jedno prepoznavanje istovremeno; stvorite nekoliko enginea za paralelne workere i izbudžetirajte memoriju za svaku kopiju modela
  • Izlaz je jedan unos po tekstualnoj liniji sa srednjom pouzdanošću znakova, filtriran s THPDFOCRTextLayerOptions.MinimumConfidence
  • Otkazivanje i TimeoutMilliseconds suradnički su; ONNX izvođenje u tijeku uvijek se dovršava
  • Uskladite DLL bitness s aplikacijom, a način CRT-a statičkih ONNX Runtime i OpenCV biblioteka s DLL-om
  • Birajte jezični profil po engineu s THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); jedan engine ne detektira jezike sam od sebe

Nativni RapidOCR adapter, procesni OCR adapteri, renderer stranica koji ih hrani i pisac nevidljivog Unicode tekstualnog sloja svi se isporučuju zajedno u HotPDF-u, nativnoj VCL PDF komponenti za Delphi i C++Builder. Ako vaša aplikacija za snimanje ili arhiviranje dokumenata treba pretraživi izlaz bez Python runtimea na ciljnom stroju, HotPDF Delphi PDF komponenta daje cijeli pipeline, a za rasporediti ostaju samo DLL i njegovi modeli