Tehnički članak

HotPDF kineski i višejezični OCR s RapidOCR-om u Delphiju

HotPDF izvodi kineski i višejezični OCR u Delphiju kroz svoj nativni RapidOCR DLL adapter: THPDFRapidOCRDLLOptions.ForLanguage mapira jezičnu oznaku poput 'zh-CN', 'zh-TW', 'ru' ili 'ar' na upareni prepoznavni model i rječnik znakova, a THotPDF.ApplyLoadedOCRTextLayer pretvara prepoznate linije u nevidljivi, pretraživi Unicode tekstualni sloj na skeniranim PDF stranicama

Natjerati demo na latinici da radi lakši je dio. Zanimljivi kvarovi počinju kad pređete na tradicionalni kineski ili ruski pa izlaz postane samouvjerena, dobro oblikovana besmislica, ili kad svaka linija tiho izgubi svoj zadnji znak, ili kad arapska stranica dođe natrag s okvirima teksta u pogrešnom redoslijedu. Nijedno od toga samo po sebi ne diže iznimku. Jezični preseti dodani u HotPDF v2.775.0 postoje uglavnom da zatvore te praznine, a četiri podvale dolje vrijedi razumjeti i ako nikad ne dirate nativni kod, jer svaka objašnjava simptom za kojim biste inače proveli dan u lovu

Kako ForLanguage bira model i rječnik?

THPDFRapidOCRDLLOptions.ForLanguage razrješuje oznaku na jedan od devet profila i vraća opcije koje pokazuju na <profile>/recognition.onnx i <profile>/dictionary.txt ispod vaše mape modela, dok zadržava dijeljeni detektor, neobavezni klasifikator kuta te zadane vrijednosti dretava, piksela i roka iz THPDFRapidOCRDLLOptions.Default. Metoda pretvara oznaku u mala slova, podvlake u crtice i reže okolne razmake, pa 'zh_TW', 'ZH-tw' i ' zh-tw ' svi doskoče na isti profil. Aliasi su eksplicitni popis, a ne prefiksno poklapanje: 'zh-Hant-TW' prihvaćen je jer je izlistan, dok proizvoljna regionalna varijanta koja nije izlistana diže EArgumentException prije nego se bilo koji model učita

HotPDF ForLanguage razrješavanje profila za THPDFRapidOCRDLLOptions: oznake poput zh_TW, ZH-tw i zh-TW normaliziraju se i uspoređuju s devet izlistanih profila, svaki pribija prepoznavni model i rječnik koji se uvijek postavljaju zajedno, dok neizlistana oznaka diže EArgumentException prije učitavanja bilo kojeg modela
jedna oznaka bira jedan pribiti par model-rječnik; detektor, klasifikator i proračuni ostaju dijeljeni, i nepoznata oznaka pada brzo umjesto da išta učita
ProfilJeziciPrimjeri oznakaPribiti model
chPojednostavljeni kineski i engleskizh, zh-CN, zh-Hans, chi_simPP-OCRv4
chinese_chtTradicionalni kineskizh-TW, zh-HK, zh-Hant, chi_traPP-OCRv3
enEngleskien, en-US, en-GB, engPP-OCRv4
latinFrancuski, njemački, španjolski, portugalski, talijanski, nizozemski, turskifr, de, es-419, pt-BR, trPP-OCRv3
japanJapanskija, ja-JP, jpnPP-OCRv4
koreanKorejskiko, ko-KR, korPP-OCRv4
cyrillicRuski, ukrajinski, bugarski, bjeloruskiru, ru-RU, uk, bgPP-OCRv3
arabicArapski, perzijski, urduar, ar-SA, fa, urPP-OCRv4
devanagariHindi, marati, nepalskihi, mr, nePP-OCRv4

Adapter sam nikad ništa ne preuzima. Datoteke osigurate jednom s priloženim pomoćnikom, na primjer tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (ili -Language All za svih devet profila), i pomoćnik postavlja dijeljeni detektor i klasifikator na korijenska imena datoteka koja Default očekuje. Nakon toga pojednostavljeni kineski sken postaje pretraživ s par linija. Instalacija enginea isti je IHPDFOCREngine spoj opisan u članku o in-process RapidOCR DLL-u i njegovoj ABI granici, pa ovaj ostaje usredotočen na jezike

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, dijeljeni detektor i klasifikator
  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
    // prazna lista stranica znači svaku stranicu; stranice koje već imaju tekst se preskaču
    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;

