Technisch artikel

HotPDF In-Process RapidOCR: native DLL-OCR in Delphi

HotPDF maakt gescande PDF-pagina's doorzoekbaar met in-process RapidOCR via HPDFCreateRapidOCRDLLOCREngine, een factory toegevoegd in v2.774.0 die HotPDFRapidOCR.dll laadt, de ONNX-detectie-, hoekclassificatie- en recognitiemodellen in het geheugen houdt en een IHPDFOCREngine teruggeeft. Die engine geeft u door aan THotPDF.ApplyLoadedOCRTextLayer, die elke pagina rendert, CPU-inferentie draait zonder Python of een kindproces, en een onzichtbare Unicode-tekstlaag wegschrijft

De motivatie is de kosten per pagina. De RapidOCR-procesadapter die eerder verscheen, HPDFCreateRapidOCREngine, start een Python-werker voor elke Recognize-aanroep, en die werker importeert zijn runtime en laadt zijn ONNX-modellen voordat hij ook maar één pixel leest. Op een archief van 500 pagina's herhaalt die opstartbelasting zich 500 keer, en deployen betekent een Python-omgeving naast een Delphi-executable verschepen. De native DLL laadt de modellen één keer, op het moment dat u de engine aanmaakt, en het deployen krimpt tot de DLL, zijn modelbestanden en een karakterwoordenboek. Wat u inruilt is de mogelijkheid om een vastgelopen recognizer neer te schieten, en het grootste deel van de engineering in deze adapter gaat over hoe u daarmee eerlijk leeft

Hoe maakt u een gescande PDF doorzoekbaar met de RapidOCR-DLL?

Een doorzoekbare PDF maken met de native RapidOCR-DLL kost één factory-aanroep en dezelfde ApplyLoadedOCRTextLayer-aanroep die elke HotPDF-OCR-engine gebruikt. De factory huist in de unit HPDFRapidOCRRecognition en valideert gretig: de DLL en de modelmap moeten bestaan, elk model- en woordenboekbestand moet resolvbaar zijn, de ABI-versie moet 1 zijn, en alle vereiste exports moeten aanwezig zijn voordat een enkel model wordt geïnitialiseerd. Configuratiefouten geven een EArgumentException; een model dat niet laadt geeft een EInvalidOperation met de diagnostische tekst die de DLL schreef

HotPDF RapidOCR DLL-factory-validatiereeks voor HPDFCreateRapidOCRDLLOCREngine: paden en modellbestanden moeten bestaan, HPDFRapidOCRAbiVersion moet 1 teruggeven, vereiste exports moeten resolvbaar zijn, en HPDFRapidOCRCreate moet de modellen initialiseren, met EArgumentException of EInvalidOperation die gretig komt voordat enige recognitie draait, de laatste met de native diagnostische tekst
validatie is met opzet gretig: configuratieproblemen komen voordat een model initialiseert, dus een fout pad of ABI bereikt nooit een recognition-deadline
uses
  SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Modellen worden hier geladen, buiten elke recognition-deadline.
  // Relatieve modelnamen in THPDFRapidOCRDLLOptions.Default resolven
  // tegen de modelmap.
  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
    // een lege paginalijst betekent elke pagina; pagina's die al tekst hebben worden overgeslagen
    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 benoemt ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx en ppocr_keys_v1.txt, met één CPU-thread, een invoerlimiet van 16.777.216 pixels en een recognition-deadline van 60.000 ms. Sinds v2.775.0 wisselt THPDFRapidOCRDLLOptions.ForLanguage een matchend recognitiemodel en woordenboek in voor Traditioneel Chinees, Russisch, Japans, Arabisch en andere profielen; waarom model en woordenboek samen moeten wisselen wordt behandeld in RapidOCR meertalige modellen en CTC-woordenboeken in HotPDF. De engine meldt zichzelf als RapidOCR (native DLL) in Info.EngineName, wat logs ondubbelzinnig houdt naast de externe Tesseract OCR-procesadapter en de ingebouwde template-matching-OCR-engine

