HotPDF vključuje THPDFBuiltInOCREngine, omejeni mehanizem OCR z ujemanjem predlog, v celoti napisan v Object Pascalu: izrisano stran binarizira z Otsujevim pragom, izloči glife kot povezane komponente in vsak glif točkuje po pokritosti sivin glede na predpomnjene predloge iz več pisav, zato lahko aplikacija Delphi ustvari iskano besedilno plast brez zunanje odvisnosti OCR. Mehanizem je bilo treba v v2.731.0 zgraditi na novo, razlog pa ni bil ujemalnik. Bili so piksli
Stari mehanizem je teste opravil. Na sintetičnih bitnih slikah je prepoznaval velike črke ASCII, na Win32 pa je to počel več mesecev. Nato so isto kodo pognali pod Win64 in rezultat ni bil nič: nobene besede, nobene diagnostike razen »found no high-contrast foreground«, nobenega sesutja. Izkazalo se je, da sta se v poti branja pikslov medsebojno izničili dve neodvisni napaki, njuno razpletanje pa dobro pokaže, zakaj koda OCR odpove tiho namesto glasno
Zakaj je stari mehanizem OCR deloval samo po naključju?
Stari mehanizem je deloval, ker so bile bitne slike predlog in ciljne bitne slike obrnjene na isti način, zato je bila navpična inverzija v bralniku pikslov za ujemalnik nevidna. TBitmap.ScanLine vrne vrstice v nasprotnem vrstnem redu od konvencije DIB s pozitivnim biHeight, ki jo predpostavlja preostala slikovna pot. Izrišite M na glavo, primerjajte ga s predlogo, ki je prav tako obrnjena na glavo, in razlika L1 je enaka pravilni primerjavi. Vsak glif se je ujemal. Nič ni bilo pravilno
Prav ta simetrija naredi ta razred napak drag. Vsak enostranski popravek prekine ujemanje: če popravite branje cilja in pustite predloge pri miru, se prepoznavanje sesuje v šum; če najprej popravite predloge, se isti zlom zgodi iz druge smeri. Postopne poti popravila ni. Zato je ponovna gradnja zamenjala celotno branje s GetDIBits glede na izrecno deklariran BITMAPINFOHEADER, pri katerem pozitiven biHeight po pogodbi pomeni vrstice od spodaj navzgor, nato pa pri kopiranju v medpomnilnik sivin enkrat, namerno, obrnila vrstni red
Druga napaka je tista, ki se je pokazala samo na Win64. HDC, poslan v GetDIBits, ne sme biti lastni pomnilniški DC bitne slike, ker je bitna slika že izbrana vanj in Windows to dokumentira kot neveljavno. Posredovanje Bitmap.Canvas.Handle je proces Win32 dopuščal, v testnem procesu Win64 pa je dosledno odpovedalo. Popravek je začasni zaslonski DC iz GetDC(0), sproščen v bloku finally, ki ni povezan z nobeno bitno sliko
procedure BitmapToGray(Bitmap: TBitmap; out Gray: TBytes);
var
Work: TBitmap;
Info: TBitmapInfo;
Buffer: TBytes;
DC: HDC;
P: PByte;
Stride, X, Y: Integer;
begin
Work := TBitmap.Create;
try
Work.Assign(Bitmap);
Work.PixelFormat := pf24bit;
Stride := ((Work.Width * 24 + 31) div 32) * 4;
SetLength(Buffer, Stride * Work.Height);
FillChar(Info, SizeOf(Info), 0);
Info.bmiHeader.biSize := SizeOf(BITMAPINFOHEADER);
Info.bmiHeader.biWidth := Work.Width;
Info.bmiHeader.biHeight := Work.Height; // pozitivno => vrstice od spodaj navzgor
Info.bmiHeader.biPlanes := 1;
Info.bmiHeader.biBitCount := 24;
Info.bmiHeader.biCompression := BI_RGB;
DC := GetDC(0); // nikoli Work.Canvas.Handle: Work je izbrana vanj
if DC = 0 then
raise EInvalidOperation.Create('Recognition bitmap pixels could not be read');
try
if GetDIBits(DC, Work.Handle, 0, Work.Height,
@Buffer[0], Info, DIB_RGB_COLORS) <> Work.Height then
raise EInvalidOperation.Create('Recognition bitmap pixels could not be read');
finally
ReleaseDC(0, DC);
end;
SetLength(Gray, Work.Width * Work.Height);
for Y := 0 to Work.Height - 1 do
begin
P := @Buffer[(Work.Height - 1 - Y) * Stride]; // en nameren obrat
for X := 0 to Work.Width - 1 do
Gray[Y * Work.Width + X] :=
(Integer(P[X * 3]) * 29 + Integer(P[X * 3 + 1]) * 150 +
Integer(P[X * 3 + 2]) * 77) shr 8;
end;
finally
Work.Free;
end;
end;
Binarizacija in povezane komponente: od sivih pikslov do okvirjev glifov
HotPDF najprej binarizira z Otsujevo metodo in se na prag lokalnega okna vrne samo, ko Otsujev postopek ni uporaben. Globalna pot zahteva resnično bimodalni histogram: mehanizem izračuna maksimum variance med razredoma, hkrati pa zahteva, da razpon sivin obsega vsaj 64 ravni, preden rezultatu zaupa. Izpran sken, stran z gradientnim ozadjem ali bitna slika, ki je skoraj v celoti črnilo, ta test spodleti. Nadomestna pot nato vsak piksel primerja s povprečjem okna 31 krat 31 z odmikom 6 ravni sivine, izračunanim s tekočimi vsotami stolpcev, tako da drsno okno ostane linearno glede na število pikslov
Izločanje glifov je označevanje 8-povezanih komponent nad nastalo masko, z izrecnim skladom namesto rekurzije, ker lahko maska celotne strani pri globokem zapolnjevanju brez težav preobremeni sklad niti Delphi. Ob označevanju se izvajata dva filtra: komponente, manjše od 9 pikslov, se zavržejo kot pikčasti šum, komponenta, ki zajema več kot tri petine širine in višine slike, pa kot okvir ali črta namesto glifa. V drugem prehodu se navpično zloženi okvirji združijo, ko je njihov vodoravni prekrivni del vsaj četrtina ožjega okvirja, s čimer se pika črke i ali j znova združi s steblom. Vse to deluje nad rastrjem, raster pa prihaja iz istega izrisovalnika, opisanega pri izrisu naložene strani PDF v bitno sliko v Delphiju, kar je pomembno iz praktičnega razloga: kakovost OCR je navzgor omejena s kakovostjo izrisa, privzeti DPI besedilne plasti 300 pa je premišljen kompromis, ne največja vrednost
Zakaj sta velika I in mala l nedoločljiva?
V Arialu se velika črka I in mala črka l rastrirata v bitno enaki navpični črti, zato ju nobena oblika značilnosti ne more ločiti in mora velikost črke priti od drugod. Odgovor mehanizma je gručenje višin na ravni vrstice. Okvirji glifov se združijo v besedilne vrstice po navpičnem prekrivanju, vsaka vrstica se analizira glede višine velikih črk in modalne osnovnice, višine v vrstici pa se razdelijo v kratko in visoko gručo. Črta v kratki gruči je l, ista črta v visoki gruči pa I
Očitna izvedba te delitve je prag s stalnim razmerjem, vendar ne deluje. Razmerje višine x proti višini velikih črk v Arialu je okoli 0.72, kar pristane natanko na vrednostih 0.70 in 0.75, po katerih vsi najprej posežejo. Premaknite konstanto za stotinko v katero koli smer in cel korpus zamenja velikost črk. HotPDF namesto tega izvede enodimenzionalno delitev k=2 z najmanjšo varianco: višine uredi, preizkusi vsako mesto reza in obdrži rez, pri katerem je vsota kvadratov odklonov znotraj gruč najmanjša. Prag tako postane lastnost strani, ne konstanta v izvorni kodi
// ClusterHeights je urejen naraščajoče; poišči delitev k=2 z najmanjšo varianco
BestSplit := 1;
BestVariance := 1E18;
for I := 1 to ClusterCount - 1 do
begin
SumA := 0;
for J := 0 to I - 1 do SumA := SumA + ClusterHeights[J];
SumB := 0;
for J := I to ClusterCount - 1 do SumB := SumB + ClusterHeights[J];
MeanA := SumA / I;
MeanB := SumB / (ClusterCount - I);
Variance := 0;
for J := 0 to I - 1 do
Variance := Variance + Sqr(ClusterHeights[J] - MeanA);
for J := I to ClusterCount - 1 do
Variance := Variance + Sqr(ClusterHeights[J] - MeanB);
if Variance < BestVariance then
begin
BestVariance := Variance;
BestSplit := I;
end;
end;
// samo razmerje med povprečjema gruč določa kratki pas
if SmallMean / TallMean <= 0.80 then
SmallGroup := ggSmall // prava pasovna višina x: male oblike
else
SmallGroup := ggTall; // en višinski pas: vse je v višini velikih črk
Line.LowercaseContext := (SmallGroup = ggSmall);
Vrstice z enim samim višinskim pasom nimajo nobenega notranjega dokaza. Naslov z velikimi črkami in napis z malimi črkami sta sama zase videti enako. Pri takih vrsticah HotPDF primerja njihovo srednjo višino s srednjo višino x na ravni strani, pridobljeno iz vrstic, ki so se res razdelile: razmerje 1.10 ali manj označi vrstico kot kontekst malih črk, razmerje 1.18 ali več kot kontekst velikih črk, vse vmes pa ostane brez omejitve. Ujemanje nato uporabi majhen bonus 0.03 za preferenco velikosti črk v smeri kandidata, ki se ujema s tem kontekstom, kar izenačene rezultate rahlo premakne, ne da bi kadar koli preglasilo jasno razliko v obliki
Zakaj je mreža predlog 12x18 zamenjevala c in o?
Mreža predlog je bila povečana z 12 krat 18 celic na 16 krat 24, ker je pri manjši ločljivosti razlika v pokritosti sivin med c in o padla pod 0.007, kar je precej znotraj praga dvoumnosti mehanizma. Vsak okvir glifa se v mrežo ponovno vzorči kot vrednosti pokritosti od 0 do 255, ne kot binarna maska, zato celica, ki je za tretjino črnilo, prebere približno 85, namesto da bi se zaokrožila na črno ali belo. Pri 12 krat 18 odprta stran c komaj preseže en stolpec celic, povprečje z izravnavanjem robov pa vrzel spere. Pri 16 krat 24 vrzel preživi ponovno vzorčenje in večina zlahka zamenljivih parov se vrne na varno razdaljo
Točkovanje je normalizirana razdalja L1 med mrežama pokritosti, povečana za kazen 0.30-kratnika logaritemske razlike razmerja stranic in 0.16-kratnika razlike gostote črnila, s trdim predhodnim filtrom, ki preskoči vsako predlogo, katere razmerje stranic se razlikuje za več kot faktor 2.6. Predloge se enkrat rastrirajo za vsak proces iz petih sistemskih pisav (Arial, Times New Roman, Courier New, Tahoma in Segoe UI) za abecedo 62 znakov, predpomnijo za kritičnim odsekom in uporabijo pri vsakem naslednjem klicu
Zadnja konstanta je zanimiva. Ko rezultat drugega kandidata pade znotraj 0.018 od zmagovalca, HotPDF zaupanje glifa omeji na 0.5, kar je pod pragom sprejema 0.55, zato glif preprosto ni izdan. To je namerno zapiranje ob napaki, ne artefakt uglaševanja: omejeni mehanizem, ki ugiba, ustvari iskano plast, katere besedilo se ne ujema s sliko, napačna beseda v besedilni plasti pa je hujša od manjkajoče, ker je oseba, ki pregleduje sken, ne vidi
Deljenje besed brez stalnega praga presledka
HotPDF prag za presledek med besedami izpelje za vsako vrstico iz porazdelitve razmikov med glifi, ne iz stalnega večkratnika povprečne širine glifa. Klasična hevristika »razmik, širši od 0.75 povprečnega pomika, je presledek« razpade takoj, ko vrstica združi števke z ozkimi črkami, saj povprečni pomik ne opisuje več ničesar stvarnega. Mehanizem namesto tega razmike za vrstico uredi in poišče največji skok med zaporednimi urejenimi vrednostmi, ki je meja med gručo znotraj besede in gručo med besedami, če ta obstaja. Trije varovali preprečijo sprožitev zaradi šuma: skok mora biti vsaj 0.22 povprečne širine glifa, prva vrzel nad rezom mora biti vsaj 0.32 te širine, zadnja vrzel pod rezom pa ne sme preseči 0.65. Če katero koli varovalo odpove, prag ostane MaxInt in celotna vrstica postane ena beseda. Prav zadnje varovalo prepreči, da bi en nenavadno širok par zaradi kerninga razdelil besedo na dve, kar je precej škodljivejša napaka od združitve dveh besed, saj združen žeton še vedno vsebuje prave znake v pravem vrstnem redu za iskanje podniza
Pisanje nevidne besedilne plasti čez skenirano sliko
ApplyLoadedOCRTextLayer prepoznane besede spremeni v iskano plast tako, da jih izriše v načinu izrisa besedila 3, torej načinu brez polnila in brez poteze, opredeljenem v ISO 32000-1 §9.3.6, in jih postavi čez skenirano sliko, iz katere izvirajo. Tok vsebine se začne z BT, ki mu sledi 3 Tr, vsaka beseda pa se postavi s tekstovno matriko, izdelano iz sporočene osnovnice, višine velikih črk, pretvorjene iz pikslov pri zahtevanem DPI, in vodoravnega merila, ki sintetični niz glifov raztegne na izmerjeno širino besede. Rezultat se kopira in išče kot besedilo, izriše pa ničesar
Obstaja različica brez mehanizma, ki vgrajeni prepoznavalnik ustvari namesto vas, in to naj uporabi večina klicateljev vgrajene poti. Prepoznavanje, preverjanje Unicode, obračun proračuna in gradnja vsebine se končajo, preden se odpre transakcija copy-on-write, zato preklic, prekoračitev proračuna ali napaka mehanizma pustijo graf objektov in številko različice nedotaknjena. Besede se filtrirajo dvakrat: mehanizem zavrže vse pod lastnim pragom zaupanja 0.55 na glif, nato THPDFOCRTextLayerOptions.MinimumConfidence (privzeto 0.5) zavrže cele besede pod pragom klicatelja
var
Doc: THotPDF;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if Doc.LoadFromFile('scan.pdf') < 1 then
Exit;
Options := THPDFOCRTextLayerOptions.Default; // DPI 300, MinimumConfidence 0.5
Options.SkipPagesWithText := True; // pusti digitalne strani pri miru
Options.UseOptionalContentGroup := True;
Options.OptionalContentGroupName := 'OCR Text Layer';
// različica brez mehanizma: HotPDF priskrbi vgrajeni omejeni prepoznavalnik
if Doc.ApplyLoadedOCRTextLayer([0], Options, Info) then
begin
Writeln(Info.AcceptedWordCount, ' words accepted by ',
string(Info.EngineName));
Doc.SaveLoadedDocument('scan-searchable.pdf');
end
else
Writeln('No text layer written: ', string(Info.Diagnostic));
finally
Doc.Free;
end;
end;
Eno omejitev je bolje povedati naravnost, kot da jo odkrijete pozneje. Nevidna plast uporablja skupno sintetično nevdelano pisavo Type0, kar zadostuje za iskanje in kopiranje v vsakem pregledovalniku, ne izpolni pa zahteve po vdelavi pisave iz ISO 19005. Če mora biti izhod PDF/A, mora klicatelj ločeno vdelati skladno pisavo. Poleg tega plast OCR vsebuje geometrijo, ne strukture, zato vrstni red branja izhaja samo iz položajev glifov; če potrebujete logični vrstni red s strani, ki že ima pravo besedilo, je izločanje besedila po vrstnem redu strukture, ki ga vodi drevo oznak, drugo orodje za drug problem
Kje se vgrajeni mehanizem konča
Vgrajeni mehanizem je namerno ozek in poznavanje njegovih robov je tisto, kar ga ohrani uporabnega. Namenjen je visokokontrastnemu strojno tiskanemu ASCII-ju iz pisav, podobnih njegovim petim predlogam, vse zunaj tega pa vrne nič besed namesto ugibanja. Konkretne meje so:
- slike do 4096 krat 4096 in 4.194.304 pikslov, z rokom prepoznavanja 2000 ms in sodelovalnim preklicem prek
THPDFCancellationToken - abeceda 62 znakov ASCII črk in števk; brez ločil, naglašenih znakov in CJK
- besedilo, poravnano z osjo, pri zasuku strani, ki ga je izrisovalnik že normaliziral; poševni skeni niso poravnani
- dvoumni pari glifov ostanejo nerazrešeni, zato lahko stran vrne delne besede ali diagnostiko »found no unambiguous ASCII words«
Ko je ta ovojnica premajhna, je stik IHPDFOCREngine. Implementirajte Recognize glede na lasten mehanizem, ga posredujte različici ApplyLoadedOCRTextLayer s tremi argumenti in vse navzdol (preslikava koordinat, obravnava zasuka, preverjanje Unicode, proračuni, atomska potrditev) ostane enako. Bitna slika je izposojena za čas sinhronega klica in je ne smete zadržati. Če želite potrditi, da je plast pravilno pristala, shranjeno datoteko znova naložite in izvedite običajno besedilno pot, opisano pri izločanju besedila iz naloženega PDF-ja v Delphiju; če se besede vrnejo, je plast resnična
Vgrajeni OCR z ujemanjem predlog, nevidna besedilna plast, izrisovalnik strani, ki ju napaja, in izločanje besedila iz naloženega dokumenta, ki ju preverja, so vsi del iste izvorne komponente VCL, brez zunanjega izvajalnega okolja OCR in brez DLL-a, ki bi ga bilo treba namestiti ob aplikaciji. Če v Delphiju ali C++Builderju gradite zajem dokumentov, arhiviranje ali iskanje po skeniranih PDF-jih, vam komponenta HotPDF Delphi PDF da celoten cevovod v eni odvisnosti