Tehnični članak

HotPDF: kitajski in večjezični OCR z RapidOCR v Delphiju

HotPDF opravlja kitajski in večjezični OCR v Delphiju skozi svoj native adapter DLL RapidOCR: THPDFRapidOCRDLLOptions.ForLanguage preslika jezikovno oznako, kot je 'zh-CN', 'zh-TW', 'ru' ali 'ar', na ujemajoč model prepoznavanja in slovar znakov, THotPDF.ApplyLoadedOCRTextLayer pa spremeni prepoznane vrstice v nevidno, iskalno plast besedila Unicode na skeniranih straneh PDF

Delujoča demonstracija v latinski pisavi je lažji del. Zanimive odpovedi se začnejo, kadar preklopite na tradicionalno kitajščino ali ruščino in izhod se spremeni v samozavestno, dobro oblikovano nesmisel, kadar vsaka vrstica tiho izgubi svoj zadnji znak, ali kadar se stran v arabščini vrne s svojimi besedilnimi okvirji v napačnem vrstnem redu. Nobeno od teh ne javi izjeme same od sebe. Jezikovne prednastavitve, dodane v HotPDF v2.775.0, obstajajo večinoma, da zaprejo te vrzeli, štiri pasti spodaj pa so vredne razumevanja, tudi če se nikoli ne dotaknete native kode, ker vsaka pojasnjuje simptom, za katerim bi sicer lahko dan lovili

Kako ForLanguage izbere model in slovar?

THPDFRapidOCRDLLOptions.ForLanguage razreši oznako na enega od devetih profilov in vrne opcije, ki kažejo na <profile>/recognition.onnx in <profile>/dictionary.txt pod vašo mapo modelov, hkrati pa obdrži deljenega zaznavalca, neobveznega klasifikatorja kota ter privzete vrednosti niti, pikslov in roka od THPDFRapidOCRDLLOptions.Default. Metoda pretvori oznako v male črke, podčrtaje spremeni v pomišljaje in obreže obdajajoče presledke, tako da 'zh_TW', 'ZH-tw' in ' zh-tw ' vsi pristanejo na istem profilu. Vzdevki so izrecen seznam, ne preskus predpone: 'zh-Hant-TW' je sprejet, ker je našteto, poljubna regionalna različica, ki ni našteta, pa javi EArgumentException, preden se naloži kateri koli model

HotPDF razreševanje profila ForLanguage za THPDFRapidOCRDLLOptions: oznake, kot so zh_TW, ZH-tw in zh-TW, so normalizirane in primerjane z devetimi naštetimi profili, vsak pa pripne par model prepoznavanja in slovar, ki sta vedno nastavljena skupaj, neznan tag pa javi EArgumentException, preden se kateri koli model naloži
Ena oznaka izbere en pripet par model-slovar; zaznavalec, klasifikator in proračuni ostanejo deljeni, neznana oznaka pa odpove hitro, namesto da bi karkoli naložila
ProfilJezikiPrimeri oznakPripet model
chPoenostavljena kitajščina in angleščinazh, zh-CN, zh-Hans, chi_simPP-OCRv4
chinese_chtTradicionalna kitajščinazh-TW, zh-HK, zh-Hant, chi_traPP-OCRv3
enAngleščinaen, en-US, en-GB, engPP-OCRv4
latinFrancoščina, nemščina, španščina, portugalščina, italijanščina, nizozemščina, turščinafr, de, es-419, pt-BR, trPP-OCRv3
japanJaponščinaja, ja-JP, jpnPP-OCRv4
koreanKorejščinako, ko-KR, korPP-OCRv4
cyrillicRuščina, ukrajinščina, bolgarščina, beloruščinaru, ru-RU, uk, bgPP-OCRv3
arabicArabščina, perzijščina, urdujščinaar, ar-SA, fa, urPP-OCRv4
devanagariHindijščina, maratščina, nepalščinahi, mr, nePP-OCRv4

