Tehnični članak

Odmik plasti besedila OCR v HotPDF: preslikava skozi CropBox

Plasti besedila OCR, meje črtnih kod in okvirji zakrivanja obrazov drsijo na obrezanih straneh PDF, kadar se piksli bitne slike preslikajo nazaj skozi MediaBox, namesto skozi okvir, ki ga je izrisovalnik dejansko rastriral: CropBox, stisnjen na MediaBox (ISO 32000-1 §14.11.2). HotPDF je to popravil za ApplyLoadedOCRTextLayer v v2.770.153, za DecodeLoadedPageBarcodes in DetectLoadedRedactionFindings pa v v2.770.154

Poročilo o hrošču, ki običajno prispe, je videti takole. Arhiv skeniranih pogodb gre skozi OCR, izhod je iskalen, zadetek iskanja po številki točke pa je poudarjen pol palca pod in levo od natisnjene številke. Večina datotek v paketu je v redu. Pokvarjene so vse prišle iz ene skenirne postaje, ki zapiše /CropBox, da obreže rob plošče. Ta ena podrobnost ločuje sliko, ki jo je videl OCR motor, od okvirja, v katerega je bila postavljena plast besedila, isto neskladje pa premika meje črtnih kod in — bolj resno — okvirje zakrivanja obrazov

Zakaj plast besedila OCR drsi proč od skeniranih besed?

Plast besedila drsi, ker se dve polovici cevovoda nista strinjali o tem, kateri pravokotnik pokriva bitna slika. V v2.766.64 je HotPDF spremenil izrisovanje, izvoz SVG, pregledovalnik in tiskanje, da spoštujejo CropBox: stran se prikaže skozi njen CropBox, stisnjen na MediaBox, kar predpisuje ISO 32000-1 §14.11.2, dodan pa je bil GetLoadedPageVisibleBox, ki vrne ta vidni okvir. Zmožnosti prepoznavanja so še naprej gradile svojo transformacijo naprava-stran iz GetLoadedPageBox(PageIndex, pbMediaBox, ...). Raster je zdaj pokrival vidni okvir, transformacija pa je še naprej predpostavljala MediaBox, vsak prepoznani položaj pa je prišel nazaj odmaknjen za vrzel med njima

Prizadeto okno je zato točno določeno. ApplyLoadedOCRTextLayer je napačno postavljala besedilo od v2.766.64 do v2.770.152. Cela strani DecodeLoadedPageBarcodes in zaznavanje obrazov znotraj DetectLoadedRedactionFindings sta ostala napačna še eno izgradnjo dlje, do v2.770.153. Pred v2.766.64 je izrisovalnik risal celoten MediaBox, tako da sta se preslikava in raster strinjala — po ceni prepoznavanja vsebine, ki je pregledovalniki nikoli ne pokažejo. Popravki so za vsako zmožnost skupaj spremenili tri stvari: transformacijo, oceno pikslovnega proračuna in okvir strani, izročenega lastnemu motorju v zapisu zahteve

Nekaj primerov ni bilo nikoli prizadetih:

  • Strani brez /CropBox ali tiste, katerih CropBox je enak MediaBox, se preslikajo enako pred in po popravku
  • DecodeLoadedPageBarcodes z nastavljenim HasRegion izriše točno regijo, ki jo podate, in preslika skozi isto regijo, tako da je bilo dekodiranje z izrecno regijo ves čas pravilno; preizkus, da regija leži znotraj strani, še vedno uporablja MediaBox
  • Najdbe zakrivanja na osnovi vzorcev (e-poštna sporočila, številke kartic in podobno) prihajajo iz izvlečenja besedila v uporabniškem prostoru, ne iz rastra, tako da so se premaknile samo najdbe zaznavanja obrazov

Trije koordinatni prostori in kateri API-ji HotPDF uporabljajo katerega

Koda HotPDF, ki se dotika prepoznavanja, se spopada s tremi prostori in večina napak preslikave pride iz mešanja dveh od njiju

  • Piksli bitne slike: izvor zgoraj levo, Y raste navzdol, enote so piksli pri zahtevanem DPI. THPDFOCRWord.Left, Top, Right in Bottom so v tem prostoru, prav tako neobvezne točke osnovne črte, rezultati, ki jih vrne lasten IHPDFBarcodeDecoder, in okvirji od lastnega IHPDFFaceDetector
  • Uporabniški prostor PDF naložene strani: izvor spodaj levo, Y raste navzgor, enote so točke, z Bottom < Top. GetLoadedPageBox in GetLoadedPageVisibleBox vrneta Left, Bottom, Right, Top v tem prostoru, prav tako pa polja PageLeft, PageBottom, PageRight in PageTop od THPDFOCRRequest, meje v THPDFDecodedBarcode in pravokotniki v THPDFRedactionFinding
  • Risalne koordinate strani HotPDF: API, s katerim gradite nove strani (izpis besedila, liki, črtne kode, povezave, polja obrazcev), dela z izvorom zgoraj levo in Y, ki raste navzdol. Ta prostor pripada ustvarjanju dokumentov in z zgoraj navedenimi API-ji naloženega dokumenta nima ničesar, zato vanj nikoli ne podajte pravokotnika uporabniškega prostora naložene strani nespremenjenega

