Az OCR szövegrétegek, barcode határok és arclefedő boxok akkor csúsznak el kivágott PDF oldalakon, amikor a bitmap pixeleket a MediaBoxon át váltod vissza ahelyett, hogy azon a boxon át tennéd, amit a renderer ténylegesen raszterezett: a MediaBoxra vágott CropBoxot (ISO 32000-1 §14.11.2). A HotPDF ezt javította az ApplyLoadedOCRTextLayer-nél v2.770.153-ban, a DecodeLoadedPageBarcodes-nál és a DetectLoadedRedactionFindings-nál pedig v2.770.154-ben
A hibajelentés, ami általában beérkezik, ilyen néz ki. Egy szkenelt szerződésarchívum átmegy az OCR-en, a kimenet kereshető, és egy záradékszám keresési találatának kiemelése fél inchvel lejjebb és balra csúszik a nyomtatott számhoz képest. A batch fájlnak a legtöbbje rendben van. A hibásak mind egyetlen szkennelő állomásról jöttek, ami platen margót nyír le egy /CropBox-szal. Ez az egy részlet választja el azt a képet, amit az OCR engine látott, attól a kerettől, amibe a szövegréteg került, és ugyanez az eltérés mozgatja a barcode határokat, súlyosabban pedig az arclefedő boxokat
Miért csúszik el az OCR szövegréteg a szkenelt szavaktól?
A szövegréteg azért csúszik, mert a pipeline két fele nem értett abban, melyik téglalapot fedi le a bitmap. v2.766.64-ben a HotPDF átállította a renderelést, az SVG exportot, a nézegetőt és a nyomtatást, hogy a CropBoxot kövessék: egy oldal a MediaBoxra vágott CropBoxán át jelenik meg, ahogy az ISO 32000-1 §14.11.2-e előírja, és hozzájött a GetLoadedPageVisibleBox, ami ezt a látható boxot adja vissza. A felismerő funkciók továbbra is a GetLoadedPageBox(PageIndex, pbMediaBox, ...)-ból építették az eszköz-oldal átváltásukat. A raszter most már a látható boxot fedte, az átváltás továbbra is a MediaBoxot tételezte fel, és minden felismert pozíció a kettő közti hézagnyival tolódva jött vissza
Az érintett időablak ezért pontosan behatárolható. Az ApplyLoadedOCRTextLayer v2.766.64-től v2.770.152-ig tévesztette meg a szöveget. Az egész oldalas DecodeLoadedPageBarcodes és a DetectLoadedRedactionFindings-en belüli arcdetektálás egy builddel tovább, v2.770.153-ig volt rossz. v2.766.64 előtt a renderer a teljes MediaBoxot rajzolta, így az átváltás és a raszter egyetértett, annak árán, hogy olyan tartalmat is felismert, amit a nézegetők soha nem mutatnak. A javítások mindegyik funkcióra három dolgot váltottak meg egyszerre: az átváltást, a pixelbudget becslést és az egyéni enginnek a request rekordban átadott oldalboxot
Több esetet soha nem érintett:
- A
/CropBoxnélküli oldalak, vagy amelyeknek a CropBoxja egyenlő a MediaBoxszal, a javítás előtt és után azonosan váltódnak - A
HasRegion-nel hívottDecodeLoadedPageBarcodespontosan azt a régiót rendereli, amit átadsz, és ugyanazon a régión át vált, így az explicit régiós dekódolás végig helyes volt; annak a tesztje, hogy a régió az oldalon belül van, továbbra is a MediaBoxot használja - A pattern alapú lefedő találatok (emailek, kártyaszámok és hasonlók) user spacebeli szövegkiextrakcióból jönnek, nem raszterből, így csak az arcdetektálásos találatok mozogtak
Három koordinátakeret, és melyiket melyik HotPDF API használja
A felismeréshez nyúló HotPDF kód három kerettel dolgozik, és a legtöbb átváltási hiba kettőjük összekutyulásából jön
- Bitmap pixelek: bal-felső hipuspont, a Y lefelé nő, az egység a kért DPI-s pixel. A
THPDFOCRWord.Left,Top,RightésBottommezői ebben a keretben vannak, ahogy az opcionális alapvonal-pontok, az eredmények, amiket egy egyéniIHPDFBarcodeDecodervisszaad, és a boxok egy egyéniIHPDFFaceDetector-től - Egy betöltött oldal PDF user spaceje: bal-alsó hipuspont, a Y felfelé nő, az egység a pont,
Bottom < Top-tal. AGetLoadedPageBoxés aGetLoadedPageVisibleBoxLeft, Bottom, Right, Top értékeket ad vissza ebben a keretben, ahogy aTHPDFOCRRequestPageLeft,PageBottom,PageRightésPageTopmezői, aTHPDFDecodedBarcodehatárai és aTHPDFRedactionFindingtéglalapjai is - HotPDF oldalrajzoló koordináták: az API, amivel új oldalakat építesz (szövegkiírás, alakzatok, barcode-ok, linkek, űrlapmezők), bal-felső hipusponttal és lefelé növő Y-nal dolgozik. Ez a keret a dokumentumgeneráláshoz tartozik, és a fenti betöltött-dokumentum API-okkal semmi köze nincs, soha ne etess bele változatlanul betöltött-oldal user space téglalapot
Az OCR szórekord szándékosan pixel alapú: az engine azt jelenti, amit a képben látott, és a konverziót az ApplyLoadedOCRTextLayer birtokolja. Ez a felosztás csak akkor működik, ha a konverzió a jó boxot használja, amit a v2.770.153 állított helyre
Az OCR, barcode-ok és arcok mögötti eszköz-oldal átváltás
A HotPDF bitmap pixeleket egyetlen affin mátrixszal váltja oldalra, amit öt bemenetből épít: a forgatásból, a DPI / 72 skálából, a bitmap magasságából és a renderelt box Left, Bottom, Right, Top értékeiből. OCR, barcode dekódolás és arcdetektálás erre egyetlen rutint oszt meg, ezért tört mindhármat ugyanúgy egyetlen rossz boxbemenet. Forgatatlan oldalnál az oldal-eszköz mátrix, az [A B C D E F], így néz ki:
A = ScaleésD = -Scale, aholScale = DPI / 72; a negatívDa user space-t (Y felfelé) bitmap térbe (Y lefelé) fordítjaB = C = 0, mert egy forgatatlan oldalnak nincs nyírása vagy tengelycseréjeE = -Left * Scale, ami a box bal élét a 0. pixeloszlopra visziF = BitmapHeight + Bottom * Scale, ami a box alsó élét az y = BitmapHeight-re, a bitmap alsó élére váltja, így a felső él a 0. sorra landol
A pixelek ennek a mátrixnak az inverzén mennek vissza az oldalra. A Request.PageRotation az oldal /Rotate-ját hordozza 0-ra, 90-re, 180-ra vagy 270-re normalizálva (minden, nem 90 többszöröse, 0-nak tekintendő), és a renderer az óramutató járásával egyezően fordítja az oldalt, ahogy az ISO 32000-1 §7.7.3.3-e megköveteli. Forgatás alatt a tengelyek cserélnek, és a box éleinek egy másik párja kerül a bitmap hipuspontjára. Inverz képletként kiírva, S = DPI / 72-tel, pixelben vett x-szel és y-nal, H pedig a bitmap magassága:
| /Rotate | Page X | Page Y | A váltás által használt boxélek |
|---|---|---|---|
| 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 |
Az utolsó oszlop magyarázza meg, miért nézett ki a hiba véletlenszerűnek élesben. Egy csak az oldal tetejét nyíró CropBox Leftet és Bottomot érintetlenül hagy, így az álló oldalak tökéletesen jöttek ki, és csak a /Rotate 270-es oldalak csúsztak. A forgatás a bitmap dimenzióit is felcseréli: 90-nél és 270-nél a bitmap (Top - Bottom) * S pixel széles és (Right - Left) * S pixel magas
Mi romlik el a MediaBox [0 0 612 792] és CropBox [36 36 576 756] esetén?
Fél inches kivágással mind a négy oldalon egy forgatatlan oldal szövegrélege pontosan 36 ponttal balra és 36 ponttal lejjebb landol a szkenelt szavaktól, ha a MediaBoxot használod. Vegyél egy US Letter oldalt, aminek a CropBoxja minden szélről 36 pontot (0,5 inchet) nyír le. A látható box 540-szer 720 pont, így a 300 DPI-s alapértelmezett OCR felbontásnál a skála 300 / 72 ≈ 4,1667, a bitmap pedig 2250-szer 3000 pixel
Tegyük fel, hogy az engine egy szót jelent pixel boxszal: Left 450, Top 600, Right 900, Bottom 660, alapvonal nélkül. A HotPDF akkor az alapvonalat a szó magasságának 20 százalékával az alsó él fölé teszi, a 648. pixel sorra, és váltja a kezdőpontot, az (450, 648)-at:
- A látható boxon át: x = 36 + 450 / 4,1667 = 144,0 és y = 36 + (3000 - 648) / 4,1667 = 600,48, ami ott van, ahol a szó nyomtatva van
- A MediaBoxon át: x = 0 + 108,0 = 108,0 és y = 0 + 564,48 = 564,48, ami egyenletes (-36, -36) pontos eltolás
Fordítsd el ugyanazt az oldalt, és a hiba iránya megváltozik, mert más élek vesznek részt a játékban. /Rotate 180-nál az X-tag a Rightot használja, és a 612 az 576 helyett 36 ponttal jobbra tolná a réteget, miközben a Bottom továbbra is 36 ponttal lefelé húzza. /Rotate 270-nél mind a Right, mind a Top túl nagy, így a réteg 36 pontot megy jobbra és 36 pontot felfelé. Egy vegyes tájolású dokumentum három irányban is mutathatja az elcsúszást, megbízható ujjlenyomat erre a hibára. A kézzel írt kód, ami a boxból származtatja a skálát, például a Bitmap.Width / (Right - Left), mindezt a koordinátákat 612 / 540 arányban, nagyjából 13 százalékkal meg is nyújtja, az eltolás tetejében
Melyik PDF dokumentumaid érintettek?
Egy PDF dokumentum akkor érintett, ha legalább egy oldalnak van a MediaBoxjától eltérő látható boxja, és ezt a HotPDF néhány sorban megmondja. Vesd össze a GetLoadedPageBox-ot pbMediaBox-szal a GetLoadedPageVisibleBox-szal minden oldalnál, és írasd ki mellé a GetLoadedPageRotation-t, hogy a fenti táblázatból meg tudd jósolni az elcsúszás irányát. A THPDFPageBoundary kínál pbCropBox-ot, pbBleedBox-ot, pbTrimBox-ot és pbArtBox-ot is, de a GetLoadedPageBox(pbCropBox) MediaBoxra esik vissza, ha nincs crop box, és nem vág, így a látható box a helyes összevetési alap
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;
// a tárolt tömb a sarkait bármely sorrendben sorolhatja
if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
// már normalizált és a MediaBoxra vágva
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;
A GetLoadedPageVisibleBox két részlete számít az ilyen szkripteknél. A függvény elbukáskor érintetlenül hagyja az out paramétereit, így egy alapértelmezett oldalméret előbeállítása a hívás előtt biztonságos minta. És ha egy formátlan CropBox egyáltalán nem metszi a MediaBoxot, a függvény a MediaBoxot adja vissza üres téglalap helyett. Ha a riport oldalakat sorol fel, és a telepített builded öregebb a v2.770.153-nál OCR-re, vagy a v2.770.154-nél barcode-okra és arcokra, frissítés után futtasd újra a felismerést azokon az oldalakon. Egy érintett build által elkötelezett OCR réteg a mentett fájlban marad, és az alapértelmezett SkipPagesWithText opció második menetben átugraná azokat az oldalakat, hacsak nem kapcsolod ki, vagy előbb nem szeded a régi réteget
Hogyan váltsa vissza pixeleit PDF térbe egy egyéni IHPDFOCREngine?
Egy egyéni IHPDFOCREngine szóboxokat adjon vissza bitmap pixelekben, és hagyja a HotPDF-re a váltást; user spacebe csak a saját döntéseidhez válts át, és akkor is a requestből kapott boxot használd, soha a MediaBoxot. v2.770.153 óta a request PageLeft, PageBottom, PageRight és PageTop mezői a renderelt látható boxot írják le, így pontosan egyeznek a Request.Bitmap-mal. Az alábbi helper a library átváltásának inverze, benne az álló oldalaknál használt tényleges bitmap magassággal is, így pixelre egyezik a HotPDF-fel
uses
System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;
// Bitmap pixel (bal-felső hipuspont, Y lefelé) PDF user spacebe
// (bal-alsó hipuspont, Y felfelé), azon a boxon át, amiből a bitmap renderelődött
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;
Valós ok arra, hogy egy engine-en belül user space-re legyen szükséged, egy zónaszabály: számlák, amiknek a levélfejét soha nem akarod kereshetővé, vagy egy pecsétterület, ami összezavarja a felismerőt. Az alábbi engine, TInterfacedObject-ből írva, hogy a hivatkozásszámláló kezelje az élettartamát, a szavakat az alapján szűri, hova esik a középpontjuk az oldalon, majd a túlélőket érintetlenül adja vissza pixel koordinátákban. A RunRecognizer a saját felismerő hívásod helyettesítője
type
TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
private
FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single; // user space
function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
out Words: THPDFOCRWords): boolean; // a te felismerőd, pixel boxok
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]; // még mindig pixelek: a HotPDF maga váltja őket
Inc(Count);
end;
SetLength(Words, Count);
Result := True;
end;
Add át az enginet a ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info)-nak, akárcsak bármely más enginet. A library validálja, amit visszakap, mielőtt megbízik benne: egy szó eldobásra kerül és Info.DroppedWordCount-ba számolódik, ha a boxa kilóg a bitmapből, ha Right <= Left vagy Bottom <= Top, vagy ha a Confidence kikerül a 0..1-ből vagy a MinimumConfidence alá esik. Több szó visszaadása, mint MaxWordsPerPage, vagy a futó összeg MaxTotalWords fölé tolása budget-hibával buktatja az egész hívást, így tartsd be a Request.MaxWords-ot az enginben. Ne váld át a szóboxokat user spacebe, mielőtt visszaadnád; a HotPDF pontértékeket kezelne pixeleknek, és a réteg a bitmap hipuspontja felé omlana össze
A saját detektor kimenetének váltása
Ugyanez a helper szolgál egy RenderLoadedPageToBitmap-ra épített házilag készített pipeline-t is, ami a látható boxot rendereli, és ugyanúgy alkalmazza a /Rotate-ot, ahogy a felismerő funkciók. Olvasd ki a boxot GetLoadedPageVisibleBox-szal, normalizáld a forgatást ugyanúgy, ahogy a HotPDF teszi, és váld át minden pixelbox két szemben lévő sarkát. A Y tengely fordul, 90 és 270 foknál pedig a tengelyek cserélnek, így a váltott sarkok rögzített sorrend nélkül jönnek ki; vedd a váltott pontok minimumát és maximumát, ahogy a HotPDF is építi a barcode határokat
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); // a te kódod, pixel box
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;
A forgatási viselkedést mélyebben a oldalforgatás kilapítása oldaldobozok törése nélkül cikk fedi le, és az ugyanezt az átváltást fogyasztó barcode dekódolási pipeline-t a forgatott QR kódok dekódolása PDF oldalakról taglalja. Ha az engined külső felismerőt burkol, a Tesseract OCR adaptér kereshető PDF-hez megmutatja ugyanennek az interfésznek a process izolációs és megszakítási oldalát
Gyorsreferencia: CropBox-biztos koordinátaváltás
- A renderer a látható boxot raszterezi, a MediaBoxra vágott CropBoxot (ISO 32000-1 §14.11.2); minden pixel-oldal váltásnak azt a boxot kell használnia, amit
GetLoadedPageVisibleBox-szal olvasol ki - A HotPDF v2.770.153 javította az
ApplyLoadedOCRTextLayer-t; a v2.770.154 az egész oldalasDecodeLoadedPageBarcodes-t és aDetectLoadedRedactionFindingsarctalálatait; a v2.766.64-től azokig a verziókig terjedő buildek érintettek - A
THPDFOCRWordboxok bitmap pixelek bal-felső hipusponttal; aGetLoadedPageBoxés aGetLoadedPageVisibleBoxPDF user space-t ad vissza bal-alsó hipusponttal ésBottom < Top-tal - A skála
DPI / 72; a DPI-ből származtasd, soha ne oldalboxból osztva a bitmap szélességébe - A /Rotate dönti el, mely élek számítanak: Left és Bottom 0-nál és 90-nél, Right és Bottom 180-nál, Right és Top 270-nél
- Adj vissza OCR szavakat pixelekben, és hagyd a HotPDF-re a váltást; csak a saját szűrőlogikádhoz válts át
- Futtasd újra az OCR-t az érintett build által feldolgozott kivágott oldalakon, és ne feledd, hogy a
SkipPagesWithTextátugorja azokat az oldalakat, amik már hordozzák a régi réteget
A felismerő funkciók, az oldalbox-lekérdezések és a betöltött-dokumentum renderelés, amiket itt használtunk, mind a HotPDF componentban szállítanak Delphihez és C++Builderhez; licencelés, próbaverziók és a teljes funkciólista a HotPDF Delphi PDF component oldalon található