Tehnički članak

HotPDF kineski i višejezični OCR sa RapidOCR u Delphi-ju

HotPDF obavlja kineski i višejezični OCR u Delphi-ju kroz svoj nativni RapidOCR DLL adapter: THPDFRapidOCRDLLOptions.ForLanguage mapira jezički tag poput 'zh-CN', 'zh-TW', 'ru' ili 'ar' na upareni model prepoznavanja i rečnik karaktera, a THotPDF.ApplyLoadedOCRTextLayer pretvara prepoznate linije u nevidljivi, pretraživi Unicode tekstualni sloj na skeniranim PDF stranicama

Naterati demo na latinici da radi je laki deo. Zanimljivi kvarovi počinju kad pređete na tradicionalni kineski ili ruski i izlaz se pretvori u samouvereno, uredno formirano bezumlje, ili kad svaka linija tiho izgubi svoj poslednji karakter, ili kad arapska stranica stigne sa svojim tekstualnim kutijama u pogrešnom redosledu. Ništa od toga ne podiže izuzetak samo od sebe. Jezički preseti dodati u HotPDF v2.775.0 postoje uglavnom da zatvore te praznine, i četiri zamke ispod vredi razumeti čak i ako nikada ne dirate nativni kod, jer svaka objašnjava simptom za koji biste inače potrošili dan u jurenju

Kako ForLanguage bira model i rečnik?

THPDFRapidOCRDLLOptions.ForLanguage razrešava tag na jedan od devet profila i vraća opcije koje pokazuju na <profile>/recognition.onnx i <profile>/dictionary.txt ispod vašeg direktorijuma modela, dok čuva deljeni detektor, opcionu klasifikator ugla i podrazumevane vrednosti niti, piksela i roka iz THPDFRapidOCRDLLOptions.Default. Metoda pretvara tag u mala slova, pretvara donje crte u crtice i odseče okolni prazan prostor, pa 'zh_TW', 'ZH-tw' i ' zh-tw ' svi doskoče na isti profil. Alijasi su eksplicitan spisak, a ne prefiksno poklapanje: 'zh-Hant-TW' se prima jer je izlistan, dok proizvoljna regionalna varijanta koja nije izlistana podiže EArgumentException pre nego što se bilo koji model učita

HotPDF ForLanguage razrešavanje profila za THPDFRapidOCRDLLOptions: tagovi poput zh_TW, ZH-tw i zh-TW normalizuju se i poklapaju protiv devet izlistanih profila, svaki pribijajući model prepoznavanja i rečnik koji se uvek postavljaju zajedno, dok neizlistani tag podiže EArgumentException pre nego što se bilo koji model učita
jedan tag bira jedan pribijen par model-rečnik; detektor, klasifikator i budžeti ostaju deljeni, i nepoznat tag pada brzo umesto da bilo šta učita
ProfilJeziciPrimer tagovaPribijen 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, nemački, španski, portugalski, italijanski, holandski, turskifr, de, es-419, pt-BR, trPP-OCRv3
japanJapanskija, ja-JP, jpnPP-OCRv4
koreanKorejskiko, ko-KR, korPP-OCRv4
cyrillicRuski, ukrajinski, bugarski, beloruskiru, ru-RU, uk, bgPP-OCRv3
arabicArapski, persijski, urduar, ar-SA, fa, urPP-OCRv4
devanagariHindi, marati, nepalskihi, mr, nePP-OCRv4

Adapter sam nikada ništa ne preuzima. Fajlove obezbedite jednom priloženim pomoćnikom, na primer tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (ili -Language All za svih devet profila), i pomoćnik postavlja deljeni detektor i klasifikator na korenska imena fajlova koja Default očekuje. Posle toga, sken na pojednostavljenom kineskom postaje pretraživ sa par linija. Instalacija engine-a je isto IHPDFOCREngine priključište opisano u članku o in-process RapidOCR DLL-u i njegovoj ABI granici, pa ovaj ostaje usredsređ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, deljeni 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 preskaču se
    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 izlazu zaslužuju napomenu. Nativni cevovod vraća jedan rezultat po detektovanoj tekstualnoj liniji, ne po reči, pa AcceptedWordCount ovde broji linije, i MinimumConfidence se poredi sa srednjim poverenjem karaktera cele linije: linija koja prosečno daje 0.45 ispušta se kao jedinica. UniqueScalarCount prijavljuje koliko različitih Unicode skalara je tekstualni sloj morao mapirati u svoj font i ToUnicode tabelu, korisna provera zdravog razuma da je CJK tekst zaista stigao umesto šačice latiničnih fallbackova. Držite interfejs engine-a živim preko dokumenata, jer se inicijalizacija modela dešava u fabrici i to je skup korak

