Teknisk artikkel

HotPDF in-process RapidOCR: native DLL-OCR i Delphi

HotPDF gjør skannede PDF-sider søkbare med in-process RapidOCR gjennom HPDFCreateRapidOCRDLLOCREngine, en fabrikk lagt til i v2.774.0 som laster HotPDFRapidOCR.dll, holder ONNX-deteksjons-, vinkelklassifiserings- og gjenkjenningsmodellene residente i minnet, og returnerer en IHPDFOCREngine. Du sender den motoren til THotPDF.ApplyLoadedOCRTextLayer, som rendrer hver side, kjører CPU-inferens uten Python eller en barne-prosess, og committer et usynlig Unicode-tekstlag

Motivasjonen er kostnad per side. RapidOCR-prosessadapteren som kom tidligere, HPDFCreateRapidOCREngine, starter en Python-worker for hvert Recognize-kall, og den workeren importerer sin runtime og laster sine ONNX-modeller før den leser én eneste piksel. På et 500-siders arkiv gjentar den oppstartsskatten seg 500 ganger, og deployering betyr å skippe et Python-miljø ved siden av en Delphi-kjørbar fil. Den native DLL-en laster modellene én gang, når du oppretter motoren, og deployeringen krymper til DLL-en, dens modellfiler og en tegnordbok. Det du gir opp til gjengjeld, er evnen til å drepe en fastlåst gjenkjenner, og mesteparten av ingeniørarbeidet i denne adapteren handler om å leve med det ærlig

Hvordan gjør du en skannet PDF søkbar med RapidOCR DLL-en?

Å lage en søkbar PDF med den native RapidOCR DLL-en tar ett fabrikk-kall og det samme ApplyLoadedOCRTextLayer-kallet enhver HotPDF OCR-motor bruker. Fabrikken bor i HPDFRapidOCRRecognition-uniten og validerer ivrig: DLL-en og modellkatalogen må finnes, hver modell- og ordbokfil må løses, ABI-versjonen må være 1, og alle påkrevde eksporter må være til stede før noen modell initialiseres. Konfigurasjonsfeil reiser EArgumentException; en modell som feiler å laste reiser EInvalidOperation med diagnostikkteksten DLL-en skrev

HotPDF RapidOCR DLL fabrikk-valideringssekvens for HPDFCreateRapidOCRDLLOCREngine: stier og modellfiler må finnes, HPDFRapidOCRAbiVersion må returnere 1, påkrevde eksporter må løses, og HPDFRapidOCRCreate må initialisere modellene, med EArgumentException eller EInvalidOperation reist ivrig før noen gjenkjenning kjører, den sistnevnte bærende den native diagnostikkteksten
Validering er ivrig med vilje: konfigurasjonsproblemer reiser før noen modell initialiseres, så en gal sti eller ABI når aldri en gjenkjenningsfrist
uses
  SysUtils, HPDFTypes, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Modeller lastes her, utenfor enhver gjenkjenningsfrist.
  // Relative modellnavn i THPDFRapidOCRDLLOptions.Default løses
  // mot modellkatalogen.
  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 betyr hver side; sider som allerede har tekst hoppes over
    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 navngir 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 inngrense på 16 777 216 piksler og en gjenkjenningsfrist på 60 000 ms. Siden v2.775.0 bytter THPDFRapidOCRDLLOptions.ForLanguage inn en matchende gjenkjenningsmodell og ordbok for tradisjonell kinesisk, russisk, japansk, arabisk og andre profiler; hvorfor modellen og ordboken må endres sammen, er dekket i RapidOCR flerspråklige modeller og CTC-ordbøker i HotPDF. Motoren rapporterer seg selv som RapidOCR (native DLL) i Info.EngineName, noe som holder logger entydige ved siden av den eksterne Tesseract OCR-prosessadapteren og den innebygde mal-sammenlignings-OCR-motoren

Hvorfor snakker C ABI-en bare int32_t og UTF-8-byter?

HotPDFRapidOCR.dll ABI-en bruker bare fastbreddde heltall, rå pekere og eksplisitte bytelengder, for Delphi, C++Builder og Free Pascal deler ingenting med MSVC utover C-kallekonvensjonen. En std::string, en std::vector eller et C++-unntak har et layout og en unwinding-modell som tilhører én kompilator og ett runtime-bibliotek. La noen av dem krysse grensen, og feilen er en korrupt stakk eller en heap-blokk frigjort av feil allokator, ikke en ren feil

