Teknisk artikel

HotPDF in-process RapidOCR: native DLL OCR i Delphi

HotPDF gør scannede PDF-sider søgbare med in-process RapidOCR gennem HPDFCreateRapidOCRDLLOCREngine, en factory tilføjet i v2.774.0, der loader HotPDFRapidOCR.dll, holder ONNX detection-, angle classification- og recognition-modeller residente i hukommelsen og returnerer en IHPDFOCREngine. Du giver den engine videre til THotPDF.ApplyLoadedOCRTextLayer, som renderer hver side, kører CPU-inference uden Python eller en barneproces og committer et usynligt Unicode-tekstlag

Motivationen er omkostning pr. side. RapidOCR-procesadapteren, der kom tidligere, HPDFCreateRapidOCREngine, starter en Python-worker ved hvert Recognize-kald, og den worker importerer sit runtime og loader sine ONNX-modeller, før den læser en enkelt pixel. På et arkiv på 500 sider gentager den startup-skat sig 500 gange, og deployment betyder at skibe et Python-miljø ved siden af en Delphi-eksekverbar. Den native DLL loader modellerne én gang, når du opretter enginen, og deployment skrumpler til DLL'en, dens modelfiler og en tegndictionary. Det, du giver afkald på, er evnen til at dræbe en fastlåst recognizer, og det meste af ingeniørarbejdet i denne adapter handler om at leve med dét på en ærlig måde

Hvordan gør du en scannet PDF søgbar med RapidOCR-DLL'en?

At lave en søgbar PDF med den native RapidOCR-DLL tager ét factory-kald og samme ApplyLoadedOCRTextLayer-kald, som hver HotPDF OCR-engine bruger. Factoryen bor i HPDFRapidOCRRecognition-uniten og validerer tidligt: DLL'en og model-mappen skal findes, hver model- og dictionary-fil skal kunne opløses, ABI-versionen skal være 1, og alle påkrævede exports skal være til stede, før nogen model initialiseres. Konfigurationsfejl rejser EArgumentException; en model, der fejler at loade, rejser EInvalidOperation med den diagnostiske tekst, DLL'en skrev

HotPDF'ens RapidOCR-DLL factory-valideringssekvens for HPDFCreateRapidOCRDLLOCREngine: stier og modelfiler skal findes, HPDFRapidOCRAbiVersion skal returnere 1, påkrævede exports skal kunne opløses, og HPDFRapidOCRCreate skal initialisere modellerne, med EArgumentException eller EInvalidOperation rejst tidligt, før nogen recognition kører, hvor den sidste bærer den native diagnostiske tekst
validering er tidligt med vilje: konfigurationsproblemer rejser, før nogen model initialiseres, så en dårlig sti eller ABI når aldrig en recognition-deadline
uses
  SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Modeller loader her, uden for enhver recognition-deadline.
  // Relative modelnavne i THPDFRapidOCRDLLOptions.Default opløses
  // mod model-mappen.
  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
    // en tom sideliste betyder alle sider; sider med tekst i forvejen skippes
    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 navngiver ch_PP-OCRv3_det_infer.onnx, ch_PP-OCRv3_rec_infer.onnx, ch_ppocr_mobile_v2.0_cls_infer.onnx og ppocr_keys_v1.txt, med én CPU-tråd, en input-grænse på 16.777.216 pixels og en recognition-deadline på 60.000 ms. Siden v2.775.0 bytter THPDFRapidOCRDLLOptions.ForLanguage en matchende recognition-model og dictionary ind for Traditionelt kinesisk, russisk, japansk, arabisk og andre profiler; hvorfor model og dictionary må skifte sammen er dækket i RapidOCR flersprogede modeller og CTC-dictionaries i HotPDF. Enginen melder sig selv som RapidOCR (native DLL) i Info.EngineName, hvilket holder logikker entydige ved siden af den eksterne Tesseract OCR-procesadapter og den indbyggede template-matching OCR-engine

Hvorfor taler C ABI'et kun int32_t og UTF-8 bytes?

HotPDFRapidOCR.dll-ABI'et bruger kun fastbredde-integers, rå pointere og eksplicitte bytelængder, for Delphi, C++Builder og Free Pascal deler intet med MSVC ud over C calling convention. En std::string, en std::vector eller en C++-exception har et layout og en unwinding-model, der tilhører én kompiler og ét runtime-bibliotek. Lad bare én af dem krydse grænsen, og fejlen er en korrupt stak eller en heap-blok frigjort af den forkerte allocator, ikke en ren fejl

