Tehnički članak

HotPDF OCR tekstualni sloj: mapiranje kroz CropBox

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
  • DecodeLoadedPageBarcodes sa postavljenim HasRegion renderuje 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, Right i Bottom su u ovom okviru, kao i opciona bazna tačke, rezultati koje vraća prilagođeni IHPDFBarcodeDecoder i kutije od prilagođenog IHPDFFaceDetector
  • PDF user space učitane stranice: poreklo dole-levo, Y raste naviše, jedinice su tačke, sa Bottom < Top. GetLoadedPageBox i GetLoadedPageVisibleBox vraćaju Left, Bottom, Right, Top u ovom okviru, kao i polja PageLeft, PageBottom, PageRight i PageTop od THPDFOCRRequest, granice u THPDFDecodedBarcode i pravougaonici u THPDFRedactionFinding
  • 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

HotPDF koordinatni okviri prepoznavanja: pikseli bitmape sa poreklom gore-levo koje koriste THPDFOCRWord kutije i prilagođeni dekoderi, PDF user space sa poreklom dole-levo koji vraćaju GetLoadedPageBox i GetLoadedPageVisibleBox, i API crtanja stranice gore-levo, koji nikada ne sme primiti pravougaonik učitane stranice nepromenjen
engine-i prijavljuju piksele jer to je ono što su videli, HotPDF ih mapira, i mešanje dva okvira je način na koji slojevi i kutije zacrniljavanja klize

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 = Scale i D = -Scale, gde je Scale = DPI / 72; negativan D preokreće user space (Y naviše) u prostor bitmape (Y naniže)
  • B = C = 0, jer nerotirana stranica nema smicanje ni zamenu osa
  • E = -Left * Scale, što pomera levu ivicu kutije na piksel kolonu 0
  • F = 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:

/RotatePage XPage YIvice kutija od kojih mapiranje zavisi
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

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

HotPDF tabela inverznog mapiranja za rotaciju stranice: na /Rotate 0 i 90 transformacija pribija Left i Bottom ivice renderovane kutije, na 180 Right i Bottom, na 270 Right i Top, pa isečena stranica klizi u drugom smeru za svaku orijentaciju u mešanom dokumentu
isti isečak od pola inča izgleda kao tri različita buga kad stranice nose različite /Rotate vrednosti, jer svaka orijentacija pribija drugi par ivica kutije

Š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
HotPDF anatomija klizanja CropBox-a na US Letter stranici sa MediaBox 0 0 612 792 i CropBox 36 36 576 756: renderer rasterizuje vidljivu kutiju na 300 DPI, pa mapiranje reči piksel 450 kroz GetLoadedPageVisibleBox daje 144.0 i 600.48 dok transformacija MediaBox-a doskoči na 108.0 i 564.48
raster pokriva CropBox, pa svaka transformacija građena iz MediaBox-a pomera svaku prepoznatu reč tačno za marginu isečka

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 je DecodeLoadedPageBarcodes cele stranice i nalaže lica iz DetectLoadedRedactionFindings; verzije od v2.766.64 do tih verzija su pogođene
  • Kutije THPDFOCRWord su pikseli bitmape sa poreklom gore-levo; GetLoadedPageBox i GetLoadedPageVisibleBox vraćaju PDF user space sa poreklom dole-levo i Bottom < 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 SkipPagesWithText preskač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