ABI versjon 1 følger derfor en kort liste med regler. Hver eksport er cdecl og returnerer en int32_t-status, der 1 betyr suksess og 0 betyr feil. Hver funksjon som kan feile, tar en kalleid diagnostikkbuffer og dens kapasitet i byte; DLL-en skriver en NUL-terminert UTF-8-melding avkortet til å passe, og adapteren dekoder den med en hard terminator i siste byte av sin egen 4 096-bytes buffer. Hvert eksport-legeme er pakket inn i try med både catch (const std::exception &) og catch (...), så en ONNX Runtime-feil, en OpenCV-assertion eller en ugyldig ordbok blir status 0 pluss tekst, aldri et unntak som rømmer inn i Pascal-kode

EksportRolleNår adapteren løser den
HPDFRapidOCRAbiVersionReturnerer 1; enhver annen verdi avvisesFørst, før noe annet
HPDFRapidOCRCreateLaster deteksjons-, valgfrie klassifiserings- og gjenkjenningsmodeller og ordbokenI fabrikken
HPDFRapidOCRRecognizeKjører én bitmap og sender ett callback per tekstlinjeI fabrikken
HPDFRapidOCRDestroyFrigjør modellinstansenI fabrikken
HPDFRapidOCRSetReadingDirectionValgfri høyre-til-venstre-rekkefølge, lagt til i v2.775.0Bare når RightToLeft er satt

Den valgfrie eksporten løses dovent med vilje: en v2.774.0-DLL som mangler den, serverer fortsatt venstre-til-høyre-forespørsler. DLL-en lastes med LoadLibraryEx med søkeflagg som dekker DLL-ens egen mappe pluss standard sikre kataloger, så ONNX Runtime- eller OpenCV-avhengigheter plassert ved siden av HotPDFRapidOCR.dll finnes uten å røre PATH. Modell- og ordbokstier reiser som UTF-8 og DLL-en konverterer dem med MultiByteToWideChar i streng modus før den åpner filer gjennom wide-character API-er, så en modellkatalog under et kinesisk eller kyrillisk brukernavn virker i stedet for å bli utvidet byte for byte til vrøvl

Én regel bor i bygget i stedet for i headeren. DLL-en lenker ONNX Runtime og OpenCV statisk, og standard CMake-konfigurasjonen bruker den statiske release CRT-en (/MT). Statiske biblioteker kompilert mot /MD blandet inn i en /MT-DLL gir link-feil i beste fall og to uavhengige heaps i verste fall, så de foreslåtte bibliotekene må matche den CRT-moden DLL-en bruker

Hva skjer mellom en TBitmap og en tekstlinje?

HotPDF gir DLL-en et uavhengig top-down BGR-øyebliksbilde av den renderte siden, og DLL-en gir tilbake ett callback per gjenkjent tekstlinje med lånt UTF-8-tekst som adapteren må kopiere før den returnerer

På Delphi tilordner adapteren sidebitmapen til en privat TBitmap, tvinger pf24bit, og leser rader med GetDIBits ved bruk av en negativ biHeight, som gir top-down-rader polstret til fire-byte-justering; den strien sendes eksplisitt. På FPC leser den gjennom CreateIntfImage, for LCL scanline-skrivinger kan oppdatere det rå bildet uten å oppfriske GDI-håndtaket. Kallerens bitmap endres aldri, og pikselbudsjettet (MaxPixels, 16 777 216 som standard og konfigurerbart opptil 67 108 864) og grensen på 32 767 piksler per dimensjon sjekkes før øyebliksbilde-bufferen allokeres

HotPDF RapidOCR DLL-pipeline fra bitmap til tekstlag: adapteren tar et øyebliksbilde av siden som top-down pf24bit BGR, DLL-en polstrer, detekterer, ordner og gjenkjenner beskjæringer, leverer ett callback per linje med lånt UTF-8-tekst, boks og konfidens, og adapteren validerer hver linje før tekstlag-commit
Piksler krysser ABI-en én gang som et øyebliksbilde, linjer kommer tilbake ett callback om gangen, og ingenting når det søkbare laget før hver sjekk passerer

Inne i DLL-en polstres øyebliksbildet med 50 hvite piksler, tekstregioner detekteres med en maksimal side på 1 024 piksler, bokser ordnes i horisontale rader, og hver beskjæring roteres valgfritt av vinkelklassifisereren før gjenkjenning. Hver tekstlinje går så gjennom et callback som mottar en const char*, et byteantall, en heltallsboks i originalbilde-piksler, og gjennomsnittlig tegnkonfidens. Tekstpekeren er gyldig bare under callback-en, så adapteren kopierer den umiddelbart, og den er streng med hva den aksepterer:

  • UTF-8 dekodes med MB_ERR_INVALID_CHARS; en misdannet sekvens feiler siden i stedet for å produsere erstatningstegn i et søkbart lag
  • C0- og C1-kontrolltegn avvises, og linjer med bare mellomrom hoppes over
  • Boksen må ligge inne i bitmapen, og konfidensen må være en endelig verdi fra 0 til 1
  • Tekst telles mot forespørselens MaxTextCodeUnits med et hardt tak på 1 048 576 UTF-16-enheter per kall, og supplementary-plane-tegn koster to enheter
  • Ethvert Pascal-unntak inne i callback-en fanges der, lagres, og gjøres om til en 0-retur, som får DLL-en til å stoppe og rapportere feil; den lagrede meldingen blir da diagnostikken

