Kleine PDF-Dateien ergeben ein riesiges BufferdImage

8

Ich versuche, OCR für PDFs durchzuführen. Der Code besteht aus 2 Schritten:

  1. Konvertieren Sie PDF in TIFF-Dateien
  2. Konvertieren Sie tiff in Text

Ich habe Ghost4j für den ersten Schritt und dann Tess4j für den zweiten Schritt verwendet. Alles funktionierte großartig, bis ich anfing, es mit mehreren Threads auszuführen, und dann traten seltsame Ausnahmen auf. Ich habe hier gelesen: https://sourceforge.net/p/tess4j/discussion/1202293/thread/44cc65c5/, dass ghost4j nicht für Multithreading geeignet ist, daher habe ich den ersten Schritt geändert, um mit PDFBox zu arbeiten.

Jetzt sieht mein Code so aus:

PDDocument doc = PDDocument.load(this.bytes);
PDFRenderer pdfRenderer = new PDFRenderer(doc);
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "tiff", os);
os.flush();
os.close();
bufferedImage.flush();

Ich versuche, diesen Code mit einer 800-KB-PDF-Datei auszuführen und beim Überprüfen des Speichers nach dem

BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);

es erhöht sich auf mehr als 500 MB !! Wenn ich dieses BufferedImage auf der Festplatte speichere, hat die Ausgabe eine Größe von 1 MB. Wenn ich also versuche, diesen Code mit 8 Threads auszuführen, erhalte ich auch die Ausnahme für die Größe des Java-Heapspeichers ...

Was fehlt mir hier? Warum führt eine 1-MB-Datei zu einer 500-MB-Bilddatei? Ich habe versucht, mit der DPI zu spielen und die Qualität zu verringern, aber die Datei ist immer noch sehr groß ... Gibt es eine andere Bibliothek, die PDF in TIFF rendern kann und die ich 10 Threads ohne Speicherprobleme ausführen könnte?

Schritte zum Reproduzieren:

  1. Laden Sie die Lebenslaufdatei des Linkedin-CEO von hier herunter - https://gofile.io/?c=TtA7XQ
  2. Ich habe dann diesen Code verwendet:

    private static void test() throws IOException {
        printUsedMemory("App started...");
        File file = new File("linkedinceoresume.pdf");
        try (PDDocument doc = PDDocument.load(file)) {
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            printUsedMemory("Before");
            for (int page = 0; page < 1; ++page) {
                BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(page, 76, ImageType.GRAY);
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                ImageIO.write(bufferedImage, "tiff", os);
                os.flush();
                os.close();
                bufferedImage.flush();
            }
        } finally {
            printUsedMemory("BufferedImage");
        }
    }
    
    private static void printUsedMemory(String text) {
        long freeMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        long mb = freeMemory / 1000000;
        System.out.println(text + "....Used memory: " + mb + " MB");
    }

und die Ausgabe ist:

App gestartet ....... Verwendeter Speicher: 42 MB

Vorher .... Verwendeter Speicher: 107 MB

BufferedImage .... Verwendeter Speicher: 171 MB

In diesem Beispiel sind es nicht 500 MB, sondern ein PDF von 70 KB. Wenn ich versuche, nur eine Seite zu rendern, erhöht sich der Speicher um ca. 70 MB ... es ist nicht proportional ...

Lior Y.
quelle
2
Bitte teilen Sie die PDF-Datei. Vielleicht, wenn eine große Bildgröße Ausgabegröße hat?
Tilman Hausherr
Können Sie die Abmessungen BufferedImagenach dem Rendern überprüfen ?
TA
3
Beachten Sie, dass ein hoher Speicherverbrauch nicht unbedingt auf einen Speicherverlust hinweist. Vielleicht enthält die Seite ein Bitmap-Objekt, das viel Speicher zum Dekodieren benötigt? Untersucht PDFBox Bilder beim Rendern in kleineren Größen? Wenn nicht, hilft das Rendern in kleiner Größe möglicherweise nicht ...
haraldK
1
Pdfbox wird standardmäßig nicht unterabgetastet, kann jedoch in PDFRenderer aktiviert werden.
Tilman Hausherr
1
@NicolasFilotto aktiviert die Unterabtastung in PDFRenderer. Aber Subsampling ist für OCR wahrscheinlich keine gute Idee.
Tilman Hausherr

Antworten:

0

Eine Dimension 3300 x 2550 von einem Byte pro Pixel würde ungefähr 70_000_000 Bytes liefern. Mit 150 dpi hätte man 22 Zoll mal 17 Zoll, viel zu groß.

Skalieren Sie das Bild also auf ca. 17 MB Speicher:

    float scale = 0.5f;
    BufferedImage bufferedImage = pdfRenderer.renderImage(page, scale, ImageType.BINARY);

Speichern Sie es als, pngum tiffzu sehen, ob dies einen Unterschied macht.

Joop Eggen
quelle
Das OP möchte OCR durchführen, daher sind 300 dpi eine gute Wahl. Aber Sie haben Recht mit dem Bildtyp. Ich habe den gleichen Vorschlag in PDFBOX-4739 gemacht. (Es stellte sich auch heraus, dass die Bilder unkomprimiert gespeichert werden)
Tilman Hausherr
@ TilmanHausherr Ich mache teilweise OCR mit 150 dpi erfolgreich, aber tatsächlich sind 300 dpi die Norm. Die Verwendung eines ByteArrayOutputStream wie oben kann ebenfalls kostspielig sein
Joop Eggen
0

Das Problem wurde in der Diskussion in PDFBOX-4739 gelöst :

  • Verwenden Sie ImageIOUtils.writeImage()statt ImageIO.write()(Sie benötigen das Teilprojekt tools), da ImageIO keine TIFF-Dateien komprimiert. ImageIOUtils versucht, je nach Quellbild LZW oder CCITT zu verwenden.
  • Speichern Sie das Bild überhaupt nicht: Es gibt eine doOCR()Methode, die ein BufferedImage als Parameter verwendet, sodass Sie es überhaupt nicht speichern müssen.
Tilman Hausherr
quelle