Dva detalja u tom ispisu zaslužuju napomenu. Nativni pipeline vraća jedan rezultat po detektiranoj tekstualnoj liniji, a ne po riječi, pa AcceptedWordCount ovdje broji linije, i MinimumConfidence uspoređuje se sa srednjom pouzdanošću znakova cijele linije: linija koja u prosjeku ima 0.45 ispada kao cjelina. UniqueScalarCount javlja koliko različitih Unicode skalara tekstualni sloj morao mapirati u svoj font i ToUnicode tablicu, koristan sanity check da je CJK tekst doista stigao umjesto šačice latiničnih fallbackova. Držite sučelje enginea živim kroz dokumente, jer se inicijalizacija modela događa u tvornici i to je skupi korak

Zašto mijenjanje samo prepoznavnog modela proizvodi smeće?

CTC prepoznavni model nikad ne ispisuje znakove, samo indekse klasa, i rječnik je jedina stvar koja indeks 1.204 pretvara u glif. Zamijenite li ch/recognition.onnx s cyrillic/recognition.onnx ali zadržite kineski rječnik, model će rado ispisivati valjane ćirilične indekse koje stari rječnik prevodi u slučajne Han znakove. Rezultat izgleda kao tekst, prolazi UTF-8 validaciju, i pretraživ je točno za ništa. Zato ForLanguage uvijek postavlja RecognitionModel i CharacterDictionary zajedno, i zato ručno građene opcije nikad ne smiju mijenjati jedno bez drugoga

Očita sigurnosna provjera, usporedba veličine rječnika sa širinom izlaza modela, potrebna je ali nije dostatna. Dva rječnika mogu imati isti broj unosa u drugom redoslijedu, i off-by-one u redoslijedu pomjera svaki znak za jednu kodnu točku. HotPDF stoga provjerava u dva stupnja kad tvornica inicijalizira model. Prvo, broj izlaznih klasa mora odgovarati unosima rječnika plus dva. Drugo, kad ONNX datoteka ugrađuje character popis metapodataka, svaki se unos rječnika uspoređuje s njim po redu, i nepoklapanje obara inicijalizaciju s EInvalidOperation i nativnom dijagnostikom umjesto da kasnije proizvede vjerojatno smeće

„Plus dva" dolazi iz rasporeda klasa. Klasa 0 jest CTC blank, klase 1 do N retci su rječnika u redoslijedu datoteke, i završna klasa jest razmak. Neki rječnici nose i vlastiti unos razmaka, i taj se redak mora zadržati točno kakav jest. Tu dobronamjerni Trim čini pravu štetu: pretvara unos od jednog razmaka u prazan string i pomjera ili lomi tablicu. Jedina sigurna normalizacija jest uklanjanje završnog carriage returna, pa se rječnik spremljen s CRLF završecima linija učita ispravno, dok se UTF-8 byte order mark, prazan redak ili unos koji sadrži tab odbijaju. Skica dolje pokazuje raspored u Pascalu; to je objašnjavajući kod, a ne HotPDF API

