OCR-tekstitasot, viivakoodirajat ja kasvojen mustauslaatikot ajautuvat sivuiltaan rajatuilla PDF-sivuilla, kun bittikarttapikselit kartoitetaan takaisin MediaBoxin kautta sen sijaan, että käyttäisi laatikkoa, jonka renderöijä oikeasti rasteroi: CropBoxia, leikattuna MediaBoxiin (ISO 32000-1 §14.11.2). HotPDF korjasi tämän funktiolle ApplyLoadedOCRTextLayer versiossa v2.770.153 sekä funktioille DecodeLoadedPageBarcodes ja DetectLoadedRedactionFindings versiossa v2.770.154
Bugiraportti, joka yleensä saapuu, näyttää tältä. Skannattu sopimusarkisto käy läpi OCR:n, tuotos on haettava, ja hakutulos lauseen numerolle korostuu puoli tuumaa alle ja vasemmalle painetusta numerosta. Useimmat erän tiedostot ovat kunnossa. Rikkinäiset tulivat kaikkea yhdestä skannausasemasta, joka kirjoittaa /CropBoxin rajaamaan skannerin lasipöydän marginaalin. Yksityiskohta erottaa toisistaan kuvan, jonka OCR-moottori näki, ja kehyksen, johon tekstitaso sijoitettiin, ja sama ristiriita siirtää viivakoodirajoja ja, vakavammin, kasvojen mustauslaatikoita
Miksi OCR-tekstitaso ajautuu pois skannatuista sanoista?
Tekstitaso ajautuu sivuiltaan, koska putken kaksi puoliskoa olivat eri mieltä siitä, minkä suorakulmion bittikartta kattaa. Versiossa v2.766.64 HotPDF muutti renderöinnin, SVG-viennin, katseluohjelman ja tulostuksen kunnioittamaan CropBoxia: sivu näytetään sen CropBoxin kautta, leikattuna sen MediaBoxiin, mikä on se, minkä ISO 32000-1 §14.11.2 määrää, ja GetLoadedPageVisibleBox lisättiin palauttamaan kyseinen näkyvä laatikko. Tunnistusominaisuudet rakensivat yhä laite–sivu-muunnoksensa funktiosta GetLoadedPageBox(PageIndex, pbMediaBox, ...). Rasteri kattoi nyt näkyvän laatikon, muunnos oletti yhä MediaBoxin, ja jokainen tunnistettu sijainti palautui siirtymänä kahden välin verran
Vaurioitunut ikkuna on siksi täsmällinen. ApplyLoadedOCRTextLayer sijoitti tekstin väärin versiosta v2.766.64 versioon v2.770.152 asti. Kokosivun DecodeLoadedPageBarcodes ja kasvojentunnistus funktion DetectLoadedRedactionFindings sisällä olivat väärin yhden koosteen pidempään, versioon v2.770.153 asti. Ennen v2.766.64 renderöijä piirsi koko MediaBoxin, joten kartoitus ja rasteri sopivat yhteen tunnistamalla sisältöä, jota katseluohjelmat eivät koskaan näytä. Korjaukset muuttivat kolmea asiaa yhdessä kutakin ominaisuutta varten: muunnosta, pikselibudjettiarviota ja pyyntötietueeseen omalle moottorille annettavaa sivulaatikkoa
Useat tapaukset eivät koskaan vaurioituneet:
- Sivut ilman
/CropBoxia tai sivut, joiden CropBox on yhtä suuri kuin MediaBox, kartoittuvat identtisesti ennen ja jälkeen korjauksen DecodeLoadedPageBarcodesarvollaHasRegionasetettuna renderöi täsmälleen antamasi alueen ja kartoittaa sen saman alueen kautta, joten eksplisiittisen alueen dekoodaus oli oikein koko ajan; tarkistus siitä, että alue on sivun sisällä, käyttää yhä MediaBoxia- Kaavapohjaiset mustauslöydökset (sähköpostit, korttinumerot ja niin edelleen) tulevat tekstin poiminasta käyttäjätilassa, eivät rasterista, joten vain kasvojentunnistuslöydökset siirtyivät
Kolme koordinaatistoa ja mitkä HotPDF-API:t käyttävät kutakin
Tunnistukseen koskeva HotPDF-koodi käsittelee kolmea koordinaatistoa, ja useimmat kartoitusbugit tulevat kahden sekoittamisesta
- Bittikarttapikselit: vasemman yläkulman alkukohta, Y kasvaa alaspäin, yksikkönä pikselit pyynnön DPI:llä. Kentät
THPDFOCRWord.Left,Top,RightjaBottomovat tässä koordinaatistossa, samoin valinnaiset perusviivapisteet, tulokset, jotka omaIHPDFBarcodeDecoderpalauttaa, sekä laatikot, jotka omaIHPDFFaceDetectorpalauttaa - Ladatun sivun PDF-käyttäjätila: vasemman alakulman alkukohta, Y kasvaa ylöspäin, yksikkönä pisteet, kun
Bottom < Top. FunktiotGetLoadedPageBoxjaGetLoadedPageVisibleBoxpalauttavat Left-, Bottom-, Right- ja Top-arvot tässä koordinaatistossa, samoinTHPDFOCRRequestin kentätPageLeft,PageBottom,PageRightjaPageTop, rajat tietueessaTHPDFDecodedBarcodesekä suorakulmiot tietueessaTHPDFRedactionFinding - HotPDF:n sivunpiirtokoordinaatit: API, jolla rakennat uusia sivuja (tekstitulostus, muodot, viivakoodit, linkit, lomakekentät), toimii vasemman yläkulman alkukohdalla ja Y:n kasvaessa alaspäin. Kyseinen koordinaatisto kuuluu asiakirjojen generointiin, eikä sillä ole mitään tekemistä yllä olevien ladattujen asiakirjojen API:jen kanssa, joten älä koskaan syötä siihen ladatun sivun käyttäjätilan suorakulmiota muuttumattomana
OCR-sanatietue on tahallaan pikselipohjainen: moottori raportoi, mitä se näki kuvassa, ja ApplyLoadedOCRTextLayer omistaa muunnoksen. Kyseinen jako toimii vain, kun muunnos käyttää oikeaa laatikkoa, ja juuri sen v2.770.153 palautti
Laite–sivu-muunnos OCR:n, viivakoodien ja kasvojen takana
HotPDF kartoittaa bittikarttapikselit sivulle yhdellä viidestä syötteestä rakennetulla affiinimatriisilla: kiertymällä, skaalalla DPI / 72, bittikartan korkeudella sekä renderöidyn laatikon Left-, Bottom-, Right- ja Top-arvoilla. OCR, viivakoodien dekoodaus ja kasvojentunnistus jakavat yhden rutiinin sen tekemiseen, minkä vuoksi yksi väärä laatikkosyöte rikkoi kaikki kolme samalla tavalla. Kiertymättömälle sivulle sivu–laite-matriisi [A B C D E F] on:
A = ScalejaD = -Scale, jossaScale = DPI / 72; negatiivinenDkääntää käyttäjätilan (Y ylös) bittikarttatilaan (Y alas)B = C = 0, koska kiertymättömällä sivulla ei ole leikkausta eikä akselien vaihtoaE = -Left * Scale, joka siirtää laatikon vasemman reunan pikselisarakkeeseen 0F = BitmapHeight + Bottom * Scale, joka kartoittaa laatikon alareunan arvoon y = BitmapHeight, bittikarttaan alareunaan, joten yläreuna laskeutuu riville 0
Pikselit palaavat sivulle kyseisen matriisin käänteiskuvan kautta. Request.PageRotation kantaa sivun /Rotaten normalisoituna arvoihin 0, 90, 180 tai 270 (mikä tahansa arvo, joka ei ole 90:n kerrannainen, käsitellään arvona 0), ja renderöijä kääntää sivun myötäpäivään, niin kuin ISO 32000-1 §7.7.3.3 vaatii. Kiertymän alla akselit vaihtuvat, ja eri pari laatikon reunoja kiinnitetään bittikartan alkukohtaan. Kirjoitettuna käänteiskaavoina, arvoilla S = DPI / 72, x ja y pikseleinä ja H bittikartan korkeutena:
| /Rotate | Sivun X | Sivun Y | Laatikon reunat, joista kartoitus riippuu |
|---|---|---|---|
| 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 |
Viimeinen sarake selittää, miksi bugi näytti tuotannossa satunnaiselta. CropBox, joka rajaa vain sivun yläosaa, jättää Leftin ja Bottomin koskemattomiksi, joten kiertymättömät sivut tulivat täydellisesti ulos ja vain sivut, jotka kantoivat arvon /Rotate 270, ajautuivat sivuiltaan. Kiertymä vaihtaa myös bittikartan dimensiot: arvoilla 90 ja 270 bittikartta on (Top - Bottom) * S pikseliä leveä ja (Right - Left) * S pikseliä korkea
Mitä menee pieleen arvoilla MediaBox [0 0 612 792] ja CropBox [36 36 576 756]?
Puolen tuuman rajauksella joka sivulta kiertymättömän sivun tekstitaso laskeutuu täsmälleen 36 pistettä vasemmalle ja 36 pistettä alle skannatuista sanoista, kun MediaBoxia käytetään. Ota US Letter -sivu, jonka CropBox rajaa 36 pistettä (0.5 tuumaa) jokaiselta reunalta. Näkyvä laatikko on 540 kertaa 720 pistettä, joten oletusarvoisella OCR-resoluutiolla 300 DPI skaala on 300 / 72 ≈ 4.1667 ja bittikartta 2250 kertaa 3000 pikseliä
Oletetaan, että moottori raportoi sanan pikselilaatikolla Left 450, Top 600, Right 900, Bottom 660 ilman perusviivaa. HotPDF sijoittaa sitten perusviivan 20 prosenttiin sanan korkeudesta alareunan yläpuolelle, pikseliriville 648, ja kartoittaa alkupisteen (450, 648):
- Näkyvän laatikon kautta: x = 36 + 450 / 4.1667 = 144.0 ja y = 36 + (3000 - 648) / 4.1667 = 600.48, mikä on kohta, jossa sana on painettu
- MediaBoxin kautta: x = 0 + 108.0 = 108.0 ja y = 0 + 564.48 = 564.48, yhtenäinen siirtymä (-36, -36) pistettä
Kierrä sama sivu, ja virheen suunta muuttuu, koska eri reunat ovat kuvioissa mukana. Arvolla /Rotate 180 X-termi käyttää Rightia, ja 612 arvon 576 sijaan työntää taso 36 pistettä oikealle, kun Bottom vetäisi yhä 36 pistettä alas. Arvolla /Rotate 270 sekä Right että Top ovat liian suuria, joten taso siirtyy 36 pistettä oikealle ja 36 pistettä ylös. Asiakirja, jolla on sekaisin suuntauksia, voi näyttää ajautumisen kolmeen suuntaan, luotettava sormenjälki tälle bugille. Käsin kirjoitettu koodi, joka johtaa skaalan laatikosta, kuten Bitmap.Width / (Right - Left), venyttää myös jokaisen koordinaatin 612 / 540, noin 13 prosenttia, siirtymän päälle
Mitkä omista PDF-asiakirjoistasi ovat vaarantuneita?
PDF-asiakirja on altistunut, kun vähintään yhdellä sivulla on näkyvä laatikko, joka eroaa sen MediaBoxista, ja HotPDF kertoo sen sinulle muutamalla rivillä. Vertaa funktiota GetLoadedPageBox arvolla pbMediaBox funktioon GetLoadedPageVisibleBox jokaisella sivulla ja tulosta GetLoadedPageRotation rinnalle, jotta voit ennakoida ajautumisen suunnan yllä olevasta taulukosta. THPDFPageBoundary tarjoaa lisäksi arvot pbCropBox, pbBleedBox, pbTrimBox ja pbArtBox, mutta GetLoadedPageBox(pbCropBox) palaa MediaBoxiin, kun crop-laatikkoa ei ole, eikä leikkaa, joten näkyvä laatikko on oikea vertailukohde
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;
// talletettu taulukko saattaa luetteloida kulmansa missä tahansa järjestyksessä
if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
// jo normalisoitu ja leikattu MediaBoxiin
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;
Kaksi yksityiskohtaa funktiossa GetLoadedPageVisibleBox merkitsee tällaisille skripteille. Funktio jättää out-parametrinsä koskemattomiksi epäonnistuessaan, joten oletussivukoon esiasettaminen ennen kutsua on turvallinen kaava. Ja kun epäkelpo CropBox ei leikkaa MediaBoxia yhtään, funktio palauttaa MediaBoxin tyhjän suorakulmion sijaan. Jos raportti luetteloi sivuja ja käytössäsi oleva kooste on vanhempi kuin v2.770.153 OCR:n kohdalla tai v2.770.154 viivakoodeille ja kasvoille, aja tunnistus uudelleen kyseisillä sivuilla päivityksen jälkeen. Vaurioituneen koosteen vahvistama OCR-taso jää tallennettuun tiedostoon, ja oletusvalinta SkipPagesWithText ohittaa kyseiset sivut toisella kierroksella, ellet kytke sitä pois tai poista vanhaa tasoa ensin
Miten oman IHPDFOCREngine:n pitäisi kartoittaa pikselit takaisin PDF-tilaan?
Oman IHPDFOCREnginen pitäisi palauttaa sanalaatikot bittikarttapikseleinä ja antaa HotPDF:n tehdä kartoitus; muunna käyttäjätilaan vain omia päätöksiäsi varten, ja käytä silloin pyynnön laatikkoa, ei koskaan MediaBoxia. Versiosta v2.770.153 alkaen pyynnön kentät PageLeft, PageBottom, PageRight ja PageTop kuvaavat renderöityä näkyvää laatikkoa, joten ne vastaavat kohtaa Request.Bitmap täsmälleen. Alla oleva avustaja on kirjaston muunnoksen käänteiskuva, mukaan lukien sen käyttö todellista bittikartan korkeutta pystysuorilla sivuilla, joten se sopii HotPDF:n kanssa yhteen pikseliin asti
uses
System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;
// Bittikarttapikseli (vasemman yläkulman alkukohta, Y alaspäin) PDF:n
// käyttäjätilaan (vasemman alakulman alkukohta, Y ylöspäin) sitä laatikkoa pitkin, josta bittikartta renderöitiin
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;
Realistinen syy tarvita käyttäjätilaa moottorin sisällä on vyöhykesääntö: laskut, joiden kirjeenpohjaa ei koskaan haluta haettavaksi, tai leima-alue, joka sekoittaa tunnistimen. Alla oleva moottori, kirjoitettu luokalla TInterfacedObject niin, että viitemääritys hoitaa sen eliniän, suodattaa sanat sen mukaan, mihin niiden keskipisteet putoavat sivulla, ja palauttaa sitten selvinneet koskemattomina pikselikoordinaateissa. RunRecognizer edustaa omaa tunnistinkutsuasi
type
TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
private
FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single; // käyttäjätila
function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
out Words: THPDFOCRWords): boolean; // oma tunnistimesi, pikselilaatikot
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]; // yhä pikseleitä: HotPDF kartoittaa ne itse
Inc(Count);
end;
SetLength(Words, Count);
Result := True;
end;
Anna moottori funktiolle ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info) kuten mille tahansa muulle moottorille. Kirjasto validoi palautuvan ennen kuin luottaa siihen: sana pudotetaan ja se lasketaan kenttään Info.DroppedWordCount, kun sen laatikko poistuu bittikartasta, kun Right <= Left tai Bottom <= Top, tai kun Confidence on väliltä 0..1 poissa tai alle MinimumConfidencein. Useampien sanojen palauttaminen kuin MaxWordsPerPage tai juoksevan summan työntäminen arvon MaxTotalWords yli kaataa koko kutsun budjettivirheellä, joten kunnioita arvoa Request.MaxWords moottorissasi. Älä muunna sanalaatikoita käyttäjätilaan ennen niiden palauttamista; HotPDF käsittelisi pistearvot pikseleinä, ja taso luhistuisi bittikartan alkukohtaa kohti
Oman detektorin tuotoksen kartoittaminen
Sama avustaja palvelee kotikutoista putkea, joka rakentuu funktion RenderLoadedPageToBitmap päälle, renderöi näkyvän laatikon ja soveltaa arvoa /Rotate aivan kuten tunnistusominaisuudet. Lue laatikko funktiolla GetLoadedPageVisibleBox, normalisoi kiertymä samalla tavalla kuin HotPDF ja kartoita jokaisen pikselilaatikon kaksi vastakkaista kulmaa. Y-akseli kääntyy ja arvoilla 90 ja 270 astetta akselit vaihtuvat, joten kartoitetut kulmat tulevat ulos missä tahansa järjestyksessä; ota kartoitettujen pisteiden minimi ja maksimi, mikä on myös tapa, jolla HotPDF rakentaa viivakoodirajat
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); // oma koodisi, pikselilaatikko
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;
Kiertymäkäyttäytyminen käsitellään syvemmin artikkelissa sivunkiertyman tasoittaminen rikkomatta sivulaatikoita ja viivakoodien dekoodausputki, joka kuluttaa saman muunnoksen, artikkelissa kiertyneiden QR-koodien dekoodaus PDF-sivuilta. Jos moottorisi kietoo ulkoisen tunnistimen, artikkeli Tesseract-OCR-sovitin haettavaan PDF:ään näyttää saman rajapinnan prosessieristyksen ja peruuttamisen puolen
Pikaopas: CropBox-turvallinen koordinaattikartoitus
- Renderöijä rasteroi näkyvän laatikon, CropBoxin leikattuna MediaBoxiin (ISO 32000-1 §14.11.2); jokaisen pikseli–sivu-kartoituksen on käytettävä kyseistä laatikkoa, luettuna funktiolla
GetLoadedPageVisibleBox - HotPDF v2.770.153 korjasi funktion
ApplyLoadedOCRTextLayer; v2.770.154 korjasi kokosivunDecodeLoadedPageBarcodesin ja kasvolöydökset funktiostaDetectLoadedRedactionFindings; koosteet versiosta v2.766.64 näihin versioihin asti ovat vaurioituneita THPDFOCRWord-laatikot ovat bittikarttapikseleitä vasemman yläkulman alkukohdalla;GetLoadedPageBoxjaGetLoadedPageVisibleBoxpalauttavat PDF-käyttäjätilan vasemman alakulman alkukohdalla ja arvollaBottom < Top- Skaala on
DPI / 72; johda se DPI:stä, ei koskaan sivulaatikosta jaettuna bittikartan leveydellä - /Rotate päättää, mitkä reunat merkitsevät: Left ja Bottom arvoilla 0 ja 90, Right ja Bottom arvolla 180, Right ja Top arvolla 270
- Palauta OCR-sanat pikseleinä ja anna HotPDF:n kartoittaa ne; muunna vain omaa suodatuslogiikkaasi varten
- Aja OCR uudelleen rajatuilla sivuilla, jotka vaurioitunut kooste käsittelee, ja muista, että
SkipPagesWithTextohittaa sivut, jotka kantavat jo vanhaa tasoa
Tunnistusominaisuudet, sivulaatikkokyselyt ja ladattujen asiakirjojen renderöinti, joita täällä käytetään, toimitetaan kaikki HotPDF-komponentissa Delphille ja C++Builderille; käyttöoikeudet, kokeiluversiot ja täysi ominaisuusluettelo ovat HotPDF Delphi PDF component -sivulla