Zapis besede OCR je namenoma na osnovi pikslov: motor poroča, kaj je videl na sliki, preslikavo pa si lasti ApplyLoadedOCRTextLayer. Ta delitev dela samo, kadar uporabi preslikava pravi okvir — to je tisto, kar je v2.770.153 povrnil

HotPDF koordinatni prostori prepoznavanja: piksli bitne slike z izvorom zgoraj levo, ki jih uporabljajo okvirji THPDFOCRWord in lastni dekodirniki, uporabniški prostor PDF z izvorom spodaj levo, ki ga vračata GetLoadedPageBox in GetLoadedPageVisibleBox, ter risalni API strani z izvorom zgoraj levo, ki nikoli ne sme prejeti pravokotnika naložene strani nespremenjenega
Motorji poročajo piksle, ker to je, kar so videli, HotPDF jih preslika, mešanje obeh prostorov pa je način, kako drsijo plasti in okvirji zakrivanja

Transformacija naprava-stran za OCR, črtne kode in obraze

HotPDF preslika piksle bitne slike na stran z eno afino matriko, zgrajeno iz petih vhodov: zasuka, merila DPI / 72, višine bitne slike ter Left, Bottom, Right in Top izrisanega okvirja. OCR, dekodiranje črtnih kod in zaznavanje obrazov si delijo za to eno samo rutino — zato je en napačen okvirni vhod pokvaril vse tri na enak način. Za nezavrtano stran je matrika stran-na-napravo [A B C D E F]:

  • A = Scale in D = -Scale, kjer je Scale = DPI / 72; negativni D obrne uporabniški prostor (Y navzgor) v pikslovni prostor (Y navzdol)
  • B = C = 0, ker nezavrtana stran nima striža niti zamenjave osi
  • E = -Left * Scale, ki premakne levi rob okvirja v pikslovni stolpec 0
  • F = BitmapHeight + Bottom * Scale, ki preslika spodnji rob okvirja na y = BitmapHeight, spodnji rob bitne slike, tako da zgornji rob pristane na vrstici 0

Piksli gredo nazaj na stran skozi inverz te matrike. Request.PageRotation nosi /Rotate strani, normaliziran na 0, 90, 180 ali 270 (vsaka vrednost, ki ni mnogokratnik 90, se obravnava kot 0), izrisovalnik pa zasuk stran v smeri urnega kazalca, kakor zahteva ISO 32000-1 §7.7.3.3. Pod zasukom se osi zamenjajo in drug par robov okvirja je pripet na izvor bitne slike. Zapisano kot inverzne formule, z S = DPI / 72, x in y v pikslih ter H višino bitne slike:

/RotateStran XStran YRobovi okvirja, od katerih je odvisna preslikava
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

Zadnji stolpec pojasnjuje, zakaj je hrošč v produkciji izgledal naključen. CropBox, ki obreže samo vrh strani, pusti Left in Bottom nedotaknjena, tako da so pokončne strani prišle ven popolnoma in drsile so samo strani z /Rotate 270. Zasuk zamenja tudi dimenziji bitne slike: pri 90 in 270 je bitna slika (Top - Bottom) * S pikslov široka in (Right - Left) * S pikslov visoka

HotPDF tabela inverzne preslikave za zasuk strani: pri /Rotate 0 in 90 transformacija pripne levi in spodnji rob izrisanega okvirja, pri 180 Right in Bottom, pri 270 pa Right in Top — zato obrezana stran drsi v drugačno smer za vsako usmerjenost v mešanem dokumentu
Isti obrez pol palca izgleda kot trije različni hrošči, ko strani nosijo različne vrednosti /Rotate, ker vsaka usmerjenost pripne drug par robov okvirja

Kaj gre narobe z MediaBox [0 0 612 792] in CropBox [36 36 576 756]?

Z obrezom pol palca na vsaki strani plast besedila nezavrtane strani pristane točno 36 točk levo in 36 točk pod skeniranimi besedami, kadar se uporabi MediaBox. Vzemimo stran US Letter, katere CropBox obreže 36 točk (0,5 palca) od vsakega roba. Vidni okvir je 540 krat 720 točk, pri privzeti OCR resoluciji 300 DPI je merilo 300 / 72 ≈ 4,1667, bitna slika pa 2250 krat 3000 pikslov

