Teknik Makale

Delphi'de HotPDF RapidOCR ile Çince ve çok dilli OCR

HotPDF, Çince ile çok dilli OCR'ı Delphi'de native RapidOCR DLL adaptörü üzerinden yapar: THPDFRapidOCRDLLOptions.ForLanguage, 'zh-CN', 'zh-TW', 'ru' ya da 'ar' gibi bir dil etiketini eşleşen bir recognition modeli ve karakter sözlüğüne eşler ve THotPDF.ApplyLoadedOCRTextLayer tanınan satırları taranmış PDF sayfalarında görünmez, aranabilir bir Unicode metin katmanına çevirir

Latin alfabesiyle bir demo çalıştırmak işin kolay kısmıdır. İlginç başarısızlıklar Geleneksel Çince'ye ya da Rusça'ya geçip çıktı kendinden emin, düzgün biçimli bir saçmalığa dönüştüğünde; her satır son karakterini sessizce yitirdiğinde ya da bir Arapça sayfa metin kutuları yanlış sırada döndüğünde başlar. Bunların hiçbiri kendi başına istisna fırlatmaz. HotPDF v2.775.0'ın eklediği dil ön ayarları çoğunlukla o boşlukları kapatmak için vardır ve aşağıdaki dört tuzak, native koda hiç dokunmayacaksanız bile anlamaya değer; çünkü her biri, yoksa bir gününüzü götürebilecek bir belirtiyi açıklar

ForLanguage bir model ile sözlüğü nasıl seçer?

THPDFRapidOCRDLLOptions.ForLanguage, bir etiketi dokuz profilden birine çözer ve model dizininiz altındaki <profile>/recognition.onnx ile <profile>/dictionary.txt'e işaret eden optionlar döndürür; paylaşılan detector'ı, isteğe bağlı açı sınıflandırıcısını ve THPDFRapidOCRDLLOptions.Default'tan gelen thread, pixel ve timeout varsayılanlarını korur. Metot etiketi küçük harfe çevirir, alt çizgileri tireye dönüştürür ve uçtaki boşlukları kırpar; böylece 'zh_TW', 'ZH-tw' ve ' zh-tw ' aynı profile düşer. Takma adlar önek eşleşmesi değil açık bir listedir: 'zh-Hant-TW' listelendiği için kabul edilir, listelenmemiş keyfi bir bölgesel varyant ise hiçbir model yüklenmeden EArgumentException fırlatır

THPDFRapidOCRDLLOptions için HotPDF ForLanguage profil çözümü: zh_TW, ZH-tw ve zh-TW gibi etiketler normalize edilir ve listelenen dokuz profille eşleştirilir; her profil daima birlikte set edilen bir recognition modeli ve sözlük sabitler, listelenmemiş etiket ise hiçbir model yüklenmeden EArgumentException fırlatır
tek etiket tek sabitlenmiş model-sözlük çiftini seçer; detector, sınıflandırıcı ve bütçeler paylaşımlı kalır ve bilinmeyen bir etiket hiçbir şey yüklemeden hızlıca düşer
ProfilDillerÖrnek etiketlerSabitlenen model
chBasitleştirilmiş Çince ve İngilizcezh, zh-CN, zh-Hans, chi_simPP-OCRv4
chinese_chtGeleneksel Çincezh-TW, zh-HK, zh-Hant, chi_traPP-OCRv3
enİngilizceen, en-US, en-GB, engPP-OCRv4
latinFransızca, Almanca, İspanyolca, Portekizce, İtalyanca, Hollandaca, Türkçefr, de, es-419, pt-BR, trPP-OCRv3
japanJaponcaja, ja-JP, jpnPP-OCRv4
koreanKoreceko, ko-KR, korPP-OCRv4
cyrillicRusça, Ukraynaca, Bulgarca, Belarusçaru, ru-RU, uk, bgPP-OCRv3
arabicArapça, Farsça, Urducaar, ar-SA, fa, urPP-OCRv4
devanagariHintçe, Marathi, Nepalcehi, mr, nePP-OCRv4

Adaptörün kendisi asla bir şey indirmeyi denemez. Dosyaları bir kez paketlenen yardımcıyla temin edersiniz — örneğin tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (dokuz profilin hepsi için -Language All) — yardımcı da paylaşılan detector ile sınıflandırıcıyı Default'un beklediği kök dosya adlarına koyar. Ondan sonra Basit Çince bir tarama birkaç satırla aranabilir olur. Motor döşemesi, süreç içi RapidOCR DLL'i ve ABI sınırı yazısında anlatılan aynı IHPDFOCREngine dikişidir; bu yazı da dillerde kalır

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

