Το PDFium Component προσθέτει ένα αναζητήσιμο επίπεδο κειμένου σε σαρωμένες σελίδες PDF από τη Delphi μέσω της ApplyOcrSearchLayer. Αποδίδει κάθε επιλεγμένη σελίδα, παραδίδει τα pixel σε έναν πάροχο OCR που παρέχετε εσείς, και ξαναγράφει τις αναγνωρισμένες λέξεις ως αόρατα αντικείμενα κειμένου τοποθετημένα πάνω από τις λέξεις στη σάρωση. Η αρχική εικόνα σελίδας ποτέ δεν αποκωδικοποιείται, ξανακωδικοποιείται ή αντικαθίσταται, οπότε το οπτικό αποτέλεσμα είναι byte προς byte η σελίδα με την οποία ξεκινήσατε
Η μηχανή αναγνώρισης σκόπιμα δεν αποτελεί μέρος της βιβλιοθήκης. Το PDFium εκθέτει απόδοση σελίδας, αντιστοίχιση συντεταγμένων, φόρτωση γραμματοσειρών, δημιουργία αντικειμένων κειμένου και αόρατες λειτουργίες απόδοσης, αλλά δεν περιέχει μηχανή OCR, και η προσποίηση του αντιθέτου θα σήμαινε ενσωμάτωση του προϊόντος αναγνώρισης κάποιου άλλου μέσα σε ένα εξάρτημα PDF. Αντ' αυτού, η αναγνώριση ζει πίσω από τη διεπαφή IPdfOcrProvider: η βιβλιοθήκη περνά pixel BGRA σταθερής διάταξης με προέλευση από πάνω, και ο πάροχος επιστρέφει κείμενο Unicode, τιμές εμπιστοσύνης και τετράπλευρα λέξεων
Τι ακριβώς είναι ένα αναζητήσιμο επίπεδο κειμένου;
Ένα σαρωμένο PDF είναι μια εικόνα ενός εγγράφου. Το περιεχόμενο της σελίδας είναι μία μεγάλη εικόνα, και δεν υπάρχει τίποτα να επιλέξετε, να αναζητήσετε, να αντιγράψετε ή να ευρετηριάσετε. Ένα αναζητήσιμο επίπεδο κειμένου προσθέτει πραγματικά αντικείμενα κειμένου πάνω από εκείνη την εικόνα με τη λειτουργία απόδοσης ρυθμισμένη σε αόρατη, ώστε τα προγράμματα προβολής να μη σχεδιάζουν τίποτα ενώ η επιλογή, η αναζήτηση και η εξαγωγή βρίσκουν τις λέξεις ακριβώς εκεί όπου εμφανίζονται
Η τοποθέτηση είναι όλο το παιχνίδι. Αν το αόρατο κείμενο κάθεται λίγα σημεία εκτός, οι επισημάνσεις επιλογής προσγειώνονται δίπλα στις λέξεις αντί πάνω τους, και η αντιγραφή μιας παραγράφου παράγει κείμενο με λάθος σειρά. Γι' αυτό η γεωμετρία πρέπει να προέρχεται από τους ίδιους μετασχηματισμούς που χρησιμοποιεί το PDFium για να αποδώσει τη σελίδα και όχι από αναλογική εικασία
Υλοποίηση του παρόχου
Το συμβόλαιο παρόχου είναι μία μέθοδος. Λαμβάνει μια εγγραφή εικόνας σελίδας που φέρει διαστάσεις, βήμα, DPI, μορφή pixel και τα ίδια τα byte pixel, συν ένα διακριτικό ακύρωσης, και επιστρέφει λέξεις ή μήνυμα σφάλματος:
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 κρατά γραμμές BGRA με προέλευση από πάνω, μήκους
// Image.Stride byte. Παραδώστε τις στη μηχανή σας, έπειτα γεμίστε
// μία καταχώριση ανά αναγνωρισμένη λέξη
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;
Τετράπλευρα αντί για ορθογώνια, επειδή μια σάρωση σπάνια είναι τετράγωνη ως προς τη σελίδα. Μια λέξη σε μια ελαφρώς περιστραμμένη σελίδα καταλαμβάνει παραλληλόγραμμο, και το TPdfOcrQuad φέρει τέσσερα σημεία γωνιών ώστε λοξές και περιστραμμένες λέξεις να διατηρούν ακριβή περιοχή επιλογής. Μηχανές που αναφέρουν μόνο πλαίσια ευθυγραμμισμένα με τους άξονες μπορούν να χρησιμοποιήσουν την FromRectangle, η οποία χτίζει το εκφυλισμένο τετράπλευρο
Γιατί οι θέσεις λέξεων δεν μπορούν να κλιμακωθούν αναλογικά;
Είναι δελεαστικό να μετατρέψετε μια συντεταγμένη pixel σε συντεταγμένη σελίδας διαιρώντας με το πλάτος απόδοσης και πολλαπλασιάζοντας με το πλάτος σελίδας. Αυτό λειτουργεί μόνο για σελίδες χωρίς περιστροφή, με CropBox ταυτόσημο με το MediaBox, και αρχή στο μηδέν, και αρκετά σαρωμένα έγγραφα αποτυγχάνουν σε τουλάχιστον μία από αυτές τις συνθήκες
Το PDFium Component αντιστοιχίζει καθεμιά από τις τέσσερις γωνίες τετραπλεύρου ξεχωριστά μέσω της FPDF_DeviceToPage, της ίδιας αντιστοίχισης που χρησιμοποίησε ο αποδότης για να παράγει τα pixel, οπότε καταχωρίσεις /Rotate και μετατοπισμένα crop box χειρίζονται εκ κατασκευής. Ο συγγενικός πίνακας για το αντικείμενο κειμένου έπειτα χτίζεται από τρία από τα αντιστοιχισμένα σημεία, τις γωνίες κάτω-αριστερά, κάτω-δεξιά και πάνω-αριστερά, που είναι ακριβώς αρκετό για να εκφράσει θέση, κλίμακα, περιστροφή και λόξωση
Το ίδιο το αντικείμενο κειμένου δημιουργείται σε μοναδιαίο μέγεθος γραμματοσειράς ώστε τα πραγματικά όριά του να μπορούν να μετρηθούν, και τα μετρημένα όρια αντικειμένου έπειτα αντιστοιχίζονται στο τετράπλευρο-στόχο. Η διαστασιολόγηση με εικαστικό μέγεθος στιγμής και η ελπίδα ότι θα ταιριάξει με τη σαρωμένη λέξη θα παρέκκλινε με κάθε αντικατάσταση γραμματοσειράς· η προηγούμενη μέτρηση κάνει την προσαρμογή ανεξάρτητη από το ποια γραμματοσειρά χρησιμοποιεί το επίπεδο
Εκτέλεσή του σε ένα έγγραφο
Η εγγραφή επιλογών ελέγχει την ανάλυση, το φιλτράρισμα και κάθε όριο. Το φιλτράρισμα εμπιστοσύνης έχει μεγαλύτερη σημασία απ' όσο φαίνεται: λέξεις-σκουπίδια σε χαμηλή εμπιστοσύνη μολύνουν τα αποτελέσματα αναζήτησης μόνιμα, και σε αντίθεση με μια λανθασμένη απόδοση, κανείς δεν το προσέχει μέχρι μια αναζήτηση να επιστρέψει ασυναρτησίες:
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; // ανάλυση αναγνώρισης
Options.MinConfidence := 0.60; // απόρριψη αβέβαιων λέξεων
Options.SkipPagesWithText := True; // αφήστε ανέπαφες τις εξ αρχής ψηφιακές σελίδες
Options.ContinueOnError := True; // μία κακή σελίδα δεν πρέπει να σταματήσει την εργασία
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 αξίζει έμφαση σε μεικτά αρχεία. Ένα PDF που ήδη φέρει πραγματικό κείμενο, είτε εξ αρχής ψηφιακό είτε προηγουμένως επεξεργασμένο, λαμβάνει δεύτερο επίπεδο κειμένου αν εκτελέσετε OCR πάνω του τυφλά, και το διπλότυπο κάνει την εξαγωγή να επιστρέφει κάθε λέξη δύο φορές. Η κατάσταση ανά σελίδα popsSkippedExistingText σας λέει ακριβώς ποιες σελίδες αφέθηκαν ανέπαφες
Όρια, ακύρωση και περιορισμός αποτυχίας
Κάθε ποσότητα που ένα εχθρικό ή απλώς τεράστιο έγγραφο μπορεί να διογκώσει έχει ανώτατο όριο: pixel ανά σελίδα και συνολικά, λέξεις ανά σελίδα και συνολικά, και χαρακτήρες ανά λέξη. Όλα ελέγχονται πριν γραφεί η σελίδα, όχι μετά, και η εκτίμηση pixel υπολογίζεται από τις διαστάσεις σελίδας και τα DPI πριν εκχωρηθεί οποιοδήποτε bitmap. Η αύξηση των DPI από 150 σε 300 τετραπλασιάζει τη μνήμη ανά σελίδα, οπότε το ανώτατο όριο ανά σελίδα είναι η παράμετρος που πρέπει να ρυθμιστεί πρώτη όταν μια παρτίδα εργασιών αρχίζει να αποτυγχάνει σε μεγάλες μορφές
Το διακριτικό ακύρωσης περνά μέσα από ολόκληρη τη διαδρομή: προοδευτική απόδοση, την κλήση παρόχου και τον βρόχο εισαγωγής ανά λέξη. Αυτό σημαίνει ότι ένας χρήστης που ακυρώνει κατά τη διάρκεια της αναγνώρισης ενός αρχείου 400 σελίδων σταματά μέσα σε μία σελίδα αντί στο τέλος του εγγράφου, και το ίδιο μοτίβο διακριτικού που χρησιμοποιείται αλλού στο εξάρτημα, περιγράφεται στην ακυρώσιμη προοδευτική απόδοση, εφαρμόζεται εδώ αμετάβλητο
Ο περιορισμός αποτυχίας είναι ανά σελίδα. Η βιβλιοθήκη συλλέγει τις λαβές αντικειμένων που εισήγαγε σε μια σελίδα και καλεί την FPDFPage_GenerateContent μία φορά, αφού τοποθετηθούν όλες οι λέξεις. Αν οτιδήποτε αποτύχει στη μέση, είτε σφάλμα παρόχου είτε πρόβλημα γραμματοσειράς, τα αντικείμενα που εισήχθησαν σε εκείνη τη σελίδα αφαιρούνται με αντίστροφη σειρά και το περιεχόμενο σελίδας αναδημιουργείται, ώστε μια αποτυχημένη σελίδα να επιστρέφει στην αρχική της κατάσταση αντί να κρατά μισό επίπεδο κειμένου. Ο βρόχος εγγράφου έπειτα συνεχίζει ή σταματά ανάλογα με το ContinueOnError, και η ενεργή σελίδα πάντα αποκαθίσταται
Επαλήθευση ότι η εικόνα πράγματι έμεινε ανέγγιχτη
Ο ισχυρότερος διαθέσιμος έλεγχος είναι επίσης ο απλούστερος: αποδώστε τη σελίδα πριν και μετά την εφαρμογή του επιπέδου στο ίδιο μέγεθος και συγκρίνετε τα bitmap. Θα πρέπει να είναι πανομοιότυπα byte προς byte, επειδή το αόρατο κείμενο δεν σχεδιάζει τίποτα και η ροή εικόνας ποτέ δεν αποκωδικοποιήθηκε. Οποιαδήποτε διαφορά σημαίνει ότι κάτι άλλο εκτός από το επίπεδο κειμένου άλλαξε τη σελίδα
Μετά από αυτό, επαληθεύστε την πλευρά κειμένου εξάγοντας από το επεξεργασμένο αρχείο και επιβεβαιώνοντας ότι οι θέσεις λέξεων προσγειώνονται στη σάρωση. Η διαδρομή εξαγωγής είναι η ίδια που περιγράφεται στην εξαγωγή κειμένου από έγγραφα PDF, και για γρήγορο οπτικό έλεγχο ευθυγράμμισης, η απόδοση σελίδων σε εικόνες όπως στη μετατροπή σελίδων PDF σε JPEG σας επιτρέπει να επικαλύψετε πλαίσια λέξεων πάνω στη σάρωση
Η στρωμάτωση OCR, η απόδοση, η εξαγωγή και η επεξεργασία εκτελούνται όλα πάνω στο ίδιο αντικείμενο εγγράφου σε Delphi, C++Builder και Lazarus· η πλήρης επιφάνεια API περιγράφεται στη σελίδα του PDFium Component για Delphi