Techninis straipsnis

HotPDF Tesseract DLL OCR: C API kvietimas iš Delphi

HotPDF paleidžia Tesseract jūsų Delphi proceso viduje per HPDFCreateTesseractDLLOCREngine – faktoriją, pridėtą v2.772.0, kuri dinamiškai įkelia Tesseract 5 suderinamą DLL, valdo jos C API (TessBaseAPIInit2, TessBaseAPIRecognize, rezultatų iteratorių) ir grąžina IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer tą variklį naudoja, kad pridėtų nematomą, ieškomą Unicode teksto sluoksnį nuskenuotiems PDF puslapiams

Tas pats atpažintojas jau buvo pasiekiamas per išorinį tesseract.exe adapterį, rašantį BMP ir skaitantį TSV. Tas kelias veikia, bet kiekvienas puslapis moka už proceso paleidimą, laikiną bitmapo failą ir teksto formatą be bazinių linijų ir be kontrolės puslapių segmentavimui. DLL kvietimas pašalina visus tris. Kartu pašalina ir proceso sieną – vadinasi, Pascal jungtis sėdi tiesiai ant C struktūrų, C booleanų ir C priskirtų eilučių. Didžioji dalis to, ką verta žinoti apie šį adapterį, yra ten, kur ta jungtis gali tylioje suklysti

Kaip paleisti Tesseract procese iš Delphi su HotPDF?

Tesseract paleidimas procese su HotPDF užima vieną faktorijos kvietimą HPDFTesseractRecognition unito viduje ir tą patį ApplyLoadedOCRTextLayer kvietimą, kurį naudoja kiekvienas HotPDF OCR variklis. Faktorija tikrina anksti. DLL failas ir tessdata katalogas privalo egzistuoti, kalbos identifikatorius gali turėti tik ASCII raides, skaitmenis, _ ir +, kiekvienas modelis tokioje kombinacijoje kaip chi_sim+eng privalo turėti atitinkantį .traineddata failą, o visi 21 reikalingi eksportai privalo išspręstis, kol variklis grąžinamas. Konfigūracijos klaidos kelia EArgumentException; neįsikelianti DLL kelia EOSError su Windows klaidos kodu ir patarimu patikrinti architektūrą bei priklausomybes

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Win64 programai reikia 64 bitų DLL; priklausomybių DLL keliauja šalia
  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
    // Tuščias puslapių sąrašas reiškia visus puslapius; puslapiai, turintys tekstą, praleidžiami
    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 nustato PageSegMode į tpsAuto, EngineMode į temDefault, TimeoutMilliseconds į 60 000 ir MaxPixels į 16 777 216. Pikselių biudžetas svarbesnis, nei atrodo. US Letter puslapis ties numatytąja 300 DPI atvaizduojasi į 2 550 × 3 300 pikselių, apie 8,4 milijono – telpa. Tas pats puslapis ties 600 DPI yra 5 100 × 6 600, apie 33,7 milijono, ir adapteris jį atmeta dar prieš Tesseract pamatant pikselį. Kelkite MaxPixels (lubos – 67 108 864) arba palikite DPI kur yra; kiekviena kraštinė dar ribojama 32 767 pikseliais

DLL įkeliama su LoadLibraryEx, naudojant paieškos vėliavas pačiam DLL aplankui plius numatytosioms saugioms direktorijoms, tad paveikslėlių bibliotekos, nuo kurių priklauso Tesseract, gali gyventi šalia neliečiant PATH ar dabartinio katalogo. HotPDF nepūkuoja ir neparsiunčia jokio OCR runtime ar modelio; abu parūpinate patys

Kas keičiasi lyginant su tesseract.exe adapteriu?

DLL adapteris maino procesų izoliaciją į tursesnį išvedimą ir mažesnes puslapiui sąnaudas. Abu adapteriai įsijungia į tą patį teksto sluoksnio srautą, tad koordinačių susiejimas, pasitikėjimo filtravimas ir viskas-arba-nieko įrašymas identiški; skiriasi tai, kaip pikseliai įeina ir žodžiai išeina

