Resultados de pequenos arquivos pdf com enorme BufferdImage

8

Estou tentando executar o OCR em pdfs. Existem 2 etapas no código:

  1. Converter PDF em arquivos TIFF
  2. Converter tiff em texto

Usei o ghost4j no primeiro passo e depois o tess4j no segundo. tudo funcionou muito bem, até que eu comecei a executá-lo multiencadeado e, em seguida, estranhas exceções ocorreram. Eu li aqui: https://sourceforge.net/p/tess4j/discussion/1202293/thread/44cc65c5/ que o ghost4j não é adequado para multi-threads, então mudei o primeiro passo para trabalhar com o PDFBox.

Então agora meu código se parece com:

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

Estou tentando executar esse código com um arquivo pdf de 800 kb e, ao verificar a memória após o

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

subir para mais de 500 MB !! Se estou salvando este BufferedImage em disco, a saída tem tamanho de 1 MB ... então, ao tentar executar esse código com 8 threads, também estou recebendo a exceção do tamanho do heap java ...

O que estou perdendo aqui? por que um arquivo de 1 MB resulta em um arquivo de imagem de 500 MB? Tentei brincar com o DPI e reduzir a qualidade, mas o arquivo ainda é muito grande ... Existe alguma outra biblioteca que possa renderizar pdf em tiff e que eu possa executar 10 threads sem problemas de memória?

Passos para reproduzir:

  1. Faça o download do currículo do Linkedin CEO aqui - https://gofile.io/?c=TtA7XQ
  2. Eu usei 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");
    }

e a saída é:

App iniciado ....... Memória usada: 42 MB

Antes .... Memória usada: 107 MB

BufferedImage .... Memória usada: 171 MB

Neste exemplo, não são 500 MB, mas um pdf de 70 kb. Quando tento renderizar apenas uma página, a memória aumenta em cerca de 70 MB ... não é proporcional ...

Lior Y
fonte
2
Por favor, compartilhe o arquivo PDF. Talvez se tenha um tamanho de saída de dimensão de imagem enorme?
Tilman Hausherr
Você pode verificar as dimensões do seu BufferedImageapós a renderização?
TA
3
Observe que o alto consumo de memória não indica necessariamente um vazamento de memória. Talvez a página contenha um objeto de bitmap que precise de muita memória para decodificar? O PDFBox subamostra imagens ao renderizar em tamanhos menores? Se não, renderizar em um tamanho pequeno pode não ajudar ...
haraldK 7/01
1
Pdfbox não subamostra por padrão, mas pode ser ativado no PDFRenderer.
Tilman Hausherr
1
O @NicolasFilotto ativa a subamostragem no PDFRenderer. Mas a subamostragem provavelmente não é uma boa ideia para o OCR.
Tilman Hausherr 13/01

Respostas:

0

Uma dimensão 3300 x 2550 de um byte por pixel forneceria cerca de 70_000_000 bytes. Com 150 dpi, um teria 22 polegadas por 17 polegadas, muito grande.

Então reduza a imagem para aprox. 17 MB de memória:

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

Guardá-lo como pngmais do que tiffpara ver se isso faz a diferença.

Joop Eggen
fonte
O OP quer fazer OCR, então 300 dpi é uma boa escolha. Mas você está certo sobre o tipo de imagem. Fiz a mesma sugestão no PDFBOX-4739. (Também saiu que as imagens são salvas descompactadas)
Tilman Hausherr
@TilmanHausherr Em parte, faço OCR com 150 dpi com êxito, mas na verdade 300 dpi é a norma. Usando um ByteArrayOutputStream como acima pode ser caro também,
Joop Eggen 13/01
0

O problema foi resolvido na discussão no PDFBOX-4739 :

  • use em ImageIOUtils.writeImage()vez de ImageIO.write()(você precisará do subprojeto de ferramentas), porque o ImageIO não compacta arquivos TIFF. O ImageIOUtils tenta usar LZW ou CCITT, dependendo da imagem de origem.
  • não salve a imagem: existe um doOCR()método que usa um BufferedImage como parâmetro, portanto, não é necessário salvar nada.
Tilman Hausherr
fonte