Tekninen artikkeli

HotPDF Tesseract DLL OCR: C API:n kutsuminen Delphistä

HotPDF ajaa Tesseractia Delphi-prosessisi sisällä funktion HPDFCreateTesseractDLLOCREngine kautta, tehtaalla, joka lisättiin versiossa v2.772.0, lataa dynaamisesti Tesseract 5 -yhteensopivan DLL:n, ajaa sen C API:a (TessBaseAPIInit2, TessBaseAPIRecognize, tulositeraattori) ja palauttaa IHPDFOCREnginen. THotPDF.ApplyLoadedOCRTextLayer käyttää kyseistä moottoria lisätäkseen näkymättömän, haettavan Unicode-tekstitason skannatuille PDF-sivuille

Sama tunnistin oli jo saavutettavissa artikkelin ulkoisen tesseract.exe-sovittimen, joka kirjoittaa BMP:n ja jäsentää TSV:n kautta. Kyseinen polku toimii, mutta jokainen sivu maksaa prosessin käynnistyksen, väliaikaisen bittikarttatiedoston ja tekstimuodon, jolla ei ole perusviivoja eikä hallintaa sivun segmentointiin. DLL:n kutsuminen poistaa kaikki kolme. Se poistaa myös prosessiseinän, mikä tarkoittaa, että Pascal-sidonta istuu suoraan C-rakenteiden, C-booleiden ja C-allokoitujen merkkijonojen päällä. Suurin osa siitä, mikä tässä sovittimessa on syytä tietää, on paikkoja, joissa kyseinen sidonta voi mennä hiljaisesti pieleen

Miten Tesseract ajetaan in-process Delphistä HotPDF:llä?

Tesseractin ajaminen in-process HotPDF:llä vie yhden tehdaskutsun yksikössä HPDFTesseractRecognition ja saman ApplyLoadedOCRTextLayer-kutsun, jota jokainen HotPDF-OCR-moottori käyttää. Tehdas validoii innokkaasti. DLL-tiedoston ja tessdata-hakemiston on oltava olemassa, kielitunnisteen saa sisältää vain ASCII-kirjaimia, numeroita, _ ja +, jokaisen yhdistelmän kuten chi_sim+eng mallin on oltava vastaava .traineddata-tiedosto, ja kaikkien 21 vaaditun viennin on ratkeava ennen kuin moottori palautetaan. Määritysvirheet nostavat EArgumentExceptionn; latautumisessaan epäonnistuva DLL nostaa EOSErrorin Windows-virhekoodilla ja vihjeellä tarkistaa arkkitehtuuri ja riippuvuudet

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Win64-sovellus tarvitsee 64-bittisen DLL:n; riippuvuus-DLL:t menevät sen viereen
  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
    // Tyhjä sivuluettelo tarkoittaa jokaista sivua; sivut, joilla on jo tekstiä, ohitetaan
    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 asettaa arvon PageSegMode muotoon tpsAuto, EngineMode muotoon temDefault, TimeoutMilliseconds arvoon 60 000 ja MaxPixels arvoon 16 777 216. Pikselibudjetti merkitsee enemmän kuin näyttää. US Letter -sivu oletusarvoisella 300 DPI:llä renderöityy 2 550 × 3 300 pikseliksi, noin 8,4 miljoonaksi, mikä mahtuu. Sama sivu 600 DPI:llä on 5 100 × 6 600, noin 33,7 miljoonaa, ja sovitin hylkää sen ennen kuin Tesseract näkee pikseliä. Nosta arvoa MaxPixels (katto on 67 108 864) tai pidä DPI paikoillaan; kummankin sivu on myös katkaistu arvoon 32 767 pikseliä

DLL ladataan funktiolla LoadLibraryEx hakuflagien kanssa DLL:n omalle kansiolle plus oletusturvalliset hakemistot, joten kuvalirjastot, joista Tesseract riippuu, voivat asua sen vieressä koskematta muuttujaan PATH tai nykyiseen hakemistoon. HotPDF ei paketoi eikä lataa mitään OCR-ajonympäristöä tai mallia; hankit molemmat itse

Mitä muuttuu verrattuna tesseract.exe-sovittimeen?