Adapter sam nikoli ne prenese ničesar. Datoteke preskrbite enkrat s priloženim pomočnikom, na primer tools/Install-RapidOCRModels.ps1 -Destination C:/OCR/models -Language ch,chinese_cht,cyrillic (ali -Language All za vseh devet profilov), pomočnik pa postavi deljenega zaznavalca in klasifikator na korenska imena datotek, ki jih pričakuje Default. Po tem postane kitajski sken v poenostavljeni pisavi iskalen s nekaj vrsticami. Omrežje motorja je isti šiv IHPDFOCREngine, opisan v članku o DLL RapidOCR v procesu in njegovi meji ABI, zato ta ostane osredotoč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, deljen zaznavalec in 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
    // prazen seznam strani pomeni vsako stran; strani z že obstoječim besedilom se preskočijo
    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;

Dve podrobnosti tistega izhoda zaslužita opombo. Native cevovod vrne en rezultat na zaznano vrstico besedila, ne na besedo, tako da AcceptedWordCount tu šteje vrstice, MinimumConfidence pa se primerja s povprečnim zaupanjem znakov cele vrstice: vrstica s povprečjem 0,45 se izpusti kot enota. UniqueScalarCount poroča, koliko različnih skalarjev Unicode je morala plast besedila preslikati v svojo pisavo in tabelo ToUnicode — koristen preizkus zdrave pameti, da je besedilo CJK dejansko prišlo, namesto peščice latinskih rezerv. Obdržite vmesnik motorja živ čez dokumente, ker se inicializacija modelov zgodi v tovarni in je drag korak

Zakaj zamenjava samo modela prepoznavanja izdela smeti?

Model prepoznavanja CTC nikoli ne izda znakov, samo razredne indekse, slovar pa je edina stvar, ki iz indeksa 1.204 naredi glif. Zamenjajte ch/recognition.onnx za cyrillic/recognition.onnx, obdržite pa kitajski slovar, in model bo z veseljem izdal veljavne cirilične indekse, ki jih stari slovar prevede v naključne znake Han. Rezultat izgleda kot besedilo, gre skozi potrjevanje UTF-8 in je iskalen za točno nič. Zato ForLanguage vedno nastavi RecognitionModel in CharacterDictionary skupaj in zakaj ročno zgrajene opcije nikoli ne smejo spremeniti enega brez drugega

Očitni varnostni preizkus, primerjava velikosti slovarja s širino izhoda modela, je nujen, a ni zadosten. Dva slovarja lahko imata isto število vnosov v drugačnem vrstnem redu in odmik za ena v vrstnem redu premakne vsak znak za eno kodno točko. HotPDF zato preverja v dveh stopnjah, kadar tovarna inicializira model. Najprej mora število izhodnih razredov biti enako vnosom slovarja plus dva. Drugič, kadar datoteka ONNX vgradi seznam metapodatkov character, se vsak vnos slovarja primerja z njim po vrstnem redu, neujemanje pa ne uspe inicializacije z EInvalidOperation in native diagnostiko, namesto da bi kasneje izdelalo verjetne smeti

"Plus dva" pride iz postavitve razredov. Razred 0 je CTC blank, razredi 1 do N so vrstice slovarja v vrstnem redu datoteke, zadnji razred pa je presledek. Nekateri slovarji nosijo tudi svoj vnos presledka in ta vrstica mora ostati točno takšna, kot je. Tu dobrohoten Trim naredi pravo škodo: spremeni vnos enega samega presledka v prazen niz in premakne ali pokvari tabelo. Edina varna normalizacija je odstranitev končnega znaka za povratkom na začetek vrstice, tako da se slovar, shranjen s končavami vrstic CRLF, naloži pravilno, oznaka vrstnega reda bajtov UTF-8, prazna vrstica ali vnos, ki vsebuje tabulator, pa so zavrnjeni. Skica spodaj prikaže postavitev v Pascalu; to je razlagalna koda, ne API HotPDF

