Tehnički članak

HotPDF in-process RapidOCR: nativni DLL OCR u Delphi-ju

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

HotPDF RapidOCR DLL sekvenca validacije fabrike za HPDFCreateRapidOCRDLLOCREngine: putanje i fajlovi modela moraju postojati, HPDFRapidOCRAbiVersion mora vratiti 1, potrebni izvozi moraju se razrešiti, i HPDFRapidOCRCreate mora inicijalizovati modele, sa EArgumentException ili EInvalidOperation podignutim rano pre bilo kog prepoznavanja, drugi noseći nativni dijagnostički tekst
validacija je namerno rana: problemi konfiguracije podižu se pre nego što bilo koji model inicijalizuje, pa loša putanja ili ABI nikada ne stigne 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 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

IzvozUlogaKad ga adapter razrešava
HPDFRapidOCRAbiVersionVraća 1; bilo koja druga vrednost se odbijaPrvo, pre bilo čega drugog
HPDFRapidOCRCreateUčitava modele detekcije, opcionu klasifikaciju, prepoznavanja i rečnikU fabrici
HPDFRapidOCRRecognizeIzvodi jednu bitmapu i emituje jedan callback po tekstualnoj linijiU fabrici
HPDFRapidOCRDestroyOslobađa instancu modelaU fabrici
HPDFRapidOCRSetReadingDirectionOpcioni redosled redova desno-u-levo, dodato u v2.775.0Samo 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

HotPDF RapidOCR DLL cevovod od bitmape do tekstualnog sloja: adapter snima stranicu kao top-down pf24bit BGR, DLL podlaže, detektuje, uređuje i prepoznaje isečke, uručuje jedan callback po liniji sa pozajmljenim UTF-8 tekstom, kutijom i poverenjem, i adapter validira svaku liniju pre uveravanja tekstualnog sloja
pikseli prelaze ABI jednom kao snimak, linije se vraćaju po jedan callback istovremeno, i ništa ne stiže u pretraživi sloj dok svaka provera ne prođe

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 MaxTextCodeUnits zahteva 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:

HotPDF OCR adapter prilagođavanja: procesni adapteri pokreću radnika i učitavaju modele na svaku stranicu ali mogu biti ubijeni i sadrže padove, dok in-process RapidOCR DLL učitava modele jednom, staje samo na kooperativnim kontrolnim tačkama, deli adresni prostor, i raspoređuje se kao DLL sa svojim modelima i rečnikom
birajte po radnom opterećenju: desktop aplikacija stranica-po-stranici ima koristi od toplog DLL-a, dok server koji guta nepouzdane skenove treba platiti procesni zid
  • 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.exe pada 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]) u HPDFRapidOCRRecognition, 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 TimeoutMilliseconds su 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