procedure MakeChineseScanSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Models: THPDFRapidOCRDLLOptions;
  Layer: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // ch/recognition.onnx + ch/dictionary.txt, paylaşılan detector ve classifier
  Models := THPDFRapidOCRDLLOptions.ForLanguage('zh-CN');
  Engine := HPDFCreateRapidOCRDLLOCREngine(
    'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
  Doc := THotPDF.Create(nil);
  try
    Doc.AutoLaunch := False;
    if Doc.LoadFromFile(SourceFile) < 1 then
      raise Exception.Create('Cannot load ' + SourceFile);
    Layer := 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, Layer, Info) then
      raise Exception.Create(string(Info.Diagnostic));
    Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
      ' lines, ', Info.UniqueScalarCount, ' distinct characters');
    Doc.SaveLoadedDocument(TargetFile);
  finally
    Doc.Free;
  end;
end;

O çıktıdaki iki ayrıntı notu hak eder. Native hattı saptanan metin satırı başına bir sonuç döndürür, kelime başına değil; dolayısıyla AcceptedWordCount burada satır sayar ve MinimumConfidence, bütün satırın ortalama karakter güveniyle karşılaştırılır: ortalaması 0,45 olan bir satır birim olarak düşürülür. UniqueScalarCount, metin katmanının fontuna ile ToUnicode tablosuna kaç ayrı Unicode skaler eşlemek zorunda kaldığını bildirir; CJK metninin gerçekten ulaştığını, bir avuç Latin yedeği yerine konduğunu gösteren faydalı bir sağlamlık sınamasıdır. Motor arayüzünü belgeler boyunca canlı tutun; çünkü model başlatma fabrikada olur ve pahalı adım odur

Yalnızca recognition modelini değiştirmek neden çöp üretir?

Bir CTC recognition modeli asla karakter çıktılamaz, yalnızca sınıf indeksi çıkarır ve indeks 1.204'ü bir glyphe çeviren tek şey sözlüktür. ch/recognition.onnx yerine cyrillic/recognition.onnx takıp Çince sözlüğü tutun; model, geçerli Kiril indekslerini memnuniyetle üretir ve eski sözlük onları rastgele Han karakterlerine çevirir. Sonuç metne benzer, UTF-8 doğrulamasından geçer ve tam olarak hiçbir şey için aranabilirdir. ForLanguage'in RecognitionModel ile CharacterDictionary'yi daima birlikte set etmesi ve elle kurulan optionların birini ötekisiz asla değiştirmemesi bundandır

Bariz güvenlik kontrolü — sözlük boyutunu model çıktı genişliğiyle karşılaştırmak — gerekli ama yeterli değildir. İki sözlük aynı sayıda girdiyi farklı sırada taşıyabilir ve sıradaki bir kayma her karakteri bir code point kaydırır. HotPDF bu yüzden fabrika modeli başlatırken iki aşamada denetler. Birincisi, çıktı sınıf sayısı sözlük girdileri artı ikiye eşit olmalıdır. İkincisi, ONNX dosyası bir character metadata listesi gömerse her sözlük girdisi onunla sırayla karşılaştırılır ve uyuşmazlık, sonra inandırıcı çöp üretmek yerine başlatmayı EInvalidOperation ve bir native teşhisle düşürür

"Artı iki", sınıf düzeninden gelir. 0. sınıf CTC blank'ıdır, 1 ile N arası sınıflar dosya sırasındaki sözlük satırlarıdır ve son sınıf bir boşluktur. Bazı sözlükler kendi boşluk girdisini de taşır ve o satır tam olarak olduğu gibi korunmalıdır. İyi niyetli bir Trim'in gerçek zarar verdiği yer burasıdır: tek boşluklu girdiyi boş bir string'e çevirir ve tabloyu kaydırır ya da kırar. Güvenli tek normalizasyon, sondaki carriage return'ü kaldırmaktır; böylece CRLF satır sonlarıyla kaydedilmiş bir sözlük doğru yüklenir, UTF-8 byte order mark, boş bir satır ya da tab taşıyan bir girdi ise reddedilir. Aşağıdaki taslak düzeni Pascal'da gösterir; açıklayıcı koddur, HotPDF API'si değil

RapidOCR sözlükleri için HotPDF CTC sınıf tablosu düzeni: 0. sınıf blank'tır, 1 ile N arası sınıflar dosya sırasındaki sözlük satırlarıdır ve yalnız boşluk girdisi korunur; son sınıf bir boşluktur ve fabrika, N artı iki çıktı sınıfını metadata dahil modelle doğrular
sözlük, sınıf indekslerini karaktere çeviren tek şeydir; dolayısıyla boyutu, sırası ve boşluk girdisi tek bir sayfa tanınmadan önce doğrulanır
// Yalnızca gösterim: bir CTC tanıyıcının beklediği sınıf tablosu
uses
  SysUtils, IOUtils;

