Műszaki cikk

HotPDF OCR szövegréteg eltolás: átváltás a CropBoxon át

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 /CropBox né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ívott DecodeLoadedPageBarcodes pontosan 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 és Bottom mezői ebben a keretben vannak, ahogy az opcionális alapvonal-pontok, az eredmények, amiket egy egyéni IHPDFBarcodeDecoder visszaad, és a boxok egy egyéni IHPDFFaceDetector-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. A GetLoadedPageBox és a GetLoadedPageVisibleBox Left, Bottom, Right, Top értékeket ad vissza ebben a keretben, ahogy a THPDFOCRRequest PageLeft, PageBottom, PageRight és PageTop mezői, a THPDFDecodedBarcode határai és a THPDFRedactionFinding té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

HotPDF felismerési koordinátakeretek: bitmap pixelek bal-felső hipusponttal, amiket a THPDFOCRWord boxok és az egyéni dekóderek használnak, PDF user space bal-alsó hipusponttal, amit a GetLoadedPageBox és a GetLoadedPageVisibleBox ad vissza, meg a bal-felső oldalrajzoló API, aminek soha nem szabad változatlanul betöltött-oldal téglalapot kapnia
az enginek pixeleket jelentenek, mert azt látták, a HotPDF váltja őket, és a két keret összekutyulása az, ahogy a rétegek és lefedő boxok elcsúsznak

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 és D = -Scale, ahol Scale = DPI / 72; a negatív D a user space-t (Y felfelé) bitmap térbe (Y lefelé) fordítja
  • B = C = 0, mert egy forgatatlan oldalnak nincs nyírása vagy tengelycseréje
  • E = -Left * Scale, ami a box bal élét a 0. pixeloszlopra viszi
  • F = 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:

/RotatePage XPage YA váltás által használt boxélek
0Left + x / SBottom + (H - y) / SLeft, Bottom
90Left + y / SBottom + x / SLeft, Bottom
180Right - x / SBottom + y / SRight, Bottom
270Right - y / STop - x / SRight, 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

HotPDF inverz váltási táblázat oldal forgatásra: /Rotate 0-nál és 90-nél az átváltás a renderelt box Left és Bottom éleit rögzíti, 180-nál a Right és Bottom éleit, 270-nél a Right és Top éleit, ezért csúszik egy kivágott oldal más-más irányba vegyes dokumentumban minden tájolásnál
ugyanaz a fél inches kivágás három különböző hibának látszik, amint az oldalak különböző /Rotate értékeket hordoznak, mert minden tájolás másik pár boxélet rögzít

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
HotPDF CropBox elcsúszás anatómia egy US Letter oldalon MediaBoxszal 0 0 612 792 és CropBoxszal 36 36 576 756: a renderer a látható boxot raszterezi 300 DPI-n, így a 450-es szópixel GetLoadedPageVisibleBoxon át váltva 144,0-t és 600,48-at ad, míg a MediaBox átváltás 108,0-ra és 564,48-ra landol
a raszter a CropBoxot fedi le, így bármely MediaBoxból épített átváltás minden felismert szót pontosan a kivágási margóval tol el

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 oldalas DecodeLoadedPageBarcodes-t és a DetectLoadedRedactionFindings arctalálatait; a v2.766.64-től azokig a verziókig terjedő buildek érintettek
  • A THPDFOCRWord boxok bitmap pixelek bal-felső hipusponttal; a GetLoadedPageBox és a GetLoadedPageVisibleBox PDF user space-t ad vissza bal-alsó hipusponttal és Bottom < 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ó