Zašto menjanje samo modela prepoznavanja proizvodi đubre?

CTC model prepoznavanja nikada ne ispisuje karaktere, samo indekse klasa, i rečnik je jedina stvar koja indeks 1.204 pretvara u znak. Zamenite ch/recognition.onnx sa cyrillic/recognition.onnx ali zadržite kineski rečnik, i model će rado ispisivati validne ćirilične indekse koje stari rečnik prevodi u nasumične Han karaktere. Rezultat izgleda kao tekst, prolazi UTF-8 validaciju, i pretraživ je za tačno ništa. Zato ForLanguage uvek postavlja RecognitionModel i CharacterDictionary zajedno, i zato ručno građene opcije nikada ne bi menjale jedno bez drugog

Očigledna sigurnosna provera, poređenje veličine rečnika sa širinom izlaza modela, potrebna je ali ne i dovoljna. Dva rečnika mogu imati isti broj unosa u drugom redosledu, i odstupanje od jednog u redosledu pomera svaki karakter za jedan code point. HotPDF zato proverava u dve faze kad fabrika inicijalizuje model. Prvo, broj izlaznih klasa mora biti jednak unosima rečnika plus dva. Drugo, kad ONNX fajl ugnezdjava character listu metapodataka, svaki unos rečnika poredi se sa njom po redu, i neslaganje pada inicijalizaciju sa EInvalidOperation i nativnom dijagnostikom umesto da kasnije proizvede uverljivo đubre

„Plus dva“ dolazi od rasporeda klasa. Klasa 0 je CTC prazan, klase 1 do N su linije rečnika u redosledu fajla, i završna klasa je razmak. Neki rečnici nose i svoj unos razmaka, i ta linija mora ostati tačno kakva jeste. Tu dobronamerni Trim čini pravu štetu: pretvara unos od jednog razmaka u prazan string i pomera ili lomi tabelu. Jedina bezbedna normalizacija je uklanjanje završnog znaka za povratak, pa rečnik sačuvan sa CRLF krajevima linija učitava se ispravno, dok UTF-8 BOM, prazna linija ili unos koji sadrži tab bivaju odbijeni. Skica ispod pokazuje raspored u Pascalu; to je objašnjavajući kod, ne HotPDF API

