Amazon Web Services ブログ

Amazon Textract を使用して、スキャンしたドキュメントから検索可能な PDF を自動的に生成する



Amazon Textract は、ほぼすべてのドキュメントからテキストとデータを簡単に抽出できる機械学習サービスです。Textract は、シンプルな光学文字認識 (OCR) という枠を超えて、フォーム内のフィールドのコンテンツや、表に保存された情報も識別します。これにより、Amazon Textract を使用して、手動での工数やカスタムコードを必要とすることなく、実質上どのようなタイプのドキュメントでも瞬時に「読み取り」、テキストとデータを正確に抽出することが可能になります。

ブログ記事「Amazon Textract を使用したドキュメントからのテキストと構造化データの自動抽出」は、Amazon Textract を使用して、機械学習 (ML) の経験なしでスキャンしたドキュメントからテキストとデータを自動的に抽出する方法を示しています。記事で取り上げられているユースケースの 1 つは、検索と検出です。Amazon Textract を使用してドキュメントからテキストと構造化データを抽出し、Amazon ES を使用してスマートインデックスを作成することにより、数百万のドキュメントを検索できます。

この記事では、Amazon Textract を使用してスキャンしたドキュメントからテキストを抽出し、検索可能な PDF ドキュメントを生成する方法を示します。このソリューションを使用すると、関連するドキュメントをダウンロードしたり、オフラインで保存されているドキュメント内を検索したり、テキストを選択してコピーしたりできます。

スキャンしたドキュメントから Amazon Textract を使用して生成された検索可能な PDF ドキュメントの例を見ることができます。スキャンしたドキュメント内の画像のテキストはロックされていますが、検索可能な PDF ドキュメント内のテキストを選択、コピー、検索することができます。

検索可能な PDF を生成するには、Amazon Textract を使用してドキュメントからテキストを抽出し、抽出したテキストを PDF ドキュメントの画像にレイヤーとして追加します。Amazon Textract は、テキスト入力ドキュメントを検出および分析し、ページ、単語、行、フォームデータ (キーと値のペア)、テーブル、選択要素などの検出されたアイテムに関する情報を返します。また、バウンディングボックス情報も提供します。これは、ドキュメントページで認識されたアイテムの位置を軸に沿って粗く表現したものです。検出されたテキストとそのバウンディングボックス情報を使用して、PDF ページにテキストを配置できます。

PDFDocument は、AWS サンプル GitHub リポジトリのサンプルライブラリであり、Amazon Textract を使用して検索可能な PDF ドキュメントを生成するために必要なロジックを提供します。 また、オープンソースの Java ライブラリ Apache PDFBox を使用して PDF ドキュメントを作成しますが、他のプログラミング言語でも同様の PDF 処理ライブラリを利用できます。

次のコード例は、サンプルライブラリを使用して、画像から検索可能な PDF ドキュメントを生成する方法を示しています。

...

//Amazon Textract を使用してテキストを抽出
List<TextLine> lines = extractText(imageBytes);

//画像とテキストを含む検索可能な PDF を生成
PDFDocument doc = new PDFDocument();
doc.addPage(image, imageType, lines);

//PDF をローカルディスクに保存
try(OutputStream outputStream = new FileOutputStream(outputDocumentName)) {
    doc.save(outputStream);
}

...

画像ドキュメントから検索可能な PDF を生成する

次のコードは、画像ドキュメントを取得し、対応する検索可能な PDF ドキュメントを生成する方法を示しています。Amazon Textract を使用してテキストを抽出し、テキストを画像のレイヤーとして追加して、検索可能な PDF を作成します。

public class DemoPdfFromLocalImage {

    public static void run(String documentName, String outputDocumentName) throws IOException {

        System.out.println("Generating searchable pdf from: " + documentName);

        ImageType imageType = ImageType.JPEG;
        if(documentName.toLowerCase().endsWith(".png"))
            imageType = ImageType.PNG;

        //画像バイトを取得
        ByteBuffer imageBytes = null;
        try(InputStream in = new FileInputStream(documentName)) {
            imageBytes = ByteBuffer.wrap(IOUtils.toByteArray(in));
        }

        //テキストを抽出
        List<TextLine> lines = extractText(imageBytes);

        //画像を取得
        BufferedImage image = getImage(documentName);

        //新しい PDF ドキュメントを作成
        PDFDocument pdfDocument = new PDFDocument();

        //テキストレイヤーと画像を含むページを PDF 文書に追加
        pdfDocument.addPage(image, imageType, lines);

        //PDF をローカルディスクに保存
        try(OutputStream outputStream = new FileOutputStream(outputDocumentName)) {
            pdfDocument.save(outputStream);
            pdfDocument.close();
        }

        System.out.println("Generated searchable pdf: " + outputDocumentName);
    }
    
    private static BufferedImage getImage(String documentName) throws IOException {

        BufferedImage image = null;

        try(InputStream in = new FileInputStream(documentName)) {
            image = ImageIO.read(in);
        }

        return image;
    }

