Műszaki cikk

HotPDF Tesseract DLL OCR: a C API hívása Delphiből

A HotPDF a Tesseractot a Delphi folyamaton belül futtatja a HPDFCreateTesseractDLLOCREngine-en át, egy v2.772.0-ban hozzáadott factoryn, ami dinamikusan betölt egy Tesseract 5 kompatibilis DLL-t, hajtja a C API-ját (TessBaseAPIInit2, TessBaseAPIRecognize, az eredményiterátor) és visszaad egy IHPDFOCREngine-t. A THotPDF.ApplyLoadedOCRTextLayer azt az enginet használja, hogy láthatatlan, kereshető Unicode szövegréteget adjon a szkenelt PDF oldalakhoz

Ugyanaz a felismerő már elérhető volt a külső tesseract.exe adaptéren át, ami BMP-t ír és TSV-t parzol. Az az út működik, de minden oldal megfizeti a folyamatindítást, egy ideiglenes bitmap fájlt és egy olyan szövegformátumot, amiben nincsenek alapvonalak és nincs kontroll az oldal-szegmentáció felett. A DLL hívása mindhármat eltünteti. Közben eltünteti a process falat is, ami azt jelenti, hogy egy Pascal binding közvetlenül a C struktúrák, C booleangek és C-allokált stringek tetején ül. Azt, ami erről az adaptérről megérné a tudást, leginkább az adja, hol mehet csendben félre ez a binding

Hogyan futtasd in-process a Tesseractot Delphiből a HotPDF-fel?

A Tesseract in-process futtatása a HotPDF-fel egy factory hívást tesz ki a HPDFTesseractRecognition unitban, meg ugyanazt az ApplyLoadedOCRTextLayer hívást, amit minden HotPDF OCR engine használ. A factory mohón validál. A DLL fájlnak és a tessdata könyvtárnak léteznie kell, a nyelvazonosító csak ASCII betűket, számokat, _-t és +-ot tartalmazhat, minden modellnek egy chi_sim+eng szerű kombinációban kell rendelkeznie egyező .traineddata fájllal, és mind a 21 szükséges exportnak fel kell oldódnia, mielőtt az engine visszaadódna. A konfigurációs hibák EArgumentException-t dobnak; a be nem tölthető DLL EOSError-t dob a Windows hibakóddal és rámutatással, hogy nézd meg az architektúrát meg a függőségeket

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Egy Win64 alkalmazásnak 64 bites DLL kell; a függőség DLL-ek mellé kerülnek
  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
    // Az üres oldallista minden oldalt jelent; a már szöveggel bíró oldalak kimaradnak
    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;

A THPDFTesseractOptions.Default a PageSegMode-ot tpsAuto-ra, az EngineMode-ot temDefault-ra, a TimeoutMilliseconds-t 60 000-re és a MaxPixels-t 16 777 216-ra állítja. A pixelbudget fontosabb, mint amennyire látszik. Egy US Letter oldal az alapértelmezett 300 DPI-n 2 550 × 3 300 pixelre renderelődik, nagyjából 8,4 millió, ami belefér. Ugyanaz az oldal 600 DPI-n 5 100 × 6 600, nagyjából 33,7 millió, és az adaptér még azelőtt elutasítja, hogy a Tesseract egyetlen pixelt látna. Emeld a MaxPixels-t (a plafon 67 108 864), vagy hagyd a DPI-t, ahol van; mindkét oldal 32 767 pixelnél is levágódik

A DLL LoadLibraryEx-sel töltődik, a DLL saját mappájára meg az alapértelmezett biztonságos könyvtárakra vonatkozó keresőflaggekkel, így a Tesseract függőségei, a képlibraryk, a mellett élhetnek PATH vagy aktuális könyvtár piszkálása nélkül. A HotPDF nem csomagol és nem tölt le semmilyen OCR runtime-ot vagy modellt; mindkettőt te szeded be

Mi változik a tesseract.exe adaptérhoz képest?

A DLL adaptér process izolációt ad oda gazdagabb kimenetért és kisebb oldalankénti költségért. Mindkét adaptér ugyanabba a szövegréteg-pipeline-ba dugózik, így a koordinátaváltás, a konfidenciaszűrés és az all-or-nothing elkötelezés azonos; az különbözik, hogyan mennek be a pixelek és jönnek ki a szavak

Szemponttesseract.exe adaptérTesseract DLL adaptér
FactoryHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
Pixelek beBMP fájl privát ideiglenes könyvtárban8 bites grayscale puffer a memóriában
Szavak kiSzószintű TSV, 64 MiB-nél levágvaEredményiterátor, UTF-8 szavanként
AlapvonalakNem elérhetőÁtengedve a TessPageIteratorBaseline-ből
Oldalszegmentáció és engine módCsak automatikus szegmentációTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
TimeoutKemény: a gyerekfolyamat lelőveKooperatív: a Tesseractnak észre kell vennie
Crash- és memóriaizolációKülön folyamatNincs, megosztja a címtér

