Estou tentando executar o OCR em pdfs. Existem 2 etapas no código:
- Converter PDF em arquivos TIFF
- 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:
- Faça o download do currículo do Linkedin CEO aqui - https://gofile.io/?c=TtA7XQ
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 ...
BufferedImage
após a renderização?Respostas:
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:
Guardá-lo como
png
mais do quetiff
para ver se isso faz a diferença.fonte
O problema foi resolvido na discussão no PDFBOX-4739 :
ImageIOUtils.writeImage()
vez deImageIO.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.doOCR()
método que usa um BufferedImage como parâmetro, portanto, não é necessário salvar nada.fonte