ABI-version 1 følger derfor en kort liste af regler. Hver export er cdecl og returnerer en int32_t-status, hvor 1 betyder succes og 0 betyder fejl. Hver funktion, der kan fejle, tager en kalder-ejet diagnostisk buffer og dens kapacitet i bytes; DLL'en skriver en NUL-termineret UTF-8-besked trunkeret til at passe, og adapteren dekoder den med en hård terminator i sidste byte af sin egen buffer på 4.096 bytes. Hver export-krop er pakket ind i try med både catch (const std::exception &) og catch (...), så en ONNX Runtime-fejl, en OpenCV-assertion eller en ugyldig dictionary bliver status 0 plus tekst, aldrig en exception, der undslipper ind i Pascal-kode

ExportRolleHvornår adapteren opløser den
HPDFRapidOCRAbiVersionReturnerer 1; enhver anden værdi afvisesFørst, før noget som helst andet
HPDFRapidOCRCreateLoader detection, valgfri classification, recognition-modeller og dictionary'enI factoryen
HPDFRapidOCRRecognizeKører én bitmap og udsender ét callback pr. tekstlinjeI factoryen
HPDFRapidOCRDestroyFrigør modelinstansenI factoryen
HPDFRapidOCRSetReadingDirectionValgfri højre-til-venstre-rækkefølge, tilføjet i v2.775.0Kun når RightToLeft er sat

Den valgfrie export opløses lazy med vilje: en v2.774.0-DLL uden den serverer stadig left-to-right-anmodninger. DLL'en loades med LoadLibraryEx med search-flag, der dækker DLL'ens egen folder plus default safe directories, så ONNX Runtime- eller OpenCV-afhængigheder lagt ved siden af HotPDFRapidOCR.dll findes uden at røre PATH. Model- og dictionary-stier rejser som UTF-8, og DLL'en konverterer dem med MultiByteToWideChar i strict mode, før den åbner filer gennem wide-character-API'er, så en model-mappe under et kinesisk eller kyrillisk brugernavn virker i stedet for at blive widenet byte for byte til volapyk

Én regel bor i builden frem for i headeren. DLL'en linker ONNX Runtime og OpenCV statisk, og default CMake-konfigurationen bruger den statiske release CRT (/MT). Statiske biblioteker kompileret mod /MD blandet ind i en /MT-DLL giver link-fejl i bedste fald og to uafhængige heaps i værste fald, så de provisionerede biblioteker skal matche, hvilken CRT-mode DLL'en bruger

Hvad sker der mellem en TBitmap og en tekstlinje?

HotPDF giver DLL'en et uafhængigt top-down BGR-snapshot af den renderede side, og DLL'en giver tilbage ét callback pr. genkendt tekstlinje med lånt UTF-8-tekst, som adapteren skal kopiere, inden den returnerer

I Delphi tildeler adapteren side-bitmap'en til en privat TBitmap, tvinger pf24bit og læser rækker med GetDIBits med en negativ biHeight, hvilket giver top-down-rækker polstret til fire-byte alignment; den stride gives eksplicit videre. I FPC læser den gennem CreateIntfImage, for LCL scanline-skrivninger kan opdatere raw-billedet uden at genopfriske GDI-handle. Kalderens bitmap modificeres aldrig, og pixel-budgettet (MaxPixels, 16.777.216 som default og konfigurerbart op til 67.108.864) og grænsen på 32.767 pixels pr. dimension tjekkes, før snapshot-bufferen allokeres

HotPDF'ens RapidOCR-DLL-pipeline fra bitmap til tekstlag: adapteren laver snapshot af siden som top-down pf24bit BGR, DLL'en polstrer, detekterer, sorterer og genkender crops, leverer ét callback pr. linje med lånt UTF-8-tekst, box og confidence, og adapteren validerer hver linje før tekstlags-committet
pixels krydser ABI'et én gang som et snapshot, linjer kommer tilbage ét callback ad gangen, og intet når det søgbare lag, før hvert tjek er passeret

Inde i DLL'en polstres snapshotet med 50 hvide pixels, tekstregioner detekteres med en maksimal side på 1.024 pixels, boxes sorteres i vandrette rækker, og hver crop roteres valgfrit af angle classifieren før recognition. Hver tekstlinje går derefter gennem et callback, der modtager en const char*, en byte-antal, en integer-box i originalbilledets pixels og den gennemsnitlige tegn-confidence. Tekstpointeren er kun gyldig under callbacken, så adapteren kopierer den straks, og den er streng med, hvad den accepterer:

  • UTF-8 dekodes med MB_ERR_INVALID_CHARS; en misdannet sekvens fejler siden i stedet for at producere replacement-tegn i et søgbart lag
  • C0- og C1-kontroltegn afvises, og linjer med kun whitespace skippes
  • Boxen skal ligge inde i bitmap'en, og confidencen skal være en endelig værdi fra 0 til 1
  • Tekst tælles mod anmodningens MaxTextCodeUnits med et hårdt loft på 1.048.576 UTF-16-enheder pr. kald, og supplementary-plane-tegn koster to enheder
  • Enhver Pascal-exception inde i callbacken fanges dér, gemmes og bliver til en 0-retur, som får DLL'en til at stoppe og melde fejl; den gemte besked bliver så diagnostikken