Egy költség nem tűnik el. Minden Recognize hívás saját API példányt hoz létre, és meghívja a TessBaseAPIInit2-t, így a nyelvi modellek oldalanként inicializálódnak, nem engineenként egyszer. Az operációs rendszer fájlgyorsítótára enyhíti az újratöltést, de nagy, többnyelvű modellkészleteknél ez marad az oldalankénti domináns fix költség, és a felismerési határidőbe beleszámít. A in-process RapidOCR DLL engine az ellenkező tervezést választja, és az ONNX modelljeit az engine élettartamára bent tartja; a határproblémák (C ABI, kölcsönzött pufferek, megszakíthatatlan natív munka) ugyanaz a család

Miért nem másolhatja le a Delphi a Tesseract monitor struktúráját?

A Delphi azért nem tudja biztonságosan tükrözni a Tesseract haladásfigyelőjét, mert az ETEXT_DESC verziófüggő belső mezőket tartalmaz, így egy kézzel átmásolt rekord néhány builden rossz offszetekre tenné a cancel callbacket és a határidőt. Semmi nem bukik el hangosan, amikor ez megtörténik. A Tesseract egyszerűen a callback pointeredet olvassa egy mezőből, amiben most már valami más van, vagy sosem látja a határidőt

Ezért kezeli a HotPDF a monitort opak pointerként, és csak exportált függvényeken át érinti: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs és TessMonitorDelete. Ha saját célra te magad bindolod a C API-t, ugyanez a minta érvényes. Az alábbi vázlat a saját binding kódod, nem HotPDF API, és tükrözi azokat a deklarációkat, amiket a HotPDF belül használ

HotPDF Tesseract DLL monitor kezelés: a verziófüggő ETEXT_DESC rekord lemásolása rossz offszetekre teszi a cancel callbacket és a határidőt, és csendben elbukik, míg a HotPDF a monitort opakként kezeli, hajtja a TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc és TessMonitorSetDeadlineMSecs hívásokat, és a cdecl callbacket kivételmentesen tartja
egy opak pointer meg öt export — ez az egész szerződés; a callback egébájtos Boolean marad, ami csak egy flaget és egy órát olvas
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, soha nem dereferálva
  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
  // Tesseract stackjén fut: flag-eket és órát olvas, soha nem dob
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Használat, a függvénypointerek GetProcAddress-szal feloldva:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end;

A vázlatnak két részlete szándékos. A callback Boolean-t ad vissza, ami Delphiben és Free Pascalban egyaránt egy bájt, egyezve a TessCancelFunc C bool-jával. A négybájtos Windows BOOL vagy Delphi LongBool felcserélhetőnek néz ki, és nem az: amikor az egyik oldal egyetlen bájtot ír, a másik négyet olvas, a visszatérési regiszter felső bájtjai azok maradnak, amik ottmaradtak, és egy false akár true-ként is beérkezhet. Ugyanez a header bonyolítja tovább a dolgot, mert olyan függvények, mint a TessPageIteratorBoundingBox, int-et adnak vissza, amit a HotPDF Integer-ként deklarál. Olvasd ki minden visszatérési érték C típusát, ahelyett hogy egy konvenciót tételeznél az egész API-ra

A második részlet, hogy a callback soha nem dob. Egy Delphi kivétel, ami átkutykol a Tesseract C++ frame-jein, definiálatlan viselkedés, így a HotPDF callbackje csak a cancellation tokent és egy monoton GetTickCount64 értéket olvas. Az adaptér az eredményt cancellation vagy timeout diagnózissá alakítja, miután a TessBaseAPIRecognize visszatért, és ezt az ellenőrzést a natív visszatérési kódtól függetlenül elvégzi

Mely natív pointereket birtokolja a Delphi oldal?

A HotPDF Tesseract DLL adaptér kérésenként három natív objektumot birtokol, az API példányt, a monitort és az eredményiterátort, és mindent más kölcsönvesz. Minden Recognize hívás saját készletet hoz létre, és finally blokkban engedi el: TessResultIteratorDelete, aztán TessMonitorDelete, aztán TessBaseAPIDelete. Az engine interfész elengedése kipakolja a libraryt