HotPDF postavitev razredne tabele CTC za slovarje RapidOCR: razred 0 je blank, razredi 1 do N so vrstice slovarja v vrstnem redu datoteke, morebiten osamljen vnos presledka pa je ohranjen, zadnji razred pa je presledek — skupaj N plus 2 izhodna razreda, ki jih tovarna preveri proti modelu, vključno z metapodatki
Slovar je edina stvar, ki iz razrednih indeksov naredi znake, zato se njegova velikost, vrstni red in vnos presledka potrdijo, preden se prepozna ena sama stran
// Samo ilustracija: razredna tabela, ki jo pričakuje CTC prepoznavalec
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);                                   // nova vrstica na koncu datoteke
  SetLength(Result, Last + 3);
  Result[0] := '';                               // razred 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: izpusti samo CR
    if (Entry = '') or (Pos(#9, Entry) > 0) then
      raise EArgumentException.Create('Invalid dictionary entry');
    Result[I + 1] := Entry;                      // nikoli Trim: ' ' je razred
  end;
  Result[Last + 2] := ' ';                       // zadnji razred: presledek
  // Length(Result) mora biti enak številu izhodnih razredov modela
end;

Kaj dejansko dela pohlepno dekodiranje CTC?

Pohlepno dekodiranje CTC izbere najvišje ocenjeni razred pri vsakem časovnem koraku, skrči zaporedne ponovitve v en znak in izpusti razred blank; blank je tisto, kar omogoča, da resnično podvojeni črki preživita. Model prepoznavanja gleda vrstico besedila kot zaporedje ozkih navpičnih rezin in za vsako rezino, oziroma časovni korak, izda verjetnost za vsak razred. Vrstica, ki vsebuje AA中, bi lahko izdelala zaporedje argmax A A blank A 中 space. Skrčitev prvih dveh korakov A da enega A, blank ga loči od naslednjega A, rezultat pa je AA中 s končnim presledkom, ki ostane nedotaknjen. Brez pravila blank bi bila book in bok nerazločljiva

HotPDF sprehod skozi GreedyCTCDecode: šest časovnih korakov glasuje argmax razrede A, A, blank, A, znak Han in presledek, zaporedne ponovitve se skrčijo, blank ponastavi varovalo ponovitev, tako da resnično podvojeni znak preživi, trije robni hrošči pa tiho izpustijo presledke med besedami, zadnji znak ali podvojene znake
Dekodirnik je ducat vrstic in vsaka meja šteje: vključite zadnji razred, vključite zadnji korak in pustite, da loči ponovitve samo blank

Ker je dekodirnik samo ducat vrstic, je zlahka zgrešiti meje, odpovedi pa so tihe. Če se notranja zanka argmax ustavi en razred prezgodaj, razred presledka nikoli ne more zmagati in vsaka vrstica pride nazaj brez presledkov med besedami, kar razbije iskanje po frazah na straneh v angleščini in latinici. Če se zunanja zanka ustavi en časovni korak prezgodaj, izgine zadnji znak vsake vrstice — za kratko vrstico lahko to je tretjina besedila. In če varovala ponovitev ne ponastavi blank, se podvojeni znaki, kot je ll, ali kitajske podvajanja, kot je 谢谢, skrčijo v enega. Dekodirnik HotPDF vključuje zadnji razred in zadnji časovni korak, ohranja ponovitve, ločene z blank, poleg tega pa zavrne ocene, ki niso končne ali padejo izven 0 do 1, ter vsako število razredov, ki se ne ujema s slovarjem. Tukaj je ista logika kot Pascal ilustracija

// Samo ilustracija: pohlepno dekodiranje CTC s pravimi mejami.
// Scores drži Steps * Classes verjetnosti, ena vrstica na časovni korak
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;                            // razred 0 je CTC blank
  for Step := 0 to Steps - 1 do             // vključi zadnji časovni korak
  begin
    Best := 0;
    BestScore := Scores[Step * Classes];
    for C := 1 to Classes - 1 do            // vključi zadnji razred (presledek)
      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 ponastavi varovalo ponovitev
  end;
end;

Pohlepno dekodiranje ni najbolj natančna razpoložljiva strategija CTC; iskanje žarka z jezikovnim modelom lahko popravi nekaj dvoumnih rezin. Za natisnjene dokumente pri 300 DPI je pohlepni rezultat običajno to, kar model ponuja, dekodirnik pa ni mesto za odpravljanje šibkosti modela. Latinski model PP-OCRv3 lahko na primer prebere ñ kot n, tudi na čistem vhodu. HotPDF tega ne prekrije s popravljanjem znakov po obdelavi, ker tabela zamenjav, ki popravi španščino, pokvari nekaj drugega, napačen znak v iskalni plasti pa je slabše od poštene zgrešitve

Kako HotPDF ureja vrstice besedila, vključno z arabščino od desne proti levi?

HotPDF razvrsti zaznane besedilne okvirje od zgoraj navzdol, združi okvirje v vrstico, kadar se navpično prekrivajo za vsaj polovico višine manjšega okvirja, in uredi vsako vrstico od leve proti desni ali od desne proti levi, kadar je omogočen RightToLeft; znaki znotraj vsake prepoznane vrstice niso nikoli obrnjeni. Združevanje šteje, ker zaznavalec pogosto razbije eno vidno vrstico v več okvirjev — na primer oznako in vrednost, ločena s široko vrzeljo — in čisto razvrščanje po zgornji koordinati bi jih prepletlo s sosednjo vrstico, kadar koli se njuna zgornja meja razlikuje za piksel ali dva

Prednastavitev arabščine nastavi RightToLeft := True, kar pove DLL-ju, da uredi okvirje v vsaki vrstici po njihovem desnem robu, od desnega roba navznoter. To je celoten učinek. Besedilo, ki ga model vrne za vrstico, je že v logičnem vrstnem redu Unicode — vrstnem redu, v katerem ga bralec arabščine bere in tipka — in to je tudi vrstni red, ki ga pričakujeta izvlečenje in iskanje besedila PDF. Mehanično obračanje niza, da bi "izgledal prav" v razhroščevalniku, bi pokvarilo iskanje, kopiranje in lepljenje ter bralnike zaslona. Dvostranski prikaz in oblikovanje glifov sta delo pregledovalnika

En motor streže enemu jezikovnemu profilu. Samodejnega zaznavanja pisave ni, zato dokument, ki meša pisave, potrebuje en motor na profil, uporabljen na straneh, ki ga uporabljajo. Ker ApplyLoadedOCRTextLayer vzame izrecen seznam strani in vsak klic zapiše kot svojo lastno transakcijo vse-ali-nič, je to enostavno

uses
  SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

function CreateRapidEngine(const Tag: string): IHPDFOCREngine;
var
  Models: THPDFRapidOCRDLLOptions;
begin
  // javi EArgumentException za neznan tag, preden se naloži kateri koli model
  Models := THPDFRapidOCRDLLOptions.ForLanguage(Tag);
  Models.MaxPixels := 33554432;          // prostor za strani A3 pri 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');  // profil chinese_cht
  Arabic := CreateRapidEngine('ar-SA');   // profil arabic, 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;

Vrstica MaxPixels je tam z razlogom. Opcije DLL se privzamejo na 16.777.216 pikslov na zahtevo, kar A4 in US Letter pri 300 DPI udobno pokrije, stran A3 pri 300 DPI pa je približno 3508 krat 4961 pikslov, torej približno 17,4 milijona, in zahteva je zavrnjena kot čez proračun. Dvignite MaxPixels (strop je 67.108.864) ali znižajte THPDFOCRTextLayerOptions.DPI za velike formate. Urejanje od desne proti levi uporablja neobvezni izvoz HPDFRapidOCRSetReadingDirection različice ABI 1; adapter ga zahteva samo, kadar je nastavljen RightToLeft, tako da starejši DLL še naprej postreže jezike od leve proti desni in odpove pri ustvarjanju motorja z EArgumentException, ki poimenuje manjkajoči izvoz za arabščino

Zakaj se novejši modeli OCR ne naložijo?

DLL RapidOCR HotPDF poveže statični ONNX Runtime 1.14, ki ne more brati modelov, shranjenih z različico ONNX IR 10, novejši izvozi, kot so modeli PP-OCRv5, pa lahko zahtevajo novejše izvajalno okolje od tega; takšen model odpove pri ustvarjanju motorja z native diagnostiko. Ta omejitev je razlog, da so jezikovni paketi pripeti na določene pare prepoznavalnika in slovarja PP-OCRv3 ter PP-OCRv4, namesto na "najnovejše", in zakaj tabela zgoraj meša obe generaciji: vsak pripet par je takšen, ki se pod tem izvajalnim okoljem naloži in potrdi

Namestitveni program uveljavi parjenje. Vsaka datoteka v svojem manifestu nosi hash SHA256, obstoječa datoteka z drugačnim hashom ustavi namestitev, namesto da bi bila prepisana, vsak prenos pa pristane pod začasnim imenom in se premakne na svoje mesto šele, ko se njegov hash ujema. To ščiti pred tiho različico problema slovarja: nekdo ročno spusti novejši recognition.onnx v mapo profila, število razredov po naključju se ujema in nič ne odpove, dokler stranka ne poroča, da iskanje ne najde besed, ki jih vidno vidi. Ob izvajanju adapter ostane offline in nikoli ne prenese manjkajočega modela. Prepoznavalec tudi potrjuje obliko modela ob nalaganju, sprejema vhod NCHW s fiksno višino 32 ali 48 pikslov ali dinamično višino, ki jo poganja pri 48

Če potrebujete pisavo, ki je noben od devetih profilov ne pokriva, lahko še vedno usmerite RecognitionModel in CharacterDictionary na svoje lastne datoteke. Isti preizkusi veljajo — to je točka: neujemajoč par odpove pri inicializaciji, ne v arhivu vaše stranke. Za strani, kjer se ne ujema noben profil RapidOCR, se adapter Tesseract za iskalni PDF priključi na isti klic ApplyLoadedOCRTextLayer, za strojno natisnjene obrazce ASCII pa vgrajeni motor OCR z ujemanjem predlog ne potrebuje modelov sploh

Hiter pregled: kontrolni seznam večjezičnega RapidOCR

  • Ustvarite opcije s THPDFRapidOCRDLLOptions.ForLanguage in EArgumentException obravnavajte kot nepodprto oznako, ne kot odpoved izvajanja
  • Spremenite RecognitionModel in CharacterDictionary skupaj, nikoli enega samega; enaka števila razredov ne dokazujejo enakega vrstnega reda znakov
  • Slovarje hranite kot UTF-8 brez BOM, vnosov nikoli ne obrezujte in pričakujte, da ima model N + 2 razredov: blank, N vnosov, presledek
  • Lasten dekodirnik CTC mora pokriti zadnji razred in zadnji časovni korak ter ohraniti ponovitve, ločene z blank
  • Uporabite en motor na jezikovni profil in podajte izrecne sezname strani za dokumente z mešanimi pisavami
  • RightToLeft spremeni samo vrstni red okvirjev; prepoznano besedilo ostane v logičnem vrstnem redu Unicode
  • Namestite modele z Install-RapidOCRModels.ps1, tako da pripetja SHA256 držita parjenje modela in slovarja; nastavite UseAngleClassifier := False, če ste namestili z -SkipClassifier
  • Dvignite MaxPixels nad privzeti 16.777.216, preden poganjate strani A3 ali večje pri 300 DPI

Jezikovne prednastavitve RapidOCR, native adapter DLL in cevovod plasti besedila OCR so del komponente HotPDF Delphi PDF za Delphi, C++Builder in Windows FPC/Lazarus, od v2.775.0 za večjezične profile