Waarom spreekt de C ABI alleen int32_t en UTF-8-bytes?

De ABI van HotPDFRapidOCR.dll gebruikt uitsluitend integers met vaste breedte, kale pointers en explicite bytelengtes, omdat Delphi, C++Builder en Free Pascal met MSVC niets delen behalve de C-aanroepconventie. Een std::string, een std::vector of een C++-exception heeft een layout en een unwinding-model die bij één compiler en één runtimebibliotheek horen. Laat er een van de grens oversteken en de falering is een gecorrumpeerde stack of een heapblok dat door de verkeerde allocator wordt vrijgegeven, niet een nette fout

ABI-versie 1 volgt daarom een korte lijst regels. Elke export is cdecl en geeft een int32_t-status terug, waarbij 1 succes betekent en 0 falering. Elke functie die kan falen neemt een diagnostische buffer in aanroeper-bezit en diens capaciteit in bytes; de DLL schrijft een met NUL afgesloten UTF-8-boodschap ingekort tot ze past, en de adapter decodeert haar met een harde terminator in de laatste byte van zijn eigen buffer van 4.096 bytes. Elke exportbody is gewikkeld in try met zowel catch (const std::exception &) als catch (...), dus een ONNX Runtime-fout, een OpenCV-assertion of een ongeldig woordenboek wordt status 0 plus tekst, nooit een exception die in Pascal-code ontsnapt

ExportRolWanneer de adapter haar resolvant
HPDFRapidOCRAbiVersionGeeft 1 terug; elke andere waarde wordt geweigerdEerst, vóór alles
HPDFRapidOCRCreateLaadt detectie, optionele classificatie, recognitiemodellen en het woordenboekIn de factory
HPDFRapidOCRRecognizeDraait één bitmap en geeft één callback per tekstregelIn de factory
HPDFRapidOCRDestroyGeeft de modelinstantie vrijIn de factory
HPDFRapidOCRSetReadingDirectionOptionele rechts-naar-links-rijvolgorde, toegevoegd in v2.775.0Alleen als RightToLeft is gezet

De optionele export wordt met opzet lazy resolvant: een DLL uit v2.774.0 die haar mist, bedient nog steeds links-naar-rechts-verzoeken. De DLL wordt geladen met LoadLibraryEx met zoekvlaggen die de eigen map van de DLL plus de default veilige directories dekken, dus ONNX Runtime- of OpenCV-afhankelijkheden naast HotPDFRapidOCR.dll worden gevonden zonder PATH aan te raken. Model- en woordenboekpaden reizen als UTF-8 en de DLL zet ze om met MultiByteToWideChar in strikte modus voordat hij bestanden opent via wide-character-API's, dus een modelmap onder een Chinese of Cyrillische gebruikersnaam werkt in plaats van byte voor byte verbreed te worden tot onzin

Eén regel huist in de build in plaats van in de header. De DLL linkt ONNX Runtime en OpenCV statisch, en de default CMake-configuratie gebruikt de statische release CRT (/MT). Statische bibliotheken gecompileerd tegen /MD gemengd in een /MT-DLL leveren hooguit linkfouten op en erger twee onafhankelijke heaps, dus de aangeleverde bibliotheken moeten matchen met welke CRT-modus de DLL ook gebruikt

Wat gebeurt er tussen een TBitmap en een tekstregel?

HotPDF geeft de DLL een onafhankelijke top-down BGR-snapshot van de gerenderde pagina, en de DLL geeft terug één callback per herkende tekstregel met geleend UTF-8-tekst die de adapter moet kopiëren voordat hij terugkeert

