小さなpdfファイルで巨大なBufferdImageが生成される


8

PDFでOCRを実行しようとしています。コードには2つのステップがあります。

  1. PDFをTIFFファイルに変換
  2. TIFFをテキストに変換する

最初のステップでghost4jを使用し、次に2番目のステップで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スレッドを実行できますか?

再現する手順:

  1. Linkedin CEOの履歴書ファイルをこちらからダウンロードしてください-https://gofile.io/?c= TtA7XQ
  2. 私はこのコードを使用しました:

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

そして出力は:

アプリ起動.......使用メモリ:42MB

以前....使用メモリ:107 MB

BufferedImage ....使用メモリ:171 MB

この例では、500 MBではなく、70 kbのPDFです。1ページだけをレンダリングしようとすると、メモリが約70 MB増加します...比例しません...


2
PDFファイルを共有してください。たぶん、巨大な画像次元の出力サイズがあるのですか?
Tilman Hausherr

BufferedImageレンダリング後のサイズを確認できますか?
TA

3
メモリ消費量が多いからといって、必ずしもメモリリークが発生しているとは限りません。おそらく、ページには、デコードするために多くのメモリを必要とするビットマップオブジェクトが含まれていますか?より小さいサイズでレンダリングする場合、PDFBoxは画像をサブサンプルしますか?そうでない場合、小さなサイズでのレンダリングは役に立たないかもしれません...
haraldK

1
Pdfboxはデフォルトではサブサンプルしませんが、PDFRendererで有効にすることができます。
Tilman Hausherr

1
@NicolasFilottoは、PDFRendererのサブサンプリングをアクティブにします。しかし、サブサンプリングはおそらくOCRにとって良い考えではありません。
Tilman Hausherr

回答:


0

ピクセルあたり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で同じ提案をしました。(画像が非圧縮で保存されていることも判明しました)
Tilman Hausherr

@TilmanHausherr 150 dpiでOCRを部分的に正常に実行しましたが、実際には300 dpiが標準です。上記のByteArrayOutputStreamの使用もコストがかかる可能性があります
Joop Eggen

0

この問題はPDFBOX-4739の議論で解決されました:

  • 使用ImageIOUtils.writeImage()の代わりに、ImageIO.write()ImageIOには圧縮TIFFファイルをしませんので、(あなたはツールのサブプロジェクトが必要になります)。ImageIOUtilsは、ソースイメージに応じて、LZWまたはCCITTを使用しようとします。
  • 画像をまったく保存しないでdoOCR()ください。BufferedImageをパラメーターとして受け取るメソッドがあるため、まったく保存する必要はありません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.