To konsekvenser betyder noget for tuning. For det første er output-enheden en linje, ikke et ord: hver linje bruger én MaxWords-slot, Info.AcceptedWordCount og Info.DroppedWordCount tæller linjer, og søgefremhævning spænder over linje-boxen. For det andet sammenlignes MinimumConfidence (0.5 som default) med linjens gennemsnitlige tegn-confidence, så en linje med ét ulæseligt tegn blandt tyve rene overlever normalt. DLL'en leverer ingen baseline, så tekstlags-pipelinen estimerer én ud fra boxen. En tom side lykkes med nul linjer, og enhver fejl rydder delresultater, så multi-side-committet forbliver alt-eller-intet

Model-ejerskab og thread safety

Hver RapidOCR-DLL-engine ejer præcis én modelinstans i hele sin levetid, og kald til Recognize på den engine serialiseres af en critical section. At holde IHPDFOCREngine-interfacet er dét, der holder modellerne varme, så det rigtige mønster til batch-arbejde er at oprette enginen én gang og genbruge den på tværs af dokumenter

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;    // oprejste scannings: ingen classifier-model loades
  Models.Threads := 4;                   // 1..64, klemmet til antallet af logiske processorer
  Models.TimeoutMilliseconds := 120000;  // pr. Recognize-kald, kooperativt
  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;  // sidste reference frigivet: modeller destrueres, derefter unloades DLL'en

Threads-værdien sætter både intra-op- og inter-op-trådantallet i hver ONNX-session, og DLL'en klemmer den til antallet af aktive processorer. To tråde, der deler én engine, kører ikke parallelt; den anden venter på locken. Den venten er ikke et blindt EnterCriticalSection: adapteren kalder TryEnterCriticalSection hvert 25. millisekund og tjekker cancellation-token og deadline mellem forsøg, så en request i køen stadig kan annulleres eller time ud. Behøver du ægte parallelisme, så opret én engine pr. worker og acceptér, at hver engine holder sin egen kopi af modellerne i hukommelsen

Nedrivningsrækkefølgen er fastlagt af engine-destruktoren: HPDFRapidOCRDestroy frigør modelinstansen først, derefter unloades DLL'en med FreeLibrary. På den native side er model-initialisering lige så omhyggelig; fejler recognition-modellen, efter detector- og classifier-sessioner allerede var bygget, frigives de sessioner, før fejlen meldes, og dictionaryens klasseantal tjekkes mod model-outputtet under initialisering frem for på den første side

Hvorfor kan et native OCR-kald ikke dræbes midt i inference?

Et native RapidOCR-kald kan ikke dræbes midt i inference, fordi det kører på din tråd, inde i din proces, midt i en ONNX Runtime-session, der ikke accepterer afbrydelse. Cancellation i HotPDF's DLL-adapter er derfor kooperativ: DLL'en kalder et abort-callback før og efter detection, efter classification og efter hver genkendt linje og stopper ved det første checkpoint, hvor callbacket returnerer 0. Et enkelt ONNX Run, der er startet, bliver færdigt først

Alternativerne er værre end at vente. TerminateThread ville efterlade CRT heap lock, ONNX Runtime's thread pool og enhver OpenCV-tilstand i vilkårlig tilstand og forgifte resten af processen. FreeLibrary, mens et kald stadig udføres, unloades kode, der ligger på stakken. Ingen af delene kan gøres sikre, så adapteren forsøger dem aldrig. Deadlinen i TimeoutMilliseconds er følgelig en kooperativ deadline, og en udløbet deadline viser sig som en engine-fejl med en timed-out-diagnostik, mens et annulleret token viser sig som otlsCancelled:

// Token oprettes af kalderen og deles med UI-tråden,
// som kalder Token.Cancel, når brugeren trykker Stop
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
  case Info.Status of
    otlsCancelled:
      // returneres ved næste stage- eller linje-grænse; dokumentet uændret
      Writeln('Cancelled');
    otlsEngineError:
      // inkluderer et kooperativt deadline-udløb og native diagnostik
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

