Tehnični članak

HotPDF in-process RapidOCR: OCR z native DLL v Delphiju

HotPDF naredi skenirane strani PDF iskalne z in-process RapidOCR skozi HPDFCreateRapidOCRDLLOCREngine, tovarno, dodano v v2.774.0, ki naloži HotPDFRapidOCR.dll, drži modele ONNX za zaznavanje, klasifikacijo kota in prepoznavanje v pomnilniku ter vrne IHPDFOCREngine. Ta motor podate THotPDF.ApplyLoadedOCRTextLayer, ki izriše vsako stran, poganja CPU inferenco brez Pythona ali podrejenega procesa in zapiše nevidno plast besedila Unicode

Motivacija je cena na stran. Procesni adapter RapidOCR, poslan prej, HPDFCreateRapidOCREngine, zažene Python delavca za vsak klic Recognize, ta delavec pa uvozi svoje izvajalno okolje in naloži modele ONNX, preden prebere en sam piksel. Na arhivu s 500 stranmi se ta davek zagona ponovi 500-krat, namestitev pa pomeni pošiljanje okolja Python ob izvršljivi datoteki Delphi. Native DLL naloži modele enkrat, ko ustvarite motor, namestitev pa se skrči na DLL, njegove datoteke modelov in slovar znakov. Kar odrežete v zameno, je zmožnost ubiti zataknjenega prepoznavalnika, večina inženiringa tega adapterja pa je o tem, kako živeti s tem pošteno

Kako naredite skenirani PDF iskalen z DLL RapidOCR?

Ustvarjanje iskalnega PDF-ja z native DLL RapidOCR vzame en klic tovarne in isti klic ApplyLoadedOCRTextLayer, ki ga uporablja vsak OCR motor HotPDF. Tovarna živi v enoti HPDFRapidOCRRecognition in potrjuje nemudoma: DLL in mapa modelov morata obstajati, vsaka datoteka modela in slovarja se mora razrešiti, različica ABI mora biti 1, vsi zahtevani izvozi pa morajo biti prisotni, preden se kateri koli model inicializira. Napake nastavitve javijo EArgumentException; model, ki se ne naloži, javi EInvalidOperation z diagnostičnim besedilom, ki ga je zapisal DLL

HotPDF zaporedje potrjevanja tovarne DLL RapidOCR za HPDFCreateRapidOCRDLLOCREngine: poti in datoteke modelov morajo obstajati, HPDFRapidOCRAbiVersion mora vrniti 1, zahtevani izvozi se morajo razrešiti, HPDFRapidOCRCreate pa mora inicializirati modele, z EArgumentException ali EInvalidOperation, javljenima nemudoma, preden teče katero koli prepoznavanje, slednje pa nosi native diagnostično besedilo
Potrjevanje je namenoma nemudoma: težave nastavitve javijo, preden se kateri koli model inicializira, tako da napačna pot ali ABI nikoli ne doseže 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 naložijo tukaj, izven vsakega roka prepoznavanja.
  // Relativna imena modelov v THPDFRapidOCRDLLOptions.Default se razrešijo
  // glede na mapo modelov.
  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
    // prazen seznam strani pomeni vsako stran; strani z že obstoječim besedilom se preskočijo
    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 poimenuje ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx in ppocr_keys_v1.txt, z eno nitjo CPU, vhodno omejitvijo 16.777.216 pikslov in rokom prepoznavanja 60.000 ms. Od v2.775.0 THPDFRapidOCRDLLOptions.ForLanguage zamenja ujemajoč model prepoznavanja in slovar za tradicionalno kitajščino, ruščino, japonščino, arabščino in druge profile; zakaj se morata model in slovar spremeniti skupaj, je pokrito v večjezičnih modelih RapidOCR in slovarjih CTC v HotPDF. Motor se prijavi kot RapidOCR (native DLL) v Info.EngineName, kar ohranja dnevnike nedvoumne ob zunanjem procesnem adapterju OCR Tesseract in vgrajenem motorju OCR z ujemanjem predlog

