Teknik Makale

HotPDF Tesseract DLL OCR: Delphi'den C API'sini çağırmak

HotPDF, Tesseract'ı Delphi sürecinizin içinde HPDFCreateTesseractDLLOCREngine üzerinden koşturur; bu, v2.772.0'da eklenen ve Tesseract 5 uyumlu bir DLL'i dinamik yükleyen, C API'sini (TessBaseAPIInit2, TessBaseAPIRecognize, result iterator) süren ve bir IHPDFOCREngine döndüren fabrikadır. THotPDF.ApplyLoadedOCRTextLayer o motoru, taranmış PDF sayfalarına görünmez, aranabilir bir Unicode metin katmanı eklemek için kullanır

Aynı tanıyıcıya BMP yazıp TSV ayrıştıran harici tesseract.exe adaptörü üzerinden de zaten ulaşılıyordu. O yol çalışır; ama her sayfa bir süreç başlatmanın, geçici bir bitmap dosyasının ve taban çizgisi olmayan, sayfa segmentasyonu üzerinde hiçbir kontrol vermeyen bir metin formatının bedelini öder. DLL'i çağırmak üçünü de kaldırır. Aynı zamanda süreç duvarını da kaldırır; yani bir Pascal bağlaması doğrudan C yapıtlarının, C Boolean'larının ve C ayrılmış string'lerin üstüne oturur. Bu adaptör hakkında bilinmeye değer olanın çoğu, o bağlamanın sessizce nerede yanlış gidebileceğidir

HotPDF ile Tesseract'ı Delphi'den süreç içinde nasıl koşturursunuz?

HotPDF ile Tesseract'ı süreç içinde koşturmak, HPDFTesseractRecognition unitinde tek bir fabrika çağrısı ve her HotPDF OCR motorunun kullandığı aynı ApplyLoadedOCRTextLayer çağrısıdır. Fabrika istekli doğrular. DLL dosyası ile tessdata dizini var olmalı, dil tanımlayıcısı yalnızca ASCII harfleri, rakamları, _ ve +'ı içerebilir, chi_sim+eng gibi bir birleşimdeki her modelin eşleşen bir .traineddata dosyası olmalı ve motor döndürülmeden önce 21 zorunlu exportun hepsi çözülmelidir. Yapılandırma hataları EArgumentException fırlatır; yüklenemeyen bir DLL, Windows hata koduyla ve mimari ile bağımlılıkları denetleme ipucuyla EOSError fırlatır

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Bir Win64 uygulaması 64 bitlik DLL ister; bağımlılık DLL'leri yanına gider
  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
    // Boş sayfa listesi her sayfa demektir; metni hâlihazırda olan sayfalar atlanır
    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, PageSegMode'u tpsAuto'ya, EngineMode'u temDefault'a, TimeoutMilliseconds'i 60.000'e ve MaxPixels'i 16.777.216'ya set eder. Pixel bütçesi göründüğünden daha önemlidir. Varsayılan 300 DPI'da bir US Letter sayfası 2.550 × 3.300, yani kabaca 8,4 milyona render olur; bu sığar. Aynı sayfa 600 DPI'da 5.100 × 6.600, kabaca 33,7 milyondur ve adaptör, Tesseract tek bir pixel görmeden onu reddeder. MaxPixels'i yükseltin (tavan 67.108.864) ya da DPI'yı olduğu yerde tutun; her kenar ayrıca 32.767 pixel'de tavanlanır

DLL, DLL'in kendi klasörünü artı varsayılan güvenli dizinleri kapsayan arama bayraklarıyla LoadLibraryEx ile yüklenir; böylece Tesseract'ın bağımlı olduğu image kütüphaneleri PATH'e ya da geçerli dizine dokunmadan yanında durabilir. HotPDF hiçbir OCR çalışma zamanını ya da modelini paketlemez ya da indirmez; ikisini de siz temin edersiniz

tesseract.exe adaptörüyle karşılaştırıldığında ne değişir?

DLL adaptörü, süreç izolasyonunu daha zengin çıktı ve daha düşük sayfa-başı ek yük için takas eder. İki adaptör de aynı metin katmanı hattına takılır; dolayısıyla koordinat eşleme, güven filtreleme ve hep-ya-da-hiç işleme birebir aynıdır. Değişen şey, pixel'lerin nasıl girdiği ile kelimelerin nasıl çıktığıdır

Yöntesseract.exe adaptörüTesseract DLL adaptörü
FabrikaHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
PixellerÖzel geçici dizinde BMP dosyasıBellekte 8 bitlik gri tonlama tamponu
KelimelerKelime düzeyli TSV, 64 MiB'de tavanlıResult iterator, kelime başına UTF-8
Taban çizgileriKullanılamazTessPageIteratorBaseline'dan geçirilir
Sayfa segmentasyonu ve motor moduYalnızca otomatik segmentasyonTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
Zaman aşımıSert: çocuk süreç öldürülürİşbirlikçi: Tesseract fark etmelidir
Çökme ve bellek izolasyonuAyrı süreçYok, adres uzayınızı paylaşır