Recimo, da motor poroča besedo s pikslovnim okvirjem Left 450, Top 600, Right 900, Bottom 660 in brez osnovne črte. HotPDF potem postavi osnovno črto 20 odstotkov višine besede nad spodnjim robom, pri pikslovni vrstici 648, in preslika začetno točko (450, 648):

  • Skozi vidni okvir: x = 36 + 450 / 4,1667 = 144,0 in y = 36 + (3000 - 648) / 4,1667 = 600,48 — to je mesto, kjer je beseda natisnjena
  • Skozi MediaBox: x = 0 + 108,0 = 108,0 in y = 0 + 564,48 = 564,48 — enakomeren premik (-36, -36) točk
HotPDF anatomija drsa CropBox na strani US Letter z MediaBox 0 0 612 792 in CropBox 36 36 576 756: izrisovalnik rastrira vidni okvir pri 300 DPI, tako da preslikava pikslovne besede 450 skozi GetLoadedPageVisibleBox da 144,0 in 600,48, transformacija MediaBox pa pristane pri 108,0 in 564,48
Raster pokriva CropBox, zato vsaka transformacija, zgrajena iz MediaBox, premakne vsako prepoznano besedo točno za rob obreza

Zavrtite isto stran in smer napake se spremeni, ker so v igri drugačni robovi. Pri /Rotate 180 člen X uporablja Right in 612 namesto 576 potisne plast 36 točk na desno, medtem ko jo Bottom še vedno vleče 36 točk navzdol. Pri /Rotate 270 sta Right in Top oba prevelika, tako da se plast premakne 36 točk desno in 36 točk navzgor. Dokument z mešanimi usmerjenostmi lahko pokaže drs v treh smereh — zanesljiv prstni odtis tega hrošča. Ročno napisana koda, ki izpelje merilo iz okvirja, na primer Bitmap.Width / (Right - Left), tudi raztegne vsako koordinato s 612 / 540, približno 13 odstotkov, še na vrhu odmika

Kateri od vaših dokumentov PDF so prizadeti?

Dokument PDF je izpostavljen, kadar ima vsaj ena stran vidni okvir, ki se razlikuje od njenega MediaBox, HotPDF pa vam to pove v nekaj vrsticah. Primerjajte GetLoadedPageBox z pbMediaBox z GetLoadedPageVisibleBox za vsako stran in natisnite GetLoadedPageRotation ob njem, da lahko iz tabele zgoraj napovedate smer drsa. THPDFPageBoundary ponuja tudi pbCropBox, pbBleedBox, pbTrimBox in pbArtBox, GetLoadedPageBox(pbCropBox) pa pade nazaj na MediaBox, kadar obrezni okvir ne obstaja, in ne stisne, zato je vidni okvir prava stvar za primerjavo

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;
      // shranjeni seznam lahko naštete svoje kote v katerem koli vrstnem redu
      if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
      if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
      // že normalizirano in stisnjeno 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;

Dve podrobnosti GetLoadedPageVisibleBox štejta za skripte, take kot ta. Funkcija pusti svoje parametre out nedotaknjene, kadar odpove, tako da je vnaprejšnja nastavitev privzete velikosti strani pred klicem varen vzorec. In kadar okvarjeni CropBox sploh ne seka MediaBox, funkcija vrne MediaBox, namesto praznega pravokotnika. Če poročilo našteje strani in je vaša nameščena izgradnja starejša od v2.770.153 za OCR ali v2.770.154 za črtne kode in obraze, ponovno pognajte prepoznavanje na teh straneh po nadgradji. Plast OCR, zapisana s prizadeto izgradnjo, ostane v shranjeni datoteki, privzeta opcija SkipPagesWithText pa bo te strani preskočila na drugem prehodu, razen če jo izklopite ali najprej odstranite staro plast

Kako naj lasten IHPDFOCREngine preslika piksle nazaj v prostor PDF?

Lasten IHPDFOCREngine naj vrača okvirje besed v pikslih bitne slike in pusti HotPDF narediti preslikavo; pretvarjajte v uporabniški prostor samo za svoje lastne odločitve in potem uporabite okvir iz zahteve, nikoli MediaBox. Od v2.770.153 PageLeft, PageBottom, PageRight in PageTop zahteve opisujejo izrisani vidni okvir, tako da se točno ujemajo z Request.Bitmap. Pomočnik spodaj je inverz transformacije knjižnice, vključno z njeno uporabo dejanske višine bitne slike za pokončne strani, tako da se s HotPDF strinja do piksla

uses
  System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;

// Piksel bitne slike (izvor zgoraj levo, Y navzdol) v uporabniški prostor PDF
// (izvor spodaj levo, Y navzgor), skozi okvir, iz katerega je bila bitna slika izrisana
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;