Zakaj C ABI govori samo int32_t in bajte UTF-8?

ABI HotPDFRapidOCR.dll uporablja samo cela števila fiksne širine, surove kazalce in izrecne bajtne dolžine, ker se Delphi, C++Builder in Free Pascal z MSVC ne delijo ničesar prek C klicnega dogovora. std::string, std::vector ali izjema C++ ima postavitev in model razvijanja, ki pripadata enemu prevajalniku in eni izvajalni knjižnici. Pustite kateremu koli od njiju prestopiti mejo in odpoved je pokvarjen sklad ali blok kopu, sproščen s strani napačnega dodeljevalca, ne čista napaka

Različica ABI 1 zato sledi kratkemu seznamu pravil. Vsak izvoz je cdecl in vrne stanje int32_t, kjer 1 pomeni uspeh in 0 odpoved. Vsaka funkcija, ki lahko odpove, vzame diagnostični predpomnilnik v lasti klicalca in njegovo zmogljivost v bajtih; DLL zapiše sporočilo UTF-8, zaključeno z NUL, skrajšano, da gre noter, adapter pa ga dekodira s trdo zaključevalko v zadnjem bajtu svojega 4.096-bajtnega predpomnilnika. Telo vsakega izvoza je ovito v try z obema catch (const std::exception &) in catch (...), tako da napaka ONNX Runtime, trditev OpenCV ali neveljaven slovar postane stanje 0 plus besedilo — nikoli izjema, ki bi ušla v kodo Pascal

IzvozVlogaKdaj ga adapter razreši
HPDFRapidOCRAbiVersionVrne 1; vsaka druga vrednost je zavrnjenaNajprej, pred čimer koli drugim
HPDFRapidOCRCreateNaloži zaznavanje, neobvezen klasifikator, modele prepoznavanja in slovarV tovarni
HPDFRapidOCRRecognizePoganja eno bitno sliko in izda enega callbacka na vrstico besedilaV tovarni
HPDFRapidOCRDestroySprosti instanco modelovV tovarni
HPDFRapidOCRSetReadingDirectionNeobvezen vrstni red vrstic od desne proti levi, dodan v v2.775.0Samo, kadar je nastavljen RightToLeft

Neobvezni izvoz se namenoma razreši lenobno: DLL v2.774.0, ki mu manjka, še vedno postreže zahteve od leve proti desni. DLL se naloži z LoadLibraryEx z iskalnimi zastavicami, ki pokrijejo lastno mapo DLL plus privzete varne imenike, tako da so odvisnosti ONNX Runtime ali OpenCV, postavljene ob HotPDFRapidOCR.dll, najdene brez dotikanja PATH. Poti modelov in slovarjev potujejo kot UTF-8, DLL pa jih pretvori z MultiByteToWideChar v strogem načinu, preden odpre datoteke skozi API-je širokih znakov, tako da mapa modelov pod kitajskim ali ciriličnim uporabniškim imenom deluje, namesto da bi bila razširjena bajt za bajtom v nesmisel

Eno pravilo živi v izgradnji, ne v glavi. DLL statično poveže ONNX Runtime in OpenCV, privzeta nastavitev CMake pa uporablja statični release CRT (/MT). Statične knjižnice, prevedene proti /MD, pomešane v DLL /MT, dajejo napake povezovanja v najboljšem primeru in dva neodvisna kopa v najslabšem, zato morajo preskrbljene knjižnice ustrezati načinu CRT, ki ga DLL uporablja

Kaj se zgodi med TBitmap in vrstico besedila?

HotPDF izroči DLL-u neodvisen zgornje-navzdol posnetek BGR izrisane strani, DLL pa vrne enega callbacka na prepoznano vrstico besedila s posojenim besedilom UTF-8, ki ga mora adapter kopirati, preden se vrne