Tek bir maliyet kaybolmaz. Her Recognize çağrısı kendi API instance'ını kurar ve TessBaseAPIInit2 çağırır; dolayısıyla dil modelleri motor başına bir kez değil, sayfa başına başlatılır. İşletim sisteminin dosya cache'i yeniden yüklemeyi yumuşatır ama büyük çok dilli model setlerinde yine de sayfa başına baskın sabit maliyettir ve tanıma son tesliminden düşer. Süreç içi RapidOCR DLL motoru ters tasarımı seçer ve ONNX modellerini motorun ömrü boyunca yerleşik tutar; sınır sorunları (C ABI, ödünç tamponlar, kesintisiz native iş) aynı ailedendir

Delphi Tesseract monitor yapısını neden kopyalayamaz?

Delphi, Tesseract ilerleme monitorunu güvenle aynalayamaz, çünkü ETEXT_DESC sürüme bağlı dâhilî alanlar taşır; elle kopyalanmış bir record, cancel callback'ini ile son teslimi bazı derlemelerde yanlış ofsetlere koyar. O olduğunda hiçbir şey gürültüyle düşmez. Tesseract ya callback işaretçinizi artık başka bir şey tutan bir alandan okur ya da son teslimi hiç görmez

HotPDF bu yüzden monitoru opak bir işaretçi sayar ve ona yalnızca export edilmiş fonksiyonlar üzerinden dokunur: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs ve TessMonitorDelete. C API'sini başka bir amaçla kendiniz bağlıyorsanız aynı örüntü geçerlidir. Aşağıdaki taslak, kendi bağlama kodunuzdur — HotPDF API'si değil — ve HotPDF'in dâhilde kullandığı bildirimleri aynalar

HotPDF Tesseract DLL monitor erişimi: sürüme bağlı ETEXT_DESC record'unu kopyalamak cancel callback'ini ile son teslimi yanlış ofsetlere koyar ve sessizce düşer; HotPDF ise monitoru opak sayar, TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc ile TessMonitorSetDeadlineMSecs'i sürer ve cdecl callback'i istisnasız tutar
opak bir işaretçi artı beş export, sözleşmenin tamamıdır; callback, yalnızca bir bayrak ile bir saat okuyan tek baytlık bir Boolean olarak kalır
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, asla dereference edilmez
  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'ın stack'inde koşar: bayrakları ve saati okuyun, asla fırlatmayın
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Kullanım; fonksiyon işaretçileri GetProcAddress ile çözülür:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end;

O taslaktaki iki ayrıntı kasıtlıdır. Callback Boolean döndürür; bu, Delphi'de de Free Pascal'da da bir bayttır ve TessCancelFunc'teki C bool'una uyar. Dört baytlık Windows BOOL'u ya da Delphi LongBool'u birbirinin yerine geçiyormuş gibi görünür ve geçmez: bir taraf tek bayt yazarken öteki dört bayt okuduğunda dönüş register'ının üst baytları orada kalmış olan her neyse odur ve bir false, true olarak gelebilir. Aynı header işi daha da zorlaştırır; TessPageIteratorBoundingBox gibi fonksiyonlar HotPDF'in Integer olarak bildirdiği bir int döndürür. Bütün API için tek bir kural varsaymak yerine her dönüş değerinin C tipini okuyun

İkinci ayrıntı, callback'in asla fırlatmamasıdır. Delphi istisnasının Tesseract'ın C++ çerçeveleri boyunca açılması tanımsız davranıştır; HotPDF'in callback'i bu yüzden yalnızca cancellation token'ı ve monotonik bir GetTickCount64 değeri okur. Adaptör sonucu, TessBaseAPIRecognize döndükten sonra bir iptal ya da zaman aşımı teşhisine çevirir ve o denetimi native dönüş kodundan bağımsız yapar

Delphi tarafı hangi native işaretçilere sahiptir?

HotPDF Tesseract DLL adaptörü istek başına üç native objecte sahiptir — API instance'ı, monitor ve result iterator — ve geri kalan her şeyi ödünç alır. Her Recognize çağrısı kendi setini kurar ve bir finally bloğunda bırakır: önce TessResultIteratorDelete, sonra TessMonitorDelete, sonra TessBaseAPIDelete. Motor arayüzünü bırakmak kütüphaneyi boşaltır