Dette er kerneafvejningen mellem HotPDF's procesadaptere og in-process-DLL'en, og ingen af siderne vinder på hver række:

HotPDF OCR-adapter-afvejninger: procesadaptere starter en worker og loader modeller ved hver side, men kan dræbes og indeholder crashes, mens in-process RapidOCR-DLL'en loader modeller én gang, kun stopper ved kooperative checkpoints, deler address space og deployes som en DLL med sine modeller og dictionary
vælg pr. workload: en side-ad-gangen-desktopapplikation har gavn af den varme DLL, mens en server, der sluger utroværdige scannings, bør betale for procesmuren
  • Startup-omkostning: Tesseract- og Python RapidOCR-adapterne starter en proces og loader modeller ved hver side; DLL'en loader modeller én gang pr. engine
  • Stopning: en barneproces kan termineres outright, og Python-workeren kører inde i en kill-on-close Job Object, så hele dens procestræ går med; DLL'en kan kun stoppe ved stage- og linje-grænser
  • Fault containment: et crash i tesseract.exe fejler én side; en access violation inde i DLL'en tager din proces med ned
  • Deployment: procesadaptere behøver et installeret program eller et Python-miljø; DLL'en behøver sig selv, sine modeller og sin dictionary, matchet til applikationens bitness
  • Hukommelse: procesadaptere frigiver alt, når barnet exit'er; en DLL-engine holder sine modeller residente, til den sidste interfacereference er frigivet

Til en interaktiv desktopapplikation, der OCR'er én side ad gangen, vinder DLL'ens responsivitet normalt. Til en server, der sluger utroværdige scanninger døgnet rundt, er procesgrænsen sin startup-omkostning værd

At bygge og deployere HotPDFRapidOCR.dll

HotPDFRapidOCR.dll bygges fra C++-kilderne i Native/RapidOCR med MSVC, C++17, et Windows SDK og CMake 3.20 eller senere, med et hjælperscript, der tager de native netværkskilder, ONNX Runtime- og OpenCV-mapperne plus en Win32- eller Win64-platform. Byg begge, hvis du skiber begge, for en 32-bit Delphi-applikation kan ikke loade en 64-bit DLL, og de statiske biblioteker, du provisionerer, skal matche target-arkitekturen lige så vel som CRT-mode

Modelsiden har sine egne kompatibilitetsgrænser. Detectoreren er en DB text detector; recognizeren accepterer CTC-modeller i NCHW-layout med en fast inputhøjde på 32 eller 48 og bruger 48 til modeller med dynamisk højde. Den medfølgende statiske ONNX Runtime kan ikke loade modeller gemt med en nyere IR-version, så nylige PP-OCRv5-eksporter fejler initialiseringen med en diagnostik i stedet for at loade delvist. Dictionary'en skal være UTF-8 uden BOM, i præcis modellens tegnrækkefølge, og dens klasseantal skal matche model-outputtet; CRLF-linjeslutninger accepteres. Recognition er offline: DLL'en downloader aldrig en manglende model

Hurtig reference

  • Factory: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) i HPDFRapidOCRRecognition, tilgængelig siden v2.774.0 i Delphi-, C++Builder- og Windows FPC/Lazarus-builds
  • Hold den returnerede IHPDFOCREngine i live på tværs af sider og dokumenter; frigiver du den, destrueres modellerne, og DLL'en unloades
  • Én engine kører én recognition ad gangen; opret flere engines til parallelle workers og budgetter hukommelse til hver modelkopi
  • Output er én post pr. tekstlinje med gennemsnitlig tegn-confidence, filtreret af THPDFOCRTextLayerOptions.MinimumConfidence
  • Cancellation og TimeoutMilliseconds er kooperative; et ONNX-run i gang bliver altid færdigt
  • Match DLL-bitness til applikationen og CRT-moden hos de statiske ONNX Runtime- og OpenCV-biblioteker til DLL'en
  • Vælg en sprogprofil pr. engine med THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); én engine detekterer ikke sprog på egen hånd

Den native RapidOCR-adapter, de procesbaserede OCR-adaptere, side-rendereren, der fodrer dem, og den usynlige Unicode-tekstlags-writer skiber alle sammen i HotPDF, en native VCL PDF-komponent til Delphi og C++Builder. Behøver din dokumentcapture- eller arkiveringsapplikation søgbar output uden et Python-runtime på target-maskinen, giver HotPDF Delphi PDF-komponenten hele pipelinen med kun DLL'en og dens modeller tilbage at deployere