Teknik Makale

HotPDF ile Delphi'de Tesseract OCR'dan aranabilir PDF

HotPDF, taranmış PDF sayfalarını Tesseract ile aranabilir PDF'e çevirir; HPDFCreateTesseractOCREngine, yerel olarak kurulmuş bir Tesseract çalıştırılabilirini IHPDFOCREngine olarak saran bir fabrikadır. O motoru ApplyLoadedOCRTextLayere geçirirsiniz; bu, her sayfayı render eder, sayfa başına bir kez Tesseract çalıştırır, kelime düzeyli TSV çıktısını ayrıştırır ve istenen tüm sayfalar için görünmez bir Unicode metin katmanını tek transactionda işler, ya da hiçbirini işlemez

HotPDF sayfa başına OCR hattı: sayfayı yapılandırılmış DPI'da render et, input.bmpyi özel bir HotPDF-OCR dizinine kaydet, Tesseract alt sürecini tessedit_create_tsv ile başlat, on iki sütunlu TSVyi ayrıştır, kelimeleri güvene göre filtrele ve görünmez metin katmanını istenen tüm sayfalar için ya da hiçbiri için işle
Adaptör yalnızca tanımayı değiştirir: render, ayrıştırma, doğrulama ve hep-ya-da-hiç işlemi mevcut metin katmanı hattında kalır; sonraki kod asla değişmez

Bu adaptörün var olma sebebi kapsamdır. Yerleşik şablon eşleştirmeli OCR motoru bilinçli olarak dardır: makinede basılmış ASCII harfleri ve rakamları, başka hiçbir şey. Aksanlı adlı faturalar, Çince sözleşmeler ve çok dilli arşivler, eğitilmiş dil modelleriyle gerçek bir tanıyıcı ister ve Tesseract bariz adaydır, çünkü uygulamanızın yanına kurabileceğiniz bir komut satırı programıdır. Bir belge kütüphanesinden harici bir program çağırmak basit gibi görünür. Değildir; adaptördeki ilginç kodun çoğu, programın ters davrandığı, takıldığı, iptal edildiği ya da asla görmemesi gereken şeyleri miras aldığı anda olanlarla ilgilidir

HotPDF bir Delphi uygulamasından Tesseract'ı nasıl sürer?

HotPDF, Tesseract'ı sayfa başına gizli bir alt süreç olarak çalıştırır; ona render edilmiş bir bitmap verir, bir TSV dosyası geri okur ve sonucu, yerleşik motorun kullandığı aynı IHPDFOCREngine dikişinden ortaya koyar. Sonraki hiçbir şey değişmez: koordinat eşleme, rotasyon işleme, Unicode doğrulama, güven filtreleme ve atomik işlem, zaten sahip olduğunuz metin katmanı hattıdır. Fabrika HPDFTesseractRecognition biriminde yaşar ve istekli doğrular: çalıştırılabilir var olmalı, tessdata dizini var olmalı, zaman aşımı 1 ile 3.600.000 milisaniye arasında olmalı ve dil tanımlayıcısı yalnızca ASCII harfleri, rakamları, _ ve + içerebilir. Son kontrol önemlidir, çünkü dil dizisi bir komut satırına düşer; eng+chi_sim meşru bir Tesseract değeridir, tırnak ya da boşluk taşıyan her şey değildir