Aspektastesseract.exe adapterisTesseract DLL adapteris
FaktorijaHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
Pikseliai įBMP failas privačiame laikinajame kataloge8 bitų pilkio skalės buferis atmintyje
Žodžiai išŽodžio lygio TSV, ribojama iki 64 MiBRezultatų iteratorius, UTF-8 žodžiui
Bazinės linijosNeprieinamosPerduodamos iš TessPageIteratorBaseline
Puslapių segmentavimas ir variklio režimasTik automatinis segmentavimasTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
TerminasKietas: antrinis procesas nutraukiamasKooperatyvinis: Tesseract privalo pastebėti
Griūties ir atminties izoliacijaAtskiras procesasNėra, dalijasi jūsų adresų erdve

Viena kaina nedingsta. Kiekvienas Recognize kvietimas sukuria savą API instanciją ir kviečia TessBaseAPIInit2, tad kalbos modeliai inicializuojami puslapiui, o ne kartą varikliui. Operacinės sistemos failų podėlis suminkština pakartotinį įkėlimą, bet didelėse daugiakalbėse modelių aibėse tai vis tiek dominuojanti fiksuota kaina puslapiui, ir ji skaitosi į atpažinimo terminą. RapidOCR DLL variklis procese pasirenka priešingą dizainą ir laiko savus ONNX modelius rezidentuotus visą variklio gyvenimą; ribos problemos (C ABI, pasiskolinti buferiai, nenutraukiamas natyvus darbas) – tos pačios šeimos

Kodėl Delphi negali nukopijuoti Tesseract monitoriaus struktūros?

Delphi negali saugiai atkartoti Tesseract pažangos monitoriaus, nes ETEXT_DESC turi nuo versijos priklausančių vidinių laukų, tad rankomis nukopijuotas įrašas atšaukimo callbacką ir terminą padeda netinkamuose poslinkiuose kai kuriuose dariniuose. Niekas negarsiai nesugriūva, kai tai atsitinka. Tesseract tiesiog skaito jūsų callbacko rodyklę iš lauko, kuriame dabar slypi kažkas kito, arba termino iš viso nemato

Todėl HotPDF monitorių traktuoja kaip nepermatomą rodyklę ir liečia tik per eksportuojamas funkcijas: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs ir TessMonitorDelete. Jeigu C API susiejate patys kitam tikslui, tas pats raštas galioja. Eskizas žemiau – jūsų pačių jungties kodas, ne HotPDF API, ir atkartoja deklaracijas, kurias HotPDF naudoja viduje

HotPDF Tesseract DLL monitoriaus tvarkymas: nuo versijos priklausančio ETEXT_DESC įrašo kopijavimas atšaukimo callbacką ir terminą padeda netinkamuose poslinkiuose ir žlunga tylioje, o HotPDF monitorių traktuoja kaip nepermatomą, valdo TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc ir TessMonitorSetDeadlineMSecs bei laiko cdecl callbacką be išimčių
nepermatoma rodyklė plius penki eksportai – visas kontraktas; callbackas lieka vieno baito Boolean, skaitantis tik vėliavą ir laikrodį
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, niekada nedereferencuojama
  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
  // Sukasi Tesseract steku: skaitykite vėliavas ir laikrodį, niekada nekelkite išimties
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Naudojimas, su funkcijų rodyklėmis, išspręstomis per GetProcAddress:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end;