DLL-sovitin vaihtaa prosessieristyksen rikkaampaan tulokseen ja pienempään sivukohtaiseen kustannukseen. Molemmat sovittimet kytkeytyvät samaan tekstitasoputkeen, joten koordinaattikartoitus, luottamusarvosuodatus ja kaikki-tai-ei mitään -vahvistus ovat identtiset; se, mikä eroaa, on se, miten pikselit menevät sisään ja sanat tulevat ulos

Näkökulmatesseract.exe-sovitinTesseract DLL -sovitin
TehdasHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
Pikselit sisäänBMP-tiedosto yksityisessä väliaikaisessa hakemistossa8-bittinen harmaasävypuskuri muistissa
Sanat ulosSanatason TSV, katkaistu arvoon 64 MiBTulositeraattori, UTF-8 sanaa kohden
PerusviivatEi saatavillaVälitetty läpi funktiosta TessPageIteratorBaseline
Sivusegmentointi ja moottoritilaVain automaattinen segmentointiTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
AikakatkaisuKova: lapsiprosessi lopetetaanYhteistoiminnallinen: Tesseractin on huomattava
Kaatumis- ja muistieristysErillinen prosessiEi mitään, jakaa osoiteavaruutesi

Yksi kustannus ei katoa. Jokainen Recognize-kutsu luo oman API-instanssin ja kutsuu funktion TessBaseAPIInit2, joten kielimallit alustetaan sivua kohden kerran moottoria kohden -tavan sijaan. Käyttöjärjestelmän tiedostovälimuisti pehmentää uudelleenlatausta, mutta suurilla monikielisillä mallijoukoilla kyseinen on yhä hallitseva kiinteä kustannus sivua kohden, ja se laskee tunnistustakarajaan. Artikkelin in-process RapidOCR -DLL-moottori ottaa vastakkaisen suunnittelun ja pitää ONNX-mallinsa muistissa moottorin eliniän ajan; rajapintaongelmat (C ABI, lainatut puskurit, keskeyttämätön natiivityö) ovat samaa perhettä

Miksi Delphi ei voi kopioida Tesseractin monitorirakennetta?

Delphi ei voi turvallisesti peilata Tesseractin edistymismonitoria, koska ETEXT_DESC sisältää versiosta riippuvia sisäisiä kenttiä, joten käsin kopioitu tietue asettaa peruutuscallbackin ja takarajan vääriin siirtymiin joillakin koosteilla. Mikään ei epäonnistu äänekkäästi, kun näin tapahtuu. Tesseract lukee yksinkertaisesti callback-osoittimesi kentästä, joka nyt pitää sisällään jotain muuta, tai ei koskaan näe takarajaa lainkaan

HotPDF käsittelee siksi monitoria läpinäkymättömänä osoittimena ja koskettaa sitä vain vietyjen funktioiden kautta: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs ja TessMonitorDelete. Jos sidot C API:n itse toista tarkoitusta varten, sama kaava pätee. Alla oleva luonnos on omaa sidontakoodiasi, ei HotPDF-API:a, ja peilaa ilmoitukset, joita HotPDF käyttää sisäisesti

HotPDF Tesseract DLL -monitorin käsittely: versiosta riippuvan ETEXT_DESC-tietueen kopioiminen asettaa peruutuscallbackin ja takarajan vääriin siirtymiin ja epäonnistuu hiljaisesti, kun taas HotPDF käsittelee monitoria läpinäkymättömänä, ajaa funktioita TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc ja TessMonitorSetDeadlineMSecs ja pitää cdecl-callbackin poikkeuksettomana
Läpinäkymätön osoitin plus viisi vientiä on koko sopimus; callback pysyy yhden tavun Booleanina, joka lukee vain flagin ja kellon
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, ei koskaan dereferensoida
  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
  // Ajautuu Tesseractin pinossa: lue flagit ja kello, älä koskaan nosta
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Käyttö, funktio-osoittimet ratkaistuna funktiolla GetProcAddress:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end;

