Teknisk artikkel

HotPDF Tesseract DLL-OCR: å kalle C-API-en fra Delphi

HotPDF kjører Tesseract inne i Delphi-prosessen din gjennom HPDFCreateTesseractDLLOCREngine, en fabrikk lagt til i v2.772.0 som dynamisk laster en Tesseract 5-kompatibel DLL, driver dens C-API (TessBaseAPIInit2, TessBaseAPIRecognize, resultatiteratoren) og returnerer en IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer bruker den motoren til å legge et usynlig, søkbart Unicode-tekstlag til skannede PDF-sider

Samme gjenkjenner var allerede tilgjengelig gjennom den eksterne tesseract.exe-adapteren som skriver en BMP og parser TSV. Den stien virker, men hver side betaler for en prosessoppstart, en midlertidig bitmapfil og et tekstformat uten baselinjer og uten kontroll over sidesegmentering. Å kalle DLL-en fjerner alle tre. Den fjerner også prosessveggen, noe som betyr at en Pascal-binding sitter direkte oppå C-strukturer, C-boolske verdier og C-allokerte strenger. Det meste som er verdt å vite om denne adapteren, er der den bindingen kan gå galt i stillhet

Hvordan kjører du Tesseract in-process fra Delphi med HotPDF?

Å kjøre Tesseract in-process med HotPDF tar ett fabrikk-kall i HPDFTesseractRecognition-uniten og det samme ApplyLoadedOCRTextLayer-kallet enhver HotPDF OCR-motor bruker. Fabrikken validerer ivrig. DLL-filen og tessdata-katalogen må finnes, språkidentifikatoren kan bare inneholde ASCII-bokstaver, sifre, _ og +, hver modell i en kombinasjon som chi_sim+eng må ha en matchende .traineddata-fil, og alle 21 påkrevde eksporter må løses før motoren returneres. Konfigurasjonsfeil reiser EArgumentException; en DLL som feiler å laste, reiser EOSError med Windows-feilkoden og et hint om å sjekke arkitektur og avhengigheter

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // En Win64-applikasjon trenger en 64-bits DLL; avhengighets-DLL-er ligger ved siden av
  Engine := HPDFCreateTesseractDLLOCREngine('C:\OCR\Win64\libtesseract-5.dll',
    'C:\OCR\tessdata', 'chi_sim+eng');   // THPDFTesseractOptions.Default
  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,
      ' words accepted, ', Info.DroppedWordCount, ' dropped');
    Doc.SaveLoadedDocument(TargetFile);
  finally
    Doc.Free;
  end;
end;

THPDFTesseractOptions.Default setter PageSegMode til tpsAuto, EngineMode til temDefault, TimeoutMilliseconds til 60 000 og MaxPixels til 16 777 216. Pikselbudsjettet betyr mer enn det ser ut som. En US Letter-side ved standard 300 DPI rendrer til 2 550 × 3 300 piksler, omtrent 8,4 millioner, noe som passer. Samme side ved 600 DPI er 5 100 × 6 600, omtrent 33,7 millioner, og adapteren avviser den før Tesseract ser én piksel. Hev MaxPixels (taket er 67 108 864) eller la DPI-en være; hver side er også taket til 32 767 piksler

DLL-en lastes med LoadLibraryEx med søkeflaggene for DLL-ens egen mappe pluss standard sikre kataloger, så bildebibliotekene Tesseract er avhengig av, kan ligge ved siden av uten å røre PATH eller gjeldende katalog. HotPDF bundler eller laster ikke ned noen OCR-runtime eller modell; du foreslår begge

Hva endres sammenlignet med tesseract.exe-adapteren?

DLL-adapteren bytter prosessisolering mot rikere utgang og lavere per-side-overhead. Begge adapterne kobler seg inn i samme tekstlags-pipeline, så koordinatmapping, konfidensfiltrering og alt-eller-ingenting-committen er identiske; det som avviker, er hvordan piksler går inn og ord kommer ut

Aspekttesseract.exe-adapterTesseract DLL-adapter
FabrikkHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
Piksler innBMP-fil i en privat midlertidig katalog8-bits grayscale-buffer i minnet
Ord utOrdnivå TSV, taket til 64 MiBResultatiterator, UTF-8 per ord
BaselinjerIkke tilgjengeligSendt videre fra TessPageIteratorBaseline
Sidesegmentering og engine modeKun automatisk segmenteringTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
TidsavbruddHardt: barne-prosessen termineresKooperativt: Tesseract må legge merke til det
Krasj- og minneisoleringSeparat prosessIngen, deler adresseområdet ditt

