Techninis straipsnis

HotPDF RapidOCR procese: natyvi DLL OCR Delphi

HotPDF skenuotus PDF puslapius padaro ieškomus su RapidOCR procese per HPDFCreateRapidOCRDLLOCREngine – faktoriją, pridėtą v2.774.0, kuri įkelia HotPDFRapidOCR.dll, laiko ONNX aptikimo, kampų klasifikavimo ir atpažinimo modelius rezidentuotus atmintyje ir grąžina IHPDFOCREngine. Tą variklį perduodate THotPDF.ApplyLoadedOCRTextLayer, kuris atvaizduoja kiekvieną puslapį, paleidžia CPU inferenciją be Python ir be antrinio proceso ir užrašo nematomą Unicode teksto sluoksnį

Motyvacija – kaina vienam puslapiui. Ankstesnis RapidOCR proceso adapteris HPDFCreateRapidOCREngine kiekvienam Recognize kvietimui paleidžia Python darbuotoją, o tas darbuotojas importuoja savo runtime ir įkelia savo ONNX modelius dar prieš perskaitęs bent vieną pikselį. 500 puslapių archyve tas paleidimo mokestis kartojasi 500 kartų, o diegimas reiškia Python aplinkos gabenimą šalia Delphi vykdomojo failo. Natyvi DLL modelius įkelia kartą – kai kuriate variklį – o diegimas sutrūnėja iki DLL, jos modelių failų ir simbolių žodyno. Ko atsisakote mainais – galimybės nudurti užstrigusį atpažintoją, o didžioji šio adapterio inžinerijos dalis yra apie sąžiningą gyvenimą su tuo

Kaip padaryti skenuotą PDF ieškomą su RapidOCR DLL?

Ieškomo PDF sukūrimas su natyvia RapidOCR DLL užima vieną faktorijos kvietimą ir tą patį ApplyLoadedOCRTextLayer kvietimą, kurį naudoja kiekvienas HotPDF OCR variklis. Faktorija įsikūrusi HPDFRapidOCRRecognition unito viduje ir tikrina anksti: DLL ir modelių katalogas privalo egzistuoti, kiekvienas modelių ir žodyno failas privalo išspręstis, ABI versija privalo būti 1, o visi reikalingi eksportai privalo būti vietoje dar prieš inicializuojant bet kurį modelį. Konfigūracijos klaidos kelia EArgumentException; neįsikeliantis modelis kelia EInvalidOperation su diagnostikos tekstu, kurį parašė DLL

HotPDF RapidOCR DLL faktorijos patikros HPDFCreateRapidOCRDLLOCREngine seka: keliai ir modelių failai privalo egzistuoti, HPDFRapidOCRAbiVersion privalo grąžinti 1, reikalingi eksportai privalo išspręstis, o HPDFRapidOCRCreate privalo inicializuoti modelius; EArgumentException arba EInvalidOperation keliama anksti, dar prieš bet kokį atpažinimą, o pastaroji neša natyvų diagnostikos tekstą
patikra tyčia anksti: konfigūracijos problemos kyla prieš inicializuojant bet kurį modelį, tad blogas kelias arba ABI niekada nepasiekia atpažinimo termino
uses
  SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Modeliai įkeliami čia, ne per atpažinimo terminą.
  // Reliatyvūs modelių vardai THPDFRapidOCRDLLOptions.Default spręndžia
  // pagal modelių katalogą.
  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
    // tuščias puslapių sąrašas reiškia visus puslapius; puslapiai, turintys tekstą, praleidžiami
    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 įvardija ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx ir ppocr_keys_v1.txt, su viena CPU gija, 16 777 216 pikselių įvedimo riba ir 60 000 ms atpažinimo terminu. Nuo v2.775.0 THPDFRapidOCRDLLOptions.ForLanguage įstatmia atitinkantį atpažinimo modelį ir žodyną Tradicinei kinų, rusų, japonų, arabų ir kitiems profiliams; kodėl modelis ir žodynas privalo keistis kartu – dengia RapidOCR daugiakalbiai modeliai ir CTC žodynai HotPDF. Variklis pats praneša kaip RapidOCR (native DLL) Info.EngineName lauke, kas laiko žurnalus vienareikšmius šalia išorinio Tesseract OCR proceso adapterio ir vidinio šablonų atitikimo OCR variklio