Kaksi yksityiskohtaa kyseisessä luonnoksessa on tahallisia. Callback palauttaa arvon Boolean, joka on yksi tavu sekä Delphissä että Free Pascalissa, vastaten C:n boolia funktiossa TessCancelFunc. Nelitavuinen Windows BOOL tai Delphin LongBool näyttää vaihdettavalta eikä ole: kun toinen puoli kirjoittaa yhden tavun ja toinen lukee neljä, paluurekisterin ylemmät tavut ovat sitä, mitä sinne jäi, ja epätosi voi saapua totena. Sama otsikkotiedosto monimutkaistaa asiaa lisää, koska funktiot kuten TessPageIteratorBoundingBox palauttavat arvon int, jonka HotPDF ilmoittaa muodossa Integer. Lue jokaisen paluuarvon C-tyyppi olettaen yhtä käytäntöä koko API:lle

Toinen yksityiskohta on, ettei callback koskaan nosta poikkeusta. Delphi-poikkeuksen purkautuminen Tesseractin C++-kehysten läpi on määrittelemätöntä käyttäytymistä, joten HotPDF:n callback lukee vain peruutustokenin ja monotonisen GetTickCount64-arvon. Sovitin muuttaa tuloksen peruutus- tai aikakatkaisudiagnostiikaksi funktion TessBaseAPIRecognize paluun jälkeen, ja se suorittaa kyseisen tarkistuksen riippumatta natiivista paluukoodista

Mitkä natiiveista osoittimista Delphi-puoli omistaa?

HotPDF Tesseract DLL -sovitin omistaa kolme natiivia objektia pyyntöä kohden, API-instanssin, monitorin ja tulositeraattorin, ja lainaa kaikkea muuta. Jokainen Recognize-kutsu luo oman joukkonsa ja vapauttaa sen finally-lohkossa: TessResultIteratorDelete, sitten TessMonitorDelete, sitten TessBaseAPIDelete. Moottorirajapinnan vapauttaminen purkaa kirjaston latauksen

HotPDF Tesseract DLL -objektien omistajuus Recognize-kutsua kohden: tulositeraattori, monitori ja API-instanssi omistetaan ja vapautetaan tuo järjestyksessä finallyn sisällä, funktiosta TessResultIteratorGetPageIterator saatu sivuiteraattori on lainattu näkymä, jota ei koskaan saa vapauttaa, ja GetUTF8Text-merkkijonot kopioidaan ja palautetaan funktiolla TessDeleteText
Kolme objektia omistettuna, kaikki muu lainattuna: vapauta kiinteässä järjestyksessä, älä koskaan tee kaksinkertaista vapautusta sivuiteraattorille, äläkä koskaan sekoita allokaattoreita
  • TessResultIteratorGetPageIterator palauttaa lainatun näkymän tulositeraattoriin, ei uutta objektia. HotPDF käyttää sitä funktioissa TessPageIteratorBoundingBox ja TessPageIteratorBaseline eikä koskaan vapauta sitä; sen erillinen poistaminen vapauttaisi saman muistin kahdesti
  • TessResultIteratorGetUTF8Text palauttaa merkkijonon, jonka DLL:n oma ajonaikainen ympäristö on allokoinut. HotPDF kopioi sen ja ojentaa sen takaisin funktiolla TessDeleteText finally-lohkossa; Pascalin FreeMem vapauttaisi sen väärällä heapilla
  • Sanateksti dekoodataan tiukalla UTF-8-validoinnilla ja pituustarkistettuna ennen muunnosta. Sanat, joissa on ohjausmerkkejä, epäkelpoa UTF-8:aa, kuvan ulkopuolisia laatikoita, käännettyjä suorakulmioita tai luottamusarvo väliltä 0–100 poissa, kaatavat pyynnön hiljaisesti paikattujen sijaan
  • Tekstiä on pyyntöä kohden enintään 1 048 576 UTF-16-koodiyksikköä, ja sanamäärän on mahtuttava pyynnön budjettiin, jonka ApplyLoadedOCRTextLayer välittää

Luottamusarvo saapuu väliltä 0–100 ja se skaalataan välille 0–1, joten THPDFOCRTextLayerOptions.MinimumConfidence tarkoittaa samaa asiaa jokaiselle moottorille. Kun Tesseract raportoi perusviivan, molemmat päätepisteet välitetään läpi; muuten tekstitasoputki palaa geometriseen arvioonsa, täsmälleen niin kuin se tekee TSV-syötteelle