To konsekvenser betyr noe for tuning. For det første er utgangsenheten en linje, ikke et ord: hver linje forbruker én MaxWords-plass, Info.AcceptedWordCount og Info.DroppedWordCount teller linjer, og søkehøydelys spenner over linjeboksen. For det andre sammenlignes MinimumConfidence (0,5 som standard) mot linjens gjennomsnittlige tegnkonfidens, så en linje med ett uleselig tegn blant tjue rene overlever som regel. DLL-en leverer ingen baseline, så tekstlag-pipelinen anslår én fra boksen. En tom side lykkes med null linjer, og enhver feil tømmer delresultater så multi-side-committen forblir alt-eller-ingenting

Modelleierskap og trådsikkerhet

Hver RapidOCR DLL-motor eier nøyaktig én modellinstans i hele sin levetid, og kall til Recognize på den motoren serialiseres av en critical section. Å holde IHPDFOCREngine-grensesnittet er det som holder modellene varme, så det riktige mønsteret for batch-arbeid er å opprette motoren én gang og gjenbruke den på tvers av 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;    // skann uten rotasjon: ingen klassifikator-modell lastes
  Models.Threads := 4;                   // 1..64, taket på logisk prosessortall
  Models.TimeoutMilliseconds := 120000;  // per Recognize-kall, kooperativ
  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;  // siste referanse sluppet: modeller ødelagt, så DLL-en losses

Threads-verdien setter både intra-op- og inter-op-trådantallet til hver ONNX-økt, og DLL-en klemmer den til antallet aktive prosessorer. To tråder som deler én motor, kjører ikke parallelt; den andre venter på låsen. Den ventingen er ikke en blind EnterCriticalSection: adapteren kaller TryEnterCriticalSection hvert 25 ms og sjekker cancellation-token og fristen mellom forsøk, så en køet forespørsel kan fortsatt kanselleres eller få tidsavbrudd. Trenger du ekte parallellitet, opprett én motor per worker og aksepter at hver motor holder sin egen kopi av modellene i minnet

Nedrivningsrekkefølgen er fastsatt av motorens destruktor: HPDFRapidOCRDestroy frigjør modellinstansen først, så FreeLibrary lossar DLL-en. På nativesiden er modellinitialisering like nøye; når gjenkjenningsmodellen feiler etter at detektor- og klassifikatorøktene allerede var bygget, slippes de øktene før feilen rapporteres, og ordbokens klasseantall sjekkes mot modellutgangen under initialisering i stedet for på første side

Hvorfor kan ikke et native OCR-kall drepes midt i inferens?

Et native RapidOCR-kall kan ikke drepes midt i inferens fordi det kjører på tråden din, inne i prosessen din, midt i en ONNX Runtime-økt som ikke aksepterer avbrytelse. Kansellering i HotPDFs DLL-adapter er derfor kooperativ: DLL-en kaller en abort-callback før og etter deteksjon, etter klassifisering, og etter hver gjenkjent linje, og stopper ved det første sjekkpunktet der callback-en returnerer 0. En enkelt ONNX Run som har startet, fullfører først

Alternativene er verre enn å vente. TerminateThread ville etterlate CRT heap-låsen, ONNX Runtime sin tråd-pool og enhver OpenCV-tilstand i den tilstanden de tilfeldigvis var i, og forgifte resten av prosessen. FreeLibrary mens et kall fortsatt kjører, lossar kode som ligger på stakken. Ingen av delene kan gjøres trygge, så adapteren forsøker dem aldri. Fristen i TimeoutMilliseconds er følgelig en kooperativ frist, og en utløpt frist viser seg som en motorfeil med en timed out-diagnostikk, mens en kansellert token viser seg som otlsCancelled:

// Token opprettes av kalleren og deles med UI-tråden,
// som kaller Token.Cancel når brukeren trykker Stopp
Options := THPDFOCRTextLayerOptions.Default;
Options.CancellationToken := Token;
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
  case Info.Status of
    otlsCancelled:
      // returnert ved neste stage- eller linjegrense; dokument uendret
      Writeln('Cancelled');
    otlsEngineError:
      // inkluderer et kooperativt fristutløp og native diagnostikk
      Writeln('Engine: ', string(Info.Diagnostic));
    otlsBudgetExceeded:
      Writeln('Budget: ', string(Info.Diagnostic));
  else
    Writeln(string(Info.Diagnostic));
  end;