Recognize çağrısı başına HotPDF Tesseract DLL object sahipliği: result iterator, monitor ve API instance'ına sahip olunur ve bu sırayla finally içinde free edilir; TessResultIteratorGetPageIterator'dan gelen page iterator asla free edilmemesi gereken ödünç bir görünümdür; GetUTF8Text string'leri kopyalanır ve TessDeleteText ile geri verilir
üç objecte sahip olunur, geri kalan her şey ödünç alınır: sabit sırayla free edin, page iterator'ı asla çift free etmeyin ve ayırıcıları asla karıştırmayın
  • TessResultIteratorGetPageIterator, result iterator'ın içine ödünç bir görünüm döndürür, yeni bir object değil. HotPDF onu TessPageIteratorBoundingBox ile TessPageIteratorBaseline için kullanır ve asla free etmez; onu ayrıca silmek aynı belleği iki kez free etmek olurdu
  • TessResultIteratorGetUTF8Text, DLL'in kendi çalışma zamanı tarafından ayrılmış bir string döndürür. HotPDF onu kopyalar ve bir finally bloğunda TessDeleteText ile geri verir; Pascal FreeMem'i yanlış heap'te bırakırdı
  • Kelime metni katı UTF-8 doğrulamasıyla decode edilir ve dönüşümden önce uzunluğu denetlenir. Kontrol karakteri taşıyan, bozuk UTF-8'li, image dışına çıkan kutulu, ters çevrilmiş dikdörtgenli ya da güveni 0-100 dışında olan kelimeler sessizce yamalanmak yerine isteği düşürür
  • İstek başına toplam metin 1.048.576 UTF-16 code unit'te tavanlıdır ve kelime sayısı, ApplyLoadedOCRTextLayer'ın aşağı aktardığı istek bütçesine sığmak zorundadır

Güven 0-100 olarak gelir ve 0-1'e ölçeklenir; dolayısıyla THPDFOCRTextLayerOptions.MinimumConfidence her motor için aynı anlama gelir. Tesseract bir taban çizgisi bildirdiğinde her iki uç geçirilir; bildirmediğinde metin katmanı hattı, TSV girdisinde yaptığı gibi geometrik tahminine döner

Bir enum, DLL'e ulaşmadan önce neden doğrulanır?

HotPDF, PageSegMode ile EngineMode'un ham ordinal'ini aralık denetiminden önce bir Integer'a kopyalar; çünkü derleyici, bir enum değişkeninin daima bildirilmiş bir değer tuttuğunu varsayıp Ord(X) > Ord(High(T))'yi sabit bir false'a katlayabilir. Ordinaller süs değildir: THPDFTesseractPageSegMode Tesseract'ın sayfa segmentasyonu numaralandırmasını 0'dan 13'e izler, THPDFTesseractEngineMode motor modu numaralandırmasını 0'dan 3'e izler ve ikisi de DLL'e sade integer olarak gider. FillChar ile kurulmuş, bir stream'den doldurulmuş ya da C++Builder'dan cast edilmiş bir integer'la geçirilmiş bir options record, 200 gibi bir bayt taşıyabilir. Kopyalanan ordinali doğrulamak bunu native kodun içinde tanımsız bir mod yerine fabrika zamanında bir EArgumentException'ye çevirir. Fabrika ayrıca kelime üretmeyen tpsOSDOnly ile tpsAutoOnly'yi reddeder ve tpsAutoOSD ile tpsSparseTextOSD için osd.traineddata ister

Tanıma zaman aşımı aslında neyi garanti eder?

Tesseract DLL zaman aşımı işbirlikçidir: HotPDF kendi işini durdurup Tesseract'a da durmasını söyleyebilir ama native kodu dönmeye zorlayamaz. Saat, Recognize başladığında işler; dolayısıyla bitmap dönüşümü ile model başlatma, tanımayla aynı bütçeyi tüketir. HotPDF geçen süreyi ile cancellation token'ı gri tonlama dönüşümü sırasında ve sonuçları gezerken kelimeler arasında denetler ve TessBaseAPIRecognize'i çağırmadan önce kalan milisaniyeleri TessMonitorSetDeadlineMSecs'e geçirir

Boşluk native çağrının içindedir. Tesseract'ın monitoruna kelime tanıma sırasında başvurulur; TessBaseAPIInit2 ya da sayfa düzeni analizi sırasında değil. Dolayısıyla yavaş bir model yüklemesi ya da patolojik bir düzen, zaman aşımı bildirilmeden son teslimi aşabilir. Pixel ve çıktı bütçeleri de native kütüphanenin kendi bellek kullanımını tavanlamaz. Öldürebileceğiniz bir işçi istiyorsanız süreç adaptörünü kullanın; dürüst takas budur, eksik bir özellik değil

HotPDF Tesseract DLL işbirlikçi zaman aşımı anatomisi: saat, Recognize başladığında işler ve gri tonlama dönüşümünü, TessBaseAPIInit2 ile düzen analizini kapsar; ama monitora yalnızca kelime tanıma sırasında başvurulur, dolayısıyla model yüklemeleri ile düzen HotPDF otlsEngineError ya da otlsCancelled bildirmeden önce son teslimi aşabilir
buradaki son teslim bir istektir, garanti değil: başlatma ile düzen analizi uzun sürebilir ve gerçekten ölebilen bir işçi süreç adaptörü ister