Én kostnad forsvinner ikke. Hvert Recognize-kall oppretter sin egen API-instans og kaller TessBaseAPIInit2, så språkmodellene initialiseres per side i stedet for én gang per motor. Operativsystemets filbuffer demper gjenlastingen, men på store flerspråklige modellsett er det fortsatt den dominerende faste kostnaden per side, og det telles mot gjenkjenningsfristen. In-process RapidOCR DLL-motoren tar det motsatte designet og holder ONNX-modellene sine residente i motorens levetid; grenseproblemene (C ABI, lånte buffere, uavbrytelig native arbeid) er samme familie

Hvorfor kan ikke Delphi kopiere Tesseract monitor-strukten?

Delphi kan ikke trygt speile Tesseract fremdriftsmonitor fordi ETEXT_DESC inneholder versjonsavhengige interne felt, så en håndkopiert record plasserer cancel-callbacken og fristen på feil offsets på noen builds. Ingenting feiler høyt når det skjer. Tesseract leser rett og slett callback-pekeren din fra et felt som nå holder noe annet, eller ser aldri fristen i det hele tatt

HotPDF behandler derfor monitoren som en opak peker og rører den bare gjennom eksporterte funksjoner: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs og TessMonitorDelete. Binder du C-API-en selv til et annet formål, gjelder samme mønster. Skissen nedenfor er din egen bindingskode, ikke HotPDF-API, og speiler deklarasjonene HotPDF bruker internt

HotPDF Tesseract DLL monitor-håndtering: å kopiere den versjonsavhengige ETEXT_DESC-recorden plasserer cancel-callbacken og fristen på feil offsets og feiler i stillhet, mens HotPDF behandler monitoren som opak, driver TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc og TessMonitorSetDeadlineMSecs, og holder cdecl-callback-en unntaksfri
En opak peker pluss fem eksporter er hele kontrakten; callback-en forblir en én-bytes Boolean som bare leser et flagg og en klokke
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, aldri dereferert
  TTessMonitorDelete = procedure(Monitor: Pointer); cdecl;
  TTessMonitorSetCancelFunc = procedure(Monitor: Pointer; Func: TTessCancelFunc); cdecl;
  TTessMonitorSetCancelThis = procedure(Monitor, CancelThis: Pointer); cdecl;
  TTessMonitorSetDeadlineMSecs = procedure(Monitor: Pointer; MSecs: Integer); cdecl;
  TTessBaseAPIRecognize = function(Handle, Monitor: Pointer): Integer; cdecl;

  TOCRJob = record
    CancelRequested: Boolean;
    DeadlineTick: UInt64;
  end;
  POCRJob = ^TOCRJob;

