带有巨大 BufferdImage 的小型 pdf 文件结果

Posted

技术标签:

【中文标题】带有巨大 BufferdImage 的小型 pdf 文件结果【英文标题】:Small pdf files results with huge BufferdImage 【发布时间】:2020-04-24 20:56:06 【问题描述】:

我正在尝试对 pdf 执行 OCR。 代码中有2个步骤:

    将 pdf 转换为 tiff 文件 将 tiff 转换为文本

第一步我使用ghost4j,第二步使用tess4j。 一切都很好,直到我开始多线程运行它,然后发生了奇怪的异常。 我在这里读到:https://sourceforge.net/p/tess4j/discussion/1202293/thread/44cc65c5/ 说ghost4j不适合多线程,所以我把第一步改成使用PDFBox。

所以现在我的代码如下所示:

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

我正在尝试使用 800 kb pdf 文件运行此代码,并且在检查内存后

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

它增加到超过 500 MB!如果我将此 BufferedImage 保存到磁盘,则输出大小为 1 MB...所以当尝试使用 8 个线程运行此代码时,我也会遇到 java 堆大小异常...

我在这里缺少什么?为什么一个 1 MB 的文件会产生一个 500 MB 的图像文件?我尝试使用 DPI 并降低质量,但文件仍然很大...... 是否有任何其他库可以将 pdf 呈现为 tiff,并且我可以执行 10 个线程而不会出现内存问题?

重现步骤:

    从这里下载 Linkedin CEO 简历文件 - https://gofile.io/?c=TtA7XQ

    我没有使用此代码:

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

输出是:

应用程序已启动.......已用内存:42 MB

之前....已用内存:107 MB

BufferedImage....已用内存:171 MB

在这个例子中,它不是 500 MB,而是 70 kb 的 pdf,当我尝试只渲染一页时,内存增加了大约 70 MB...这不成比例...

【问题讨论】:

请分享PDF文件。也许如果有一个巨大的图像尺寸输出尺寸? 渲染后可以查看BufferedImage的尺寸吗? 请注意,高内存消耗不一定表示内存泄漏。也许页面包含需要大量内存来解码的位图对象? PDFBox 在以较小尺寸渲染时是否对图像进行二次采样?如果没有,以小尺寸渲染可能无济于事...... Pdfbox 默认不进行二次采样,但可以在 PDFRenderer 中启用。 @NicolasFilotto 在 PDFRenderer 中激活子采样。但是二次采样对于 OCR 来说可能不是一个好主意。 【参考方案1】:

每像素一个字节的 3300 X 2550 尺寸将提供大约 70_000_000 个字节。 使用 150 dpi 时,尺寸为 22 英寸 x 17 英寸,太大了。

所以将图片缩小到大约。 17 MB 内存:

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

将其保存为png 而不是tiff,看看这是否会有所不同。

【讨论】:

OP想做OCR,所以300dpi是个不错的选择。但是您在图像类型上是对的,我在 PDFBOX-4739 中提出了相同的建议。 (还发现图片是未压缩保存的) @TilmanHausherr 我部分成功地使用 150 dpi 进行 OCR,但实际上 300 dpi 是常态。使用上面的 ByteArrayOutputStream 可能也很昂贵,【参考方案2】:

问题已在PDFBOX-4739的讨论中解决:

使用ImageIOUtils.writeImage() 而不是ImageIO.write()(您将需要工具子项目),因为 ImageIO 不压缩 TIFF 文件。 ImageIOUtils 尝试使用 LZW 或 CCITT,具体取决于源图像。 根本不保存图像:有一个doOCR() 方法以BufferedImage 作为参数,所以根本不需要保存。

【讨论】:

以上是关于带有巨大 BufferdImage 的小型 pdf 文件结果的主要内容,如果未能解决你的问题,请参考以下文章

多个小型 spritesheet 或一个巨大的 spritesheet 以提高性能? ---(java游戏开发)[关闭]

使用Flying Saucer在PDF标题后的巨大空白区域

在单机模式下使用 Zend_PDF,如何设置土地类的包含

使用带有小型表格的 UITableViewController?

Ghostscript 正在生成巨大的文件

带有 Zend 框架的巨大 mysql 表