PDFium Component lisää haettavan tekstikerroksen skannattuihin PDF-sivuihin Delphistä metodilla ApplyOcrSearchLayer. Se renderöi jokaisen valitun sivun, antaa pikselit itse toimittamallesi OCR-tarjoajalle ja kirjoittaa tunnistetut sanat takaisin näkymättöminä tekstiobjekteina, jotka on sijoitettu skannauksen sanojen päälle. Alkuperäistä sivukuvaa ei koskaan puraeta, koodata uudelleen tai korvata, joten visuaalinen tulos on tavu tavulta sama sivu, jolla aloitit
Tunnistusmoottori ei tarkoituksella kuulu kirjastoon. PDFium paljastaa sivun renderöinnin, koordinaattien kuvauksen, fontin latauksen, tekstiobjektien luonnin ja näkymättömät renderöintitilat, mutta se ei sisällä OCR-moottoria, ja toisin väittäminen tarkoittaisi jonkun tunnistustuotteen pakkaamista PDF-komponenttiin. Sen sijaan tunnistus asuu IPdfOcrProvider-rajapinnan takana: kirjasto välittää kiinteän asettelun, ylälähtöiset BGRA-pikselit, ja tarjoaja palauttaa Unicode-tekstin, luottamusarvot ja sanojen nelikulmiot
Mikä täsmälleen on haettava tekstikerros?
Skannattu PDF on kuva dokumentista. Sivun sisältö on yksi suuri kuva, eikä siinä ole mitään valittavaa, haettavaa, kopioitavaa tai indeksoitavaa. Haettava tekstikerros lisää todellisia tekstiobjekteja tuon kuvan päälle renderöintitilalla asetettuna näkymättömäksi, joten katseluohjelmat eivät piirrä mitään, mutta valinta, haku ja poiminta löytävät sanat täsmälleen siitä, missä ne esiintyvät
Sijoittelu on koko peli. Jos näkymätön teksti asettuu muutaman pisteen verran väärin, valinnan korostukset osuvat sanojen viereen niiden päälle osumisen sijaan, ja kappaleen kopiointi tuottaa tekstiä väärässä järjestyksessä. Tästä syystä geometrian täytyy tulla samoista muunnoksista, joita PDFium käyttää sivun renderöintiin, ei suhteellisesta arvauksesta
Tarjoajan toteuttaminen
Tarjoajan sopimus on yksi metodi. Se vastaanottaa sivukuvatietueen, joka kantaa mitat, riviaskeleen, DPI:n, pikselimuodon ja itse pikselitavut, sekä peruutustunnisteen, ja palauttaa sanat tai virheilmoituksen:
uses
PDFium;
type
TMyOcrProvider = class(TInterfacedObject, IPdfOcrProvider)
public
function RecognizePage(const Image: TPdfOcrImage;
const CancellationToken: IPdfCancellationToken;
out Words: TPdfOcrWords; out ErrorMessage: string): Boolean;
end;
function TMyOcrProvider.RecognizePage(const Image: TPdfOcrImage;
const CancellationToken: IPdfCancellationToken;
out Words: TPdfOcrWords; out ErrorMessage: string): Boolean;
var
I: Integer;
begin
// Image.Pixels sisältää ylälähtöiset BGRA-rivit, joiden pituus on Image.Stride tavua.
// Anna ne moottorillesi ja täytä sitten yksi merkintä jokaista tunnistettua sanaa kohden
SetLength(Words, RecognisedCount);
for I := 0 to RecognisedCount - 1 do
begin
Words[I].Text := EngineWordText(I);
Words[I].Confidence := EngineWordConfidence(I); // 0..1
Words[I].Quad := TPdfOcrQuad.FromRectangle(
EngineLeft(I), EngineTop(I), EngineRight(I), EngineBottom(I));
end;
ErrorMessage := '';
Result := True;
end;
Nelikulmioita suorakulmioiden sijaan, koska skannaus on harvoin täysin suorassa sivuun nähden. Sana hieman kierretyllä sivulla käyttää suunnikkaan muotoista aluetta, ja TPdfOcrQuad kantaa neljä kulmapistettä, jotta vinot ja kierretyt sanat säilyttävät tarkan valinta-alueen. Moottorit, jotka raportoivat vain akselinsuuntaisia laatikoita, voivat käyttää metodia FromRectangle, joka rakentaa degeneroituneen nelikulmion
Miksi sanojen sijainteja ei voi skaalata suhteellisesti?
On houkuttelevaa muuntaa pikselikoordinaatti sivukoordinaatiksi jakamalla renderöintileveydellä ja kertomalla sivun leveydellä. Se toimii vain sivuille, joilla ei ole kiertoa, joiden CropBox on identtinen MediaBoxin kanssa ja joiden origo on nollassa, ja monet skannatut dokumentit epäonnistuvat vähintään yhdessä näistä ehdoista
PDFium Component kuvaa jokaisen neljästä nelikulmion kulmasta erikseen metodilla FPDF_DeviceToPage, samalla kuvauksella, jota renderöijä käytti pikselien tuottamiseen, joten /Rotate-merkinnät ja siirretyt leikkausalueet käsitellään rakenteellisesti. Tekstiobjektin affiinimatriisi rakennetaan sitten kolmesta kuvatusta pisteestä, vasemmasta alakulmasta, oikeasta alakulmasta ja vasemmasta yläkulmasta, mikä riittää täsmälleen ilmaisemaan sijainnin, skaalan, kierron ja vinoutuksen
Itse tekstiobjekti luodaan yksikköfonttikoossa, jotta sen todelliset fonttirajat voidaan mitata, ja mitatut objektirajat kuvataan sitten kohdenelikulmioon. Koon asettaminen arvatulla pistekoolla toivoen sen täsmäävän skannattuun sanaan ajautuisi väärin joka fontinkorvauksen kohdalla; mittaaminen ensin tekee sovituksesta riippumattoman siitä, mitä fonttia kerros käyttää
Sen ajaminen dokumentin yli
Asetustietue hallitsee resoluutiota, suodatusta ja jokaista budjettia. Luottamussuodatuksella on enemmän merkitystä kuin miltä se näyttää: roskasanat alhaisella luottamuksella saastuttavat hakutuloksia pysyvästi, ja toisin kuin väärä renderöinti, kukaan ei huomaa, ennen kuin haku palauttaa hölynpölyä:
var
Pdf: TPdf;
Options: TPdfOcrOptions;
Report: TPdfOcrReport;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'scanned-contract.pdf';
Pdf.LoadDocument;
Options := TPdfOcrOptions.Default;
Options.Dpi := 300; // tunnistuksen resoluutio
Options.MinConfidence := 0.60; // hylkää epävarmat sanat
Options.SkipPagesWithText := True; // jätä syntyperäisesti digitaaliset sivut rauhaan
Options.ContinueOnError := True; // yksi huono sivu ei saa pysäyttää työtä
Options.MaxPixelsPerPage := 40 * 1000 * 1000;
if Pdf.ApplyOcrSearchLayer(TMyOcrProvider.Create, Options, Report) then
Pdf.SaveAs('scanned-contract-searchable.pdf');
for I := 0 to High(Report.Pages) do
if Report.Pages[I].Status = popsFailed then
Writeln(Format('page %d failed: %s',
[Report.Pages[I].PageNumber, Report.Pages[I].ErrorMessage]));
Writeln(Format('%d word(s) inserted, %d rejected, %d page(s) skipped',
[Report.InsertedWordCount, Report.RejectedWordCount,
Report.SkippedPageCount]));
finally
Pdf.Free;
end;
end;
SkipPagesWithText ansaitsee korostuksen sekamuotoisissa arkistoissa. PDF, joka jo kantaa todellista tekstiä, olipa se syntyperäisesti digitaalinen tai aiemmin käsitelty, saa toisen tekstikerroksen, jos ajat OCR:n sen yli sokeasti, ja kaksoiskappale saa poiminnan palauttamaan jokaisen sanan kahdesti. Sivukohtainen tila popsSkippedExistingText kertoo täsmälleen, mitkä sivut jätettiin rauhaan
Budjetit, peruutus ja virheiden hallinta
Jokaisella suureella, jota vihamielinen tai vain valtava dokumentti voi paisuttaa, on katto: pikselit sivua kohden ja yhteensä, sanat sivua kohden ja yhteensä, sekä merkit sanaa kohden. Kaikki nämä tarkistetaan ennen sivun kirjoittamista, ei jälkeen, ja pikseliarvio lasketaan sivun mitoista ja DPI:stä ennen kuin mitään bittikarttaa varataan. DPI:n nostaminen 150:sta 300:aan nelinkertaistaa muistin sivua kohden, joten sivukohtainen katto on ensimmäinen parametri, jota kannattaa säätää, kun eräajo alkaa epäonnistua suurilla muodoilla
Peruutustunniste kulkee koko polun läpi: progressiivisen renderöinnin, tarjoajakutsun ja sanakohtaisen lisäyssilmukan. Tämä tarkoittaa, että käyttäjä, joka peruuttaa 400-sivuisen tiedoston tunnistuksen aikana, pysähtyy yhden sivun sisällä eikä dokumentin lopussa, ja sama tunnistemalli, jota käytetään muualla komponentissa ja joka kuvataan artikkelissa peruutettava progressiivinen renderöinti, pätee tässä muuttumattomana
Virheiden hallinta on sivukohtaista. Kirjasto kerää sivulle lisäämänsä objektikahvat ja kutsuu metodia FPDFPage_GenerateContent kerran sen jälkeen, kun kaikki sanat on sijoitettu. Jos jokin epäonnistuu kesken, olipa kyseessä tarjoajavirhe tai fonttiongelma, sivulle lisätyt objektit poistetaan käänteisessä järjestyksessä ja sivun sisältö luodaan uudelleen, joten epäonnistunut sivu palautuu alkuperäiseen tilaansa sen sijaan, että se säilyttäisi puolittaisen tekstikerroksen. Dokumenttisilmukka jatkuu tai pysähtyy sen jälkeen asetuksen ContinueOnError mukaan, ja aktiivinen sivu palautetaan aina
Sen todentaminen, että kuva todella pysyi koskemattomana
Vahvin käytettävissä oleva tarkistus on myös yksinkertaisin: renderöi sivu ennen kerroksen lisäämistä ja sen jälkeen samassa koossa ja vertaa bittikarttoja. Niiden pitäisi olla identtisiä tavu tavulta, koska näkymätön teksti ei piirrä mitään ja kuvavirtaa ei koskaan purettu. Mikä tahansa ero tarkoittaa, että jokin muu kuin tekstikerros muutti sivua
Sen jälkeen todenna tekstipuoli poimimalla käsitellystä tiedostosta ja varmistamalla, että sanojen sijainnit osuvat skannaukseen. Poimintapolku on sama, joka kuvataan artikkelissa tekstin poiminta PDF-dokumenteista, ja kohdistuksen nopeaan visuaaliseen tarkistukseen sivujen renderöinti kuviksi, kuten artikkelissa PDF-sivujen muuntaminen JPEG-kuviksi, antaa sinun asettaa sanalaatikot päällekkäin skannauksen kanssa
OCR-kerrostus, renderöinti, poiminta ja muokkaus toimivat kaikki samaa dokumenttiobjektia vasten Delphissä, C++Builderissa ja Lazaruksessa; täydellinen API-pinta kuvataan sivulla PDFium Component for Delphi -sivulla