V Delphiju adapter dodeli bitno sliko strani zasebnemu TBitmap, vsili pf24bit in bere vrstice z GetDIBits z negativnim biHeight, kar da vrstice od zgoraj navzdol, oblazinjene na poravnavo štirih bajtov; ta korak je podan izrecno. Na FPC bere skozi CreateIntfImage, ker lahko zapisi scanline LCL posodobijo surovo sliko, brez osvežitve ročaja GDI. Bitna slika klicalca ni nikoli spremenjena, pikslovni proračun (MaxPixels, privzeto 16.777.216 in nastavljivo do 67.108.864) ter omejitev 32.767 pikslov na dimenzijo pa sta preverjena, preden se dodeli predpomnilnik posnetka

HotPDF cevovod DLL RapidOCR od bitne slike do plasti besedila: adapter posname stran kot zgornje-navzdol pf24bit BGR, DLL oblžini, zazna, uredi in prepozna izreze, izroči enega callbacka na vrstico s posojenim besedilom UTF-8, okvirom in zaupanjem, adapter pa potrjuje vsako vrstico, preden se zapiše plast besedila
Piksli prečkajo ABI enkrat kot posnetek, vrstice prihajajo nazaj enega callbacka naenkrat in nič ne doseže iskalne plasti, dokler ne gre skozi vsako preverjanje

Znotraj DLL-ja je posnetek oblžinjen s 50 belimi piksli, besedilne regije so zaznane z največjo stranico 1.024 pikslov, okvirji so urejeni v vodoravne vrstice, vsak izrez pa je po potrebi zasukan s strani klasifikatorja kota, preden ga prepozna. Vsaka vrstica besedila nato gre skozi callback, ki prejme const char*, število bajtov, celoštevilčen okvir v pikslih izvirne slike in povprečno zaupanje znakov. Kazalec besedila je veljaven samo med callbackom, zato ga adapter takoj kopira in je strog glede tega, kaj sprejme:

  • UTF-8 se dekodira z MB_ERR_INVALID_CHARS; okvarjeno zaporedje razveljavi stran, namesto da bi izdelalo zamenjalne znake v iskalni plasti
  • Kontrolni znaki C0 in C1 so zavrnjeni, vrstice samo s presledki pa so preskočene
  • Okvir mora ležati znotraj bitne slike in zaupanje mora biti končna vrednost od 0 do 1
  • Besedilo se šteje proti MaxTextCodeUnits zahteve s trdim stropom 1.048.576 enot UTF-16 na klic, znaki dopolnilne ravnine pa stanejo dve enoti
  • Katera koli izjema Pascal znotraj callbacka je ujeta tam, shranjena in spremenjena v vrnitev 0, kar naredi, da se DLL ustavi in poroča odpoved; shranjeno sporočilo potem postane diagnostika

Dve posledici sta pomembni za uglaševanje. Prva: enota izhoda je vrstica, ne beseda — vsaka vrstica porabi eno mesto MaxWords, Info.AcceptedWordCount in Info.DroppedWordCount štejeta vrstice, poudarjanje iskanja pa sega čez okvir vrstice. Druga: MinimumConfidence (privzeto 0,5) se primerja s povprečnim zaupanjem znakov vrstice, tako da vrstica z enim neberljivim znakom med dvajsetimi čistimi običajno preživi. DLL ne ponudi osnovne črte, zato jo cevovod plasti besedila oceni iz okvirja. Praza stran uspe z nič vrsticami, vsaka odpoved pa počisti delne rezultate, tako da večstranski zapis ostane vse-ali-nič

Lastnina modelov in varnost niti

Vsak motor DLL RapidOCR je lastnik točno ene instance modelov za svoje celotno življenje, klici Recognize na tem motorju pa so serializirani s kritičnim odsekom. Držanje vmesnika IHPDFOCREngine je tisto, kar ohranja modele tople, zato je pravi vzorec za paketno delo ustvariti motor enkrat in ga uporabljati čez 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;    // pokončni skeni: model klasifikatorja se ne naloži
  Models.Threads := 4;                   // 1..64, stisnjeno na število logičnih procesorjev
  Models.TimeoutMilliseconds := 120000;  // na klic Recognize, sodelovalno
  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;  // zadnji sklic sproščen: modeli uničeni, nato se DLL raznaloži