uses
  SysUtils, HPDFTypes, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string;
  Token: THPDFCancellationToken);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // eksik çalıştırılabilir, eksik tessdata, kötü bir dil tanımlayıcısı ya da
  // 1..3600000 ms dışında bir zaman aşımı için EArgumentException fırlatır
  Engine := HPDFCreateTesseractOCREngine(
    'C:\OCR\Tesseract\tesseract.exe',
    'C:\OCR\Tesseract\tessdata',
    'eng+chi_sim',      // '+' ile birleştirilmiş birkaç model
    120000);            // sayfa başına sınır, varsayılan 60000
  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
    Options.CancellationToken := Token;
    // boş sayfa listesi tüm sayfalar demektir; metinli sayfalar varsayılan atlanır
    if Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
    begin
      Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
        ' words accepted, ', Info.DroppedWordCount, ' dropped');
      Doc.SaveLoadedDocument(TargetFile);
    end
    else
      case Info.Status of
        otlsCancelled:      Writeln('Cancelled, document unchanged');
        otlsEngineError:    Writeln('Engine: ', string(Info.Diagnostic));
        otlsBudgetExceeded: Writeln('Budget: ', string(Info.Diagnostic));
      else
        Writeln(string(Info.Diagnostic));
      end;
  finally
    Doc.Free;
  end;
end;

Her sayfa için Recognize, geçici yol altında HotPDF-OCR-{GUID} adında özel bir dizin oluşturur, render edilmiş bitmapi input.bmp olarak kaydeder ve tesseract input.bmp output --tessdata-dir … -l … --dpi N --psm 3 -c tessedit_create_tsv=1 başlatır; her yol argümanı, ters bölü ve gömülü tırnaklar için Windows komut satırı kaçırma kurallarıyla tırnaklanır. --dpi değeri THPDFOCRTextLayerOptions.DPIden gelen render DPIsidir, böylece Tesseract çözünürlüğü görüntü üst verisinden asla tahmin etmek zorunda kalmaz; --psm 3 tümüyle otomatik sayfa bölümleme ister. Motor kendini Tesseract (local CLI) olarak bildirir; Info.EngineNamee düşen budur. Tesseract ve dil modelleri HotPDF ile paketlenmez; onları kurmak uygulamanın işidir

TSV parserı neden bu kadar katı?

HotPDF'in TSV parserı herhangi bir bozuk satırda tüm sayfayı batırır, çünkü kısmen ayrıştırılmış bir kelime listesi görüntüyle sessizce çelişen bir metin katmanı üretir. Tesseract'ın TSV çıktısının sabit bir on iki sütunlu başlığı vardır, levelden texte; HotPDF ilk satırı, isteğe bağlı bir bayt sırası işaretini soyduktan sonra o tam başlıkla karşılaştırır. Sonraki her satır tam olarak on iki alana bölünmeli ve bölünme on birinci sekmeden sonra durmalı, böylece tanınan metnin içindeki bir sekme on üçüncü bir sütun kurmak yerine kelimenin parçası kalır. Yalnızca seviye 5 satırları kelimedir; 1 ile 4 arası seviyeler sayfaları, blokları, paragrafları ve satırları anlatır ve atlanırlar. Metni boş ya da saf boşluk olan seviye 5 satırları da atlanır, çünkü boş bir kelimenin kutusu vardır ama konumlanacak ya da aranacak bir şeyi yoktur. Geri kalan her şey sertçe sınanır: tamsayı geometri, değişmez en-US biçimiyle ayrıştırılmış bir güven — Alman bir yerel ayar 93.5i çöp olarak okumasın — bitmapin tümüyle içinde duran bir kutu ve 0 ile 100 arasında bir güven. Tek bir başarısızlık istisna fırlatır, motor False döndürür ve kelime dizisi temizlenir. Regresyon testleri tam olarak o vakayı içerir: geçerli bir kelimeyi bozuk bir satırın izlemesi sıfır kelime vermeli, bir değil

HotPDF'de her Tesseract TSV satırının geçtiği altı kapı: tam on iki sütunlu başlık, tam olarak on iki alan, yalnızca seviye 5, boş olmayan metin, bitmapin içinde bir kutu ve değişmez biçimde ayrıştırılmış 0 ile 100 arası güven; tek bir bozuk satır tüm sayfayı sıfır kelimeye kadar batırır
Kısmen ayrıştırılmış bir kelime listesi görüntüyle sessizce çelişirdi; parser, zaten okuduğu kelimeleri tutmak yerine ilk bozuk satırda tüm sayfayı reddeder
// HPDFLocalTSVRecognition içindeki seviye-5 döngüsünden derlenmiş
if (Fields.Count <> 12) or not TryStrToInt(Fields[0], Level) then
  raise EConvertError.Create('Invalid Local OCR TSV row');