Du to eskizo detalės tyčiniai. Callbackas grąžina Boolean, kuris Delphi ir Free Pascal yra vienas baitas – sutampa su C bool TessCancelFunc viduje. Keturių baitų Windows BOOL arba Delphi LongBool atrodo sukeičiami ir nėra: kai viena pusė rašo vieną baitą, o kita skaito keturis, grąžinamojo registro aukštieji baitai yra tai, kas ten liko, ir false gali atkelti kaip true. Tas pats antraštės failas dar painioja, nes tokios funkcijos kaip TessPageIteratorBoundingBox grąžina int, kurį HotPDF deklaruoja kaip Integer. Skaitykite kiekvienos grąžinamos reikšmės C tipą, vietoj to, kad visai API priskirtumėte vieną konvenciją

Antra detalė – callbackas niekada nekelia išimties. Delphi išimtis, išsivyniojanti pro Tesseract C++ kadrus, yra neapibrėžta elgsena, tad HotPDF callbackas skaito tik atšaukimo tokeną ir monotonišką GetTickCount64 reikšmę. Adapteris rezultatą paverčia atšaukimo arba termino diagnostika, kai grįžta TessBaseAPIRecognize, ir atlieka tą patikrą nepriklausomai nuo natyvios grąžos kodo

Kurias natyvias rodykles valdo Delphi pusė?

HotPDF Tesseract DLL adapteris valdo tris natyvius objektus užklausai – API instanciją, monitorių ir rezultatų iteratorių – ir viską kitą pasiskolina. Kiekvienas Recognize kvietimas sukuria savą rinkinį ir jį atlaisvina finally bloke: TessResultIteratorDelete, tada TessMonitorDelete, tada TessBaseAPIDelete. Variklio sąsajos atleidimas iškrauna biblioteką

HotPDF Tesseract DLL objektų nuosavybė kiekvienam Recognize kvietimui: rezultatų iteratorius, monitorius ir API instancija valdomi ir atlaisvinami ta tvarka finally viduje, puslapių iteratorius iš TessResultIteratorGetPageIterator yra pasiskolintas vaizdas, kurio niekada negalima atlaisvinti, o GetUTF8Text eilutės nukopijuojamos ir grąžinamos per TessDeleteText
trys valdomi objektai, viskas kita pasiskolinta: atlaisvinkite fiksuota tvarka, niekada nedvigubinkite puslapių iteratoriaus atlaisvinimo ir niekada nemaišykite allocatorių
  • TessResultIteratorGetPageIterator grąžina pasiskolintą vaizdą į rezultatų iteratorių, o ne naują objektą. HotPDF jį naudoja TessPageIteratorBoundingBox ir TessPageIteratorBaseline ir niekada neatlaisvina; atskirai jį ištrinus ta pati atmintis būtų atlaisvinta dukart
  • TessResultIteratorGetUTF8Text grąžina eilutę, priskirtą pačios DLL runtime. HotPDF ją nukopijuoja ir grąžina per TessDeleteText finally bloke; Pascal FreeMem atlaisvintų ją netinkamoje kravoje
  • Žodžių tekstas dekoduojamas su griežta UTF-8 patikra ir ilgio tikrinimu prieš konversiją. Žodžiai su valdymo simboliais, sugadintu UTF-8, langeliais už atvaizdo ribų, apverčiais stačiakampiais arba pasitikėjimu už 0–100 ribų žlugdo užklausą vietoj tyliojo lopymo
  • Iš viso teksto užklausai ribojama iki 1 048 576 UTF-16 kodo vienetų, o žodžių skaičius privalo tilpti į ApplyLoadedOCRTextLayer atiduotą užklausos biudžetą

Pasitikėjimas atkeliauja kaip 0–100 ir masteluoja į 0–1, tad THPDFOCRTextLayerOptions.MinimumConfidence kiekvienam varikliui reiškia tą patį. Kai Tesseract praneša bazinę liniją, abu galai perduodami; kitaip teksto sluoksnio srautas grįžta prie savo geometrinio įvertinimo – lygiai kaip daro TSV įvedimui

Kodėl patikrinti enum dar prieš jį pasiekiant DLL?