Op Delphi wijst de adapter de pagina-bitmap toe aan een privé-TBitmap, forceert pf24bit en leest rijen met GetDIBits met een negatieve biHeight, wat top-down rijen oplevert opgevuld tot uitlijning op vier bytes; die stride wordt expliciet doorgegeven. Op FPC leest hij via CreateIntfImage, omdat LCL-scanline-schrijfacties de rauwe afbeelding kunnen bijwerken zonder de GDI-handle te verversen. De bitmap van de aanroeper wordt nooit gewijzigd, en het pixelbudget (MaxPixels, 16.777.216 by default en configureerbaar tot 67.108.864) en de limiet van 32.767 pixels per dimensie worden gecontroleerd voordat de snapshotbuffer wordt toegewezen

HotPDF RapidOCR DLL-pijplijn van bitmap naar tekstlaag: de adapter maakt een top-down pf24bit BGR-snapshot van de pagina, de DLL vult aan, detecteert, ordent en herkent crops, levert één callback per regel met geleende UTF-8-tekst, box en confidence, en de adapter valideert elke regel vóór de tekstlaag-commit
pixels steken de ABI één keer over als snapshot, regels komen terug één callback per keer, en niets bereikt de doorzoekbare laag voordat elke controle is geslaagd

Binnen de DLL wordt de snapshot aangevuld met 50 witte pixels, tekstregio's worden gedetecteerd met een maximumzijde van 1.024 pixels, boxes worden geordend in horizontale rijen, en elke crop wordt optioneel gedraaid door de hoekclassificator vóór de herkenning. Elke tekstregel gaat daarna door een callback die een const char* ontvangt, een byteaantal, een integer box in originele-afbeelding-pixels en de gemiddelde karakterconfidence. De tekstpointer is alleen geldig tijdens de callback, dus de adapter kopieert hem onmiddellijk, en hij is streng in wat hij accepteert:

  • UTF-8 wordt gedecodeerd met MB_ERR_INVALID_CHARS; een misvormde reeks laat de pagina falen in plaats van vervangingstekens te produceren in een doorzoekbare laag
  • C0- en C1-stuurttekens worden geweigerd, en regels met alleen witruimte worden overgeslagen
  • De box moet binnen de bitmap liggen en de confidence moet een eindige waarde van 0 tot 1 zijn
  • Tekst wordt geteld tegen de MaxTextCodeUnits van het verzoek met een hard plafond van 1.048.576 UTF-16-eenheden per aanroep, en tekens uit het supplementaire vlak kosten twee eenheden
  • Elke Pascal-exception binnen de callback wordt daar gevangen, bewaard en omgezet in een retour van 0, wat de DLL laat stoppen en falen melden; de bewaarde boodschap wordt daarna de diagnostiek

Twee gevolgen tellen voor tuning. Ten eerste is de uitvoereenheid een regel, geen woord: elke regel verbruikt één MaxWords-slot, Info.AcceptedWordCount en Info.DroppedWordCount tellen regels, en zoekmarkering overspant de regelbox. Ten tweede wordt MinimumConfidence (0.5 by default) vergeleken met de gemiddelde karakterconfidence van de regel, dus een regel met één onleesbaar teken tussen twintig nette overleeft doorgaans. De DLL levert geen baseline, dus de tekstlaag-pijplijn schat er een uit de box. Een lege pagina slaagt met nul regels, en elke falering wist partiele resultaten zodat de multi-page-commit alles-of-niets blijft

Modeleigendom en thread safety

Elke RapidOCR-DLL-engine bezit precies één modelinstantie voor zijn hele levensduur, en aanroepen van Recognize op die engine worden geserialiseerd door een critical section. De IHPDFOCREngine-interface vasthouden is wat de modellen warm houdt, dus het juiste patroon voor batchwerk is de engine één keer aanmaken en hergebruiken over documenten heen

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;    // rechtopstaande scans: er wordt geen classifier-model geladen
  Models.Threads := 4;                   // 1..64, geklemd op het aantal logische processors
  Models.TimeoutMilliseconds := 120000;  // per Recognize-aanroep, coöperatief
  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;  // laatste referentie vrijgegeven: modellen vernietigd, daarna wordt de DLL ontladen

