Odborný článok

Tesseract DLL OCR v HotPDF: volanie C API z Delphi

HotPDF púšťa Tesseract vo vnútri vášho Delphi procesu cez HPDFCreateTesseractDLLOCREngine, factory pridanú vo v2.772.0, ktorá dynamicky načíta DLL kompatibilné s Tesseractom 5, ovláda jeho C API (TessBaseAPIInit2, TessBaseAPIRecognize, result iterator) a vráti IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer použije ten engine na pridanie neviditeľnej, prehľadávateľnej Unicode textovej vrstvy na naskenované PDF strany

Ten istý recognizer bol už dostupný cez externý adaptér tesseract.exe, ktorý zapisuje BMP a parsuje TSV. Tá cesta funguje, ale každá strana platí za štart procesu, dočasný bitmapový súbor a textový formát bez baseline a bez kontroly nad segmentáciou strany. Volanie DLL odstráni všetky tri. Odstráni aj múr procesu, čo znamená, že Pascal binding sedí priamo na C štruktúrach, C booleovských hodnotách a C-alokovaných reťazcoch. Väčšina z toho, čo sa o tomto adaptéri oplatí vedieť, je miesto, kde taký binding môže potichu pokaziť

Ako pustíte Tesseract in-process z Delphi s HotPDF?

Pustenie Tesseractu in-process s HotPDF je jedno factory volanie v jednotke HPDFTesseractRecognition a to isté volanie ApplyLoadedOCRTextLayer, ktoré používa každý OCR engine HotPDF. Factory validuje záhorlivé. Súbor DLL aj adresár tessdata musia existovať, identifikátor jazyka smie obsahovať len ASCII písmená, číslice, _ a +, každý model v kombinácii ako chi_sim+eng musí mať zodpovedajúci súbor .traineddata a všetkých 21 povinných exportov sa musí vyriešiť skôr, než sa engine vráti. Chyby konfigurácie vyhodia EArgumentException; DLL, ktorý sa nepodarí načítať, vyhodí EOSError s Windows chybovým kódom a tipom skontrolovať architektúru a závislosti

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Win64 aplikácia potrebuje 64-bitové DLL; závislostné DLL sedia vedľa
  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
    // Prázdny zoznam strán znamená každá strana; strany, ktoré už text majú, sa preskočia
    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 nastaví PageSegMode na tpsAuto, EngineMode na temDefault, TimeoutMilliseconds na 60 000 a MaxPixels na 16 777 216. Pixelový rozpočet má väčší význam, než vyzerá. Strana US Letter pri predvolených 300 DPI sa renderuje na 2 550 × 3 300 pixelov, zhruba 8,4 milióna, čo sa zmestí. Tá istá strana pri 600 DPI je 5 100 × 6 600, zhruba 33,7 milióna, a adaptér ju odmietne skôr, než Tesseract uvidí pixel. Zvýšte MaxPixels (strop je 67 108 864) alebo nechajte DPI tam, kde je; každá strana je navyše kapovaná na 32 767 pixelov

DLL sa načítava cez LoadLibraryEx s vyhľadávacími príznakmi pre vlastný priečinok DLL plus predvolené bezpečné adresáre, takže obrazové knižnice, na ktorých Tesseract závisí, môžu bývať vedľa neho bez siahania na PATH alebo aktuálny adresár. HotPDF nebali ani nestahuje žiadny OCR runtime ani modely; oboje provisionsujete sami

Čo sa mení v porovnaní s adaptérom tesseract.exe?

Adaptér DLL mení izoláciu procesu za bohatší výstup a nižšiu réžiu na stranu. Oba adaptéry sa zapájajú do tej istej text-layer pipeline, takže mapovanie súradníc, filtrovanie podľa confidence aj all-or-nothing commit sú identické; líši sa to, ako pixely idú dnu a slová von

HľadiskoAdaptér tesseract.exeAdaptér Tesseract DLL
FactoryHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
Pixely dnuSúbor BMP v súkromnom dočasnom adresári8-bitový grayscale buffer v pamäti
Slová vonWord-level TSV, kapovaný na 64 MiBResult iterator, UTF-8 na slovo
BaselinyNedostupnéPrepásnuté z TessPageIteratorBaseline
Segmentácia strany a režim enginuLen automatická segmentáciaTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
TimeoutTvrdý: child proces sa ukončíKooperatívny: Tesseract si to musí všimnúť
Izolácia pádov a pamätiSamostatný procesŽiadna, zdieľa váš adresný priestor