HotPDF Tesseract DLL objektumtulajdonlás Recognize hívásonként: az eredményiterátor, a monitor és az API példány birtokolt, és ebben a sorrendben szabadulnak fel a finally-ban, a TessResultIteratorGetPageIteratorből kapott oldaliterátor kölcsönzött nézet, amit soha nem szabad felszabadítani, a GetUTF8Text stringek pedig lemásolódnak és TessDeleteTexttel adódnak vissza
három objektum birtokolt, minden más kölcsönzött: szabadítsd fel rögzített sorrendben, soha ne free-doublezd az oldaliterátort, és soha ne keverd az allokátorokat
  • A TessResultIteratorGetPageIterator a result iteratorba vezető kölcsönzött nézetet ad vissza, nem új objektumot. A HotPDF a TessPageIteratorBoundingBox-hoz és a TessPageIteratorBaseline-hez használja, és soha nem szabadítja fel; külön törlése ugyanazt a memóriát szabadítaná fel kétszer
  • A TessResultIteratorGetUTF8Text a DLL saját runtime-ja által allokált stringet ad vissza. A HotPDF lemásolja, és TessDeleteText-tel adja vissza finally blokkban; a Pascal FreeMem-je rossz heapen szabadítaná fel
  • A szószöveg szigorú UTF-8 validációval dekódolódik, és hossz-ellenőrzést kap konverzió előtt. Kontrollkarakteres, rosszul formált UTF-8-as, a képen kívüli boxos, invertált téglalapú vagy 0-100-on kívüli konfidenciájú szavak a kérést buktatják, csendes javítás helyett
  • A kérésenkénti teljes szöveg 1 048 576 UTF-16 kódegységnél van levágva, és a szószámnak be kell férnie abba a request budgetbe, amit az ApplyLoadedOCRTextLayer ad le

A konfidencia 0-100-ként érkezik, és 0-1-re skálázódik, így a THPDFOCRTextLayerOptions.MinimumConfidence minden enginnél ugyanazt jelenti. Amikor a Tesseract alapvonalat jelent, mindkét végpont átengedésre kerül; egyébként a szövegréteg-pipeline a geometriai becslésére esik vissza, pontosan úgy, ahogy TSV bemenetnél teszi

Miért validálj enumot, mielőtt elérné a DLL-t?

A HotPDF a PageSegMode és EngineMode nyers ordinálisát Integer-be másolja, mielőtt tartományellenőrizné, mert a kompilátor feltételezheti, hogy egy enum változó mindig deklarált értéket tart, és az Ord(X) > Ord(High(T))-t konstans false-ra hajtja. Az ordinálisak nem díszek: a THPDFTesseractPageSegMode a Tesseract oldal-szegmentáció számozását követi 0-tól 13-ig, a THPDFTesseractEngineMode az engine mód számozását 0-tól 3-ig, és mindkettő sima egészként megy a DLL-be. Egy FillChar-ral épített, streamből töltött vagy C++Builderből castolt egészként átadott opciórekord bármilyen, például 200-as bájtot hordozhat. A lemásolt ordinális validálása azt factory időben EArgumentException-má alakítja, definiálatlan mód helyett a natív kódban. A factory emellett elutasítja a tpsOSDOnly-t és a tpsAutoOnly-t, amik szavakat nem produkálnak, és megköveteli az osd.traineddata-t a tpsAutoOSD-hoz és tpsSparseTextOSD-höz

Mit garantál valójában a felismerési timeout?

A Tesseract DLL timeout kooperatív: a HotPDF meg tudja állítani a saját munkáját, és megkérheti a Tesseractot a megállásra, de nem tudja rákényszeríteni a natív kódra a visszatérést. Az óra akkor indul, amikor a Recognize elkezdődik, így a bitmap konverzió és a modellinicializálás ugyanabból a budgetből fogy. A HotPDF az eltelt időt és a cancellation tokent a grayscale konverzió alatt és a szavak közt ellenőrzi az eredmények iterálása közben, és a hátralévő milliszekundumokat adja át a TessMonitorSetDeadlineMSecs-nek, mielőtt meghívná a TessBaseAPIRecognize-t

A rés a natív híváson belül van. A Tesseract monitora a szófelismerés alatt konzultál, a TessBaseAPIInit2 vagy az oldal-layout elemzés alatt nem, így egy lassú modellbetöltés vagy egy patologikus layout lefuthat a határidőn, mielőtt a timeout jelentődnék. A pixel- és kimeneti budgetek a natív library saját memóriahasználatát sem korlátozzák. Ha olyan workerre van szükséged, amit lelőhetsz, használd a process adaptért; ez a becsületes kompromisszum, nem hiányzó funkció

HotPDF Tesseract DLL kooperatív timeout anatómia: az óra a Recognize indulásakor indul, és lefedi a grayscale konverziót, a TessBaseAPIInit2-t és a layout elemzést, de a monitor csak a szófelismerés alatt konzultál, így a modellbetöltések és a layout túlfuthatnak, mielőtt a HotPDF otlsEngineError-t vagy otlsCancelled-t jelentene
itt egy határidő kérés, nem garancia: az init és a layout elemzés hosszan futhat, egy valóban lelőhető workerhez pedig a process adaptér kell