function ShouldCancel(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
begin
  // Kjører på Tesseracts stakk: les flagg og klokken, reis aldri
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Bruk, med funksjonspekerne løst av GetProcAddress:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end;

To detaljer i den skissen er bevisste. Callback-en returnerer Boolean, som er én byte i både Delphi og Free Pascal, og matcher C bool i TessCancelFunc. Den fire-bytes Windows BOOL eller Delphi LongBool ser utbytbar ut og er det ikke: når den ene siden skriver én byte og den andre leser fire, er de øvre bytene i returregisteret det som tilfeldigvis lå der, og en false kan ankomme som true. Samme header kompliserer ting ytterligere, fordi funksjoner som TessPageIteratorBoundingBox returnerer en int, som HotPDF deklarerer som Integer. Les C-typen til hver returverdi i stedet for å anta én konvensjon for hele API-et

Den andre detaljen er at callback-en aldri reiser. Et Delphi-unntak som spoler tilbake gjennom Tesseracts C++-rammer er udefinert atferd, så HotPDFs callback leser bare cancellation-token og en monoton GetTickCount64-verdi. Adapteren gjør resultatet om til en kansellerings- eller tidsavbruddsdiagnostikk etter at TessBaseAPIRecognize returnerer, og den utfører den sjekken uansett den native returkoden

Hvilke native pekere eier Delphi-siden?

HotPDF Tesseract DLL-adapteren eier tre native objekter per forespørsel, API-instansen, monitoren og resultatiteratoren, og låner alt annet. Hvert Recognize-kall oppretter sitt eget sett og slipper det i en finally-blokk: TessResultIteratorDelete, så TessMonitorDelete, så TessBaseAPIDelete. Å slippe motor-grensesnittet lossar biblioteket

HotPDF Tesseract DLL objekteierskap per Recognize-kall: resultatiteratoren, monitoren og API-instansen eies og frigjøres i den rekkefølgen inne i finally, sideiteratoren fra TessResultIteratorGetPageIterator er et lånt view som aldri må frigjøres, og GetUTF8Text-strenger kopieres og returneres via TessDeleteText
Tre objekter eid, alt annet lånt: frigjør i fast rekkefølge, aldri dobbeltfrigi sideiteratoren, og bland aldri allokatorer
  • TessResultIteratorGetPageIterator returnerer et lånt view inn i resultatiteratoren, ikke et nytt objekt. HotPDF bruker det til TessPageIteratorBoundingBox og TessPageIteratorBaseline og frigjør det aldri; å slette det separat ville frigjort samme minne to ganger
  • TessResultIteratorGetUTF8Text returnerer en streng allokert av DLL-ens egen runtime. HotPDF kopierer den og gir den tilbake gjennom TessDeleteText i en finally-blokk; Pascal FreeMem ville sluppet den på feil heap
  • Ordtekst dekodes med streng UTF-8-validering og lengdesjekkes før konvertering. Ord med kontrolltegn, misdannet UTF-8, bokser utenfor bildet, inverterte rektangler eller konfidens utenfor 0–100 feiler forespørselen i stedet for å bli lappet i stillhet
  • Total tekst per forespørsel er taket til 1 048 576 UTF-16 kodeenheter, og ordantallet må passe i forespørselsbudsjettet gitt videre fra ApplyLoadedOCRTextLayer

Konfidens ankommer som 0–100 og skaleres til 0–1, så THPDFOCRTextLayerOptions.MinimumConfidence betyr det samme for enhver motor. Når Tesseract rapporterer en baseline, sendes begge endepunktene videre; ellers faller tekstlags-pipelinen tilbake på sitt geometriske anslag, nøyaktig som den gjør for TSV-input

Hvorfor validere en enum før den når DLL-en?

HotPDF kopierer den rå ordinalen til PageSegMode og EngineMode inn i en Integer før områdekontroll, for en kompilator kan anta at en enum-variabel alltid holder en deklarert verdi og folde Ord(X) > Ord(High(T)) til en konstant false. Ordinalene er ikke pynt: THPDFTesseractPageSegMode følger Tesseracts sidesegmenteringsnummerering fra 0 til 13, THPDFTesseractEngineMode følger engine mode-nummereringen fra 0 til 3, og begge går til DLL-en som rene heltall. En alternativrecord bygget med FillChar, fylt fra en strøm, eller sendt fra C++Builder med et castet heltall, kan bære en byte som 200. Å validere den kopierte ordinalen gjør det om til en EArgumentException ved fabrikk-tid i stedet for en udefinert modus inne i native kode. Fabrikken avviser også tpsOSDOnly og tpsAutoOnly, som ikke produserer ord, og krever osd.traineddata for tpsAutoOSD og tpsSparseTextOSD

Hva garanterer gjenkjennings-tidsavbruddet egentlig?

Tesseract DLL-tidsavbruddet er kooperativt: HotPDF kan stoppe sitt eget arbeid og be Tesseract om å stoppe, men det kan ikke tvinge native kode til å returnere. Klokken starter når Recognize begynner, så bitmap-konvertering og modellinitialisering forbruker samme budsjett som gjenkjenning. HotPDF sjekker forløpt tid og cancellation-token under grayscale-konvertering og mellom ord mens resultatene itereres, og sender gjenværende millisekunder til TessMonitorSetDeadlineMSecs før det kaller TessBaseAPIRecognize

Gapet er inne i det native kallet. Tesseracts monitor konsulteres under ordgjenkjenning, ikke under TessBaseAPIInit2 eller sidelayout-analyse, så en treg modellasting eller et patologisk layout kan løpe forbi fristen før tidsavbruddet rapporteres. Piksel- og utgangsbudsjetter takserer heller ikke det native bibliotekets eget minnebruk. Trenger du en worker du kan drepe, bruk prosessadapteren; det er den ærlige avveiningen, ikke en manglende funksjon

HotPDF Tesseract DLL kooperativ tidsavbrudd-anatomi: klokken starter når Recognize begynner og dekker grayscale-konvertering, TessBaseAPIInit2 og layout-analyse, men monitoren konsulteres bare under ordgjenkjenning, så modellastinger og layout kan løpe forbi før HotPDF rapporterer otlsEngineError eller otlsCancelled
En frist her er en forespørsel, ikke en garanti: init og layout-analyse kan kjøre lenge, og en worker du virkelig kan drepe, trenger prosessadapteren

Sidesegmentering er der DLL-adapteren tjener husleien sin på vanskelig input. Skjemaer, etiketter og skannede tabeller med spredte felt gjenkjennes ofte bedre med tpsSparseText enn med automatisk segmentering, som prøver å sette sammen kolonner og avsnitt som ikke finnes

procedure OCRFormPages(Doc: THotPDF; const Pages: array of Integer);
var
  Engine: IHPDFOCREngine;
  TessOptions: THPDFTesseractOptions;
  LayerOptions: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  TessOptions := THPDFTesseractOptions.Default;
  TessOptions.PageSegMode := tpsSparseText;  // spredte felt, ingen kolonnesamling
  TessOptions.EngineMode := temLSTMOnly;     // trenger LSTM-modeller i tessdata
  TessOptions.TimeoutMilliseconds := 20000;  // inkluderer modellinitialisering
  Engine := HPDFCreateTesseractDLLOCREngine('C:\OCR\Win64\libtesseract-5.dll',
    'C:\OCR\tessdata', 'eng+deu', TessOptions);

  LayerOptions := THPDFOCRTextLayerOptions.Default;
  LayerOptions.MinimumConfidence := 0.6;
  if not Doc.ApplyLoadedOCRTextLayer(Pages, Engine, LayerOptions, Info) then
    case Info.Status of
      otlsCancelled:
        Writeln('OCR cancelled, document unchanged');
      otlsEngineError:
        Writeln('Tesseract failed or timed out: ', string(Info.Diagnostic));
    else
      Writeln(string(Info.Diagnostic));
    end;
end;

Et tidsavbrudd viser seg som otlsEngineError med diagnostikken Tesseract DLL OCR timed out, mens en kansellert token viser seg som otlsCancelled. I begge tilfeller har ApplyLoadedOCRTextLayer gjenkjent hver valgte side før den starter commit-transaksjonen, så en feil på side 40 av 50 lar det lastede dokumentet være nøyaktig som det var. Merk at tpsSingleLine, tpsSingleBlock og tpsSparseText endrer bare segmentering; ingen av dem retter opp en skjev skann

Free Pascal og Lazarus: foreldede piksler og tapt kinesisk

Begge Tesseract-fabrikkene virker i Windows Free Pascal- og Lazarus Win32- og Win64-bygg siden v2.772.1, etter to FPC-spesifikke fikser. Bygg Lazarus-pakken på nytt for målarkitekturen først; den generelle porteringen er dekket i HotPDF på Free Pascal og Lazarus Win64

Den første fiksen gjelder piksler. En LCL TBitmap skrevet gjennom scanlines kan oppdatere sitt rå bilde uten å oppfriske Windows bitmap-håndtaket, så GetDIBits på det håndtaket returnerer de gamle pikslene. Symptomet var gåtefullt: tekst tegnet direkte på en bitmap ble gjenkjent, mens en side rendret av HotPDFs PDF-renderer ga en tom ordliste. På FPC leser adapteren nå et formatbevisst øyebliksbilde gjennom CreateIntfImage, som respekterer det rå bildets pikselformat og rekkefølge. Delphi-bygget beholder GetDIBits-stien på en privat 24-bits kopi. Ingen av buildene endrer kallerens bitmap

Den andre fiksen tilhører tesseract.exe-adapteren. FPCs TStringList lagrer ANSI-strenger, så å tilordne dekodet UTF-8 TSV-tekst til Lines.Text slo i stillhet bort hvert kinesiske eller supplementary-plane-tegn systemets ANSI-kodepage ikke kunne representere. FPC-stien beholder nå TSV-en som UTF-8-byter, stripper BOM-en på byte-nivå og dekoder hvert ord til UnicodeString individuelt. DLL-adapteren hadde aldri dette problemet, fordi den dekoder hvert ord direkte fra iteratoren

Hurtigreferanse

  • Fabrikk: HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]) i HPDFTesseractRecognition, lagt til i v2.772.0, FPC-støtte i v2.772.1
  • Standardverdier: tpsAuto, temDefault, 60 000 ms, 16 777 216 piksler; tidsavbruddsområde 1–3 600 000 ms, pikseltak 67 108 864
  • Match DLL-bittheten til applikasjonen og plasser avhengighets-DLL-er ved siden av Tesseract DLL-en
  • Behandle monitoren som opak; kopier aldri ETEXT_DESC inn i en Pascal-record
  • Deklarer cancel-callback-en cdecl med ett-bytes Boolean-resultat, og la aldri et unntak rømme fra den
  • Frigjør iteratortekst med TessDeleteText; frigjør aldri sideiteratoren hentet fra resultatiteratoren
  • Forvent at fristen er kooperativ: modellinitialisering og layout-analyse kan løpe forbi den
  • Bruk tesseract.exe-adapteren når du trenger hard terminering eller krasjisolasjon

Tesseract DLL-adapteren, prosessadapterne og den innebygde OCR-motoren skiper alle med HotPDF Delphi PDF-komponenten for Delphi, C++Builder og Free Pascal; se HotPDF produktsiden for utgaver og nedlastinger