OCR tekstualni slojevi, granice barkodova i kutije zacrniljavanja lica klize na isečenim PDF stranicama kad se pikseli bitmape mapiraju nazad kroz MediaBox umesto kroz kutiju koju je renderer zaista rasterizovao: CropBox isečen na MediaBox (ISO 32000-1 §14.11.2). HotPDF je to popravio za ApplyLoadedOCRTextLayer u v2.770.153, a za DecodeLoadedPageBarcodes i DetectLoadedRedactionFindings u v2.770.154
Izveštaj o grešci koji obično stiže izgleda ovako. Arhiv skeniranih ugovora prođe kroz OCR, izlaz je pretraživ, i pogodak pretrage za brojem tače istaknut je pola inča ispod i levo od štampanog broja. Većina fajlova u seriji u redu je. Pokvareni svi su došli sa jedne skenirajuće stanice koja upisuje /CropBox da odseče marginu stola. Taj jedan detalj razdvaja sliku koju je OCR engine video od okvira u koji je tekstualni sloj postavljen, i isto neslaganje pomera granice barkodova i, ozbiljnije, kutije zacrniljavanja lica
Zašto se OCR tekstualni sloj odmiče od skeniranih reči?
Tekstualni sloj klizi jer se dve polovine cevovoda nisu slagale o tome koji pravougaonik bitmapa pokriva. U v2.766.64, HotPDF je promenio renderovanje, SVG izvoz, pregledač i štampu da poštuju CropBox: stranica se prikazuje kroz svoj CropBox isečen na MediaBox, što propisuje ISO 32000-1 §14.11.2, i dodat je GetLoadedPageVisibleBox da vrati tu vidljivu kutiju. Prepoznavne funkcije nastavile su da grade svoju transformaciju uređaj-stranica iz GetLoadedPageBox(PageIndex, pbMediaBox, ...). Raster sada pokriva vidljivu kutiju, transformacija i dalje pretpostavljala MediaBox, i svaka prepoznata pozicija vraćana je pomerena za razmak između dve
Pogođeni prozor je zato precizan. ApplyLoadedOCRTextLayer je pogrešno postavljao tekst od v2.766.64 do v2.770.152. DecodeLoadedPageBarcodes cele stranice i detekcija lica unutar DetectLoadedRedactionFindings ostali su pogrešni jednu verziju duže, do v2.770.153. Pre v2.766.64 renderer je crtao ceo MediaBox, pa su se mapiranje i raster slagali, po cenu prepoznavanja sadržaja koji pregledači nikada ne prikazuju. Popravke su izmenile tri stvari zajedno za svaku funkciju: transformaciju, procenu piksel budžeta i kutiju stranica predatu prilagođenom engine-u u zapisu zahteva
Nekoliko slučajeva nikada nije bilo pogođeno:
- Stranice bez
/CropBox, ili čiji je CropBox jednak MediaBox-u, mapiraju se identično pre i posle popravke DecodeLoadedPageBarcodessa postavljenimHasRegionrenderuje tačno region koji predaste i mapira kroz taj isti region, pa je dekodiranje eksplicitnog regiona bilo ispravno tokom celog perioda; provera da region leži unutar stranice i dalje koristi MediaBox- Nalazi zacrniljavanja zasnovani na obrascima (imejlovi, brojevi kartica i tako dalje) dolaze iz izdvajanja teksta u user space, a ne iz rastera, pa su se pomerili samo nalazi detekcije lica
Tri koordinatna okvira i koje HotPDF API-je svaki koristi
HotPDF kod koji dodiruje prepoznavanje bavi se tri okvira, i većina grešaka mapiranja dolazi od mešanja dva od njih
- Pikseli bitmape: poreklo gore-levo, Y raste naniže, jedinice su pikseli na zahtevani DPI.
THPDFOCRWord.Left,Top,RightiBottomsu u ovom okviru, kao i opciona bazna tačke, rezultati koje vraća prilagođeniIHPDFBarcodeDecoderi kutije od prilagođenogIHPDFFaceDetector - PDF user space učitane stranice: poreklo dole-levo, Y raste naviše, jedinice su tačke, sa
Bottom < Top.GetLoadedPageBoxiGetLoadedPageVisibleBoxvraćaju Left, Bottom, Right, Top u ovom okviru, kao i poljaPageLeft,PageBottom,PageRightiPageTopodTHPDFOCRRequest, granice uTHPDFDecodedBarcodei pravougaonici uTHPDFRedactionFinding - HotPDF koordinate crtanja stranica: API kojim gradite nove stranice (izlaz teksta, oblici, barkodovi, linkovi, polja obrasca) radi sa poreklom gore-levo i Y rastućim naniže. Taj okvir pripada generisanju dokumenta i nema veze sa gore navedenim API-jima učitanog dokumenta, pa nikada ne uvajte pravougaonik user space učitane stranice u njega nepromenjen
Zapis OCR reči namerno je baziran na pikselima: engine prijavljuje ono što je video u slici, a ApplyLoadedOCRTextLayer poseduje konverziju. Ta podela radi samo kad konverzija koristi pravu kutiju, što je v2.770.153 vratio
Transformacija uređaj-stranica iza OCR-a, barkodova i lica
HotPDF mapira piksele bitmape na stranicu jednom afinom matricom građenom iz pet ulaza: rotacije, razmere DPI / 72, visine bitmape i Left, Bottom, Right i Top renderovane kutije. OCR, dekodiranje barkodova i detekcija lica dele jednu rutinu za to, pa je jedan pogrešan ulaz kutije polomio sva tri na isti način. Za nerotiranu stranicu matrica stranica-u-uređaj [A B C D E F] je:
A = ScaleiD = -Scale, gde jeScale = DPI / 72; negativanDpreokreće user space (Y naviše) u prostor bitmape (Y naniže)B = C = 0, jer nerotirana stranica nema smicanje ni zamenu osaE = -Left * Scale, što pomera levu ivicu kutije na piksel kolonu 0F = BitmapHeight + Bottom * Scale, što mapira donju ivicu kutije na y = BitmapHeight, donju ivicu bitmape, pa gornja ivica doskoči na red 0
Pikseli se vraćaju na stranicu kroz inverz te matrice. Request.PageRotation nosi /Rotate stranice normalizovan na 0, 90, 180 ili 270 (bilo koja vrednost koja nije višekratnik 90 tretira se kao 0), i renderer okreće stranicu u smeru kazaljke kako ISO 32000-1 §7.7.3.3 traži. Pod rotacijom ose se zamene i drugi par ivica kutije pribije na poreklo bitmape. Zapisano kao inverzne formule, sa S = DPI / 72, x i y u pikselima i H visinom bitmape:
| /Rotate | Page X | Page Y | Ivice kutija od kojih mapiranje zavisi |
|---|---|---|---|
| 0 | Left + x / S | Bottom + (H - y) / S | Left, Bottom |
| 90 | Left + y / S | Bottom + x / S | Left, Bottom |
| 180 | Right - x / S | Bottom + y / S | Right, Bottom |
| 270 | Right - y / S | Top - x / S | Right, Top |
Poslednja kolona objašnjava zašto je bug izgledao nasumično u produkciji. CropBox koji odseče samo vrh stranice ostavlja Left i Bottom netaknute, pa su uspravne stranice izlazile savršeno i samo stranice koje nose /Rotate 270 klizale su. Rotacija takođe menja dimenzije bitmape: na 90 i 270 bitmapa je (Top - Bottom) * S piksela široka i (Right - Left) * S piksela visoka
Šta se pokvari sa MediaBox [0 0 612 792] i CropBox [36 36 576 756]?
Sa isečkom od pola inča na svakoj strani, tekstualni sloj nerotirane stranice doskoči tačno 36 tačaka levo i 36 tačaka ispod skeniranih reči kad se koristi MediaBox. Uzmite US Letter stranicu čiji CropBox odseče 36 tačaka (0.5 inča) sa svake ivice. Vidljiva kutija je 540 sa 720 tačaka, pa je na podrazumevanoj OCR rezoluciji od 300 DPI razmera 300 / 72 ≈ 4.1667 i bitmapa je 2250 sa 3000 piksela
Pretpostavimo da engine prijavljuje reč sa piksel kutijom Left 450, Top 600, Right 900, Bottom 660 i bez bazne linije. HotPDF zatim postavlja baznu liniju 20 odsto visine reči iznad donje ivice, na piksel redu 648, i mapira početnu tačku (450, 648):
- Kroz vidljivu kutiju: x = 36 + 450 / 4.1667 = 144.0 i y = 36 + (3000 - 648) / 4.1667 = 600.48, što je mesto gde je reč odštampana
- Kroz MediaBox: x = 0 + 108.0 = 108.0 i y = 0 + 564.48 = 564.48, ujednačen pomeraj od (-36, -36) tačaka
Okrenite istu stranicu i smer greške se menja, jer su uključene različite ivice. Na /Rotate 180 X član koristi Right, i 612 umesto 576 gura sloj 36 tačaka udesno dok Bottom i dalje vuče 36 tačaka naniže. Na /Rotate 270 i Right i Top su preveliki, pa se sloj pomera 36 tačaka udesno i 36 tačaka naviše. Dokument sa mešanim orijentacijama može pokazati klizanje u tri smera, pouzdan otisak ovog buga. Ručno pisani kod koji izvodi razmeru iz kutije, poput Bitmap.Width / (Right - Left), takođe razvlači svaku koordinatu sa 612 / 540, otprilike 13 odsto, na vrhu pomeraja
Koji su vaši PDF dokumenti pogođeni?
PDF dokument je izložen kad bar jedna stranica ima vidljivu kutiju koja se razlikuje od njenog MediaBox-a, i HotPDF vam to može reći u par linija. Uporedite GetLoadedPageBox sa pbMediaBox protiv GetLoadedPageVisibleBox za svaku stranicu, i ispišite GetLoadedPageRotation uz to da predvidite smer klizanja iz tabele gore. THPDFPageBoundary nudi i pbCropBox, pbBleedBox, pbTrimBox i pbArtBox, ali GetLoadedPageBox(pbCropBox) vraća se na MediaBox kad crop kutija ne postoji i ne iseče, pa je vidljiva kutija prava stvar za poređenje
uses
System.SysUtils, HPDFDoc;
procedure ReportCroppedPages(const FileName: string);
var
Pdf: THotPDF;
I: Integer;
ML, MB, MR, MT, VL, VB, VR, VT, Tmp: Single;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile(FileName) < 1 then
raise Exception.Create('Cannot load ' + FileName);
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
if not Pdf.GetLoadedPageBox(I, pbMediaBox, ML, MB, MR, MT) then
Continue;
// pohranjen niz može izlistati svoje uglove bilo kojim redom
if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
// već normalizovano i isečeno na MediaBox
if not Pdf.GetLoadedPageVisibleBox(I, VL, VB, VR, VT) then
Continue;
if (Abs(VL - ML) > 0.01) or (Abs(VB - MB) > 0.01) or
(Abs(VR - MR) > 0.01) or (Abs(VT - MT) > 0.01) then
Writeln(Format('Page %d MediaBox [%g %g %g %g] visible [%g %g %g %g] /Rotate %d',
[I + 1, ML, MB, MR, MT, VL, VB, VR, VT,
Pdf.GetLoadedPageRotation(I)]));
end;
finally
Pdf.Free;
end;
end;
Dva detalja GetLoadedPageVisibleBox su bitna za skripte poput ove. Funkcija ostavlja svoje out parametre netaknutima kad padne, pa prethodno postavljanje podrazumevane veličine stranice pre poziva bezbedan je obrazac. I kad deformisani CropBox uopšte ne preseca MediaBox, funkcija vraća MediaBox umesto praznog pravougaonika. Ako izveštaj izlista stranice a vaša raspoređena verzija je starija od v2.770.153 za OCR, ili v2.770.154 za barkodove i lica, ponovo pokrenite prepoznavanje na tim stranicama posle nadogradnje. OCR sloj uveren od pogođene verzije ostaje u sačuvanom fajlu, i podrazumevana opcija SkipPagesWithText preskočiće te stranice u drugom prolazu osim ako je isključite ili prvo uklonite stari sloj
Kako treba prilagođeni IHPDFOCREngine mapirati piksele nazad u PDF prostor?
Prilagođeni IHPDFOCREngine treba da vraća kutije reči u pikselima bitmape i pusti HotPDF da mapira; pretvarajte u user space samo za sopstvene odluke, i tada koristite kutiju iz zahteva, nikada MediaBox. Od v2.770.153 PageLeft, PageBottom, PageRight i PageTop zahteva opisuju renderovanu vidljivu kutiju, pa se poklapaju sa Request.Bitmap tačno. Pomoćnik ispod je inverz transformacije biblioteke, uključujući njenu upotrebu stvarne visine bitmape za uspravne stranice, pa se slaže sa HotPDF-om do piksela
uses
System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;
// Piksel bitmape (poreklo gore-levo, Y naniže) u PDF user space
// (poreklo dole-levo, Y naviše), kroz kutiju iz koje je bitmapa renderovana
procedure HotPixelToPage(Rotation, DPI, BitmapHeight: Integer;
Left, Bottom, Right, Top: Single; X, Y: Double;
out PageX, PageY: Double);
var
S: Double;
begin
S := DPI / 72.0;
case Rotation of
90: begin PageX := Left + Y / S; PageY := Bottom + X / S; end;
180: begin PageX := Right - X / S; PageY := Bottom + Y / S; end;
270: begin PageX := Right - Y / S; PageY := Top - X / S; end;
else
PageX := Left + X / S;
PageY := Bottom + (BitmapHeight - Y) / S;
end;
end;
Realan razlog da treba user space unutar engine-a je pravilo zone: fakture čiji glavni tekst nikada ne želite pretraživim, ili oblast pečata koja zbunjuje prepoznavač. Engine ispod, pisan sa TInterfacedObject da brojanje referenci rukuje njegovim vekom, filtrira reči po tome gde njihovi centri padaju na stranici, pa vraća preživele netaknute u piksel koordinatama. RunRecognizer zamenjuje vaš poziv prepoznavača
type
TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
private
FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single; // user space
function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
out Words: THPDFOCRWords): boolean; // vaš prepoznavač, piksel kutije
public
constructor Create(SkipLeft, SkipBottom, SkipRight, SkipTop: Single);
function GetName: AnsiString;
function Recognize(const Request: THPDFOCRRequest;
out Words: THPDFOCRWords; out Diagnostic: AnsiString): boolean;
end;
function TZoneFilterOCREngine.Recognize(const Request: THPDFOCRRequest;
out Words: THPDFOCRWords; out Diagnostic: AnsiString): boolean;
var
Raw: THPDFOCRWords;
I, Count: Integer;
CX, CY: Double;
begin
Diagnostic := '';
SetLength(Words, 0);
if not RunRecognizer(Request.Bitmap, Request.MaxWords, Raw) then
begin
Diagnostic := 'recognizer failed';
Exit(False);
end;
SetLength(Words, Length(Raw));
Count := 0;
for I := 0 to High(Raw) do
begin
HotPixelToPage(Request.PageRotation, Request.DPI,
Request.Bitmap.Height, Request.PageLeft, Request.PageBottom,
Request.PageRight, Request.PageTop,
(Raw[I].Left + Raw[I].Right) / 2, (Raw[I].Top + Raw[I].Bottom) / 2,
CX, CY);
if (CX >= FSkipLeft) and (CX <= FSkipRight) and
(CY >= FSkipBottom) and (CY <= FSkipTop) then
Continue;
Words[Count] := Raw[I]; // i dalje pikseli: HotPDF ih sam mapira
Inc(Count);
end;
SetLength(Words, Count);
Result := True;
end;
Predajte engine ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info) kao i svaki drugi engine. Biblioteka validira ono što se vrati pre nego što mu veruje: reč se ispušta i broji u Info.DroppedWordCount kad njena kutija napusti bitmapu, kad je Right <= Left ili Bottom <= Top, ili kad je Confidence van 0..1 ili ispod MinimumConfidence. Vraćanje više reči nego MaxWordsPerPage, ili guranje tekućeg zbira preko MaxTotalWords, pada celu poziv sa greškom budžeta, pa poštujte Request.MaxWords u engine-u. Ne pretvarajte kutije reči u user space pre vraćanja; HotPDF bi tretirao vrednosti tačaka kao piksele i sloj bi se sručio ka poreklu bitmape
Mapiranje izlaza sopstvenog detektora
Isti pomoćnik služi domaćem cevovodu građenom na RenderLoadedPageToBitmap, koji renderuje vidljivu kutiju i primenjuje /Rotate baš kao i prepoznavne funkcije. Pročitajte kutiju sa GetLoadedPageVisibleBox, normalizujte rotaciju na isti način kao HotPDF, i mapirajte dva suprotna ugla svake piksel kutije. Y osa se preokreće i, na 90 i 270 stepeni, ose se zamene, pa mapirani uglovi izlaze bez fiksnog reda; uzmite minimum i maksimum mapiranih tačaka, što je i način na koji HotPDF gradi granice barkodova
const
DPI = 200;
var
Pdf: THotPDF;
Bmp: TBitmap;
VL, VB, VR, VT: Single;
Rotation: Integer;
PxL, PxT, PxR, PxB, X1, Y1, X2, Y2: Double;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-ids.pdf');
if not Pdf.GetLoadedPageVisibleBox(0, VL, VB, VR, VT) then Exit;
Rotation := Pdf.GetLoadedPageRotation(0) mod 360;
if Rotation < 0 then Inc(Rotation, 360);
if (Rotation <> 90) and (Rotation <> 180) and (Rotation <> 270) then
Rotation := 0;
Bmp := Pdf.RenderLoadedPageToBitmap(0, DPI);
if Bmp = nil then Exit;
try
MyDetector(Bmp, PxL, PxT, PxR, PxB); // vaš kod, piksel kutija
HotPixelToPage(Rotation, DPI, Bmp.Height, VL, VB, VR, VT,
PxL, PxT, X1, Y1);
HotPixelToPage(Rotation, DPI, Bmp.Height, VL, VB, VR, VT,
PxR, PxB, X2, Y2);
Writeln(Format('User-space box [%.2f %.2f %.2f %.2f]',
[Min(X1, X2), Min(Y1, Y2), Max(X1, X2), Max(Y1, Y2)]));
finally
Bmp.Free;
end;
finally
Pdf.Free;
end;
end;
Ponašanje rotacije pokriveno je dublje u izravnavanju rotacije stranice bez lomljenja kutija stranica, a cevovod dekodiranja barkodova koji troši istu transformaciju u dekodiranju rotiranih QR kodova sa PDF stranica. Ako vaš engine omota eksterni prepoznavač, Tesseract OCR adapter za pretraživi PDF pokriva stranu izolacije procesa i otkazivanja istog interfejsa
Brzi pregled: mapiranje koordinata bezbedno za CropBox
- Renderer rasterizuje vidljivu kutiju, CropBox isečen na MediaBox (ISO 32000-1 §14.11.2); svako mapiranje piksel-stranica mora koristiti tu kutiju, pročitanu sa
GetLoadedPageVisibleBox - HotPDF v2.770.153 popravio je
ApplyLoadedOCRTextLayer; v2.770.154 popravio jeDecodeLoadedPageBarcodescele stranice i nalaže lica izDetectLoadedRedactionFindings; verzije od v2.766.64 do tih verzija su pogođene - Kutije
THPDFOCRWordsu pikseli bitmape sa poreklom gore-levo;GetLoadedPageBoxiGetLoadedPageVisibleBoxvraćaju PDF user space sa poreklom dole-levo iBottom < Top - Razmera je
DPI / 72; izvodite je iz DPI-ja, nikada iz kutije stranice podeljene u širinu bitmape - /Rotate odlučuje koje ivice su bitne: Left i Bottom na 0 i 90, Right i Bottom na 180, Right i Top na 270
- Vraćajte OCR reči u pikselima i pustite HotPDF da ih mapira; pretvarajte samo za sopstvenu logiku filtriranja
- Ponovo pokrenite OCR na isečenim stranicama obrađenim pogođenom verzijom, i zapamtite da
SkipPagesWithTextpreskače stranice koje već nose stari sloj
Prepoznavne funkcije, upiti kutija stranica i renderovanje učitanog dokumenta korišćeni ovde svi se isporučuju u HotPDF komponenti za Delphi i C++Builder; licenciranje, trial preuzimanja i kompletna lista funkcija su na stranici HotPDF Delphi PDF komponente