Jedna cena nezmizne. Každé volanie Recognize vytvára vlastnú API inštanciu a volá TessBaseAPIInit2, takže jazykové modely sa inicializujú na stranu, nie raz na engine. Súborová cache operačného systému zmierni opätovné načítanie, ale na veľkých viacjazyčných sadách modelov je to aj tak dominantná fixná cena na stranu a počíta sa do termínu rozpoznávania. In-process engine RapidOCR DLL volí opačný dizajn a drží svoje ONNX modely rezidentné po celý život enginu; hraničné problémy (C ABI, požičané buffre, neprerušiteľná natívna práca) sú tá istá rodina

Prečo nemôže Delphi skopírovať štruktúru monitora Tesseractu?

Delphi nedokáže bezpečne zrkadliť progress monitor Tesseractu, lebo ETEXT_DESC obsahuje verziou podmienené interné polia, takže ručne okopírovaný záznam položí cancel callback aj termín na nesprávne ofsety na niektorých zostaveniach. Nič z toho nespadne hlasno. Tesseract jednoducho číta váš callback pointer z poľa, v ktorom teraz sedí niečo iné, alebo termín nikdy nevidí vôbec

HotPDF preto berie monitor ako opaque pointer a siaha naň len cez exportované funkcie: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs a TessMonitorDelete. Ak si C API bindujete sami na iný účel, platí ten istý vzor. Náčrt nižšie je váš vlastný binding kód, nie API HotPDF, a zrkadlí deklarácie, ktoré HotPDF používa interne

HotPDF obsluha monitora Tesseract DLL: skopírovanie verziou podmieneného záznamu ETEXT_DESC položí cancel callback aj termín na nesprávne ofsety a zlyhá potichu, zatiaľ čo HotPDF berie monitor ako opaque, ovláda TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc a TessMonitorSetDeadlineMSecs a drží cdecl callback bez výnimiek
opaque pointer plus päť exportov je celá zmluva; callback ostanú jednobajtový Boolean, ktorý číta len príznak a hodiny
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, nikdy nedereferencované
  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
  // Beží na stacku Tesseractu: čítajte príznaky a hodiny, nikdy nevyhadzujte
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Použitie, pričom ukazovatele funkcií sa preložia cez GetProcAddress:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end.

Dva detaily v tom náčrte sú zámerné. Callback vracia Boolean, čo je jeden bajt v Delphi aj Free Pascal, sediaci s C bool v TessCancelFunc. Štvorbajtové Windows BOOL alebo Delphi LongBool vyzerá zameniteľne a nie je: keď jedna strana zapíše jediný bajt a druhá číta štyri, horné bajty návratového registru sú čokoľvek, čo tam zostalo, a false sa dokáže dostať ako true. Tá istá hlavička veci komplikuje ďalej, lebo funkcie ako TessPageIteratorBoundingBox vracajú int, ktoré HotPDF deklaruje ako Integer. Čítajte C typ každej návratovej hodnoty namiesto predpokladu jednej konvencie pre celé API

Druhý detail je, že callback nikdy nevyhadzuje. Delphi výnimka rozvíjajúca sa cez C++ rámy Tesseractu je undefined behavior, takže callback HotPDF číta len cancellation token a monotónnu hodnotu GetTickCount64. Adaptér zmení výsledok na diagnostiku zrušenia alebo termínu po tom, čo sa TessBaseAPIRecognize vráti, a tú kontrolu vykoná bez ohľadu na natívny návratový kód

Ktoré natívne pointery vlastní strana Delphi?

Adaptér Tesseract DLL v HotPDF vlastní tri natívne objekty na požiadavku, inštanciu API, monitor aj result iterator a všetko ostatné si požičiava. Každé volanie Recognize vytvára vlastnú sadu a uvoľní ju v bloku finally: TessResultIteratorDelete, potom TessMonitorDelete, potom TessBaseAPIDelete. Uvoľnenie rozhrania enginu unloaduje knižnicu