Kodėl C ABI kalba tik int32_t ir UTF-8 baitais?

HotPDFRapidOCR.dll ABI naudoja tik fiksuoto pločio sveikuosius, žalias rodykles ir aiškius baitų ilgius, nes Delphi, C++Builder ir Free Pascal su MSVC bendra nieko neturi, išskyrus C kvietimo konvenciją. std::string, std::vector arba C++ išimtis turi išdėstymą ir išvyniojimo modelį, priklausančius vienam kompiliatoriui ir vienai runtime bibliotekai. Leiskite bet kuriam jų pereiti ribą – gedimas bus sugadintas stekas arba kravos blokas, atlaisvintas netinkamo allocatoriaus, o ne švari klaida

Todėl ABI versija 1 laikosi trumpo taisyklių sąrašo. Kiekvienas eksportas yra cdecl ir grąžina int32_t būseną, kur 1 reiškia sėkmę, o 0 – nesėkmę. Kiekviena galianti suklysti funkcija priima kvietėjui priklausančią diagnostikos buferį ir jo talpą baitais; DLL parašo NUL baigtą UTF-8 pranešimą, nukirptą iki tilpo, o adapteris jį dekoduoja su kietu terminatoriumi paskutiniame savo 4 096 baitų buferio baite. Kiekvieno eksporto kūnas apvyniotas try su abiem catch (const std::exception &) ir catch (...), tad ONNX Runtime klaida, OpenCV teiginys arba netinkamas žodynas tampa būsena 0 plius tekstas – niekada ne išimtimi, pabėgančia į Pascal kodą

EksportasVaidmuoKada adapteris jį išspręndžia
HPDFRapidOCRAbiVersionGrąžina 1; bet kuri kita reikšmė atmetamaPirmiausia, prieš viską kitą
HPDFRapidOCRCreateĮkelia aptikimo, pasirinktinio klasifikavimo ir atpažinimo modelius bei žodynąFaktorijoje
HPDFRapidOCRRecognizePaleidžia vieną bitmapą ir išduoda po vieną callback kiekvienai teksto eiluteiFaktorijoje
HPDFRapidOCRDestroyAtlaisvina modelio instancijąFaktorijoje
HPDFRapidOCRSetReadingDirectionPasirinktinė iš dešinės į kairę eilučių tvarka, pridėta v2.775.0Tik kai nustatyta RightToLeft

Pasirinktinis eksportas išspręndžiamas tingiai tyčia: v2.774.0 DLL, kurios jo nėra, vis tiek aptarnauja iš kairės į dešinę užklausas. DLL įkeliama su LoadLibraryEx, paieškos vėliavomis, dengiančiomis paties DLL aplanką plius numatytąsias saugias direktorijas, tad ONNX Runtime arba OpenCV priklausomybės, padėtos šalia HotPDFRapidOCR.dll, randamos neliečiant PATH. Modelių ir žodyno keliai keliauja kaip UTF-8, ir DLL juos konvertuoja MultiByteToWideChar griežtu režimu prieš atverdama failus per plačiųjų simbolių API, tad modelių katalogas po kinų ar kirilicos naudotojo vardu veikia, vietoj to, kad būtų išplėstas baitas po baito į beprotiškumą

Viena taisyklė gyvena sąrankoje, o ne antraštėje. DLL statiškai susieja ONNX Runtime ir OpenCV, o numatytoji CMake konfigūracija naudoja statinę release CRT (/MT). Statiškos bibliotekos, sukompiliuotos pagal /MD, įmaišytos į /MT DLL, geriausiu atveju duoda susiejimo klaidas, o blogiausiu – dvi nepriklausomas kravas, tad parūpintosios bibliotekos privalo atitikti tą CRT režimą, kurį naudoja DLL

Kas vyksta tarp TBitmap ir teksto eilutės?

HotPDF atiduoda DLL nepriklausomą atvaizduoto puslapio snapshotą iš viršaus žemyn BGR, o DLL grąžina po vieną callback kiekvienai atpažintai teksto eilutei su pasiskolintu UTF-8 tekstu, kurį adapteris privalo nukopijuoti prieš grįždamas