Az oldal-szegmentáció az, ahol a DLL adaptér megkeresi az értelmét a nehéz bemeneten. Űrlapok, címkék és szétszórt mezős szkenelt táblázatok gyakran jobban felismerhetők tpsSparseText-tel, mint automatikus szegmentációval, ami olyan oszlopokat és bekezdéseket akarna összerakni, amik nincsenek ott

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;  // szétszórt mezők, nincs oszlopösszeállítás
  TessOptions.EngineMode := temLSTMOnly;     // LSTM modelleket igényel a tessdataban
  TessOptions.TimeoutMilliseconds := 20000;  // tartalmazza a modellinicializálást
  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;

A timeout otlsEngineError-ként jelenik meg a Tesseract DLL OCR timed out diagnózissal, a megszakított token pedig otlsCancelled-ként. Mindkét esetben az ApplyLoadedOCRTextLayer minden kiválasztott oldalt felismert, mielőtt elkezdené az elkötelezési tranzakciót, így egy hiba az 50-ből 40. oldalon pontosan úgy hagyja ott a betöltött dokumentumot, ahogy volt. Ne feledd, hogy a tpsSingleLine, tpsSingleBlock és tpsSparseText csak a szegmentációt változtatja; egyikük sem egyenesíti ki a dőlt szkennet

Free Pascal és Lazarus: elavult pixelek és elveszett kínai

Mindkét Tesseract factory működik Windows Free Pascal és Lazarus Win32 és Win64 buildekben v2.772.1 óta, két FPC-specifikus javítás után. Építsd újra a Lazarus package-et a célarchitektúrára előbb; az általános portot a HotPDF Free Pascalon és Lazarus Win64-en cikk fedi le

Az első javítás a pixeleket érinti. Egy scanline-okon át írt LCL TBitmap frissítheti a nyers képét a Windows bitmap handle frissítése nélkül, így a GetDIBits azon a handlen a régi pixeleket adja vissza. A tünet zavarba ejtő volt: egy bitmapre közvetlenül rajzolt szöveg felismerődött, míg a HotPDF PDF rendererje által renderelt oldal üres szólistát adott. FPC-n az adaptér most CreateIntfImage-en át formátumérzékeny pillanatképet olvas, ami tiszteletben tartja a nyers kép pixelformátumát és sorrendjét. A Delphi build a GetDIBits utat tartja egy privát 24 bites másolaton. Egyik build sem módosítja a hívó bitmapjét

A második javítás a tesseract.exe adaptért érinti. Az FPC TStringList-je ANSI stringeket tárol, így a dekódolt UTF-8 TSV szöveg Lines.Text-hez rendelése csendben eldobta minden kínai vagy kiegészítő síkbeli karaktert, amit a rendszer ANSI kódlapja nem tudott ábrázolni. Az FPC út most UTF-8 bájtokként tartja a TSV-t, bájtszinten levágja a BOM-ot, és minden szót külön UnicodeString-gé dekódol. A DLL adaptérnál ez a probléma sosem volt, mert minden szót közvetlenül az iterátorból dekódol

Gyorsreferencia

  • Factory: HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]) a HPDFTesseractRecognition-ban, v2.772.0-ban érkezett, FPC támogatás v2.772.1-ben
  • Alapértékek: tpsAuto, temDefault, 60 000 ms, 16 777 216 pixel; timeout tartomány 1–3 600 000 ms, pixelplafon 67 108 864
  • Illeszd a DLL bitségségét az alkalmazáshoz, és a függőség DLL-eket tedd a Tesseract DLL mellé
  • Kezeld a monitort opakként; soha ne másold ETEXT_DESC-et Pascal rekordba
  • Dekláráld a cancel callbacket cdecl-lel, egébájtos Boolean eredménnyel, és soha ne engedj kivételt kiszökni belőle
  • Az iterátor szövegét TessDeleteText-tel szabadítsd fel; az eredményiterátorból kapott oldaliterátort soha ne szabadítsd fel
  • Számolj azzal, hogy a határidő kooperatív: a modellinicializálás és a layout elemzés túlfuthat rajta
  • Használd a tesseract.exe adaptért, amikor kemény leállításra vagy crash-izolációra van szükséged

A Tesseract DLL adaptér, a process adaptérek és a beépített OCR engine mind a HotPDF Delphi PDF componentban szállítanak Delphihez, C++Builderhez és Free Pascalhoz; kiadásokért és letöltésekért lásd a HotPDF termékoldalát