HotPDF žaliąjį PageSegMode ir EngineMode ordinalą nukopijuoja į Integer prieš intervalo patikrą, nes kompiliatorius gali manyti, kad enum kintamasis visada laiko deklaruotą reikšmę, ir sutraukti Ord(X) > Ord(High(T)) į konstantą false. Ordinalai ne puošmena: THPDFTesseractPageSegMode seka Tesseract puslapių segmentavimo numeraciją nuo 0 iki 13, THPDFTesseractEngineMode – variklio režimo numeraciją nuo 0 iki 3, ir abu keliauja į DLL kaip paprasti sveikieji. Nustatymų įrašas, sudėtas su FillChar, užpildytas iš srauto arba perduotas iš C++Builder su cast sveikuoju, gali nešti baitą, tokį kaip 200. Nukopijuoto ordinalo patvirtinimas tą paverčia EArgumentException faktorijos metu, o ne neapibrėžtu režimu natyviame kode. Faktorija dar atmeta tpsOSDOnly ir tpsAutoOnly, kurie neduoda jokių žodžių, ir reikalauja osd.traineddata tpsAutoOSD bei tpsSparseTextOSD atvejais

Ką atpažinimo terminas iš tikrųjų garantuoja?

Tesseract DLL terminas kooperatyvinis: HotPDF gali sustabdyti savą darbą ir paprašyti Tesseract sustoti, bet negali priversti natyvaus kodo grįžti. Laikrodis paleidžiamas, kai prasideda Recognize, tad bitmapo konversija ir modelių inicializacija vartoja tą patį biudžetą kaip atpažinimas. HotPDF tikrina praėjusį laiką ir atšaukimo tokeną pilkio skalės konversijos metu ir tarp žodžių, iteruojant rezultatus, o likusias milisekundes perduoda TessMonitorSetDeadlineMSecs prieš kviesdamas TessBaseAPIRecognize

Tarpas slypi natyviojo kvietimo viduje. Tesseract monitorius konsultuojamas žodžių atpažinimo metu, o ne TessBaseAPIInit2 ar puslapio išdėstymo analizės metu, tad lėtas modelio įkėlimas arba patologinis išdėstymas gali nubėgti pro terminą dar prieš pranešant termino pabaigą. Pikselių ir išvesties biudžetai taip pat neriboja pačios natyvios bibliotekos atminties vartojimo. Jeigu reikia darbuotojo, kurį galite nudurti, naudokite proceso adapterį; tai sąžiningas kompromisas, ne dingusi funkcija

HotPDF Tesseract DLL kooperatyvinio termino anatomija: laikrodis paleidžiamas, kai prasideda Recognize, ir dengia pilkio skalės konversiją, TessBaseAPIInit2 bei išdėstymo analizę, bet monitorius konsultuojamas tik žodžių atpažinimo metu, tad modelių įkėlimai ir išdėstymas gali peržengti, kol HotPDF praneša otlsEngineError arba otlsCancelled
terminas čia – prašymas, ne garantija: inicializacija ir išdėstymo analizė gali bėgti ilgai, o darbuotojui, kurį tikrai galite nudurti, reikia proceso adapterio

Puslapių segmentavimas – vieta, kur DLL adapteris užsidirba savo duoną sunkiame įvedime. Formos, etiketės ir nuskenuotos lentelės su išmėtytais laukais dažnai atpažįstamos geriau su tpsSparseText nei su automatiniu segmentavimu, kuris bando surinkti stulpelius ir pastraipas, ten nesantis

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;  // išmėtyti laukai, be stulpelių surinkimo
  TessOptions.EngineMode := temLSTMOnly;     // reikalauja LSTM modelių tessdata viduje
  TessOptions.TimeoutMilliseconds := 20000;  // apima modelių inicializaciją
  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;