De Threads-waarde stelt zowel het intra-op- als het inter-op-aantal threads van elke ONNX-sessie in, en de DLL klemt haar op het aantal actieve processors. Twee threads die één engine delen draaien niet parallel; de tweede wacht op de lock. Dat wachten is geen blinde EnterCriticalSection: de adapter roept TryEnterCriticalSection elke 25 ms aan en controleert de cancellation token en de deadline tussen pogingen, dus een in de wachtrij staand verzoek kan alsnog worden geannuleerd of op tijd uitvallen. Heeft u echte paralleliteit nodig, maak dan één engine per werker en accepteer dat elke engine zijn eigen kopie van de modellen in het geheugen houdt

De teardown-volgorde staat vast in de engine-destructor: HPDFRapidOCRDestroy geeft eerst de modelinstantie vrij, daarna ontlaadt FreeLibrary de DLL. Aan de native kant is de modelinitialisatie even zorgvuldig; faalt het recognitiemodel nadat de detector- en classifier-sessies al waren gebouwd, dan worden die sessies vrijgegeven voordat de fout wordt gemeld, en het klasseaantal van het woordenboek wordt tijdens de initialisatie gecontroleerd tegen de modeluitvoer in plaats van op de eerste pagina

Waarom kan een native OCR-aanroep niet midden in de inferentie worden gedood?

Een native RapidOCR-aanroep kan niet midden in de inferentie worden gedood omdat hij op uw thread draait, binnen uw proces, midden in een ONNX Runtime-sessie die onderbreking niet accepteert. Cancellation in de DLL-adapter van HotPDF is daarom coöperatief: de DLL roept een abort-callback aan vóór en na detectie, na classificatie en na elke herkende regel, en stopt op het eerste checkpoint waar de callback 0 teruggeeft. Een ONNX Run die eenmaal is gestart maakt eerst af

De alternatieven zijn erger dan wachten. TerminateThread zou de CRT-heaplock, de threadpool van ONNX Runtime en elke OpenCV-status achterlaten in welke toestand ze toevallig verkeerden, en vergiftigt daarmee de rest van het proces. FreeLibrary terwijl een aanroep nog draait ontlaadt code die op de stack staat. Geen van beide is veilig te maken, dus de adapter probeert ze nooit. De deadline in TimeoutMilliseconds is daardoor een coöperatieve deadline, en een verlopen deadline komt naar boven als enginefout met een timed-out-diagnostiek, terwijl een geannuleerde token naar boven komt als otlsCancelled:

// De token wordt door de aanroeper aangemaakt en gedeeld met de UI-thread,
// die Token.Cancel aanroept zodra de gebruiker op Stop drukt
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
  case Info.Status of
    otlsCancelled:
      // teruggegeven op de volgende stage- of lijngrens; document ongewijzigd
      Writeln('Cancelled');
    otlsEngineError:
      // omvat het aflopen van een coöperatieve deadline en native diagnostiek
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

Dit is de kernafweging tussen de procesadapters van HotPDF en de in-process DLL, en geen van beide wint op elke rij:

HotPDF OCR-adapterafwegingen: procesadapters starten een werker en laden modellen bij elke pagina maar zijn killbaar en bevatten crashes, terwijl de in-process RapidOCR-DLL modellen één keer laadt, alleen stopt bij coöperatieve checkpoints, de adresruimte deelt en deployt als DLL met zijn modellen en woordenboek
kiest per werklast: een desktop-applicatie die pagina voor pagina werkt profiteert van de warme DLL, terwijl een server die niet-vertrouwde scans verwerkt voor de procesmuur moet betalen
  • Opstartkosten: de Tesseract- en Python RapidOCR-adapters starten een proces en laden modellen voor elke pagina; de DLL laadt modellen één keer per engine
  • Stoppen: een kindproces kan ronduit worden beëindigd, en de Python-werker draait binnen een kill-on-close Job Object zodat zijn hele procesboom meegaat; de DLL kan alleen stoppen op stage- en lijngrenzen
  • Fault containment: een crash in tesseract.exe laat één pagina falen; een access violation binnen de DLL trekt uw proces neer
  • Deployen: procesadapters hebben een geïnstalleerd programma of een Python-omgeving nodig; de DLL heeft zichzelf, zijn modellen en zijn woordenboek nodig, gematcht op de bitness van de applicatie
  • Geheugen: procesadapters geven alles vrij zodra het kind afsluit; een DLL-engine houdt zijn modellen in het geheugen tot de laatste interfacereferentie is vrijgegeven

