OCR tekstualni slojevi, okviri barkodova i okviri zamračivanja lica odskaču na obrezanim PDF stranicama kad se bitmapni pikseli mapiraju natrag kroz MediaBox umjesto kroz okvir koji je renderer doista rasterizirao: CropBox obrezan 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
Prijave bugova koje obično stignu izgledaju ovako. Skenirani arhiv ugovora prolazi kroz OCR, izlaz je pretraživ, i pogodak pretraživanja za brojem stavke istaknut je pola inča ispod i lijevo od tiskanog broja. Većina datoteka u seriji u redu je. Pokvarene sve su došle s jedne skenirajuće stanice koja zapisuje /CropBox da obreže rub stakla. Taj jedan detalj odvaja sliku koju je OCR engine vidio od okvira u koji je tekstualni sloj postavljen, i isto nepoklapanje mijenja okvire barkodova i, ozbiljnije, okvire zamračivanja lica
Zašto se OCR tekstualni sloj odmiče od skeniranih riječi?
Tekstualni se sloj odmiče jer se dvije polovice pipelinea nisu slagale oko toga koji pravokutnik bitmapa pokriva. U v2.766.64 HotPDF je promijenio renderiranje, SVG izvoz, preglednik i ispis da poštuje CropBox: stranica se prikazuje kroz svoj CropBox obrezan na MediaBox, što propisuje ISO 32000-1 §14.11.2, i dodan je GetLoadedPageVisibleBox da vraća taj vidljivi okvir. Prepoznavne značajke nastavile su graditi svoju device-to-page transformaciju iz GetLoadedPageBox(PageIndex, pbMediaBox, ...). Raster sada pokriva vidljivi okvir, transformacija je i dalje pretpostavljala MediaBox, i svaka se prepoznata pozicija vraćala pomaknuta za razmak između njih dvaju
Zahvaćeni je prozor stoga precizan. ApplyLoadedOCRTextLayer je krivo postavljao tekst od v2.766.64 do v2.770.152. Cjelostanični DecodeLoadedPageBarcodes i detekcija lica unutar DetectLoadedRedactionFindings ostali su pogrešni verziju duže, do v2.770.153. Prije v2.766.64 renderer je crtao cijeli MediaBox, pa su se mapiranje i raster poklapali, po cijenu prepoznavanja sadržaja koji preglednici nikad ne prikazuju. Popravci su zajedno promijenili tri stvari za svaku značajku: transformaciju, procjenu pikselnog proračuna i okvir stranice predan prilagođenom engineu u zapisu zahtjeva
Nekoliko slučajeva nikad nije bilo zahvaćeno:
- Stranice bez
/CropBox-a, ili čiji je CropBox jednak MediaBoxu, mapiraju se identično prije i poslije popravka DecodeLoadedPageBarcodess postavljenimHasRegionrenderira točno regiju koju prenesete i mapira kroz tu istu regiju, pa je dekodiranje eksplicitne regije cijelo vrijeme bilo ispravno; provjera da regija leži unutar stranice i dalje koristi MediaBox- Nalazi zamračivanja temeljeni na obrascima (e-pošta, brojevi kartica i tako dalje) dolaze iz izdvajanja teksta u user spaceu, a ne iz rastera, pa su se pomaknuli samo nalazi detekcije lica
Tri koordinatna okvira i koje HotPDF API-je svaki koristi
HotPDF kod koji dira prepoznavanje bavi se s tri okvira, i većina bugova mapiranja dolazi od miješanja dva od njih
- Bitmapni pikseli: ishodište gore lijevo, Y raste prema dolje, jedinice su pikseli na zahtjevani DPI.
THPDFOCRWord.Left,Top,RightiBottomu su tom okviru, kao i neobavezne baseline točke, rezultati koje vraća prilagođeniIHPDFBarcodeDecoderi okviri od prilagođenogIHPDFFaceDetector - PDF user space učitane stranice: ishodište dolje lijevo, Y raste prema gore, jedinice su točke, s
Bottom < Top.GetLoadedPageBoxiGetLoadedPageVisibleBoxvraćaju Left, Bottom, Right, Top u tom okviru, kao i poljaPageLeft,PageBottom,PageRightiPageTopodTHPDFOCRRequest, granice uTHPDFDecodedBarcodei pravokutnici uTHPDFRedactionFinding - HotPDF koordinate crtanja stranica: API kojim gradite nove stranice (tekstovni izlaz, oblici, barkodovi, poveznice, polja obrazaca) radi s ishodištem gore lijevo i Y rastućim prema dolje. Taj okvir pripada generiranju dokumenata i nema nikakve veze s gore navedenim API-jima učitanih dokumenata, pa nikad ne uvodite u njega pravokutnik user spacea učitane stranice nepromijenjen
OCR zapis riječi namjerno je pikselno baziran: engine javlja što je vidio u slici, a ApplyLoadedOCRTextLayer posjeduje pretvorbu. Ta podjela radi samo kad pretvorba koristi pravi okvir, što je v2.770.153 vratio
Device-to-page transformacija iza OCR-a, barkodova i lica
HotPDF mapira bitmapne piksele na stranicu jednom affinom matricom građenom iz pet ulaza: rotacije, mjerila DPI / 72, visine bitmape te Left, Bottom, Right i Top renderiranog okvira. OCR, dekodiranje barkodova i detekcija lica svi dijele jednu rutinu za to, pa je jedan pogrešan ulaz okvira slomio sve tri na isti način. Za nerotiranu stranicu page-to-device matrica [A B C D E F] jest:
A = ScaleiD = -Scale, gdje jeScale = DPI / 72; negativanDokreće user space (Y gore) u bitmapni prostor (Y dolje)B = C = 0, jer nerotirana stranica nema smicanja ni zamjene između osiE = -Left * Scale, što pomjera lijevi rub okvira na pikselni stupac 0F = BitmapHeight + Bottom * Scale, što mapira donji rub okvira na y = BitmapHeight, donji rub bitmape, pa gornji rub doskoči na redak 0
Pikseli se vraćaju na stranicu kroz inverz te matrice. Request.PageRotation nosi /Rotate stranice normaliziran na 0, 90, 180 ili 270 (svaka vrijednost koja nije višekratnik broja 90 tretira se kao 0), i renderer okreće stranicu u smjeru kazaljke kako zahtijeva ISO 32000-1 §7.7.3.3. Pod rotacijom osi se mijenjaju i drugi par rubova okvira pribija se na ishodište bitmape. Napisano kao inverzne formule, s S = DPI / 72, x i y u pikselima i H visinom bitmape:
| /Rotate | Stranica X | Stranica Y | Rubovi okvira od kojih mapiranje ovisi |
|---|---|---|---|
| 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 |
Zadnji stupac objašnjava zašto je bug u proizvodnji izgledao nasumičan. CropBox koji reže samo vrh stranice ostavlja Left i Bottom netaknutima, pa su uspravne stranice izašle savršeno i samo su stranice s /Rotate 270 odstupale. Rotacija također mijenja dimenzije bitmape: na 90 i 270 bitmapa je (Top - Bottom) * S piksela široka i (Right - Left) * S piksela visoka
Što pođe po zlu s MediaBox [0 0 612 792] i CropBox [36 36 576 756]?
S rezom od pola inča na svakoj strani tekstualni sloj nerotirane stranice doskoči točno 36 točaka lijevo i 36 točaka ispod skeniranih riječi kad se koristi MediaBox. Uzmite US Letter stranicu čiji CropBox reže 36 točaka (0,5 inča) s ruba. Vidljivi okvir je 540 puta 720 točaka, pa je pri zadanoj OCR rezoluciji od 300 DPI mjerilo 300 / 72 ≈ 4.1667, a bitmapa 2250 puta 3000 piksela
Pretpostavimo da engine javlja riječ s pikselnim okvirom Left 450, Top 600, Right 900, Bottom 660 i bez baselinea. HotPDF tada postavlja baseline 20 posto visine riječi iznad donjeg ruba, na pikselnom retku 648, i mapira početnu točku (450, 648):
- Kroz vidljivi okvir: x = 36 + 450 / 4.1667 = 144.0 i y = 36 + (3000 - 648) / 4.1667 = 600.48, što je mjesto gdje je riječ tiskana
- Kroz MediaBox: x = 0 + 108.0 = 108.0 i y = 0 + 564.48 = 564.48, jednolik pomak od (-36, -36) točaka
Okrenite istu stranicu i smjer pogreške se mijenja, jer su umiješani različiti rubovi. Na /Rotate 180 X član koristi Right, i 612 umjesto 576 tjera sloj 36 točaka udesno dok ga Bottom i dalje vuče 36 točaka dolje. Na /Rotate 270 i Right i Top preveliki su, pa se sloj pomjera 36 točaka udesno i 36 točaka gore. Dokument s mješovitim orijentacijama može pokazati odstupanje u tri smjera, pouzdani otisak ovog buga. Ručno pisani kod koji izvodi mjerilo iz okvira, poput Bitmap.Width / (Right - Left), također rasteže svaku koordinatu s 612 / 540, otprilike 13 posto, na vrhu pomaka
Koji su vaši PDF dokumenti zahvaćeni?
PDF dokument je izložen kad barem jedna stranica ima vidljivi okvir koji se razlikuje od njezina MediaBoxa, i HotPDF vam to može reći u par linija. Usporedite GetLoadedPageBox s pbMediaBox protiv GetLoadedPageVisibleBox za svaku stranicu, i ispišite GetLoadedPageRotation uz to da iz tablice gore možete predvidjeti smjer odstupanja. THPDFPageBoundary nudi i pbCropBox, pbBleedBox, pbTrimBox i pbArtBox, ali GetLoadedPageBox(pbCropBox) vraća se na MediaBox kad crop box ne postoji i ne obreže, pa je vidljivi okvir prava stvar za usporedbu
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;
// spremljeno polje može izlistati svoje uglove u bilo kojem redoslijedu
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ć normalizirano i obrezano 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 GetLoadedPageVisibleBoxa važna su za skripte poput ove. Funkcija ostavlja svoje out parametre netaknutima kad padne, pa je predpostavljanje zadane veličine stranice prije poziva siguran obrazac. A kad neispravan CropBox uopće ne siječe MediaBox, funkcija vraća MediaBox umjesto praznog pravokutnika. Ako izvješ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, ponovno pokrenite prepoznavanje na tim stranicama nakon nadogradnje. OCR sloj izvršen od zahvaćene verzije ostaje u spremljenoj datoteci, i zadana opcija SkipPagesWithText preskočit će te stranice u drugom prolazu osim ako je isključite ili prvo uklonite stari sloj
Kako bi prilagođeni IHPDFOCREngine trebao mapirati piksele natrag u PDF prostor?
Prilagođeni IHPDFOCREngine trebao bi vraćati okvire riječi u bitmapnim pikselima i prepustiti HotPDF-u mapiranje; pretvarajte u user space samo za vlastite odluke, i tada koristite okvir iz zahtjeva, nikad MediaBox. Od v2.770.153 PageLeft, PageBottom, PageRight i PageTop zahtjeva opisuju renderirani vidljivi okvir, pa se poklapaju s Request.Bitmap točno. Pomoćnik dolje inverz je transformacije biblioteke, uključujući njezinu upotrebu stvarne visine bitmape za uspravne stranice, pa se s HotPDF-om poklapa do piksela
uses
System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;
// Bitmapni piksel (ishodište gore lijevo, Y dolje) u PDF user space
// (ishodište dolje lijevo, Y gore), kroz okvir iz kojeg je bitmapa renderirana
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 user space trebate unutar enginea jest pravilo zone: fakture čije zaglavlje nikad ne želite pretraživim, ili područje pečata koje zbuni prepoznavatelj. Engine dolje, napisan s TInterfacedObjectom da brojanje referenci rukuje njegovim životnim vijekom, filtrira riječi prema tome gdje im centri padaju na stranici, pa vraća preživjele netaknute u pikselnim koordinatama. RunRecognizer zamjenjuje vaš vlastiti poziv prepoznavatelja
type
TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
private
FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single; // user space
function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
out Words: THPDFOCRWords): boolean; // vaš prepoznavatelj, pikselni okviri
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]; // još uvijek pikseli: HotPDF ih sam mapira
Inc(Count);
end;
SetLength(Words, Count);
Result := True;
end;
Predajte engine ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info) kao i bilo koji drugi engine. Biblioteka validira ono što se vrati prije nego mu vjeruje: riječ se ispada i broji u Info.DroppedWordCount kad njezin okvir napusti bitmapu, kad je Right <= Left ili Bottom <= Top, ili kad je Confidence izvan 0..1 ili ispod MinimumConfidence. Vraćanje više riječi od MaxWordsPerPage, ili guranje tekućeg zbroja preko MaxTotalWords, obara cijeli poziv s proračunskom pogreškom, pa poštujte Request.MaxWords u engineu. Ne pretvarajte okvire riječi u user space prije vraćanja; HotPDF bi vrijednosti točaka tretirao kao piksele i sloj bi se urušio prema ishodištu bitmape
Mapiranje izlaza vlastitog detektora
Isti pomoćnik poslužuje vlastiti pipeline građen na RenderLoadedPageToBitmap, koji renderira vidljivi okvir i primjenjuje /Rotate baš kao prepoznavne značajke. Pročitajte okvir s GetLoadedPageVisibleBox, normalizirajte rotaciju na isti način kao HotPDF, i mapirajte dva suprotna ugla svakog pikselnog okvira. Y se os okreće i, na 90 i 270 stupnjeva, osi se zamjenjuju, pa mapirani uglovi izlaze bez fiksnog redoslijeda; uzmite minimum i maksimum mapiranih toč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, pikselni okvir
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 obrađeno je dublje u izravnavanju rotacije stranice bez lomljenja okvira stranica, a pipeline dekodiranja barkodova koji troši istu transformaciju u dekodiranju rotiranih QR kodova s PDF stranica. Ako vaš engine omota vanjskog prepoznavatelja, Tesseract OCR adapter za pretraživi PDF pokazuje stranu izolacije procesa i otkazivanja istog sučelja
Brza referenca: CropBox-sigurno koordinatno mapiranje
- Renderer rasterizira vidljivi okvir, CropBox obrezan na MediaBox (ISO 32000-1 §14.11.2); svako mapiranje piksel-u-stranicu mora koristiti taj okvir, pročitan s
GetLoadedPageVisibleBox - HotPDF v2.770.153 popravio je
ApplyLoadedOCRTextLayer; v2.770.154 popravio je cjelostaničniDecodeLoadedPageBarcodesi nalaze lica izDetectLoadedRedactionFindings; verzije od v2.766.64 do tih verzija zahvaćene su THPDFOCRWordokviri bitmapni su pikseli s ishodištem gore lijevo;GetLoadedPageBoxiGetLoadedPageVisibleBoxvraćaju PDF user space s ishodištem dolje lijevo iBottom < Top- Mjerilo je
DPI / 72; izvodite ga iz DPI-ja, nikad iz okvira stranice podijeljenog u širinu bitmape - /Rotate odlučuje koji rubovi važe: Left i Bottom na 0 i 90, Right i Bottom na 180, Right i Top na 270
- Vraćajte OCR riječi u pikselima i prepustite HotPDF-u mapiranje; pretvarajte samo za vlastitu logiku filtriranja
- Ponovno pokrenite OCR na obrezanim stranicama obrađenim zahvaćenom verzijom, i sjetite se da
SkipPagesWithTextpreskače stranice koje već nose stari sloj
Prepoznavne značajke, upiti okvira stranica i renderiranje učitanih dokumenata korišteni ovdje svi se isporučuju u HotPDF komponenti za Delphi i C++Builder; licenciranje, probna preuzimanja i potpuni popis značajki nalaze se na stranici HotPDF Delphi PDF komponente