HotPDF uruchamia Tesseracta wewnątrz twojego procesu Delphi przez HPDFCreateTesseractDLLOCREngine — fabrykę dodaną w v2.772.0, która dynamicznie ładuje zgodną z Tesseract 5 DLL, obsługuje jej C API (TessBaseAPIInit2, TessBaseAPIRecognize, iterator wyników) i zwraca IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer używa tego engine'a, żeby dodać niewidoczną, przeszukiwalną warstwę tekstu Unicode do zeskanowanych stron PDF
Ten sam rozpoznawacz był już osiągalny przez zewnętrzny adapter tesseract.exe, który pisze BMP i parsuje TSV. Ta ścieżka działa, ale każda strona płaci za odpalenie procesu, tymczasowy plik bitmapy i format tekstu bez linii bazowych i bez kontroli nad segmentacją strony. Wywołanie DLL usuwa wszystkie trzy. Usuwa też ścianę procesu, a to znaczy, że wiązanie Pascal siedzi wprost na strukturach C, booleanach C i napisach alokowanych przez C. Większość tego, co warto wiedzieć o tym adapterze, to miejsca, w których takie wiązanie potrafi po cichu pójść źle
Jak uruchomić Tesseracta w procesie z Delphi przez HotPDF?
Uruchomienie Tesseracta w procesie z HotPDF to jedno wywołanie fabryki w jednostce HPDFTesseractRecognition i to samo wywołanie ApplyLoadedOCRTextLayer, którego używa każdy engine OCR HotPDF. Fabryka waliduje z góry. Plik DLL i katalog tessdata muszą istnieć, identyfikator języka może zawierać tylko litery ASCII, cyfry, _ i +, każdy model w kombinacji takiej jak chi_sim+eng musi mieć pasujący plik .traineddata, a wszystkie 21 wymaganych eksportów musi się rozwiązać, zanim engine zostanie zwrócony. Błędy konfiguracji podnoszą EArgumentException; DLL, która nie chce się załadować, podnosi EOSError z kodem błędu Windows i sugestią sprawdzenia architektury i zależności
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Aplikacja Win64 potrzebuje 64-bitowej DLL; DLL zależności lądują obok
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
// Pusta lista stron znaczy każdą stronę; strony mające już tekst są pomijane
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 ustawia PageSegMode na tpsAuto, EngineMode na temDefault, TimeoutMilliseconds na 60 000 i MaxPixels na 16 777 216. Budżet pikseli ma większe znaczenie, niż wygląda. Strona US Letter przy domyślnych 300 DPI renderuje się do 2550 × 3300 pikseli, około 8,4 miliona, co się mieści. Ta sama strona przy 600 DPI to 5100 × 6600, około 33,7 miliona, i adapter odrzuca ją, zanim Tesseract zobaczy piksel. Podnieś MaxPixels (sufit to 67 108 864) albo zostaw DPI gdzie jest; każdy bok jest dodatkowo ograniczony do 32 767 pikseli
DLL jest ładowana przez LoadLibraryEx z flagami wyszukiwania dla własnego folderu DLL plus domyślnych bezpiecznych katalogów, więc biblioteki obrazów, od których Tesseract zależy, mogą mieszkać obok niego bez ruszania PATH ani katalogu bieżącego. HotPDF nie dołącza ani nie pobiera żadnego runtime'u ani modelu OCR; oba zaopatrujesz sam
Co się zmienia względem adaptera tesseract.exe?
Adapter DLL wymienia izolację procesów na bogatsze wyjście i niższy narzut na stronę. Oba adaptery wpinają się w ten sam potok warstwy tekstu, więc mapowanie współrzędnych, filtrowanie pewności i zatwierdzanie albo wszystko, albo nic są identyczne; różni się to, jak piksele wchodzą i słowa wychodzą
| Aspekt | Adapter tesseract.exe | Adapter DLL Tesseract |
|---|---|---|
| Fabryka | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| Wejście pikseli | Plik BMP w prywatnym katalogu tymczasowym | 8-bitowy bufor w skali szarości w pamięci |
| Wyjście słów | TSV per słowo, z sufitem 64 MiB | Iterator wyników, UTF-8 na słowo |
| Linie bazowe | Niedostępne | Przekazywane z TessPageIteratorBaseline |
| Segmentacja strony i tryb engine | Tylko segmentacja automatyczna | THPDFTesseractPageSegMode, THPDFTesseractEngineMode |
| Timeout | Twardy: proces potomny jest kończony | Kooperatywny: Tesseract musi się zorientować |
| Izolacja awarii i pamięci | Osobny proces | Brak, dzieli twoją przestrzeń adresową |
Jeden koszt nie znika. Każde wywołanie Recognize tworzy własną instancję API i woła TessBaseAPIInit2, więc modele językowe są inicjalizowane per strona, a nie raz na engine. Plikowy cache systemu operacyjnego łagodzi przeładowanie, ale na dużych zestawach modeli wielojęzycznych to wciąż dominujący stały koszt na stronę i wlicza się do deadline'u rozpoznawania. Engine DLL RapidOCR w procesie przyjmuje odwrotną konstrukcję i trzyma swoje modele ONNX rezydentnie przez życie engine'a; problemy graniczne (ABI C, pożyczone bufory, nieprzerywalna praca natywna) to ta sama rodzina
Dlaczego Delphi nie może skopiować struktury monitora Tesseracta?
Delphi nie może bezpiecznie odzwierciedlić monitora postępu Tesseracta, bo ETEXT_DESC zawiera pola wewnętrzne zależne od wersji, więc rekord przepisany ręcznie kładzie callback anulowania i deadline na złych offsetach na niektórych kompilacjach. Nic nie wybucha głośno, gdy to się stanie. Tesseract po prostu czyta twój wskaźnik callbacka z pola, które trzyma teraz coś innego, albo w ogóle nie widzi deadline'u
HotPDF traktuje więc monitor jako nieprzezroczysty wskaźnik i dotyka go wyłącznie przez wyeksportowane funkcje: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs i TessMonitorDelete. Jeśli wiążesz C API samodzielnie w innym celu, ten sam wzorzec obowiązuje. Szkic poniżej to twój własny kod wiązania, nie API HotPDF, i odzwierciedla deklaracje, których HotPDF używa wewnętrznie
type
// C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
TTessMonitorCreate = function: Pointer; cdecl; // ETEXT_DESC*, nigdy nie dereferencjonowany
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
// Biegnie na stosie Tesseracta: czytaj flagi i zegar, nigdy nie podnoś wyjątku
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// Użycie, ze wskaźnikami funkcji rozwiązanymi przez GetProcAddress:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
Dwa szczegóły w tym szkicu są zamierzone. Callback zwraca Boolean, czyli jeden bajt i w Delphi, i w Free Pascal, pasujący do C bool w TessCancelFunc. Czterobajtowe Windows BOOL albo Delphi LongBool wygląda wymiennie i nie jest: gdy jedna strona zapisuje pojedynczy bajt, a druga czyta cztery, górne bajty rejestru zwrotnego zawierają to, co tam zostało, i false może przyjść jako true. Ten sam nagłówek komplikuje sprawę dalej, bo funkcje takie jak TessPageIteratorBoundingBox zwracają int, które HotPDF deklaruje jako Integer. Czytaj typ C każdej wartości zwrotnej, zamiast zakładać jedną konwencję dla całego API
Drugi szczegół: callback nigdy nie podnosi wyjątku. Wyjątek Delphi rozwijający się przez ramki C++ Tesseracta to zachowanie niezdefiniowane, więc callback HotPDF czyta wyłącznie token anulowania i monotoniczną wartość GetTickCount64. Adapter zamienia wynik w diagnostykę anulowania albo timeoutu po powrocie TessBaseAPIRecognize i wykonuje to sprawdzenie niezależnie od natywnego kodu zwrotnego
Które natywne wskaźniki posiada strona Delphi?
Adapter DLL Tesseracta w HotPDF posiada trzy natywne obiekty na żądanie — instancję API, monitor i iterator wyników — a wszystko inne pożycza. Każde wywołanie Recognize tworzy własny zestaw i zwalnia go w bloku finally: TessResultIteratorDelete, potem TessMonitorDelete, potem TessBaseAPIDelete. Zwolnienie interfejsu engine'a wyładowuje bibliotekę
TessResultIteratorGetPageIteratorzwraca pożyczony widok wewnątrz iteratora wyników, nie nowy obiekt. HotPDF używa go doTessPageIteratorBoundingBoxiTessPageIteratorBaselinei nigdy go nie zwalnia; skasowanie go osobno zwolniłoby tę samą pamięć dwa razyTessResultIteratorGetUTF8Textzwraca napis alokowany przez własny runtime DLL. HotPDF go kopiuje i oddaje przezTessDeleteTextw blokufinally; PascaloweFreeMemzwolniłoby go na złej stercie- Tekst słowa jest dekodowany ze ścisłą walidacją UTF-8 i sprawdzany na długość przed konwersją. Słowa ze znakami kontrolnymi, zniekształconym UTF-8, boxami poza obrazem, prostokątami odwróconymi albo pewnością poza 0–100 wywalają żądanie, zamiast być po cichu łatanymi
- Łączny tekst na żądanie ma sufit 1 048 576 jednostek kodowych UTF-16, a liczba słów musi się mieścić w budżecie żądania przekazanym przez
ApplyLoadedOCRTextLayer
Pewność przychodzi jako 0–100 i jest skalowana do 0–1, więc THPDFOCRTextLayerOptions.MinimumConfidence znaczy to samo dla każdego engine'a. Gdy Tesseract raportuje linię bazową, oba jej końce są przekazywane; w przeciwnym razie potok warstwy tekstu cofa się do swojego szacunku geometrycznego, dokładnie jak przy wejściu TSV
Dlaczego walidować enum, zanim dotrze do DLL?
HotPDF kopiuje surowy porządek PageSegMode i EngineMode do Integer przed testem zakresu, bo kompilator może założyć, że zmienna enumowa zawsze trzyma zadeklarowaną wartość, i zwinąć Ord(X) > Ord(High(T)) do stałego false. Porządki nie są dekoracją: THPDFTesseractPageSegMode podąża za numeracją segmentacji strony Tesseracta od 0 do 13, THPDFTesseractEngineMode za numeracją trybów engine od 0 do 3, i oba jadą do DLL jako gołe liczby całkowite. Rekord opcji zbudowany przez FillChar, wypełniony ze strumienia albo przekazany z C++Buildera z rzutowaną liczbą może nieść bajt typu 200. Walidacja skopiowanego porządku zamienia to w EArgumentException w czasie budowania fabryki zamiast w niezdefiniowany tryb wewnątrz kodu natywnego. Fabryka odrzuca też tpsOSDOnly i tpsAutoOnly, które nie produkują słów, i wymaga osd.traineddata dla tpsAutoOSD i tpsSparseTextOSD
Co faktycznie gwarantuje timeout rozpoznawania?
Timeout DLL Tesseracta jest kooperatywny: HotPDF może zatrzymać własną pracę i poprosić Tesseracta, żeby stanął, ale nie może zmusić kodu natywnego do powrotu. Zegar startuje, gdy Recognize się zaczyna, więc konwersja bitmapy i inicjalizacja modeli zjadają ten sam budżet co rozpoznawanie. HotPDF sprawdza upływ czasu i token anulowania podczas konwersji do skali szarości i między słowami przy iterowaniu wyników, a pozostałe milisekundy przekazuje do TessMonitorSetDeadlineMSecs przed wywołaniem TessBaseAPIRecognize
Szczelina jest wewnątrz wywołania natywnego. Monitor Tesseracta jest odpytywany podczas rozpoznawania słów, nie podczas TessBaseAPIInit2 ani analizy układu strony, więc powolne ładowanie modeli albo patologiczny układ mogą przebiec za deadline, zanim timeout zostanie zgłoszony. Budżety pikseli i wyjścia też nie ograniczają własnego zużycia pamięci biblioteki natywnej. Jeśli potrzebujesz pracownika, którego można zabić, użyj adaptera procesowego; to uczciwy kompromis, nie brakująca funkcja
Segmentacja strony to miejsce, gdzie adapter DLL zarabia na trudnym wejściu. Formularze, etykiety i zeskanowane tabele z rozrzuconymi polami często rozpoznają się lepiej przy tpsSparseText niż przy segmentacji automatycznej, która próbuje składać kolumny i akapity, których tam nie ma
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; // pola rozrzucone, bez składania kolumn
TessOptions.EngineMode := temLSTMOnly; // wymaga modeli LSTM w tessdata
TessOptions.TimeoutMilliseconds := 20000; // łącznie z inicjalizacją modeli
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;
Timeout wychodzi jako otlsEngineError z diagnostyką Tesseract DLL OCR timed out, a anulowany token jako otlsCancelled. W obu przypadkach ApplyLoadedOCRTextLayer rozpoznało każdą wybraną stronę, zanim zaczęło transakcję zatwierdzania, więc porażka na stronie 40 z 50 zostawia wczytany dokument dokładnie taki, jaki był. Uwaga: tpsSingleLine, tpsSingleBlock i tpsSparseText zmieniają tylko segmentację; żaden z nich nie wyprostuje skrzywionego skanu
Free Pascal i Lazarus: stare piksele i zgubiony chiński
Obie fabryki Tesseracta działają w kompilacjach Windows Free Pascal i Lazarus Win32 oraz Win64 od v2.772.1, po dwóch poprawkach specyficznych dla FPC. Przebuduj najpierw pakiet Lazarusa dla architektury docelowej; ogólny port opisuje HotPDF na Free Pascal i Lazarus Win64
Pierwsza poprawka dotyczy pikseli. LCL-owe TBitmap pisane przez scanline potrafi zaktualizować surowy obraz bez odświeżenia uchwytu bitmapy Windows, więc GetDIBits na tym uchwycie zwraca stare piksele. Objaw był zagadkowy: tekst narysowany wprost na bitmapie był rozpoznawany, a strona wyrenderowana przez renderer PDF HotPDF produkowała pustą listę słów. Na FPC adapter czyta teraz migawkę świadomą formatu przez CreateIntfImage, która respektuje format pikseli i kolejność wierszy surowego obrazu. Kompilacja Delphi trzyma ścieżkę GetDIBits na prywatnej kopii 24-bitowej. Żadna kompilacja nie modyfikuje bitmapy wołającego
Druga poprawka należy do adaptera tesseract.exe. TStringList w FPC trzyma napisy ANSI, więc przypisanie zdekodowanego tekstu TSV UTF-8 do Lines.Text po cichu gubiło każdy chiński znak albo znak z płaszczyzny uzupełniającej, którego systemowa strona kodowa ANSI nie umiała przedstawić. Ścieżka FPC trzyma teraz TSV jako bajty UTF-8, zrzuca BOM na poziomie bajtów i dekoduje każde słowo osobno do UnicodeString. Adapter DLL nigdy tego problemu nie miał, bo dekoduje każde słowo wprost z iteratora
Ściąga
- Fabryka:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])wHPDFTesseractRecognition, dodana w v2.772.0, wsparcie FPC w v2.772.1 - Domyślne:
tpsAuto,temDefault, 60 000 ms, 16 777 216 pikseli; zakres timeoutu 1–3 600 000 ms, sufit pikseli 67 108 864 - Dopasuj bitowość DLL do aplikacji i połóż DLL zależności obok DLL Tesseracta
- Traktuj monitor jako nieprzezroczysty; nigdy nie kopiuj
ETEXT_DESCdo rekordu Pascal - Deklaruj callback anulowania jako
cdeclz jednobajtowym wynikiemBooleani nigdy nie wypuszczaj z niego wyjątku - Zwalniaj tekst iteratora przez
TessDeleteText; nigdy nie zwalniaj iteratora strony uzyskanego z iteratora wyników - Oczekuj, że deadline jest kooperatywny: inicjalizacja modeli i analiza układu mogą go przeskoczyć
- Używaj adaptera tesseract.exe, gdy potrzebujesz twardego zakończenia albo izolacji awarii
Adapter DLL Tesseracta, adaptery procesowe i wbudowany engine OCR jadą wszystkie z komponentem HotPDF Delphi PDF dla Delphi, C++Buildera i Free Pascal; wydania i pobrania znajdziesz na stronie produktu HotPDF