    private static List<TextLine> extractText(ByteBuffer imageBytes) {

        AmazonTextract client = AmazonTextractClientBuilder.defaultClient();

        DetectDocumentTextRequest request = new DetectDocumentTextRequest()
                .withDocument(new Document()
                        .withBytes(imageBytes));

        DetectDocumentTextResult result = client.detectDocumentText(request);

        List<TextLine> lines = new ArrayList<TextLine>();
        List<Block> blocks = result.getBlocks();
        BoundingBox boundingBox = null;
        for (Block block : blocks) {
            if ((block.getBlockType()).equals("LINE")) {
                boundingBox = block.getGeometry().getBoundingBox();
                lines.add(new TextLine(boundingBox.getLeft(),
                        boundingBox.getTop(),
                        boundingBox.getWidth(),
                        boundingBox.getHeight(),
                        block.getText()));
            }
        }

        return lines;
    }
}

PDF ドキュメントから検索可能な PDF を生成する

次のサンプルコードは、Amazon S3 バケットから入力 PDF ドキュメントを取得し、対応する検索可能な PDF ドキュメントを生成します。Amazon Textract を使用して PDF ドキュメントからテキストを抽出し、各ページの画像を含むレイヤーとしてテキストを追加して、検索可能な PDF を作成します。

public class DemoPdfFromS3Pdf {
    public static void run(String bucketName, String documentName, String outputDocumentName) throws IOException, InterruptedException {

        System.out.println("Generating searchable pdf from: " + bucketName + "/" + documentName);

        //Amazon Textract を使用してテキストを抽出
        List<ArrayList<TextLine>> linesInPages = extractText(bucketName, documentName);

        //Amazon S3 から入力 PDF ドキュメントを取得
        InputStream inputPdf = getPdfFromS3(bucketName, documentName);

        //新しい PDF ドキュメントを作成
        PDFDocument pdfDocument = new PDFDocument();

        //PDF ドキュメントの各ページにテキストレイヤーと画像を追加
        PDDocument inputDocument = PDDocument.load(inputPdf);
        PDFRenderer pdfRenderer = new PDFRenderer(inputDocument);
        BufferedImage image = null;
        for (int page = 0; page < inputDocument.getNumberOfPages(); ++page) {
            image = pdfRenderer.renderImageWithDPI(page, 300, org.apache.pdfbox.rendering.ImageType.RGB);

            pdfDocument.addPage(image, ImageType.JPEG, linesInPages.get(page));

            System.out.println("Processed page index: " + page);
        }

        //PDF をストリームに保存
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        pdfDocument.save(os);
        pdfDocument.close();
        inputDocument.close();

        //PDF を S3 にアップロード
        UploadToS3(bucketName, outputDocumentName, "application/pdf", os.toByteArray());

        System.out.println("Generated searchable pdf: " + bucketName + "/" + outputDocumentName);
    }

    private static List<ArrayList<TextLine>> extractText(String bucketName, String documentName) throws InterruptedException {

        AmazonTextract client = AmazonTextractClientBuilder.defaultClient();

        StartDocumentTextDetectionRequest req = new StartDocumentTextDetectionRequest()
                .withDocumentLocation(new DocumentLocation()
                        .withS3Object(new S3Object()
                                .withBucket(bucketName)
                                .withName(documentName)))
                .withJobTag("DetectingText");

        StartDocumentTextDetectionResult startDocumentTextDetectionResult = client.startDocumentTextDetection(req);
        String startJobId = startDocumentTextDetectionResult.getJobId();

        System.out.println("Text detection job started with Id: " + startJobId);

        GetDocumentTextDetectionRequest documentTextDetectionRequest = null;
        GetDocumentTextDetectionResult response = null;

        String jobStatus = "IN_PROGRESS";

        while (jobStatus.equals("IN_PROGRESS")) {
            System.out.println("Waiting for job to complete...");
            TimeUnit.SECONDS.sleep(10);
            documentTextDetectionRequest = new GetDocumentTextDetectionRequest()
                    .withJobId(startJobId)
                    .withMaxResults(1);

            response = client.getDocumentTextDetection(documentTextDetectionRequest);
            jobStatus = response.getJobStatus();
        }

        int maxResults = 1000;
        String paginationToken = null;
        Boolean finished = false;

        List<ArrayList<TextLine>> pages = new ArrayList<ArrayList<TextLine>>();
        ArrayList<TextLine> page = null;
        BoundingBox boundingBox = null;

        while (finished == false) {
            documentTextDetectionRequest = new GetDocumentTextDetectionRequest()
                    .withJobId(startJobId)
                    .withMaxResults(maxResults)
                    .withNextToken(paginationToken);
            response = client.getDocumentTextDetection(documentTextDetectionRequest);

            //ブロック情報を表示
            List<Block> blocks = response.getBlocks();
            for (Block block : blocks) {
                if (block.getBlockType().equals("PAGE")) {
                    page = new ArrayList<TextLine>();
                    pages.add(page);
                } else if (block.getBlockType().equals("LINE")) {
                    boundingBox = block.getGeometry().getBoundingBox();
                    page.add(new TextLine(boundingBox.getLeft(),
                            boundingBox.getTop(),
                            boundingBox.getWidth(),
                            boundingBox.getHeight(),
                            block.getText()));
                }
            }
            paginationToken = response.getNextToken();
            if (paginationToken == null)
                finished = true;
        }

        return pages;
    }