if Level <> 5 then Continue;                 // sayfa/blok/paragraf/satır satırları
WordText := Fields[11];
if Trim(WordText) = '' then Continue;        // boşluk kelimelerin konumu yoktur
if not TryStrToInt(Fields[6], X) or not TryStrToInt(Fields[7], Y) or
  not TryStrToInt(Fields[8], W) or not TryStrToInt(Fields[9], H) or
  not TryStrToFloat(Fields[10], Confidence, Settings) then
  raise EConvertError.Create('Invalid Local OCR word geometry');
if (X < 0) or (Y < 0) or (W <= 0) or (H <= 0) or
  (Int64(X) + W > Request.Bitmap.Width) or
  (Int64(Y) + H > Request.Bitmap.Height) or
  not ((Confidence >= 0) and (Confidence <= 100)) then
  raise EConvertError.Create('Local OCR word is outside the image');
Words[Count].Confidence := Confidence / 100;  // hat 0..1 bekler

O son satır, beklemediğiniz bir varsayılanla etkileşir. Tesseract güveni 0 ile 100 arasında koşar, hat 0 ile 1 arasında çalışır ve THPDFOCRTextLayerOptions.MinimumConfidence varsayılanı 0,5'tir; 50nin altındaki her Tesseract kelimesi Info.DroppedWordCountte sayılır ve sayfaya asla ulaşmaz. Temiz bir 300 DPI taramada bu makul bir tabandır. Gürültülü bir faksta sayfanın şaşırtıcı bir payını düşürebilir ve doğru hamle, eşiği düşürmeden önce düşen sayıya bakmaktır; çünkü düşük güvenli kelimeler, tam olarak yanlış olma ihtimali en yüksek olanlardır

Tesseract alt süreci neyi miras alır?

Tesseract alt süreci HotPDF'ten tam olarak iki tutamaç miras alır: standart girdi ve çıktı için bir NUL tutamacı ve standart hata için bir dosya tutamacı. O hassasiyet konunun ta kendisidir. bInheritHandles = True ile CreateProcess, standart tutamaçları bir çocuğa geçme yoludur ama tek başına ana süreçteki her kalıtsal tutamacı geçirir; uygulamanızdaki alakasız kodun açtığı dosyalar, pipe'lar ve olaylar dahil. Alt süreç sonra o nesneleri çıkana dek canlı tutar; Tesseract bir sayfayı öğütürken bir dosya kilitli kalır ya da bir pipe ucunu hiç görmez. HotPDF o boşluğu genişletilmiş başlangıç kaydıyla kapatır: STARTUPINFOEX, PROC_THREAD_ATTRIBUTE_HANDLE_LIST taşıyan bir öznitelik listesi ve EXTENDED_STARTUPINFO_PRESENT oluşturma bayrağı. Tutamaç listesi yerindeyken bInheritHandles yine de True olmak zorundadır ama sınırı yalnızca listelenen tutamaçlar geçer. Aynı kapatma düşüncesi, çocuğun güvenilmeyen kod olduğu işçi süreçlerinde PDF görüntü codec'lerini izole etmeki de sürer; burada çocuk güvenilirdir ama ana, kendi tutamaç tablosunun tek sahibi değildir