Voor een interactieve desktopapplicatie die één pagina per keer OCR't wint de responsiviteit van de DLL doorgaans. Voor een server die de klok rond niet-vertrouwde scans verwerkt, is de procesgrens zijn opstartkosten waard

HotPDFRapidOCR.dll bouwen en deployen

HotPDFRapidOCR.dll wordt gebouwd vanuit de C++-bronnen in Native/RapidOCR met MSVC, C++17, een Windows SDK en CMake 3.20 of later, met behulp van een helperscript dat de native netwerkbronnen, de ONNX Runtime- en OpenCV-directories plus een platform Win32 of Win64 neemt. Bouw allebei als u allebei verscheept, want een 32-bit Delphi-applicatie kan geen 64-bit DLL laden, en de statische bibliotheken die u aanlevert moeten matchen op doelarchitectuur én CRT-modus

De modelkant heeft zijn eigen compatibiliteitsgrenzen. De detector is een DB-tekstdetector; de recognizer accepteert CTC-modellen in NCHW-layout met een vaste invoerhoogte van 32 of 48, en gebruikt 48 voor modellen met een dynamische hoogte. De meegeleverde statische ONNX Runtime kan modellen opgeslagen met een nieuwere IR-versie niet laden, dus recente PP-OCRv5-exports falen bij initialisatie met een diagnostiek in plaats van partiel te laden. Het woordenboek moet UTF-8 zonder BOM zijn, in exact de karaktervolgorde van het model, en zijn klasseaantal moet matchen met de modeluitvoer; CRLF-regeleinden worden geaccepteerd. Herkenning is offline: de DLL downloadt nooit een ontbrekend model

Snelnaslag

  • Factory: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) in HPDFRapidOCRRecognition, beschikbaar sinds v2.774.0 in Delphi-, C++Builder- en Windows FPC/Lazarus-builds
  • Houd de teruggegeven IHPDFOCREngine in leven over pagina's en documenten heen; haar vrijgeven vernietigt de modellen en ontlaadt de DLL
  • Eén engine draait één herkenning tegelijk; maak meerdere engines voor parallelle werkers en budgetteer geheugen per modelkopie
  • De uitvoer is één entry per tekstregel met gemiddelde karakterconfidence, gefilterd door THPDFOCRTextLayerOptions.MinimumConfidence
  • Annulering en TimeoutMilliseconds zijn coöperatief; een ONNX-run die bezig is maakt altijd af
  • Match de DLL-bitness op de applicatie en de CRT-modus van de statische ONNX Runtime- en OpenCV-bibliotheken op de DLL
  • Kies per engine een taalprofiel met THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); één engine detecteert geen talen uit zichzelf

De native RapidOCR-adapter, de procesgebaseerde OCR-adapters, de paginarender die hen voedt en de onzichtbare Unicode-tekstlaag-schrijver worden samen geleverd in HotPDF, een native VCL PDF-component voor Delphi en C++Builder. Heeft uw documentverzamel- of archiveringsapplicatie doorzoekbare uitvoer nodig zonder Python-runtime op de doelmachine, dan levert de HotPDF Delphi PDF-component de hele pijplijn met alleen de DLL en zijn modellen nog te deployen