    private static InputStream getPdfFromS3(String bucketName, String documentName) throws IOException {

        AmazonS3 s3client = AmazonS3ClientBuilder.defaultClient();
        com.amazonaws.services.s3.model.S3Object fullObject = s3client.getObject(new GetObjectRequest(bucketName, documentName));
        InputStream in = fullObject.getObjectContent();
        return in;
    }

    private static void UploadToS3(String bucketName, String objectName, String contentType, byte[] bytes) {
        AmazonS3 s3client = AmazonS3ClientBuilder.defaultClient();
        ByteArrayInputStream baInputStream = new ByteArrayInputStream(bytes);
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(bytes.length);
        metadata.setContentType(contentType);
        PutObjectRequest putRequest = new PutObjectRequest(bucketName, objectName, baInputStream, metadata);
        s3client.putObject(putRequest);
    }
}

ローカルマシンでコードを実行する

ローカルマシンでコードを実行するには、次の手順を実行します。サンプルコードは、GitHub リポジトリから入手できます。

  1. AWS アカウントと AWS CLI を設定します。

詳細については、Amazon Textract の開始方法を参照してください。

  1. GitHub リポジトリから searchablepdf.zip をダウンロードして解凍します。
  2. まだインストールされていない場合は、Apache Maven をインストールします。
  3. プロジェクトディレクトリで、mvn package を実行します。
  4. java -cp target/searchable-pdf-1.0.jar Demo を実行します。

これにより、メインクラスとして「Demo」を使用して Java プロジェクトが実行されます。

デフォルトでは、ローカルドライブ上の画像から検索可能な PDF を作成する最初の例だけが有効になっています。他の例を実行するには、Demo クラスの関連する行のコメントを外します。

Lambda でコードを実行する

Lambda でコードを実行するには、次の手順を実行します。サンプルコードは、GitHub リポジトリから入手できます。

  1. GitHub リポジトリから searchablepdf.zip をダウンロードして解凍します。
  2. まだインストールされていない場合は、Apache Maven をインストールします。
  3. プロジェクトディレクトリで、mvn package を実行します。

このビルドは、必要な変換を行うために pom.xml の情報を使用して、project-dir/target/searchable-pdf1.0.jar に .jar を作成します。これは、すべての依存関係を含むスタンドアロンの .jar (.zip ファイル) です。これはデプロイパッケージであり、Lambda にアップロードして関数を作成できます。 詳細については、Java の AWS Lambda デプロイパッケージを参照してください。DemoLambda には、S3 イベントを読み取り、入力ドキュメントのタイプに基づいてアクションを実行するために必要なすべてのコードがあります。

  1. 前に作成した S3 バケットに対する読み取りおよび書き込みのアクセス許可を備えた Java 8 および IAM ロールを持つ Lambda を作成します。
  2. Amazon Textract を呼び出すアクセス許可も持つように IAM ロールを設定します。
  3. ハンドラーを、DemoLambda::handleRequest に設定します。
  4. タイムアウトを 5 分に延ばします。
  5. 前に作成した .jar ファイルをアップロードします。
  6. S3 バケットを作成します。
  7. S3 バケットで、documents というラベルのフォルダを作成します。
  8. オブジェクトがドキュメントフォルダにアップロードされたときに Lambda 関数が実行されるように、Lambda 関数にトリガーを追加します。

必ずドキュメントフォルダに対してトリガーを設定してください。バケット全体に対してトリガーを追加すると、出力 PDF ドキュメントが生成されるたびに関数もトリガーされます。

  1. 画像 (.jpeg または .png) または PDF ドキュメントを S3 バケットのドキュメントフォルダにアップロードします。

数秒で、S3 バケットに検索可能な PDF ドキュメントが表示されるはずです。

これらの手順は、S3 と Lambda の簡単な統合を示しています。大規模なドキュメント処理については、次の GitHub リポジトリのリファレンスアーキテクチャを参照してください。

まとめ

この記事では、Amazon Textract を使用して、検索可能な PDF ドキュメントを自動的に生成する方法を示しました。Amazon ES を使用してスマート検索インデックスを作成することにより、数百万のドキュメントを検索して関連ファイルを見つけることができます。検索可能な PDF ドキュメントを使用すると、オフラインで使用するためにダウンロードしたテキストを選択してコピーし、ドキュメント内で検索することができます。

Amazon Textract のさまざまなテキストおよびデータ抽出機能の詳細については、Amazon Textract の仕組みを参照してください。


著者について

Kashif Imran は、アマゾン ウェブ サービスのソリューションアーキテクトです。彼は AWS における最大規模の戦略的なお客様数社と連携し、技術的なガイダンスと設計アドバイスを提供しています。また、彼の専門知識は、アプリケーションアーキテクチャー、サーバーレス、コンテナ、NoSQL 、機械学習にまで多岐にわたります。