Tekninen artikkeli

Poimi taulukoita PDF-sivuilta Delphissä PDFiumilla

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

Kaavio PDFium Componentin taulukontunnistusputkesta Delphissä, jossa viivoitettujen viivojen leikkauspisteet ja tyhjätilalla tasatut sanarivit syöttävät yhtä pisteytettyä taulukkotietuetta CSV- ja JSON-viennillä
Viivoitettu tunnistus leikkaa piirretyt viivat, kun taas tyhjätilatunnistus laskee tasattuja sanaruuturivejä; MinRows- ja MinColumns-rajat ylittävät ehdokkaat saavat luottamuspisteen ja DetectionMode-arvon
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ää

Kaavio yhdistettyjen solujen palauttamisesta PDFiumin taulukonpoiminnassa Delphille, jossa union-find liittää vierekkäiset perussolut aina, kun mikään rajaviiva ei kata niiden välistä väliä, tuottaen RowSpan- ja ColumnSpan-arvot
Union-find yhdistää naapuriperussoluja, joiden jaetulla välillä ei ole piirrettyä rajaa, joten yhdistetty otsikko palautuu yhtenä soluna, jossa on ColumnSpan asetettuna, sen sijaan että olisi yksi täytetty solu tyhjien reunustamana

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

Kaavio taulukon jatkumisesta PDF-sivujen yli Delphissä, jossa neljä tiukkaa porttia päättävät, liittyykö yhden sivun alin fragmentti seuraavan sivun ylimpään fragmenttiin
Asiakirjatason poiminta linkittää fragmentit vain, kun kaikki neljä porttia täyttyvät, mikä estää toisiinsa liittymättömiä neljäsarakkeisia taulukoita sulautumasta yhdeksi kuvitteelliseksi jättitaulukoksi

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