Dette er kjerneavveiningen mellom HotPDFs prosessadaptere og in-process DLL-en, og ingen av sidene vinner på hver rad:

HotPDF OCR-adapteravveininger: prosessadaptere starter en worker og laster modeller på hver side, men kan drepe og inneholde krasj, mens in-process RapidOCR DLL-en laster modeller én gang, stopper bare ved kooperative sjekkpunkter, deler adresseområdet og deployeres som en DLL med sine modeller og ordbok
Velg per arbeidsmengde: en side-om-gangen desktop-app har nytte av den varme DLL-en, mens en server som tar imot utruelige skann bør betale for prosessveggen
  • Oppstartskostnad: Tesseract- og Python RapidOCR-adapterne starter en prosess og laster modeller for hver side; DLL-en laster modeller én gang per motor
  • Å stoppe: en barne-prosess kan termineres outright, og Python-workeren kjører inne i en kill-on-close Job Object så hele prosesstreet følger med; DLL-en kan bare stoppe ved stage- og linjegrenser
  • Feilinnkapsling: et krasj i tesseract.exe feiler én side; en access violation inne i DLL-en tar prosessen din ned
  • Deployering: prosessadaptere trenger et installert program eller et Python-miljø; DLL-en trenger seg selv, sine modeller og sin ordbok, matchet til applikasjonens bitthet
  • Minne: prosessadaptere slipper alt når barnet avslutter; en DLL-motor holder modellene sine residente til den siste grensesnittreferansen slippes

For en interaktiv desktop-applikasjon som OCR-er en side om gangen, vinner DLL-ens responsivitet som regel. For en server som tar imot utruelige skann døgnet rundt, er prosessgrensen verdt oppstartskostnaden sin

Bygge og deployere HotPDFRapidOCR.dll

HotPDFRapidOCR.dll bygges fra C++-kildene i Native/RapidOCR med MSVC, C++17, en Windows SDK og CMake 3.20 eller senere, ved bruk av et hjelperskript som tar de native nettverkskildene, ONNX Runtime- og OpenCV-katalogene pluss en Win32- eller Win64-plattform. Bygg begge hvis du skiper begge, for en 32-bits Delphi-applikasjon kan ikke laste en 64-bits DLL, og de statiske bibliotekene du foreslår, må matche målarkitekturen så vel som CRT-moden

Modellsiden har sine egne kompatibilitetsgrenser. Detektoren er en DB text-detektor; gjenkjenneren godtar CTC-modeller i NCHW-layout med en fast inngangshøyde på 32 eller 48, og bruker 48 for modeller med dynamisk høyde. Den medfølgende statiske ONNX Runtime-en kan ikke laste modeller lagret med en nyere IR-versjon, så ferske PP-OCRv5-eksporter feiler initialiseringen med en diagnostikk i stedet for å laste delvis. Ordboken må være UTF-8 uten BOM, i nøyaktig modellens tegnrekkefølge, og dens klasseantall må matche modellutgangen; CRLF-linjeslutt godtas. Gjenkjenning er offline: DLL-en laster aldri ned en manglende modell

Hurtigreferanse

  • Fabrikk: HPDFCreateRapidOCRDLLOCREngine(LibraryPath, ModelDirectory[, Options]) i HPDFRapidOCRRecognition, tilgjengelig siden v2.774.0 i Delphi-, C++Builder- og Windows FPC/Lazarus-bygg
  • Hold den returnerte IHPDFOCREngine i live på tvers av sider og dokumenter; å slippe den ødelegger modellene og lossar DLL-en
  • Én motor kjører én gjenkjenning om gangen; opprett flere motorer for parallelle workere og budsjetter minne for hver modellkopi
  • Utgangen er én oppføring per tekstlinje med gjennomsnittlig tegnkonfidens, filtrert av THPDFOCRTextLayerOptions.MinimumConfidence
  • Kansellering og TimeoutMilliseconds er kooperative; en pågående ONNX-kjøring fullføres alltid
  • Match DLL-bittheten til applikasjonen og CRT-moden til de statiske ONNX Runtime- og OpenCV-bibliotekene til DLL-en
  • Velg en språkprofil per motor med THPDFRapidOCRDLLOptions.ForLanguage (v2.775.0); én motor detekterer ikke språk på egen hånd

Den native RapidOCR-adapteren, de prosessbaserte OCR-adapterne, siderendereren som mater dem, og den usynlige Unicode-tekstlag-skriveren skiper alle sammen i HotPDF, en native VCL PDF-komponent for Delphi og C++Builder. Trenger applikasjonen din for dokumentfangst eller arkivering søkbar utgang uten et Python-miljø på målmaskinen, gir HotPDF Delphi PDF-komponenten hele pipelinen med bare DLL-en og dens modeller igjen å deployere