HotPDF CTC raspored tabele klasa za RapidOCR rečnike: klasa 0 je prazan, klase 1 do N su linije rečnika u redosledu fajla sa zadržanim usamljenim unosom razmaka, i završna klasa je razmak, dajući N plus 2 izlazne klase koje fabrika verifikuje protiv modela, metapodatke uključeno
rečnik je jedina stvar koja indekse klasa pretvara u karaktere, pa se njegova veličina, redosled i unos razmaka verifikuju pre nego što se ijedna stranica prepozna
// Samo ilustracija: tabela klasa koju CTC prepoznavač 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 red na kraju fajla
  SetLength(Result, Last + 3);
  Result[0] := '';                               // klasa 0: CTC prazan
  for I := 0 to Last do
  begin
    Entry := Lines[I];
    if (Entry <> '') and (Entry[Length(Entry)] = #13) then
      SetLength(Entry, Length(Entry) - 1);       // CRLF: odbaci samo CR
    if (Entry = '') or (Pos(#9, Entry) > 0) then
      raise EArgumentException.Create('Invalid dictionary entry');
    Result[I + 1] := Entry;                      // nikada Trim: ' ' je klasa
  end;
  Result[Last + 2] := ' ';                       // završna klasa: razmak
  // Length(Result) mora biti jednak broju izlaznih klasa modela
end;

Šta pohlepno CTC dekodiranje zapravo radi?

Pohlepno CTC dekodiranje bira najbolje ocenjenu klasu na svakom vremenskom koraku, sruši uzastopne ponove u jedan karakter, i baca klasu prazan; prazan je ono što dozvoljava istinski udvostručenim slovima da prežive. Model prepoznavanja gleda tekstualnu liniju kao niz uskih vertikalnih isečaka, i za svaki isečak, ili vremenski korak, ispisuje verovatnoću za svaku klasu. Linija koja sadrži AA中 mogla bi proizvesti argmax sekvencu A A blank A 中 space. Srušavanje prva dva A koraka daje jedno A, prazan ga odvaja od sledećeg A, i rezultat je AA中 sa završnim razmakom netaknutim. Bez pravila praznog, book i bok bili bi nerazlučivi

HotPDF GreedyCTCDecode obilazak: šest vremenskih koraka glasa argmax klase A, A, prazan, A, Han karakter i razmak, uzastopni ponovi se sruskaju, prazan resetuje čuvarku ponova pa istinski udvostručeno slovo preživi, i tri granicna buga tiho ispuštaju razmake reči, poslednji karakter ili udvostručene karaktere
dekoder je tucet linija i svaka granica je bitna: uključite poslednju klasu, uključite poslednji korak, i pustite samo prazan da razdvoji ponove

Pošto je dekoder samo tucet linija, lako je pogrešiti granice, i kvarovi su tihi. Ako unutrašnja argmax petlja stane jednu klasu pre, klasa razmaka nikada ne može pobediti i svaka linija se vraća bez razmaka reči, što polomi pretragu fraza na engleskim i latiničnim stranicama. Ako spoljašnja petlja stane jedan vremenski korak pre, poslednji karakter svake linije nestane, što za kratku liniju može biti trećina teksta. A ako čuvarku ponova ne resetuje prazan, udvostručeni karakteri poput ll ili kineskih reduplikacija poput 谢谢 sruše se u jedan. HotPDF dekoder uključuje poslednju klasu i poslednji vremenski korak, čuva ponove razdvojene praznim, i dodatno odbija ocene koje nisu konačne ili padaju van 0 do 1, i bilo koji broj klasa koji se ne poklapa sa rečnikom. Evo iste logike kao Pascal ilustracija

// Samo ilustracija: pohlepno CTC dekodiranje sa ispravnim granicama.
// Scores drži Steps * Classes verovatnoća, jedan red 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 prazan
  for Step := 0 to Steps - 1 do             // uključite poslednji vremenski korak
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // uključite poslednju 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;                       // prazan resetuje čuvarku ponova
  end;
end;

Pohlepno dekodiranje nije najtačnija dostupna CTC strategija; beam pretraga sa jezičkim modelom može popraviti neke dvosmislene isečke. Za štampane dokumente na 300 DPI pohlepni rezultat obično je ono što model ima da ponudi, i dekoder nije mesto da se kompenziraju slabosti modela. Latinični PP-OCRv3 model na primer može pročitati ñ kao n čak i na čistom unosu. HotPDF to ne prekriva post-processing zamenama karaktera, jer zamenska tabela koja popravi španski polomi nešto drugo, i pogrešan karakter u pretraživom sloju gore je od poštenog promašaja

Kako HotPDF uređuje tekstualne linije, uključujući arapski s-desna-u-levo?

HotPDF sortira detektovane tekstualne kutije odozgo naniže, grupiše kutije u red kad se vertikalno preklapaju najmanje polovinom visine manje kutije, i uređuje svaki red s-leva-u-desno, ili s-desna-u-levo kad je RightToLeft uključen; karakteri unutar svake prepoznate linije nikada se ne obrću. Grupisanje je bitno jer detektor često cepa jednu vizuelnu liniju na više kutija, na primer oznaku i vrednost razdvojene širokim razmakom, i čisto sortiranje po gornjoj koordinati ispreplelo bi ih sa susednom linijom kad god se njihovi vrhovi razlikuju za piksel ili dva

Arapski preset postavlja RightToLeft := True, što govori DLL-u da uređuje kutije u svakom redu po njihovoj desnoj ivici, od desne margine ka unutrašnjosti. To je ceo efekat. Tekst koji model vraća za liniju već je u Unicode logičkom redosledu, redosledu u kojem arapski čitalac čita i kuca, i to je takođe redosled koji izdvajanje teksta iz PDF-a i pretraga očekuju. Mehaničko obrtanje stringa da bi „izgledao ispravno“ u debuggeru polomilo bi pretragu, kopiranje i lepljenje i čitače ekrana. Dvosmerni prikaz i oblikovanje znakova posao su pregledača

Jedan engine servira jedan jezički profil. Nema automatske detekcije pisma, pa dokument koji meša pisma treba jedan engine po profilu, primenjen na stranice koje ga koriste. Pošto ApplyLoadedOCRTextLayer prima eksplicitnu listu stranica i uveruje svaki poziv kao sopstvenu transakciju sve-ili-ništa, to je jednostavno

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // podiže EArgumentException za nepoznat tag, pre nego što se bilo koji model učita
  Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
  Models.MaxPixels := 33554432;          // prostora 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');   // arabic 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 podrazumevaju 16.777.216 piksela po zahtevu, što A4 i US Letter na 300 DPI pokriva sa udobnošću, ali A3 stranica na 300 DPI je oko 3508 sa 4961 piksela, otprilike 17,4 miliona, i zahtev se odbija kao preko budžeta. Povećajte MaxPixels (plafon je 67.108.864) ili spustite THPDFOCRTextLayerOptions.DPI za velike formate. Redosled s-desna-u-levo koristi opcion izvoz HPDFRapidOCRSetReadingDirection ABI verzije 1; adapter ga traži samo kad je RightToLeft postavljen, pa stariji DLL i dalje servira jezike s-levo-u-desno i pada pri stvaranju engine-a sa EArgumentException koja imenuje nedostajući izvoz za arapski

Zašto noviji OCR modeli ne uspevaju da se učitaju?

HotPDF RapidOCR DLL vezuje statički ONNX Runtime 1.14, koji ne može čitati modele sačuvane ONNX IR verzijom 10, i noviji izvozi poput PP-OCRv5 modela mogu tražiti noviji runtime od toga; takav model pada pri stvaranju engine-a sa nativnom dijagnostikom. To ograničenje je razlog što su jezički paketi pribijeni na konkretno uparene PP-OCRv3 i PP-OCRv4 prepoznavače i rečnike umesto na „najnovije“, i zašto tabela gore meša dve generacije: svaki pribijen par jedan je koji se učita i verifikuje pod tim runtime-om

Instaler sprovodi uparivanje. Svaki fajl u svom manifestu nosi SHA256 heš, postojeći fajl sa drugačijim hešom zaustavlja instalaciju umesto da se prepiše, i svako preuzimanje doskoči pod privremenim imenom i tek se pomera na mesto posle što se njegov heš poklopi. To štiti od tihe verzije problema rečnika: neko ručno spusti noviji recognition.onnx u folder profila, broj klasa slučajno se poklopi, i ništa ne padne dok kupac ne prijavi da pretraga ne nalazi reči koje očigledno vide. U vreme rada adapter ostaje oflajn i nikada ne dovlči nedostajući model. Prepoznavač takođe validira oblik modela pri učitavanju, primajući NCHW unos sa fiksnom visinom 32 ili 48 piksela ili dinamičkom visinom, koju izvodi na 48

Ako treba pismo koje nijedan od devet profila ne pokriva, i dalje možete usmeriti RecognitionModel i CharacterDictionary na sopstvene fajlove. Iste provere važe, što je poenta: nepoklopljen par pada pri inicijalizaciji, ne u arhivu vašeg kupca. Za stranice gde nijedan RapidOCR profil ne prija, Tesseract adapter za pretraživi PDF priključuje se na isti poziv ApplyLoadedOCRTextLayer, a za mašinski štampane ASCII obrasce ugrađeni OCR engine sa poklapanjem šablona ne trebaju modeli uopšte

Brzi pregled: višejezična RapidOCR proverna lista

  • Stvarajte opcije sa THPDFRapidOCRDLLOptions.ForLanguage i tretirajte EArgumentException kao nepodržan tag, ne kao grešku u vreme rada
  • Menjajte RecognitionModel i CharacterDictionary zajedno, nikada jedno samo; jednaki brojevi klasa ne dokazuju jednak redosled karaktera
  • Držite rečnike kao UTF-8 bez BOM-a, nikada ne odsecajte unose, i očekujte da model ima N + 2 klase: prazan, N unosa, razmak
  • Prilagođeni CTC dekoder mora pokriti poslednju klasu i poslednji vremenski korak i čuvati ponove razdvojene praznim
  • Koristite jedan engine po jezičkom profilu i predajte eksplicitne liste stranica za dokumente mešanih pisama
  • RightToLeft menja samo redosled kutija; prepoznati tekst ostaje u Unicode logičkom redosledu
  • Instalirajte modele sa Install-RapidOCRModels.ps1 da SHA256 pribijanja drže uparivanje modela i rečnika; postavite UseAngleClassifier := False ako ste instalirali sa -SkipClassifier
  • Podignite MaxPixels iznad podrazumevanih 16.777.216 pre pokretanja A3 ili većih stranica na 300 DPI

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