Miksi enum validoidaan ennen kuin se ylettää DLL:ään?

HotPDF kopioi arvojen PageSegMode ja EngineMode raakaordinaalit Integeriin ennen aluetarkistusta, koska kääntäjä saa olettaa, että enum-muuttuja pitää aina ilmoitettua arvoa, ja taittaa lausekkeen Ord(X) > Ord(High(T)) vakioarvoksi epätosi. Ordinaalit eivät ole koristeita: THPDFTesseractPageSegMode noudattaa Tesseractin sivusegmentoinnin numerointia arvoista 0–13, THPDFTesseractEngineMode noudattaa moottoritilan numerointia arvoista 0–3, ja molemmat menevät DLL:lle pelkkisinä kokonaislukuina. Funktiolla FillChar rakennettu asetustietue, streamista täytetty tai C++Builderista valetulla kokonaisluvulla annettu voi kantaa tavua kuten 200. Kopioidun ordinaalin validointi muuttaa sen arvoksi EArgumentException tehtaassa epäselvän tilan sijaan natiivissa koodissa. Tehdas hylkää lisäksi arvot tpsOSDOnly ja tpsAutoOnly, jotka eivät tuota sanoja, ja vaatii tiedoston osd.traineddata arvoille tpsAutoOSD ja tpsSparseTextOSD

Mitä tunnistuksen aikakatkaisu oikeastaan takaa?

Tesseract DLL -aikakatkaisu on yhteistoiminnallinen: HotPDF voi pysäyttää oman työnsä ja pyytää Tesseractia pysähtymään, mutta se ei voi pakottaa natiivia koodia palaamaan. Kello käynnistyy, kun Recognize alkaa, joten bittikarttamuunnos ja mallin alustus kuluttavat samaa budjettia kuin tunnistus. HotPDF tarkistaa kulunutta aikaa ja peruutustokenia harmaasävymunnoksen aikana ja sanojen välillä tulosten iteroinnin aikana, ja välittää jäljellä olevat millisekunnit funktiolle TessMonitorSetDeadlineMSecs ennen kuin kutsuu funktiota TessBaseAPIRecognize

Aukko on natiivin kutsun sisällä. Tesseractin monitoria kuullaan sanatunnistuksen aikana, ei funktion TessBaseAPIInit2 tai sivun asetteluanalyysin aikana, joten hidas mallin lataus tai patologinen asettelu voi ajaa takarajan yli ennen kuin aikakatkaisu raportoidaan. Pikseli- ja tulostusbudjetit eivät myöskään rajoita natiivin kirjaston omaa muistinkäyttöä. Jos tarvitset työläisen, jonka voi tappaa, käytä prosessisovitinta; kyseinen on rehellinen kompromissi, ei puuttuva ominaisuus

HotPDF Tesseract DLL -yhteistoiminnallisen aikakatkaisun anatomia: kello käynnistyy, kun Recognize alkaa, ja kattaa harmaasävymuunnoksen, TessBaseAPIInit2:n ja asetteluanalyysin, mutta monitoria kuullaan vain sanatunnistuksen aikana, joten mallien lataukset ja asettelu voivat ylittää rajan ennen kuin HotPDF raportoi arvon otlsEngineError tai otlsCancelled
Takaraja täällä on pyyntö, ei takuu: alustus ja asetteluanalyysi voivat ajaa pitkään, ja työläinen, jonka oikeasti voi tappaa, tarvitsee prosessisovittimen

Sivusegmentointi on paikka, jossa DLL-sovitin ansaitsee paikkansa vaikealla syötteellä. Lomakkeet, nimikkeet ja hajallaan olevia kenttiä sisältävät skannatut taulukot tunnistuvat usein paremmin arvolla tpsSparseText kuin automaattisella segmentoinnilla, joka yrittää koota sarakkeita ja kappaleita, joita ei ole olemassa

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;  // hajallaan olevat kentät, ei sarakkeiden kokoamista
  TessOptions.EngineMode := temLSTMOnly;     // tarvitsee LSTM-mallit tessdataan
  TessOptions.TimeoutMilliseconds := 20000;  // sisältää mallin alustuksen
  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;