HotPDF vlastníctvo objektov Tesseract DLL na volanie Recognize: result iterator, monitor aj inštancia API sú vlastnené a uvoľňované v tom poradí vo vnútri finally, page iterator z TessResultIteratorGetPageIterator je požičaný pohľad, ktorý sa nesmie nikdy uvoľniť, a reťazce GetUTF8Text sa skopírujú a vrátia cez TessDeleteText
tri objekty vlastnené, všetko ostatné požičané: uvoľňujte v fixnom poradí, nikdy nerobte double-free page iteratoru a nikdy nemiešajte alokátory
  • TessResultIteratorGetPageIterator vracia požičaný pohľad do result iteratoru, nie nový objekt. HotPDF ho používa pre TessPageIteratorBoundingBox a TessPageIteratorBaseline a nikdy ho neuvolňuje; samostatné zmazanie by uvoľnilo tú istú pamäť dvakrát
  • TessResultIteratorGetUTF8Text vracia reťazec alokovaný vlastným runtime DLL. HotPDF ho skopíruje a podá späť cez TessDeleteText v bloku finally; Pascal FreeMem by ho uvoľnil na nesprávnom heap
  • Text slova sa dekóduje s prísnou UTF-8 validáciou a kontrolou dĺžky skôr, než sa prevedie. Slová s riadiacimi znakmi, deformovaným UTF-8, boxmi mimo obrazu, prevrátenými obdĺžnikmi alebo confidence mimo 0–100 prepasujú požiadavku namiesto tichej opravy
  • Celý text na požiadavku je kapovaný na 1 048 576 UTF-16 code unitov a počet slov sa musí zmestiť do rozpočtu požiadavky odovzdaného z ApplyLoadedOCRTextLayer

Confidence prichádza ako 0–100 a škáluje sa na 0–1, takže THPDFOCRTextLayerOptions.MinimumConfidence znamená to isté pre každý engine. Keď Tesseract hlási baseline, prepasú sa oba koncové body; inak text-layer pipeline prepadne na svoj geometrický odhad, presne ako pri TSV vstupe

Prečo validovať enum skôr, než sa dostane do DLL?

HotPDF kopíruje holý ordinál PageSegMode a EngineMode do Integer skôr, než kontroluje rozsah, lebo kompilátor môže predpokladať, že enum premenná vždy drží deklarovanú hodnotu, a zloží Ord(X) > Ord(High(T)) na konštantné false. Ordinály nie sú dekorácia: THPDFTesseractPageSegMode nasleduje číslovanie segmentácie strán Tesseractu od 0 do 13, THPDFTesseractEngineMode nasleduje číslovanie režimov enginu od 0 do 3 a obe idú do DLL ako holé celé čísla. Options záznam postavený cez FillChar, naplnený zo streamu alebo podaný z C++Builder s pretypovaným celým číslom dokáže niesť bajt ako 200. Validácia skopírovaného ordinálu zmení to na EArgumentException vo chvíli factory namiesto nedefinovaného režimu vo vnútri natívneho kódu. Factory navyše odmieta tpsOSDOnly a tpsAutoOnly, ktoré nevyrobia žiadne slová, a vyžaduje osd.traineddata pre tpsAutoOSD a tpsSparseTextOSD

Čo termín rozpoznávania naozaj garantuje?

Termín Tesseract DLL je kooperatívny: HotPDF dokáže zastaviť vlastnú prácu a poprosiť Tesseract, aby zastavil, ale nedokáže prinútiť natívny kód sa vrátiť. Hodiny bežia od začiatku Recognize, takže konverzia bitmapy aj inicializácia modelov konzumujú ten istý rozpočet ako rozpoznávanie. HotPDF kontroluje uplynulý čas a cancellation token počas grayscale konverzie aj medzi slovami pri iterovaní výsledkov a podá zostávajúce milisekundy do TessMonitorSetDeadlineMSecs skôr, než zavolá TessBaseAPIRecognize

Medzera je vo vnútri natívneho volania. Monitor Tesseractu sa konzultuje počas rozpoznávania slov, nie počas TessBaseAPIInit2 ani analýzy rozloženia strany, takže pomalé načítanie modelu alebo patologické rozloženie dokáže bežať za termínom skôr, než sa timeout nahlási. Pixelové aj výstupné rozpočty tiež nekapujú vlastnú spotrebu pamäte natívnej knižnice. Ak potrebujete workera, ktorého dokážete zabiť, použite procesový adaptér; to je poctivý kompromis, nie chýbajúca funkcia

HotPDF anatómia kooperatívneho termínu Tesseract DLL: hodiny bežia od začiatku Recognize a pokrývajú grayscale konverziu, TessBaseAPIInit2 aj analýzu rozloženia, ale monitor sa konzultuje len počas rozpoznávania slov, takže načítania modelov aj rozloženie môžu prebehnúť termín skôr, než HotPDF nahlási otlsEngineError alebo otlsCancelled
termín tu je žiadosť, nie garancia: init aj analýza rozloženia môžu bežať dlho a workera, ktorého dokážete naozaj zabiť, potrebuje procesový adaptér