HotPDF CTC raspored tablice klasa za RapidOCR rječnike: klasa 0 jest blank, klase 1 do N retci su rječnika u redoslijedu datoteke s bilo kojim usamljenim unosom razmaka zadržanim, i završna klasa jest razmak, dajući N plus 2 izlazne klase koje tvornica provjerava protiv modela, metapodatke uključno
rječnik je jedina stvar koja indekse klasa pretvara u znakove, pa se njegova veličina, redoslijed i unos razmaka provjeravaju prije nego se prepozna ijedna stranica
// Samo ilustracija: tablica klasa koju CTC prepoznavatelj očekuje
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);                                   // novi redak na kraju datoteke
  SetLength(Result, Last + 3);
  Result[0] := '';                               // klasa 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: ispusti samo CR
    if (Entry = '') or (Pos(#9, Entry) > 0) then
      raise EArgumentException.Create('Invalid dictionary entry');
    Result[I + 1] := Entry;                      // nikad Trim: ' ' je klasa
  end;
  Result[Last + 2] := ' ';                       // završna klasa: razmak
  // Length(Result) mora odgovarati broju izlaznih klasa modela
end;

Što pohlepno CTC dekodiranje zapravo radi?

Pohlepno CTC dekodiranje bira najviše bodovanu klasu u svakom vremenskom koraku, urušava uzastopne ponavke u jedan znak i ispušta blank klasu; blank je ono što dozvoljava da doista udvostručena slova prežive. Prepoznavni model gleda tekstualnu liniju kao niz uskih okomitih rezova, i za svaki rez, odnosno vremenski korak, ispisuje vjerojatnost za svaku klasu. Linija koja sadrži AA中 mogla bi proizvesti argmax niz A A blank A 中 space. Urušavanje prva dva A koraka daje jedno A, blank ga odvaja od sljedećeg A, i rezultat je AA中 sa završnim razmakom netaknutim. Bez blank pravila book i bok bili bi nerazlučivi

HotPDF GreedyCTCDecode prolaz: šest vremenskih koraka glasa argmax klase A, A, blank, A, Han znak i razmak, uzastopne se ponavke urušavaju, blank resetira čuvara ponavke pa doista udvostručeno slovo preživi, i tri rubna buga tiho ispuštaju razmaknice riječi, zadnji znak ili udvojene znakove
dekoder je desetak linija i svaka granica važi: uključite zadnju klasu, uključite zadnji korak, i pustite samo blank da odvaja ponavke

Budući da je dekoder desetak linija, lako je granice pogriješiti, i kvarovi su tihi. Ako se unutarnja argmax petlja zaustavi jednu klasu prerano, klasa razmaka nikad ne može pobijediti i svaka linija dolazi natrag bez razmaknica riječi, što razbilo frazno pretraživanje na engleskim i latiničnim stranicama. Ako se vanjska petlja zaustavi jedan vremenski korak prerano, zadnji znak svake linije nestane, što za kratku liniju može biti trećina teksta. A ako čuvar ponavke ne resetira blank, udvostručeni znakovi poput ll ili kineske reduplikacije poput 谢谢 uruše se u jedan. HotPDF dekoder uključuje zadnju klasu i zadnji vremenski korak, zadržava ponavke odvojene blankom, i dodatno odbija bodove koji nisu konačni ili padaju izvan 0 do 1, i bilo koji broj klasa koji se ne poklapa s rječnikom. Evo iste logike kao Pascal ilustracija

// Samo ilustracija: pohlepno CTC dekodiranje s ispravnim granicama.
// Scores drži Steps * Classes vjerojatnosti, jedan redak po vremenskom koraku
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;                            // klasa 0 je CTC blank
  for Step := 0 to Steps - 1 do             // uključi zadnji vremenski korak
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // uključi zadnju klasu (razmak)
      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 resetira čuvara ponavke
  end;
end;

Pohlepno dekodiranje nije najtočnija dostupna CTC strategija; beam search s jezičnim modelom može popraviti neke dvosmislene rezove. Za tiskane dokumente na 300 DPI pohlepni rezultat obično je ono što model ima za ponuditi, i dekoder nije mjesto za nadoknadu slabosti modela. Latinični PP-OCRv3 model, na primjer, može pročitati ñ kao n i na čistom ulazu. HotPDF to ne prekriva postobradnim zamjenama znakova, jer tablica zamjena koja popravi španjolski nešto drugo slomi, i pogrešan znak u pretraživom sloju gori je od poštenog promašaja

Kako HotPDF reda tekstualne linije, uključujući arapski s desna na lijevo?

HotPDF sortira detektirane tekstualne okvire od vrha prema dnu, grupira okvire u redak kad se okomito preklapaju za barem polovicu visine manjeg okvira, i reda svaki redak s lijeva na desno, ili s desna na lijevo kad je RightToLeft omogućen; znakovi unutar svake prepoznate linije nikad se ne okreću. Grupiranje je važno jer detektor često razdvaja jednu vizualnu liniju u nekoliko okvira, na primjer oznaku i vrijednost odvojene širokim razmakom, i čisto sortiranje po gornjoj koordinati ispreplelo bi ih sa susjednom linijom kad god im se vrhovi razlikuju za piksel ili dva

Arapski preset postavlja RightToLeft := True, što DLL-u govori da reda okvire u svakom retku po njihovu desnom rubu, od desnog ruba prema unutra. To je cijeli učinak. Tekst koji model vraća za liniju već je u Unicode logičkom redoslijedu, onom u kojem arapski čitatelj čita i tipka, i to je i redoslijed koji PDF izdvajanje teksta i pretraživanje očekuju. Mehaničko okretanje stringa da „izgleda ispravno" u debuggeru slomilo bi pretraživanje, kopiranje i lijepljenje i čitače ekrana. Dvosmjerni prikaz i oblikovanje glifova posao su preglednika

Jedan engine poslužuje jedan jezični profil. Nema automatske detekcije pisma, pa dokument koji miješa pisma treba jedan engine po profilu, primijenjen na stranice koje ga koriste. Budući da ApplyLoadedOCRTextLayer prima eksplicitnu listu stranica i izvršava svaki poziv kao vlastitu transakciju sve-ili-ništa, to je jednostavno

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // diže EArgumentException za nepoznatu oznaku, prije učitavanja bilo kojeg modela
  Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
  Models.MaxPixels := 33554432;          // prostor za A3 stranice na 300 DPI
  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 profil
  Arabic := CreateRapidEngine('ar-SA');   // arapski profil, 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;

Linija MaxPixels tamo je s razlogom. Opcije DLL-a zadano su 16.777.216 piksela po zahtjevu, što A4 i US Letter na 300 DPI pokriva udobno, ali A3 stranica na 300 DPI oko je 3508 puta 4961 piksela, otprilike 17,4 milijuna, i zahtjev se odbija kao preko proračuna. Podignite MaxPixels (strop je 67.108.864) ili spustite THPDFOCRTextLayerOptions.DPI za velike formate. Redoslijed s desna na lijevo koristi neobavezni izvoz HPDFRapidOCRSetReadingDirection ABI verzije 1; adapter ga traži samo kad je RightToLeft postavljen, pa stariji DLL i dalje poslužuje jezike s lijeva na desno i pada pri stvaranju enginea s EArgumentException koji imenuje nedostajući izvoz za arapski

Zašto se noviji OCR modeli ne uspijevaju učitati?

HotPDF RapidOCR DLL povezuje statički ONNX Runtime 1.14, koji ne može čitati modele spremljene ONNX IR verzijom 10, a noviji izvozi poput PP-OCRv5 modela mogu tražiti noviji runtime od toga; takav model pada pri stvaranju enginea s nativnom dijagnostikom. To ograničenje razlog je što su jezični paketi pribiti na specifične PP-OCRv3 i PP-OCRv4 parove prepoznavatelja i rječnika umjesto na „najnovije", i što tablica gore miješa dvije generacije: svaki pribiti par onaj je koji se učita i potvrdi pod tim runtimeom

Instalater provodi uparivanje. Svaka datoteka u manifestu nosi SHA256 hash, postojeća datoteka s drugačijim hashom zaustavlja instalaciju umjesto da se prepiše, i svako se preuzimanje doskoči pod privremenim imenom i tek pomjera na mjesto nakon što se njegov hash poklopi. To štiti od tihe verzije problema rječnika: netko ručno ubaci noviji recognition.onnx u mapu profila, broj klasa slučajno se poklopi, i ništa ne padne dok kupac ne prijavi da pretraživanje ne nalazi riječi koje očito vidi. U vrijeme izvođenja adapter ostaje offline i nikad ne dohvaća model koji nedostaje. Prepoznavatelj također validira oblik modela pri učitavanju, prihvativši NCHW ulaz s fiksnom visinom 32 ili 48 piksela ili dinamičkom visinom, koju izvodi na 48

Ako trebate pismo koje nijedan od devet profila ne pokriva, i dalje možete usmjeriti RecognitionModel i CharacterDictionary na vlastite datoteke. Iste se provjere primjenjuju, i to je poanta: neuparen par pada pri inicijalizaciji, a ne u arhivu vašeg kupca. Za stranice gdje nijedan RapidOCR profil ne odgovara, Tesseract adapter za pretraživi PDF priključuje se na isti poziv ApplyLoadedOCRTextLayer, a za strojno tiskane ASCII obrasce ugrađeni OCR engine s podudaranjem predložaka ne treba nijedan model

Brza referenca: višejezična RapidOCR kontrolna lista

  • Stvarajte opcije s THPDFRapidOCRDLLOptions.ForLanguage i tretirajte EArgumentException kao nepodržanu oznaku, a ne runtime kvar
  • Mijenjajte RecognitionModel i CharacterDictionary zajedno, nikad jedno; jednaki brojevi klasa ne dokazuju jednak redoslijed znakova
  • Držite rječnike kao UTF-8 bez BOM-a, nikad ne režite unose, i očekujte da model ima N + 2 klase: blank, N unosa, razmak
  • Prilagođeni CTC dekoder mora pokrivati zadnju klasu i zadnji vremenski korak i zadržavati ponavke odvojene blankom
  • Koristite jedan engine po jezičnom profilu i prenosite eksplicitne liste stranica za dokumente s mješovitim pismima
  • RightToLeft mijenja samo redoslijed okvira; prepoznati tekst ostaje u Unicode logičkom redoslijedu
  • Instalirajte modele s Install-RapidOCRModels.ps1 da SHA256 pribitja drže uparivanje modela i rječnika; postavite UseAngleClassifier := False ako ste instalirali s -SkipClassifier
  • Podignite MaxPixels iznad zadanih 16.777.216 prije izvođenja A3 ili većih stranica na 300 DPI

RapidOCR jezični preseti, nativni DLL adapter i OCR pipeline tekstualnog sloja dio su HotPDF Delphi PDF komponente za Delphi, C++Builder i Windows FPC/Lazarus, počevši od v2.775.0 za višejezične profile