PDFium Component tunnistaa taulukoita PDF-sivulla ja palauttaa ne solujen ruudukkona rivi- ja sarakespan-arvoineen, otsikkoriveineen ja luottamusarvoineen, komennoilla ExtractTables yhdelle sivulle ja ExtractDocumentTables koko asiakirjalle. Jokainen taulukko muuntuu CSV:ksi tai JSON:ksi yhdellä kutsulla, ja taulukot, jotka jatkuvat sivunvaihdon yli, voidaan linkittää jatkoketjuksi
PDF:llä ei ole taulukko-objektia. Taulukko PDF:ssä on joukko tekstirivejä, jotka on sijoitettu niin, että ihminen lukee ne ruudukkona, joskus niiden ympärille piirrettyjen viivojen kanssa ja usein ilman. Ruudukon palauttaminen tarkoittaa tarkoituksen rekonstruoimista, jota tiedosto ei koskaan tallentanut, minkä vuoksi jokainen poimintatyökalu tuottaa hieman erilaisia tuloksia ja miksi työkalu, joka kertoo luottamuksensa, on hyödyllisempi kuin sellainen, joka ei kerro
Kaksi tunnistustilaa kahdelle taulukkotyypille
Viivoitettu tunnistus käyttää piirrettyjä viivoja. Jokainen viivoitettu polkusegmentti muunnetaan sivukoordinaatteihin sivuobjektin matriisin kautta, vaaka- ja pystyviivat leikataan, ja leikkauspisteet muodostavat yhdistetyt komponentit. Jokaisesta komponentista tulee oma lajiteltu X- ja Y-positioiden ruudukko, mikä pitää kaksi erillistä taulukkoa samalla sivulla yhdistymästä yhdeksi järjettömäksi ruudukoksi
Tyhjätilatunnistus käsittelee taulukoita, jotka on piirretty tasauksella viivojen sijaan. Sanaruudut ryhmitellään visuaalisiksi riveiksi, aukot rivin sisällä jakavat sen ehdokassarakkeisiin, ja taulukko hyväksytään vain, kun vähintään MinRows riviä toistaa vähintään MinColumns vasemmalle tasattua ankkuria AlignmentTolerance:n sisällä. Rivivälikerroin on oletuksena 3, mikä kattaa noin 30 pisteen perusviivavälin, tyypillisen 12 pisteen tekstille, sallimatta yksittäisen rivin, jossa on useita tekstirivejä, esiintyä taulukkona
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'annual-report.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 12; // 1-pohjainen
Options := TPdfTableExtractionOptions.Default;
Options.DetectRuledTables := True;
Options.DetectWhitespaceTables := True;
Options.MinConfidence := 0.6; // oletus on 0.5
Options.HeaderRowCount := 1;
Tables := Pdf.ExtractTables(Options);
for I := 0 to High(Tables) do
Writeln(Format('table %d: %dx%d cells, confidence %.2f, mode %d',
[I, Tables[I].RowCount, Tables[I].ColumnCount,
Tables[I].Confidence, Ord(Tables[I].DetectionMode)]));
if Length(Tables) > 0 then
SaveText('page12-table0.csv', Tables[0].ToCsv);
finally
Pdf.Free;
end;
end;
Miten yhdistetyt solut palautetaan?
Tämä on osa, jonka naiivit poimintatyökalut saavat väärin. Yhdistettyä solua ei voida tunnistaa pelkästä globaalista ruudukosta, koska ruudukko on johdettu kaikista sivun viivoista, ja yhdistetyltä alueelta yksinkertaisesti puuttuu sisäviiva, joka olisi erottanut sen
Tässä käytetty sääntö on paikallinen: kaksi vierekkäistä perussolua yhdistetään, kun mikään rajaviiva ei kata niiden välistä väliä. Union-find liittää ne, tuloksena olevista suorakulmaisista komponenteista tulee RowSpan- ja ColumnSpan-arvoja, ja teksti kohdistetaan perussolulle sen keskipisteen mukaan ja seuraa sitten kyseistä solua sen yhdistämisjuureen. Näin tehtynä kustannus pysyy myös lineaarisena sanojen ja solujen summassa, sen sijaan että se olisi neliöllinen skannaus, jonka saisit testaamalla jokaista sanaa jokaista solua vastaan
Käytännön vaikutus on, että talouspöytäkirja, jossa on yhdistetty "Total"-otsikko kolmen sarakkeen yli, tulee ulos yhtenä solmuna, jonka span on kolme, sen sijaan että olisi yksi täytetty solu ja kaksi mystistä tyhjää
Jatkuvuus sivujen yli
Pitkät taulukot katkeavat sivujen yli, ja jokaisen sivun fragmentin käsittely itsenäisenä taulukkona pakottaa kutsujan ompelemaan ne yhteen. ExtractDocumentTables voi linkittää ne sen sijaan, mutta vain tiukoin ehdoin: fragmentin täytyy olla alin taulukko aiemmalla sivulla, seuraavan täytyy olla ylin taulukko seuraavalla sivulla, sivunumeroiden täytyy olla vierekkäisiä, ja sarakerajojen täytyy täsmätä
Kaikki neljä ehtoa yhdessä estävät ilmeisen virheen, joka on kaikkien neljäsarakkeisten taulukoiden ketjuttaminen asiakirjassa yhdeksi kuvitteelliseksi jättitaulukoksi, koska niillä sattuu olemaan sama sarakemäärä. Kun ehdot täyttyvät, taulukot jakavat jatkuvuusryhmätunnisteen ja kantavat jatkuvuusmetatietoa; kun eivät täyty, saat erillisiä taulukoita ja voit päättää itse
Asiakirjatason poiminta jakaa MaxCells- ja MaxTables-budjetit sivujen kesken sen sijaan, että nollaisi ne per sivu, ja se palauttaa aktiivisen sivun finally-lohkossa, joten katseluohjelmassa ajettu poiminta jättää käyttäjän katsomaan sitä sivua, jolla hän oli
Vieminen turmelematta dataa
Molemmat viejät ovat tarkoituksellisia paon suhteen. CSV lainaa aina kentät ja kahdentaa sisäiset lainausmerkit, mikä välttää klassisen epäonnistumisen, jossa pilkun sisältävä solu hiljaisesti muuttuu kahdeksi sarakkeeksi. Yhdistetyillä soluilla sisältö tuotetaan vain ylävasemman ankkurin kohdalla, joten CSV-edestakainen kierros ei kahdenna kattavaa otsikkoa niiden sarakkeiden yli, jotka se kattaa
JSON säilyttää Unicode-merkistön sen sijaan, että pakenisi sen ASCII:ksi, pakenee kontrollimerkit, ja sisältää metatiedon, jota kuluttaja tarvitsee arvioidakseen laadun: tunnistustila, luottamus, rajat, span-arvot, otsikkoliput ja jatkuvuustieto. Jos syötät poimittuja taulukoita jatkokäsittelyjärjestelmään, suosi JSON:ia, koska CSV-rivi ei voi kertoa, että se taulukko, josta se tuli, sai 0,51 luottamuksen:
// Asiakirjanlaajuinen poiminta, säilyttäen vain luotettavat taulukot
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
begin
if Tables[I].Confidence < 0.75 then
begin
Log(Format('page %d table needs review (%.2f)',
[Tables[I].PageNumber, Tables[I].Confidence]));
Continue;
end;
if Tables[I].ContinuationGroup > 0 then
AppendToGroup(Tables[I].ContinuationGroup, Tables[I].ToJson)
else
EmitStandalone(Tables[I].ToJson);
end;
Viritys, ja milloin kannattaa lopettaa
Kolme asetusta merkitsevät enemmän kuin loput. MinConfidence on laatuportti, ja 0,5 on tarkoituksella salliva; nosta sitä automatisoituun sisäänottoon ja laske sitä katselukäyttöliittymälle, jossa ihminen vahvistaa jokaisen tuloksen. MinColumnGap päättää, mikä lasketaan sarakerajaksi tyhjätilatilassa, ja tiiviisti asetetut taulukot tiheissä raporteissa saattavat tarvita sen laskemista 12 pisteen oletuksesta. MaxRowGapFactor päättää, milloin pystysuuntainen etäisyys lopettaa taulukon, mikä on merkityksellistä taulukoille, joissa on satunnaisia tyhjiä rivejä
Ole rehellinen rajoista. Viivoitetut taulukot poimivat luotettavasti. Siististi tasatut tyhjätilataulukot poimivat hyvin. Taulukot, joissa on kierretty teksti, sisäkkäiset taulukot, tai solut, joiden sisältö rivittyy näyttämään toiselta riviltä, tarvitsevat tarkastelun riippumatta siitä, miten parametrit asetetaan. Niitä varten strukturoitu tekstimalli antaa raaka-aineen toimialuekohtaisen lukijan rakentamiseen, kuvattu artikkelissa strukturoidut tekstilohkot ja lukujärjestys
Yksi hyödyllinen yhdistelmä: kun skannatussa asiakirjassa ei ole lainkaan tekstiä, taulukon tunnistuksella ei ole mitään työstettävää ennen kuin tekstikerros on olemassa. Lisää sellainen ensin, kuvattu artikkelissa haettavan tekstikerroksen lisääminen skannattuihin PDF-tiedostoihin, ja poimi sitten. Sanaruudut, jotka OCR-tarjoaja palauttaa, ovat täsmälleen se syöte, jota tyhjätilatunnistus tarvitsee
Taulukoiden poiminta, strukturoitu teksti ja uudelleenrivitys lukevat kaikki samasta sivumallista Delphissä, C++Builderissa ja Lazarusissa; koko API on kuvattu sivulla PDFium Component for Delphi -sivu