HotPDF'de Tesseract alt süreç tutamaç mirası: bInheritHandles ile düz bir CreateProcess her kalıtsal dosya, pipe ve olay tutamacını çocuğa geçirir; PROC_THREAD_ATTRIBUTE_HANDLE_LIST ile STARTUPINFOEX ise kümeyi stdin ve stdout için bir NUL tutamacı artı stderr dosya tutamacıyla sınırlar
Öznitelik listesi olmadan alt süreç, çıkana dek alakasız nesneleri canlı tutar; dosyaları kilitler, pipe'ları aç bırakır. Onunla yalnızca listelenen iki tutamaç sınırı geçer
// sabitler adlarıyla gösteriliyor; kaynak sayısal değerlerini geçirir
// iki tutamaç da bInheritHandle = True ile oluşturulur
InheritedHandles[0] := NullHandle;    // stdin ve stdout
InheritedHandles[1] := ErrorHandle;   // özel dizinde stderr.txt
InitializeProcThreadAttributeList(Startup.AttributeList, 1, 0, AttributeBytes);
UpdateProcThreadAttribute(Startup.AttributeList, 0,
  PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
  @InheritedHandles[0], SizeOf(InheritedHandles), nil, nil);
CreateProcess(PChar(Executable), PChar(Command), nil, nil,
  True,                                        // tutamaç listesi bunu gerektirir
  CREATE_NO_WINDOW or EXTENDED_STARTUPINFO_PRESENT,
  nil, PChar(DirectoryName), Startup.StartupInfo, ProcessInfo);

İptal edilmiş bir OCR çalıştırması neden motor hatası gibi görünebilir?

İptal edilmiş bir OCR çalıştırması motor hatası gibi görünür, çünkü IHPDFOCREngine.Recognize tek bir Boolean döndürür ve False, hem "Tesseract başarısız oldu" hem de "kullanıcı Cancel'a bastı" demektir. Adaptör, alt süreç çalışırken iptal tokenini ve zaman aşımını her 25 milisaniyede yoklar; token tetiklendiğinde Recognize içinde istisna fırlatır, kendi istisnasını yakalar, temizlik yapar ve bir tanıyla False döndürür. Hat bunu motor hatası saysaydı çağıran, kullanıcının bilinçli durdurduğu bir iş için otlsEngineError görürdü. ApplyLoadedOCRTextLayer bu yüzden Recognize False döndürdüğünde önce tokeni kontrol eder ve sonucu yalnızca token set edilmemişse motor hatasına çevirir. O sıralama çok sayfalı sözleşmeyi korur: tanıma, doğrulama, bütçe muhasebesi ve içerik kurulumu, graf transactionı açılmadan önce istenen her sayfa için çalışır; 50 sayfalık işte 40. sayfadaki bir iptal otlsCancelled bildirir ve belgeyi, ilk 39 sayfa dahil, dokunulmamış bırakır. Sonradan açıklanacak kısmen aranabilir bir dosya yoktur ve başarısızlık işlemenin gerisi aynı sınırlı tarzı izler:

  • Zaman aşımı Recognize çağrısı başınadır, başlangıcından itibaren ölçülür; varsayılan 60.000 ms tüm belgeye değil her sayfaya uygulanır
  • Zaman aşımında ya da iptalde hâlâ koşan bir alt süreç sonlandırılır, en çok 5 saniye beklenir ve özel dizini bir finally bloğunda silinir
  • output.tsv 64 MiB ile stderr.txt 1 MiB ile sınırlıdır; alt süreç çalışırken de çıktıktan sonra da kontrol edilir
  • Kelime sayısı ve UTF-16 kod birimleri, kalan MaxWordsPerPage, MaxTotalWords ve MaxTextCodeUnits bütçeleriyle sayfa başına sınırlandırılır; aşmak kelime listesini kırpmak yerine çalıştırmayı batırır
  • Standart çıktı NULe gider, çünkü Tesseract output.tsvyi yazar; standart hata ise bir dosyaya gider, böylece sıfır olmayan çıkış kodu motorun kendi şikâyetinden en çok 4.096 karakterle bildirilir — genelde bir .traineddata dosyasının eksik olduğunu öğrenmenin en hızlı yolu

Tanınan kelimeler görünmez bir metin katmanına nasıl dönüşür?