Sayfa segmentasyonu, DLL adaptörünün zorlu girdide parladığı yerdir. Dağınık alanlı formlar, etiketler ve taranmış tablolar, olmayan sütunları ve paragrafları kurmaya çalışan otomatik segmentasyondan çok tpsSparseText ile daha iyi tanınabilir

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;  // dağınık alanlar, sütun kurma yok
  TessOptions.EngineMode := temLSTMOnly;     // tessdata'da LSTM modelleri ister
  TessOptions.TimeoutMilliseconds := 20000;  // model başlatmasını içerir
  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;

Bir zaman aşımı, Tesseract DLL OCR timed out teşhisiyle otlsEngineError olarak yüzeye çıkar; iptal edilen token ise otlsCancelled olarak. Her iki durumda da ApplyLoadedOCRTextLayer, işleme işlemini başlatmadan önce seçilen her sayfayı tanımış olur; dolayısıyla 50 sayfanın 40'ındaki bir başarısızlık yüklenen belgeyi tam olarak olduğu gibi bırakır. tpsSingleLine, tpsSingleBlock ve tpsSparseText'in yalnızca segmentasyonu değiştirdiğini unutmayın; hiçbiri eğik bir taramayı düzleştirmez

Free Pascal ve Lazarus: bayat pixel'ler ve kaybolan Çince

İki Tesseract fabrikası da, iki FPC'ye özgü düzeltmenin ardından, Windows Free Pascal ile Lazarus Win32 ve Win64 derlemelerinde v2.772.1'den beri çalışır. Önce Lazarus paketini hedef mimari için yeniden derleyin; genel port Free Pascal ve Lazarus Win64'te HotPDFde kapsanır

İlk düzeltme pixel'lerle ilgilidir. Scanline'lar üzerinden yazılan bir LCL TBitmap, Windows bitmap handle'ını tazelemeden ham image'ini güncelleyebilir; o handle üzerindeki GetDIBits böylece eski pixel'leri döndürür. Belirti şaşırtıcıydı: doğrudan bir bitmap üzerine çizilen metin tanınıyordu, HotPDF'in PDF renderer'ının render ettiği bir sayfa ise boş bir kelime listesi üretiyordu. FPC'de adaptör artık ham image'in pixel formatını ve satır sırasını gözeten, CreateIntfImage üzerinden format-farkındalıklı bir anlık görüntü okur. Delphi derlemesi, özel bir 24 bitlik kopyada GetDIBits yolunu korur. İki derleme de çağıranın bitmap'ini değiştirmez

İkinci düzeltme tesseract.exe adaptörüne aittir. FPC'nin TStringList'i ANSI string saklar; dolayısıyla decode edilmiş UTF-8 TSV metnini Lines.Text'e atamak, sistem ANSI kod sayfasının temsil edemediği her Çince ya da yardımcı düzlem karakterini sessizce düşürüyordu. FPC yolu artık TSV'yi UTF-8 baytları olarak tutar, BOM'u bayt düzeyinde soyar ve her kelimeyi tek tek UnicodeString'e decode eder. DLL adaptöründe bu sorun hiç olmadı; çünkü her kelimeyi doğrudan iterator'dan decode eder

Hızlı başvuru

  • Fabrika: HPDFTesseractRecognition'da HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]), v2.772.0'da eklendi, FPC desteği v2.772.1'de
  • Varsayılanlar: tpsAuto, temDefault, 60.000 ms, 16.777.216 pixel; zaman aşımı aralığı 1-3.600.000 ms, pixel tavanı 67.108.864
  • DLL bitliğini uygulamayla eşleştirin ve bağımlılık DLL'lerini Tesseract DLL'inin yanına koyun
  • Monitoru opak sayın; ETEXT_DESC'i asla bir Pascal record'una kopyalamayın
  • Cancel callback'ini tek baytlık Boolean sonuçla cdecl bildirin ve ondan bir istisna asla kaçmasın
  • Iterator metnini TessDeleteText ile free edin; result iterator'dan alınan page iterator'ı asla free etmeyin
  • Son teslimin işbirlikçi olmasını bekleyin: model başlatma ile düzen analizi onu aşabilir
  • Sert sonlandırma ya da çökme izolasyonu gerektiğinde tesseract.exe adaptörünü kullanın

Tesseract DLL adaptörü, süreç adaptörleri ve yerleşik OCR motoru, Delphi, C++Builder ve Free Pascal için HotPDF Delphi PDF bileşeniyle gelir; sürümler ile indirmeler için HotPDF ürün sayfasına bakın