Vrednost Threads nastavi število nitih intra-op in inter-op vsake seje ONNX, DLL pa jo stisne na število aktivnih procesorjev. Dve niti, ki si delita enega motorja, ne tečeta vzporedno; druga čaka na zaklep. To čakanje ni slep EnterCriticalSection: adapter kliče TryEnterCriticalSection vsakih 25 ms in preverja žeton preklica ter rok med poskusi, tako da lahko čakajoča zahteva še vedno prekliče ali poteče. Če potrebujete pravo vzporednost, ustvarite enega motorja na delavca in sprejmite, da vsak motor drži svojo kopijo modelov v pomnilniku

Razstavljanje je fiksirano z destruktorjem motorja: HPDFRapidOCRDestroy najprej sprosti instanco modelov, nato FreeLibrary raznaloži DLL. Na native strani je inicializacija modelov enako skrbna; kadar model prepoznavanja odpove, potem ko sta bili seji zaznavalnika in klasifikatorja že zgrajeni, sta ti seji sproščeni, preden je napaka prijavljena, število razredov slovarja pa se preveri proti izhodu modela med inicializacijo, ne na prvi strani

Zakaj native klica OCR ni mogoče ubiti sredi inference?

Native klica RapidOCR ni mogoče ubiti sredi inference, ker teče na vaši niti, znotraj vašega procesa, sredi seje ONNX Runtime, ki ne sprejema prekinitve. Preklic v DLL adapterju HotPDF je zato sodelovalen: DLL kliče abort callback pred in po zaznavanju, po klasifikaciji in po vsaki prepoznani vrstici ter se ustavi na prvem kontrolni točki, kjer callback vrne 0. Posamezen Run ONNX, ki se je že začel, se najprej konča

Alternative so slabše od čakanja. TerminateThread bi pustil zaklep kopa CRT, bazen nitih ONNX Runtime in katero koli stanje OpenCV v kakršnem koli stanju, v katerem se je znašla, in zastrupil preostanek procesa. FreeLibrary med izvajanjem klica raznaloži kodo, ki je na skladu. Nobenega ni mogoče narediti varnega, zato adapter ju nikoli ne poskusi. Rok v TimeoutMilliseconds je posledično sodelovalni rok, potekli rok se pokaže kot napaka motorja z diagnostiko o času, preklican žeton pa kot otlsCancelled:

// Žeton ustvari klicalec in ga deli z UI nitjo,
// ki pokliče Token.Cancel, kadar uporabnik pritisne Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
  case Info.Status of
    otlsCancelled:
      // vrnjeno na naslednji meji faze ali vrstice; dokument nespremenjen
      Writeln('Cancelled');
    otlsEngineError:
      // vključuje potek sodelovalnega roka in native diagnostiko
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

To je osrednje trgovanje med procesnimi adapterji HotPDF in DLL-jem v procesu, in nobena stran ne zmaga v vsaki vrstici:

HotPDF trgovine adapterjev OCR: procesni adapterji zaženejo delavca in naložijo modele na vsako stran, a jih je mogoče ubiti in vsebujejo sesutja, medtem ko DLL RapidOCR v procesu naloži modele enkrat, se ustavi samo na sodelovalnih kontrolnih točkah, si deli naslovni prostor in se namesti kot DLL s svojimi modeli in slovarjem
Izberite glede na delo: namizna aplikacija stran-na-enkrat ima korist od toplega DLL-ja, strežnik, ki prebada nezaupljive skene, pa naj plača za procesno steno
  • Cena zagona: adapterja Tesseract in Python RapidOCR zaženeta proces in naložita modele za vsako stran; DLL naloži modele enkrat na motor
  • Ustavljanje: podrejeni proces je mogoče ubiti izrecno in Python delavec teče znotraj Job Object kill-on-close, tako da gre z njim celo njegovo drevo procesov; DLL pa se lahko ustavi samo na mejah faz in vrstic
  • Vsebovanje napak: sesutje v tesseract.exe pokvari eno stran; kršitev dostopa znotraj DLL-ja potegne dol vaš proces
  • Namestitev: procesni adapterji potrebujejo nameščen program ali okolje Python; DLL potrebuje sebe, svoje modele in svoj slovar, ujemajoče z bitnostjo aplikacije
  • Pomnilnik: procesni adapterji sprostijo vse, ko podrejeni proces izide; motor DLL drži svoje modele v pomnilniku, dokler se ne sprosti zadnji sklic vmesnika