HotPDF, Tesseract kelimelerini metin render modu 3 ile — ISO 32000-1 §9.3.6'da tanımlanan ne doldurma ne çizme modu — görünmez metin olarak yazar; sayfa taranmış görüntüyü göstermeye devam ederken arama ve kopyalama tanınan kelimeler üzerinde çalışır. İçerik akışı BTyi 3 Tr ile açar ve her kelime baselineında bir Tm matrisi, render DPIsında piksel cinsinden kutu yüksekliğinden türetilmiş bir font boyutu ve glyph dizisini ölçülen kutu genişliğine geren bir Tz yatay ölçeği alır; arama vurgusunun görüntüdeki kelimenin üzerinde, onun üzerinde sürüklenmek yerine oturmasının sebebi budur

Tesseract'ın TSVsinde kutular var ama baseline yoktur; adaptör her kelimeyi baselinesız bildirir ve hat baselineı, alt kenarın üzerinde kutu yüksekliğinin beşte biri olarak kestirir. Metnin kendisi, Identity-H kodlamalı ve üretilmiş bir ToUnicode CMap taşıyan paylaşılan gömülü olmayan bir Type0 fonttan geçer; tüm çalıştırma boyunca her ayrı Unicode skaler için bir CID — Çincenin, aksanlı Latinin ve ek düzlem karakterlerinin kopyalama ve aramadan sağ çıkmasının yolu budur. O tasarımın baştan söylenmeye değer iki sınırı vardır: bir çalıştırma en çok 65.535 ayrı skaler taşıyabilir ve gömülü olmayan font, ISO 19005'in font gömme gereksinimini karşılamaz; PDF/A çıktısı ayrıca gömülmüş uyumlu bir font ister. Sonucu kontrol etmek basittir ve otomatikleştirmeye değer: kaydedin, yeniden yükleyin ve Delphi'de yüklü bir PDF'ten metin çıkarmak yazısındaki sıradan yüklü belge metin yolunu çalıştırın; kelimeler beklenen sayfalardan dönerse katman gerçektir

Aynı TSV protokolünde RapidOCR ve diğer motorlar

HotPDF, RapidOCR için aynı süreç çalıştırıcıyı ve TSV parserı HPDFCreateRapidOCREngine(PythonExecutable, BridgeScript, ModelDirectory, TimeoutMilliseconds) üzerinden yeniden kullanır; Basit Çince taramalar için daha işlevsel tercih budur. Komut satırı özdeştir; yalnızca köprü script yolu Python çalıştırılabilirinden sonra yerleştirilir ve dil chi_sime sabitlenir. HotPDF köprüyü tools/OCR/rapidocr_tsv.py olarak dağıtır; rapidocr ile onnxruntime paketlerini ve üç yerel ONNX modelini bekler, otomatik model indirmelerini kapatır ve Tesseract biçimli TSV yazar, böylece Delphi tarafı ikinci bir parsera ihtiyaç duymaz. Info.EngineNamede bildirilen motor adı RapidOCR (local ONNX)dir. O biçim genel tarifi önerir: Tesseract tarzı argüman listesini kabul eden ve on iki sütunlu TSV çıkaran küçük bir scriptle sardığınız her tanıyıcı, tutamaç izolasyonunu, zaman aşımını, iptali, çıktı bütçelerini ve hep-ya-da-hiç işlemi bedavaya devralır. Adaptörler yalnızca Windows'tur, bir anda tek sayfayı senkron çalıştırır ve renderın ürettiğinin ötesinde görüntüyü düzleştirmez ya da ön işlemez; giren görüntü kalitesi çıkanın tavanını hâlâ belirler

Tesseract ve RapidOCR adaptörleri, görünmez metin katmanı yazıcısı, onları besleyen sayfa renderı ve sonucu doğrulayan metin çıkarımı, Delphi ve C++Builder için aynı yerli VCL componentiyle gelir. Belge yakalama ya da arşivleme uygulamasına OCR ekliyorsanız HotPDF Delphi PDF component, yalnızca OCR motorunun kurulumunun kaldığı hattı size verir