Segmentácia strany je miesto, kde adaptér DLL zarobí na ťažkom vstupe. Formuláre, nálepky a naskenované tabuľky s roztrúsenými poľami sa často rozpoznajú lepšie s tpsSparseText než s automatickou segmentáciou, ktorá sa snaží poskladať stĺpce a odstavce, ktoré tam nie sú

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;  // roztrúsené polia, žiadne poskladanie stĺpcov
  TessOptions.EngineMode := temLSTMOnly;     // potrebuje LSTM modely v tessdata
  TessOptions.TimeoutMilliseconds := 20000;  // vrátane inicializácie modelov
  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;

Termín sa ukáže ako otlsEngineError s diagnostikou Tesseract DLL OCR timed out, zatiaľ čo zrušený token sa ukáže ako otlsCancelled. V oboch prípadoch ApplyLoadedOCRTextLayer rozpoznal každú vybranú stranu skôr, než začne transakciu commitu, takže zlyhanie na strane 40 z 50 nechá načítaný dokument presne taký, aký bol. Všimnite si, že tpsSingleLine, tpsSingleBlock a tpsSparseText menia len segmentáciu; ani jeden nenarovná naklonený sken

Free Pascal a Lazarus: zastaralé pixely a stratená čínština

Obe Tesseract factory fungujú vo Windows Free Pascal a Lazarus zostaveniach Win32 aj Win64 od v2.772.1, po dvoch opravách špecifických pre FPC. Najprv zostavte Lazarus balík pre cieľovú architektúru; všeobecný port pokrýva HotPDF na Free Pascal a Lazarus Win64

Prvá oprava sa týka pixelov. LCL TBitmap zapisovaný cez scanlines dokáže aktualizovať holý obraz bez obnovenia Windows bitmap handle, takže GetDIBits na tom handle vráti staré pixely. Príznak bol mätúci: text nakreslený priamo na bitmapu sa rozpoznal, zatiaľ čo strana vyrenderovaná PDF rendererom HotPDF vyprodukovať prázdny zoznam slov. Vo FPC teraz adaptér číta formát-aware snapshot cez CreateIntfImage, ktorý rešpektuje pixelový formát a poradie riadkov holého obrazu. Zostavenie Delphi drží cestu GetDIBits na súkromnej 24-bitovej kópii. Ani jedno zostavenie nemodifikuje bitmapu volajúceho

Druhá oprava patrí adaptéru tesseract.exe. TStringList vo FPC ukladá ANSI reťazce, takže priradenie dekódovaného UTF-8 TSV textu do Lines.Text potichu zahodilo každý čínsky alebo doplnkovorojový znak, ktorý systémová ANSI kódová strana nedokázala zastúpiť. Cesta FPC teraz drží TSV ako UTF-8 bajty, odstrihne BOM na úrovni bajtov a dekóduje každé slovo do UnicodeString osobitne. Adaptér DLL tento problém nikdy nemal, lebo dekóduje každé slovo priamo z iteratoru

Rýchla referencia

  • Factory: HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]) v HPDFTesseractRecognition, pridaná vo v2.772.0, podpora FPC vo v2.772.1
  • Predvolené: tpsAuto, temDefault, 60 000 ms, 16 777 216 pixelov; rozsah termínu 1–3 600 000 ms, pixelový strop 67 108 864
  • Slaďte bitness DLL s aplikáciou a položte závislostné DLL vedľa DLL Tesseractu
  • Berte monitor ako opaque; nikdy nekopírujte ETEXT_DESC do Pascal záznamu
  • Deklarujte cancel callback cdecl s jednobajtovým výsledkom Boolean a nikdy nenechajte cezeň utiecť výnimku
  • Uvoľňujte text iteratora cez TessDeleteText; nikdy neuvolňujte page iterator získaný z result iteratoru
  • Očakávajte, že termín je kooperatívny: inicializácia modelov aj analýza rozloženia ho dokážu prebehnúť
  • Používajte adaptér tesseract.exe, keď potrebujete tvrdé ukončenie alebo izoláciu pádov

Adaptér Tesseract DLL, procesové adaptéry aj vstavaný OCR engine dodáva HotPDF Delphi PDF component pre Delphi, C++Builder a Free Pascal; pozrite produktovú stránku HotPDF pre edície a stiahnutia