Delphi adapteris puslapio bitmapą priskiria privatam TBitmap, priverčia pf24bit ir skaito eilutes su GetDIBits, naudodamas neigiamą biHeight, kas duoda iš viršaus žemyn einančias eilutes, padengtas iki keturių baitų lygiavos; tas stride perduodamas aiškiai. FPC skaito per CreateIntfImage, nes LCL scanline rašymai gali atnaujinti žalią atvaizdą neatnaujinę GDI rankenos. Kvietėjo bitmapas niekada nekeičiamas, o pikselių biudžetas (MaxPixels, pagal nutylėjimą 16 777 216 ir konfigūruojamas iki 67 108 864) ir 32 767 pikselių riba matmeniui patikrinami dar prieš skiriant snapshoto buferį

HotPDF RapidOCR DLL srautas nuo bitmapo iki teksto sluoksnio: adapteris padaro puslapio snapshotą kaip iš viršaus žemyn pf24bit BGR, DLL padengia, aptinka, surikiuoja ir atpažįsta iškarpas, atiduoda po callback kiekvienai eilutei su pasiskolintu UTF-8 tekstu, langeliu ir pasitikėjimu, o adapteris patvirtina kiekvieną eilutę prieš teksto sluoksnio įrašymą
pikseliai kertą ABI kartą kaip snapshotas, eilutės grįžta po callbacką kartą, ir niekas nepasiekia ieškomo sluoksnio, kol nepraeina kiekviena patikra

DLL viduje snapshotas padengiamas 50 baltų pikselių, teksto sritys aptinkamos su 1 024 pikselių didžiausiąja kraštine, langeliai surikiuojami į horizontalias eilutes, o kiekviena iškarpa prieš atpažinimą pasirinktinai pasukama kampų klasifikatoriaus. Kiekviena teksto eilutė tada keliauja pro callbacką, gaunantį const char*, baitų skaičių, sveikąjį langelį originalaus atvaizdo pikseliais ir vidutinę simbolių pasitikėjimą. Teksto rodyklė teisėta tik callbacko metu, tad adapteris ją nukopijuoja nedelsdamas ir yra griežtas dėl to, ką priima:

  • UTF-8 dekoduojamas su MB_ERR_INVALID_CHARS; sugadinta seka žlugdo puslapį vietoj pakaitinių simbolių gamybos ieškomame sluoksnyje
  • C0 ir C1 valdymo simboliai atmetami, o vien iš tarpų susidedančios eilutės praleidžiamos
  • Langelis privalo slypėti bitmapo viduje, o pasitikėjimas privalo būti baigtinė reikšmė nuo 0 iki 1
  • Tekstas skaičiuojamas pagal užklausos MaxTextCodeUnits su kietomis 1 048 576 UTF-16 vienetų lubomis vienam kvietimui, o papildomos plokštumos simboliai kainuoja du vienetus
  • Bet kokia Pascal išimtis callbacko viduje ten pagaujama, užsaugoma ir paverčiama į 0 grąžą, dėl ko DLL sustoja ir praneša nesėkmę; užsaugotas pranešimas tada tampa diagnostika

Dvi pasekmės svarbios derinimui. Pirma, išvesties vienetas – eilutė, o ne žodis: kiekviena eilutė suvartoja vieną MaxWords vietą, Info.AcceptedWordCount ir Info.DroppedWordCount skaičiuoja eilutes, o paieškos paryškinimas dengia eilutės langelį. Antra, MinimumConfidence (pagal nutylėjimą 0.5) lyginama su eilutės vidutine simbolių pasitikėjimo reikšme, tad eilutė su vienu neskaitytinu simboliu tarp dvidešimties švarių paprastai išgyvena. DLL neteikia bazinės linijos, tad teksto sluoksnio srautas ją įvertina iš langelio. Tuščias puslapis pavyksta su nuliu eilučių, o bet kokia nesėkmė išvalo dalinius rezultatus, tad kelių puslapių įrašymas lieka viskas-arba-nieko

Modelių nuosavybė ir gijų sauga

Kiekvienas RapidOCR DLL variklis valdo lygiai vieną modelio instanciją per visą savo gyvenimą, o Recognize kvietimai tam varikliui serializuojami kritine sekcija. IHPDFOCREngine sąsajos laikymas yra tai, kas laiko modelius šiltus, tad teisingas paketinio darbo raštas – sukurti variklį kartą ir naudoti jį per dokumentus

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;    // statmeniems skenams: klasifikatoriaus modelis neįkeliamas
  Models.Threads := 4;                   // 1..64, ribojama iki loginių procesorių skaičiaus
  Models.TimeoutMilliseconds := 120000;  // vienam Recognize kvietimui, kooperatyvinis
  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;  // atleista paskutinė nuoroda: modeliai sunaikinti, tada DLL iškraunama