function BuildCTCClassTable(const FileName: string): TArray<string>;
var
  Text, Entry: string;
  Lines: TArray<string>;
  I, Last: Integer;
begin
  Text := TEncoding.UTF8.GetString(TFile.ReadAllBytes(FileName));
  if (Text <> '') and (Text[1] = #$FEFF) then
    raise EArgumentException.Create('Dictionary must be UTF-8 without a BOM');
  Lines := Text.Split([#10]);
  Last := High(Lines);
  if (Last >= 0) and (Lines[Last] = '') then
    Dec(Last);                                   // dosya sonundaki satır sonu
  SetLength(Result, Last + 3);
  Result[0] := '';                               // sınıf 0: CTC blank
  for I := 0 to Last do
  begin
    Entry := Lines[I];
    if (Entry <> '') and (Entry[Length(Entry)] = #13) then
      SetLength(Entry, Length(Entry) - 1);       // CRLF: yalnızca CR düşer
    if (Entry = '') or (Pos(#9, Entry) > 0) then
      raise EArgumentException.Create('Invalid dictionary entry');
    Result[I + 1] := Entry;                      // asla Trim: ' ' bir sınıftır
  end;
  Result[Last + 2] := ' ';                       // son sınıf: boşluk
  // Length(Result), model çıktı sınıf sayısına eşit olmalı
end;

Greedy CTC decoding aslında ne yapar?

Greedy CTC decoding her zaman adımında en yüksek puanlı sınıfı seçer, art arda tekrarları tek karaktere çöker ve blank sınıfını atar; genuinely ikilenmiş harflerin hayatta kalmasını sağlayan şey blank'tır. Bir recognition modeli bir metin satırına, dar dikey dilimler dizisi olarak bakar ve her dilim için, yani her zaman adımı için her sınıfın bir olasılığını çıkarır. AA中 içeren bir satır A A blank A 中 space argmax dizisi üretebilir. İlk iki A adımının çökertilmesi tek A verir, blank onu sonraki A'dan ayırır ve sonuç, sondaki boşluğu sapasağlam tutan AA中 'dur. Blank kuralı olmasa book ile bok ayırt edilemezdi

HotPDF GreedyCTCDecode gezintisi: altı zaman adımı A, A, blank, A, bir Han karakteri ve boşluk argmax sınıflarına oy verir; art arda tekrarlar çöker, blank tekrar korumasını sıfırlar ve gerçekten ikilenmiş harf hayatta kalır; üç sınır hatası sessizce kelime aralığını, son karakteri ya da ikilenmiş karakterleri düşürür
decoder bir düzine satırdır ve her sınır önemlidir: son sınıfı dahil edin, son adımı dahil edin ve tekrarları yalnızca bir blank ayırsın

Decoder yalnızca bir düzine satır olduğundan sınırları kaçırmak kolaydır ve başarısızlıklar sessizdir. İç argmax döngüsü bir sınıf erken durursa boşluk sınıfı asla kazanamaz ve her satır kelime aralığısız döner; bu da İngilizce ve Latin sayfalarda ifade aramasını mahveder. Dış döngü bir zaman adımı erken durursa her satırın son karakteri kaybolur; kısa bir satır için metnin üçte biri olabilir. Ve tekrar koruması blank ile sıfırlanmazsa ll gibi ikilenmiş karakterler ile 谢谢 gibi Çince ikilemeler tekine çöker. HotPDF decoder'ı son sınıfı ile son zaman adımını dahil eder, blank ile ayrılmış tekrarları korur ve ayrıca sonlu olmayan ya da 0 ile 1 aralığının dışına düşen puanları ve sözlükle uyuşmayan her sınıf sayısını reddeder. Aynı mantığın Pascal gösterimi şöyle

// Yalnızca gösterim: doğru sınırlarla greedy CTC decoding.
// Scores, Steps * Classes olasılık taşır, zaman adımı başına bir satır
function GreedyCTCDecode(const Scores: array of Single;
  Steps, Classes: Integer; const Characters: array of string): string;
var
  Step, C, Best, Previous: Integer;
  BestScore: Single;
begin
  if (Classes < 3) or (Length(Characters) <> Classes) or
    (Length(Scores) <> Steps * Classes) then
    raise EArgumentException.Create('Model output does not match the dictionary');
  Result := '';
  Previous := 0;                            // sınıf 0, CTC blank'tır
  for Step := 0 to Steps - 1 do             // son zaman adımını dahil et
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // son sınıfı (boşluk) dahil et
      if Scores[Step * Classes + C] > BestScore then
      begin
        Best := C;
        BestScore := Scores[Step * Classes + C];
      end;
    if (Best <> 0) and (Best <> Previous) then
      Result := Result + Characters[Best];
    Previous := Best;                       // blank, tekrar korumasını sıfırlar
  end;
end;

Greedy decoding, var olan en isabetli CTC stratejisi değildir; dil modeliyle beam search bazı belirsiz dilimleri düzeltebilir. 300 DPI'da basılı belgeler için greedy sonucu genelde modelin sunabildiğinin ta kendisidir ve decoder, model zayıflıklarını telafi edilecek yer değildir. Latin PP-OCRv3 modeli örneğin temiz girdide bile ñ'yi n olarak okuyabilir. HotPDF bunu sonradan işleme karakter değiştirmeleriyle kapatmaz; çünkü İspanyolcayı düzelten bir değiştirme tablosu başka bir şeyi bozar ve aranabilir bir katmandaki yanlış karakter, dürüst bir ıskalamadan kötüdür

HotPDF metin satırlarını, sağdan sola Arapça dahil, nasıl sıralar?

HotPDF saptanan metin kutularını yukarıdan aşağıya sıralar, kutuları küçük olan kutu yüksekliğinin en az yarısı kadar dikeyde üst üste bindiğinde bir satıra gruplar ve her satırı soldan sağa — RightToLeft açıksa sağdan sola — sıralar; tanınan her satırın içindeki karakterler asla ters çevrilmez. Gruplama önemlidir, çünkü detector tek bir görsel satırı sık sık birkaç kutuya böler; örneğin geniş bir boşlukla ayrılmış etiket ile değer. Saf bir üst-koordinat sıralaması, üstleri birer ikişer pixel farklı olduğunda onları komşu satırla araya sokardı

Arapça ön ayarı RightToLeft := True set eder; bu da DLL'e her satırdaki kutuları sağ kenarlarına göre, sağ kenar boşluğundan içeri doğru sıralamasını söyler. Etkinin tamamı budur. Modelin bir satır için döndürdüğü metin hâlihazırda Unicode mantıksal sırasındadır — bir Arapça okurun okuduğu ve yazdığı sıra — PDF metin extraction'ının ve aramanın beklediği sıra da odur. String'i debugger'da "doğru görünmesi" için mekanik olarak ters çevirmek aramayı, kopyala-yapıştırmayı ve ekran okuyucuları kırar. Çift yönlü görüntüleme ile glyph şekillendirme viewer'ın işidir

Tek motor tek dil profili sunar. Otomatik yazı tipi detection yoktur; dolayısıyla yazı tiplerini karıştıran bir belge, profil başına bir motor ve o profili kullanan sayfalara uygulanmak ister. ApplyLoadedOCRTextLayer açık bir sayfa listesi aldığı ve her çağrıyı kendi hep-ya-da-hiç işlemi olarak işlediği için bu dağı laf kolaydır

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // bilinmeyen etiket için EArgumentException fırlatır, hiçbir model yüklenmeden
  Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
  Models.MaxPixels := 33554432;          // 300 DPI'da A3 sayfalara pay
  Result := HPDFCreateRapidOCRDLLOCREngine(
    'C:\OCR\Win64\HotPDFRapidOCR.dll', 'C:\OCR\models', Models);
end;

procedure OCRMixedArchive(Doc: THotPDF);
var
  Chinese, Arabic: IHPDFOCREngine;
  Layer: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  Chinese := CreateRapidEngine('zh-TW');  // chinese_cht profili
  Arabic := CreateRapidEngine('ar-SA');   // arabic profili, RightToLeft = True
  Layer := THPDFOCRTextLayerOptions.Default;
  if not Doc.ApplyLoadedOCRTextLayer([0, 1, 2], Chinese, Layer, Info) then
    raise Exception.Create(string(Info.Diagnostic));
  if not Doc.ApplyLoadedOCRTextLayer([3], Arabic, Layer, Info) then
    raise Exception.Create(string(Info.Diagnostic));
end;

MaxPixels satırı orada bir nedenden dolayı durur. DLL optionları istek başına varsayılan 16.777.216 pixeldir; bu, A4 ile US Letter'i 300 DPI'da rahatça karşılar ama 300 DPI'da bir A3 sayfa kabaca 3508'ye 4961, yani 17,4 milyon pixeldir ve istek bütçe aşımı diye reddedilir. Büyük formatlar için MaxPixels'i yükseltin (tavan 67.108.864) ya da THPDFOCRTextLayerOptions.DPI'yi düşürün. Sağdan sola sıralama, ABI sürüm 1'in isteğe bağlı HPDFRapidOCRSetReadingDirection export'unu kullanır; adaptör onu yalnızca RightToLeft setliyken ister, böylece eski bir DLL soldan sağa dilleri sunmaya devam eder ve Arapça için eksik exportu adıyla anan bir EArgumentException ile motor kurulumunda düşer

Daha yeni OCR modelleri neden yüklenemez?

HotPDF RapidOCR DLL'i statik bir ONNX Runtime 1.14 bağlar; o da ONNX IR sürüm 10 ile kaydedilmiş modelleri okuyamaz ve PP-OCRv5 modelleri gibi daha yeni export'lar ondan daha yeni bir çalışma zamanı isteyebilir; böyle bir model, native bir teşhisle motor kurulumunda düşer. Dil paketlerinin "en son" yerine belirli PP-OCRv3 ile PP-OCRv4 recognizer-sözlük çiftlerine sabitlenmesinin nedeni o kısıttır ve yukarıdaki tablonun iki nesli karıştırması da bundandır: sabitlenen her çift, o çalışma zamanı altında yüklenen ve doğrulanan bir çifttir

Kurucu çiftlemeyi zorunlu kılar. Manifestosundaki her dosya bir SHA256 hash taşır, farklı hash'li mevcut bir dosya üzerine yazılmak yerine kurulumu durdurur ve her indirme geçici bir ad altına düşer, hash'i eşleşince yerine taşınır. Bu, sözlük sorununun sessiz versiyonuna karşı korur: biri profile klasörüne elle daha yeni bir recognition.onnx bırakır, sınıf sayısı tesadüfen uyar ve bir müşteri apaçık gördüğü kelimeleri aramanın bulamadığını bildirene dek hiçbir şey düşmez. Çalışma zamanında adaptör çevrimdışı kalır ve eksik modeli asla çeker. Recognizer ayrıca model şeklini yükleme sırasında doğrular; sabit 32 ya da 48 pixel yükseklikli ya da dinamik yükseklikli NCHW girdisini kabul eder ve dinamik olanı 48'de koşturur

Dokuz profilin hiçbirinin kapsamadığı bir yazı tipine ihtiyacınız varsa RecognitionModel ile CharacterDictionary'yi kendi dosyalarınıza yönlendirebilirsiniz. Aynı kontroller geçerlidir — mesele de zaten budur: uyuşmayan bir çift başlatmada düşer, müşterinizin arşivinde değil. Hiçbir RapidOCR profilinin uymadığı sayfalar için aranabilir PDF için Tesseract adaptörü aynı ApplyLoadedOCRTextLayer çağrısına takılır; makine basımı ASCII formlar içinse yerleşik şablon eşleştirmeli OCR motoru hiçbir modele ihtiyaç duymaz

Hızlı başvuru: çok dilli RapidOCR kontrol listesi

  • Optionları THPDFRapidOCRDLLOptions.ForLanguage ile kurun ve EArgumentException'yi çalışma zamanı arızası değil, desteklenmeyen etiket sayın
  • RecognitionModel ile CharacterDictionary'yi birlikte değiştirin, asla tek başına; eşit sınıf sayıları eşit karakter sırasını kanıtlamaz
  • Sözlükleri BOM'suz UTF-8 tutun, girdileri asla kırpmayın ve modelin N + 2 sınıfı olmasını bekleyin: blank, N girdi, boşluk
  • Özel bir CTC decoder'ı son sınıfı ve son zaman adımını kapsamalı ve blank ile ayrılmış tekrarları korumalıdır
  • Dil profili başına bir motor kullanın ve karışık yazı tipli belgeler için açık sayfa listeleri geçirin
  • RightToLeft yalnızca kutu sırasını değiştirir; tanınan metin Unicode mantıksal sırasında kalır
  • Modelleri SHA256 sabitlemeleri model-sözlük çiftleşmesini tutsun diye Install-RapidOCRModels.ps1 ile kurun; -SkipClassifier ile kurduysanız UseAngleClassifier := False set edin
  • 300 DPI'da A3 ya da daha büyük sayfalar koşturmadan önce MaxPixels'i 16.777.216 varsayılanının üzerine çıkarın

RapidOCR dil ön ayarları, native DLL adaptörü ve OCR metin katmanı hattı, çok dilli profiller için v2.775.0'dan itibaren Delphi, C++Builder ve Windows FPC/Lazarus için HotPDF Delphi PDF Component'in parçasıdır