Realističen razlog, da potrebujete uporabniški prostor znotraj motorja, je pravilo con: računi, katerih glave nikoli ne želite iskalne, ali območje žiga, ki zmede prepoznavalnik. Motor spodaj, napisan z TInterfacedObject, tako da števec sklicev ureja njegovo življenje, filtrira besede po tem, kamor padejo njihova središča na strani, potem pa vrne preživele nedotaknjene v pikslovnih koordinatah. RunRecognizer stoji za vašim lastnim klicem prepoznavalnika

type
  TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
  private
    FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single;  // uporabniški prostor
    function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
      out Words: THPDFOCRWords): boolean;  // vaš prepoznavalec, pikslovni okvirji
  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];  // še vedno piksli: HotPDF jih preslika sam
    Inc(Count);
  end;
  SetLength(Words, Count);
  Result := True;
end;

Motor izročite ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info) kot kateremu koli drugemu motorju. Knjižnica potrjuje, kar pride nazaj, preden mu zaupa: beseda je izpuščena in prešteje v Info.DroppedWordCount, kadar njen okvir zapusti bitno sliko, kadar je Right <= Left ali Bottom <= Top, ali kadar je Confidence izven 0..1 ali pod MinimumConfidence. Vračanje več besed kot MaxWordsPerPage ali potiskanje tekoče vsote čez MaxTotalWords ne uspe celega klica z napako proračuna, zato spoštujte Request.MaxWords v motorju. Ne pretvarjajte okvirjev besed v uporabniški prostor, preden jih vrnete; HotPDF bi vrednosti točk obravnaval kot piksle in plast bi se zrušila proti izvoru bitne slike

Preslikava izhoda lastnega zaznavalnika

Isti pomočnik služi doma zgrajenemu cevovodu na osnovi RenderLoadedPageToBitmap, ki izriše vidni okvir in uveljavi /Rotate točno kakor zmožnosti prepoznavanja. Preberite okvir z GetLoadedPageVisibleBox, normalizirajte zasuk na enak način kot HotPDF in preslikajte dva nasprotna kot vsakega pikslovnega okvirja. Os Y se obrne in pri 90 ter 270 stopinjah se osi zamenjajo, tako da preslikani koti pridejo ven v nobenem fiksnem vrstnem redu; vzemite minimum in maksimum preslikanih točk — tako HotPDF gradi tudi meje črtnih kod

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ša koda, pikslovni 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;

Obnašanje zasuka je pokrito globje v izravnava zasuka strani brez lomljenja okvirjev strani, cevovod dekodiranja črtnih kod, ki porablja isto transformacijo, pa v dekodiranju zavrtanih kod QR s strani PDF. Če vaš motor ovije zunanjega prepoznavalca, adapter OCR Tesseract za iskalni PDF pokaže stran izolacije procesov in preklica istega vmesnika

Hiter pregled: preslikava koordinat, varna za CropBox

  • Izrisovalnik rastrira vidni okvir, CropBox stisnjen na MediaBox (ISO 32000-1 §14.11.2); vsaka preslikava piksel-stran mora uporabiti ta okvir, prebran z GetLoadedPageVisibleBox
  • HotPDF v2.770.153 je popravil ApplyLoadedOCRTextLayer; v2.770.154 je popravil cela strani DecodeLoadedPageBarcodes in najdbe obrazov iz DetectLoadedRedactionFindings; izgradnje od v2.766.64 do teh različic so prizadete
  • Okvirji THPDFOCRWord so piksli bitne slike z izvorom zgoraj levo; GetLoadedPageBox in GetLoadedPageVisibleBox vrneta uporabniški prostor PDF z izvorom spodaj levo in Bottom < Top
  • Merilo je DPI / 72; izpeljite ga iz DPI, nikoli iz okvirja strani, deljenega v širino bitne slike
  • /Rotate odloča, kateri robovi štejejo: Left in Bottom pri 0 in 90, Right in Bottom pri 180, Right in Top pri 270
  • Vrňite besede OCR v pikslih in pustite HotPDF, da jih preslika; pretvarjajte samo za svojo lastno logiko filtriranja
  • Ponovno pognajte OCR na obrezanih straneh, obdelanih s prizadeto izgradnjo, in pomnite, da SkipPagesWithText preskoči strani, ki že nosijo staro plast

Zmožnosti prepoznavanja, poizvedbe okvirjev strani in izrisovanje naloženega dokumenta, uporabljeni tukaj, vsi prihajajo v komponenti HotPDF za Delphi in C++Builder; licenciranje, preskusni prenosi in celoten seznam zmožnosti so na strani komponente HotPDF Delphi PDF