Aikakatkaisu ilmestyy arvona otlsEngineError diagnostiikalla Tesseract DLL OCR timed out, kun taas peruutettu tokeni ilmestyy arvona otlsCancelled. Molemmissa tapauksissa ApplyLoadedOCRTextLayer on tunnistanut jokaisen valitun sivun ennen kuin aloittaa vahvistustransaktion, joten epäonnistuminen sivulla 40/50 jättää ladatun asiakirjan täsmälleen sellaiseksi kuin se oli. Huomaa, että arvot tpsSingleLine, tpsSingleBlock ja tpsSparseText muuttavat vain segmentointia; yksikään niistä ei suorista vinoon skannattua sivua

Free Pascal ja Lazarus: vanhentuneet pikselit ja kadonnut kiina

Molemmat Tesseract-tehtaat toimivat Windows Free Pascalissa ja Lazarus Win32- ja Win64-koosteissa versiosta v2.772.1 alkaen, kahden FPC-kohtaisen korjauksen jälkeen. Koosta Lazarus-paketti uudelleen kohdearkkitehtuurille ensin; yleinen porttaus käsitellään artikkelissa HotPDF Free Pascalilla ja Lazarus Win64:llä

Ensimmäinen korjaus koskee pikseleitä. LCL-TBitmap, joka kirjoitetaan scanlinejen kautta, voi päivittää raakakuvansa päivittämättä Windowsin bittikarttakahvaa, joten GetDIBits kyseisellä kahvalla palauttaa vanhat pikselit. Oire oli mystinen: suoraan bittikartalle piirretty teksti tunnistuttiin, kun taas HotPDF:n PDF-renderöijän renderöimä sivu tuotti tyhjän sanaluettelon. FPC:llä sovitin lukee nyt muototietoisen snapshotin funktion CreateIntfImage kautta, joka kunnioittaa raakakuvan pikselimuotoa ja rivijärjestystä. Delphi-kooste pitää GetDIBits-polun yksityisessä 24-bittisessä kopiossa. Kumpikaan kooste ei muokkaa kutsujan bittikarttaa

Toinen korjaus kuuluu tesseract.exe-sovittimelle. FPC:n TStringList tallettaa ANSI-merkkijonoja, joten dekoodatun UTF-8-TSV-tekstin sijoittaminen arvoon Lines.Text pudotti hiljaisesti jokaisen kiinalaisen tai täydentävän tason merkin, jota järjestelmän ANSI-koodisivu ei voinut esittää. FPC-polku pitää nyt TSV:n UTF-8-tavuina, poistaa BOM:n tavutasolla ja dekoodaa jokaisen sanan yksitellen arvoon UnicodeString. DLL-sovittimella ei koskaan ollut kyseistä ongelmaa, koska se dekoodaa jokaisen sanan suoraan iteraattorista

Pikamuistio

  • Tehdas: HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]) yksikössä HPDFTesseractRecognition, lisätty versiossa v2.772.0, FPC-tuki versiossa v2.772.1
  • Oletukset: tpsAuto, temDefault, 60 000 ms, 16 777 216 pikseliä; aikakatkaisualue 1–3 600 000 ms, pikselikatto 67 108 864
  • Sovita DLL:n bittisyys sovellukseen ja sijoita riippuvuus-DLL:t Tesseract-DLL:n viereen
  • Käsittele monitoria läpinäkymättömänä; älä koskaan kopioi arvoa ETEXT_DESC Pascal-tietueeseen
  • Ilmoita peruutuscallback muodossa cdecl yhden tavun Boolean-tuloksella, äläkä koskaan anna poikkeuksen paeta siitä
  • Vapauta iteraattorin teksti funktiolla TessDeleteText; älä koskaan vapauta tulositeraattorista saatua sivuiteraattoria
  • Oleta takarajan olevan yhteistoiminnallinen: mallin alustus ja asetteluanalyysi voivat ylittää sen
  • Käytä tesseract.exe-sovitinta, kun tarvitset kovan lopetuksen tai kaatumiseristyksen

Tesseract DLL -sovitin, prosessisovittimet ja sisäänrakennettu OCR-moottori toimitetaan kaikki HotPDF Delphi -komponentin mukana Delphille, C++Builderille ja Free Pascalille; katso HotPDF-tuotesivulta versiot ja lataukset