Terminas iškyla kaip otlsEngineError su diagnostika Tesseract DLL OCR timed out, o atšauktas tokenas – kaip otlsCancelled. Abiem atvejais ApplyLoadedOCRTextLayer yra atpažinęs kiekvieną pažymėtą puslapį, kol pradeda įrašymo transakciją, tad nesėkmė 40-ame puslapyje iš 50 palieka įkeltą dokumentą lygiai tokį, koks buvo. Pastebėkite, kad tpsSingleLine, tpsSingleBlock ir tpsSparseText keičia tik segmentavimą; nė vienas nenulenkina pakrypusios skenuotės

Free Pascal ir Lazarus: pasenę pikseliai ir dingusi kinų

Abi Tesseract faktorijos veikia Windows Free Pascal ir Lazarus Win32 bei Win64 dariniuose nuo v2.772.1, po dviejų FPC specifinių pataisų. Pirmiausia perstatykite Lazarus paketą tikslinei architektūrai; bendrasis pernešimas aprašytas HotPDF ant Free Pascal ir Lazarus Win64

Pirmoji pataisa liečia pikselius. LCL TBitmap, rašytas per scanline, gali atnaujinti žalią atvaizdą neatnaujinęs Windows bitmapo rankenos, tad GetDIBits ant tos rankenos grąžina senus pikselius. Simptomas buvo klaikus: tekstas, pieštas tiesiai ant bitmapo, atpažįstamas, o HotPDF PDF atvaizduoklio atvaizduotas puslapis duodavo tuščią žodžių sąrašą. FPC adapteris dabar skaito formatą žinantį snapshotą per CreateIntfImage, kuris gerbia žaliojo atvaizdo pikselių formatą ir eilučių tvarką. Delphi darinys GetDIBits kelią palieka ant privačios 24 bitų kopijos. Nė vienas darinys nekeičia kvietėjo bitmapo

Antroji pataisa priklauso tesseract.exe adapteriui. FPC TStringList saugo ANSI eilutes, tad dekoduoto UTF-8 TSV teksto priskyrimas Lines.Text tylioje numetdavo kiekvieną kinų ar papildomos plokštumos simbolį, kurio sistemos ANSI kodo puslapis negalėjo atvaizduoti. FPC kelias dabar laiko TSV kaip UTF-8 baitus, nuplauna BOM baitų lygyje ir kiekvieną žodį dekoduoja į UnicodeString atskirai. DLL adapteris šios problemos niekada neturėjo, nes kiekvieną žodį dekoduoja tiesiai iš iteratoriaus

Trumpa atmintinė

  • Faktorija: HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]) HPDFTesseractRecognition unito viduje, pridėta v2.772.0, FPC palaikymas v2.772.1
  • Numatytosios: tpsAuto, temDefault, 60 000 ms, 16 777 216 pikselių; termino intervalas 1–3 600 000 ms, pikselių lubos 67 108 864
  • Suderinkite DLL bitiškumą su programa ir padėkite priklausomybių DLL šalia Tesseract DLL
  • Monitorių traktuokite kaip nepermatomą; niekada nekopijuokite ETEXT_DESC į Pascal įrašą
  • Atšaukimo callbacką deklaruokite cdecl su vieno baito Boolean rezultatu ir niekada neleiskite išimčiai iš jo pabėgti
  • Iteratoriaus tekstą atlaisvinkite su TessDeleteText; niekada neatlaisvinkite puslapių iteratoriaus, gauto iš rezultatų iteratoriaus
  • Tikėkitės kooperatyvinio termino: modelių inicializacija ir išdėstymo analizė gali jį peržengti
  • Naudokite tesseract.exe adapterį, kai reikia kieto nutraukimo ar griūties izoliacijos

Tesseract DLL adapteris, proceso adapteriai ir vidinis OCR variklis visi keliauja su HotPDF Delphi PDF komponentu, skirto Delphi, C++Builder ir Free Pascal; leidimams ir parsisiuntimams žiūrėkite HotPDF produkto puslapį