Pequeños resultados de archivos pdf con gran BufferdImage

8

Estoy tratando de realizar OCR en archivos PDF. Hay 2 pasos en el código:

  1. Convertir pdf a archivos tiff
  2. Convertir tiff a texto

Usé ghost4j para el primer paso, y luego tess4j para el segundo. todo funcionó muy bien, hasta que comencé a ejecutarlo con varios subprocesos, y luego ocurrieron extrañas excepciones. Leí aquí: https://sourceforge.net/p/tess4j/discussion/1202293/thread/44cc65c5/ que ghost4j no es adecuado para multihilo, así que cambié el primer paso para trabajar con PDFBox.

Entonces ahora mi código se ve así:

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();

Estoy tratando de ejecutar este código con un archivo pdf de 800 kb, y cuando verifico la memoria después de

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

¡Se eleva a más de 500 MB! si estoy guardando esta imagen Buffered en el disco, la salida es de 1 MB de tamaño ... así que cuando intento ejecutar este código con 8 hilos, también obtengo la excepción de tamaño de almacenamiento dinámico de Java ...

¿Que me estoy perdiendo aqui? ¿Por qué un archivo de 1 MB da como resultado un archivo de imagen de 500 MB? Traté de jugar con el DPI y reducir la calidad, pero el archivo sigue siendo muy grande ... ¿Hay alguna otra biblioteca que pueda procesar PDF en TIFF y que pueda ejecutar 10 hilos sin problemas de memoria?

Pasos para reproducir:

  1. Descargue el archivo de currículum de Linkedin CEO desde aquí: https://gofile.io/?c=TtA7XQ
  2. Yo que usé este código:

    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");
    }

y la salida es:

Aplicación iniciada ....... Memoria usada: 42 MB

Antes .... Memoria usada: 107 MB

BufferedImage .... Memoria usada: 171 MB

En este ejemplo, no son 500 MB, sino un pdf de 70 kb, cuando intento renderizar solo una página, la memoria aumenta en aproximadamente 70 MB ... no es proporcional ...

Lior Y
fuente
2
Por favor comparta el archivo PDF. Tal vez si tiene un tamaño de salida de dimensión de imagen enorme?
Tilman Hausherr
¿Puedes verificar las dimensiones de tu BufferedImagedespués de renderizar?
TA
3
Tenga en cuenta que el alto consumo de memoria no necesariamente indica una pérdida de memoria. ¿Quizás la página contiene un objeto de mapa de bits que necesita mucha memoria para decodificar? ¿PDFBox submuestra imágenes al renderizar en tamaños más pequeños? Si no, renderizar en un tamaño pequeño puede no ayudar ...
haraldK
1
Pdfbox no submuestra por defecto, pero se puede habilitar en PDFRenderer.
Tilman Hausherr
1
@NicolasFilotpara activar el submuestreo en PDFRenderer. Pero el submuestreo probablemente no sea una buena idea para OCR.
Tilman Hausherr

Respuestas:

0

Una dimensión 3300 X 2550 de un byte por píxel entregaría alrededor de 70_000_000 bytes. Con 150 ppp, uno tendría 22 pulgadas por 17 pulgadas, demasiado grande.

Así que reduzca la imagen a aprox. 17 MB de memoria:

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

Guárdelo en pnglugar de tiffver si eso marca la diferencia.

Joop Eggen
fuente
El OP quiere hacer OCR, por lo que 300 ppp es una buena opción. Pero tienes razón en el tipo de imagen, he hecho la misma sugerencia en PDFBOX-4739. (También salió que las imágenes se guardan sin comprimir)
Tilman Hausherr
@TilmanHausherr Realizo OCR en parte con 150 ppp con éxito, pero de hecho 300 ppp es la norma. Usar un ByteArrayOutputStream como el anterior también puede ser costoso,
Joop Eggen
0

El problema se resolvió en la discusión en PDFBOX-4739 :

  • use en ImageIOUtils.writeImage()lugar de ImageIO.write()(necesitará el subproyecto de herramientas), porque ImageIO no comprime los archivos TIFF. ImageIOUtils intenta usar LZW o CCITT, dependiendo de la imagen de origen.
  • no guarde la imagen en absoluto: hay un doOCR()método que toma una imagen Buffered como parámetro, por lo que no es necesario guardarla.
Tilman Hausherr
fuente