Threads reikšmė nustato ir intra-op, ir inter-op gijų skaičių kiekvienam ONNX seansui, o DLL ją nukirpia iki aktyvių procesorių skaičiaus. Dvi gijos, dalijančios vieną variklį, nesibėga lygiagrečiai; antroji laukia rakto. Tas laukimas nėra aklas EnterCriticalSection: adapteris kviečia TryEnterCriticalSection kas 25 ms ir tarp bandymų tikrina atšaukimo tokeną ir terminą, tad eilėje stovinti užklausa vis dar gali būti atšaukta arba pasibaigti. Jei reikia tikro lygiagretumo, kurkite po variklį kiekvienam darbuotojui ir susitaikykite, kad kiekvienas variklis atmintyje laiko savą modelių kopiją

Išardymo tvarka fiksuota variklio destruktoriuje: HPDFRapidOCRDestroy pirmiausia atlaisvina modelio instanciją, tada FreeLibrary iškrauna DLL. Natyvioje pusėje modelių inicializacija vienodai atsargi: kai atpažinimo modelis žlunga, kai aptikiklio ir klasifikatoriaus seansai jau pastatyti, tie seansai atlaisvinami dar prieš pranešant klaidą, o žodyno klasių skaičius tikrinamas pagal modelio išvestį inicializacijos metu, o ne pirmajame puslapyje

Kodėl natyvaus OCR kvietimo negalima nudurti inferencijos viduryje?

Natyvaus RapidOCR kvietimo negalima nudurti inferencijos viduryje, nes jis sukasi jūsų gijoje, jūsų proceso viduje, ONNX Runtime seanso, nepriimančio nutraukimo, viduryje. Atšaukimas HotPDF DLL adapteryje todėl kooperatyvinis: DLL kviečia nutraukimo callbacką prieš ir po aptikimo, po klasifikavimo ir po kiekvienos atpažintos eilutės ir sustoja ties pirmuoju kontrolės tašku, kuriame callbackas grąžina 0. Vienas prasidėjęs ONNX Run pirmiausia baigsis

Alternatyvos blogesnės už laukimą. TerminateThread paliktų CRT kravos raktą, ONNX Runtime gijų baseiną ir bet kokią OpenCV būseną tokioje padėtyje, kokioje jos atsidūrė, apnuodydami likusį procesą. FreeLibrary, kai kvietimas tebevyksta, iškrauna kodą, esantį steku. Nė vieno negalima padaryti saugiu, tad adapteris jų niekada nebando. Terminas TimeoutMilliseconds dėl to kooperatyvinis, ir pasibaigęs terminas iškyla kaip variklio klaida su termino pralaimėjimo diagnostika, o atšauktas tokenas – kaip otlsCancelled:

// Tokeną sukuria kvietėjas ir dalijasi su UI gija,
// kuri kviečia Token.Cancel, kai naudotojas spaudžia Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
  case Info.Status of
    otlsCancelled:
      // grąžinta kitoje stadijoje ar eilutės riboje; dokumentas nepakitęs
      Writeln('Cancelled');
    otlsEngineError:
      // apima kooperatyvinio termino pabaigą ir natyvią diagnostiką
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

Tai pagrindinis kompromisas tarp HotPDF proceso adapterių ir DLL procese, ir nė viena pusė nelaimi visuose laukuose:

HotPDF OCR adapterių kompromisai: proceso adapteriai kiekvienam puslapiui paleidžia darbuotoją ir įkelia modelius, bet gali būti nudurti ir sulaiko griūves, o procese veikianti RapidOCR DLL modelius įkelia kartą, sustoja tik kooperatyviniuose kontrolės taškuose, dalijasi adresų erdve ir diegiama kaip DLL su savais modeliais ir žodynu
rinkitės pagal darbo krūvį: puslapį-po-karto darbastalio programai naudinga šilta DLL, o serveriui, ryjančiam nepatikimus skenus, verta sumokėti už proceso sieną
  • Paleidimo kaina: Tesseract ir Python RapidOCR adapteriai kiekvienam puslapiui paleidžia procesą ir įkelia modelius; DLL modelius įkelia kartą varikliui
  • Sustabdymas: antrinis procesas gali būti nutrauktas tiesiogiai, o Python darbuotojas sukasi kill-on-close Job Object viduje, tad su juo keliauja ir visas proceso medis; DLL gali sustoti tik stadijų ir eilučių ribose
  • Gedimų izoliacija: griūtis tesseract.exe viduje žlugdo vieną puslapį; access violation DLL viduje nugriauna jūsų procesą
  • Diegimas: proceso adapteriams reikia įdiegtos programos arba Python aplinkos; DLL reikia pačios jos, savų modelių ir žodyno, suderintų su programos bitiškumu
  • Atmintis: proceso adapteriai viską atlaisvina, kai antrinis baigiasi; DLL variklis laiko savo modelius rezidentuotus, kol atleidžiama paskutinė sąsajos nuoroda

Interaktyviai darbastalio programai, kuri OCRina po puslapį, DLL reaktyvumas paprastai laimi. Serveriui, ryjančiam nepatikimus skenus visą parą, proceso riba atsiperka savo paleidimo kaina

HotPDFRapidOCR.dll kūrimas ir diegimas

HotPDFRapidOCR.dll statoma iš C++ šaltinių Native/RapidOCR su MSVC, C++17, Windows SDK ir CMake 3.20 ar vėlesne, naudojant pagalbinį scenarijų, kuris priima natyvius tinklo šaltinius, ONNX Runtime ir OpenCV katalogus plius Win32 arba Win64 platformą. Statykite abi, jei gabenate abi, nes 32 bitų Delphi programa negali įkelti 64 bitų DLL, o jūsų parūpintos statiškos bibliotekos privalo atitikti tikslinę architektūrą taip pat kaip ir CRT režimą

Modelių pusė turi savų suderinamumo ribų. Aptikiklis yra DB teksto aptikiklis; atpažintojas priima CTC modelius NCHW išdėstyme su fiksuotu 32 arba 48 įvedimo aukščiu, o modeliams su dinamišku aukščiu naudoja 48. Pridėtasis statinis ONNX Runtime negali įkelti naujesne IR versija išsaugotų modelių, tad naujausi PP-OCRv5 eksportai žlunga inicializuodamiesi su diagnostika vietoj dalinio įkėlimo. Žodynas privalo būti UTF-8 be BOM, tiksliai modelio simbolių tvarka, o jo klasių skaičius privalo atitikti modelio išvestį; CRLF eilučių pabaigos priimamos. Atpažinimas neprisijungęs: DLL dingusio modelio niekada neparsiunčia

Trumpa atmintinė

  • Faktorija: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) HPDFRapidOCRRecognition unitas, prieinama nuo v2.774.0 Delphi, C++Builder ir Windows FPC/Lazarus dariniuose
  • Laikykite grąžintąjį IHPDFOCREngine gyvą per puslapius ir dokumentus; jį atleidus sunaikinami modeliai ir iškraunama DLL
  • Vienas variklis vienu metu vykdo vieną atpažinimą; kurkite kelis variklius lygiagretiems darbuotojams ir planuokite atmintį kiekvienai modelių kopijai
  • Išvestis – po įrašą kiekvienai teksto eilutei su vidutine simbolių pasitikėjimo reikšme, filtruojama THPDFOCRTextLayerOptions.MinimumConfidence
  • Atšaukimas ir TimeoutMilliseconds kooperatyviniai; vykstantis ONNX ėjimas visada užbaigiamas
  • Suderinkite DLL bitiškumą su programa, o statiškų ONNX Runtime ir OpenCV bibliotekų CRT režimą – su DLL
  • Rinkitės kalbos profilį varikliui su THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); vienas variklis savarankiškai kalbų neatpažįsta

Natyvis RapidOCR adapteris, procesiniai OCR adapteriai, juos maitinantis puslapių atvaizduoklis ir nematomas Unicode teksto sluoksnio rašytojas visi keliauja kartu HotPDF – natyvioje VCL PDF komponentėje Delphi ir C++Builder. Jeigu jūsų dokumentų fiksavimo ar archyvavimo programai reikia ieškomo išvesties be Python runtime tikslinėje mašinoje, HotPDF Delphi PDF komponentas pateikia visą srautą, o likus diegti tik DLL ir jos modelius