Za interaktivno namizno aplikacijo, ki OCR-a stran naenkrat, običajno zmaga odzivnost DLL-ja. Za strežnik, ki prebada nezaupljive skene non-stop, je procesna meja vredna svoje cene zagona

Izgradnja in namestitev HotPDFRapidOCR.dll

HotPDFRapidOCR.dll se gradi iz virov C++ v Native/RapidOCR z MSVC, C++17, Windows SDK in CMake 3.20 ali novejšim, s pomožnim skriptom, ki vzame imenike izvornih omrežnih virov, ONNX Runtime in OpenCV ter platformo Win32 ali Win64. Zgradite oba, če pošiljate oba, ker 32-bitna aplikacija Delphi ne more naložiti 64-bitnega DLL-ja, statične knjižnice, ki jih preskrbite, pa morajo ustrezati ciljni arhitekturi kot tudi načinu CRT

Stran modelov ima svoje meje skladnosti. Zaznavalec je besedilni zaznavalec DB; prepoznavalec sprejema modele CTC v postavitvi NCHW s fiksno vhodno višino 32 ali 48 in uporablja 48 za modele z dinamično višino. Vgrajeni statični ONNX Runtime ne more naložiti modelov, shranjenih z novejšo različico IR, tako da nedavni izvozi PP-OCRv5 ne uspejo inicializacije z diagnostiko, namesto da bi se naložili delno. Slovar mora biti UTF-8 brez BOM, v točno vrstnem redu znakov modela, njegovo število razredov pa se mora ujemati z izhodom modela; koncaji vrstic CRLF so sprejeti. Prepoznavanje je offline: DLL nikoli ne prenese manjkajočega modela

Hiter pregled

  • Tovarna: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) v HPDFRapidOCRRecognition, na voljo od v2.774.0 v Delphi, C++Builder in izgradnjah Windows FPC/Lazarus
  • Obdržite vrnjeni IHPDFOCREngine živ čez strani in dokumente; njegovo sproščanje uniči modele in raznaloži DLL
  • En motor poganja eno prepoznavanje naenkrat; ustvarite več motorjev za vzporedne delavce in načrtujte pomnilnik za vsako kopijo modelov
  • Izhod je en vnos na vrstico besedila s povprečnim zaupanjem znakov, filtriran s THPDFOCRTextLayerOptions.MinimumConfidence
  • Preklic in TimeoutMilliseconds sta sodelovalna; tekoči Run ONNX se vedno zaključi
  • Ujemite bitnost DLL-ja z aplikacijo in način CRT statičnih knjižnic ONNX Runtime in OpenCV z DLL-jem
  • Izberite jezikovni profil na motor s THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); en motor sam ne zaznava jezikov

Native adapter RapidOCR, procesni adapterji OCR, izrisovalnik strani, ki ju napaja, in pisec nevidne plasti besedila Unicode vsi prihajajo skupaj v HotPDF, izvorno komponento PDF VCL za Delphi in C++Builder. Če vaša aplikacija za zajem ali arhiviranje dokumentov potrebuje iskalni izhod brez izvajalnega okolja Python na ciljnem računalniku, komponenta HotPDF Delphi PDF ponudi celoten cevovod, za namestitev pa ostane samo DLL in njegovi modeli