Google Cloud Vision API でPDFからEPUBを作成してみた

Google Cloud Vision API を使って、PDFから文字を抽出し、簡易的なEPUB を作成してみました
2021.12.15

西田@大阪@MAD事業部です。

本エントリは クラスメソッド Google Cloud Advent Calendar 2021 の 15日目 の記事です。

今回は Google Cloud Vision API を使って、PDFから文字を抽出し、簡易的なEPUB を作成してみました

Cloud Vision APIとは

Cloud Vision API は機械学習の知識がなくても、簡単に画像の解析が可能でサービスです。画像、PDF/TIFF からテキストを抽出したり(OCR)、ランドマーク検出、顔検出などができます。

参考: 機能リスト | Cloud Vision API | Google Cloud

構成

Input 用の Cloud Storage に PDFファイルをアップロードすると、 Cloud Function が起動し、 Cloud Vision API を利用した文字解析を行い、結果出力用の Cloud Storage に解析結果のJSONを出力します。さらに、出力された JSON を元に EPUB を作っていきます

事前準備

本エントリはCloud SDK がインストールされ、 Cloud Functions、Cloud Build API、Cloud Storage、Cloud Vision API が有効になっている前提で書かれています

参考:

Cloud SDK のインストール | Google Cloud

Console を使用したクイックスタート | Google Cloud Functions に関するドキュメント

Cloud Storage のチュートリアル | Google Cloud Functions に関するドキュメント

始める前に | Cloud Vision API | Google Cloud

Cloud Storage の作成

アップロード用と、結果出力用のバケットを作成します。今回作成したスクリプトではアップロードしたバケットに "-outputs" とサフィックスがついたバケットに結果を出力するようにしています

それぞれのバケットを作成します

例) sample-bucket ⇒ sample-bucket-outputs

gsutil mb gs://${YOUR_INPUT_BUCKET}
gsutil mb gs://${YOUR_INPUT_BUCKET}-outputs

コード

こちら にコードをアップロードしてます。 それぞれのファイルを解説していきます

./requirements.txt

使用しているライブラリを列挙してます

google-cloud-vision==2.6.2
google-cloud-storage==1.43.0
ebooklib==0.17.1

./main.py

大きく分けて Cloud Vision API で解析してる処理とEPUBを作成している処理を分けて解説します

Cloud Vison API で解析

全体のソース

def pdf_to_epub(data, context):
    from google.cloud import vision

    mime_type = 'application/pdf'
    batch_size = 2

    # get gcs uri from data
    bucket_name = data['bucket']
    output_bucket_name = f'{bucket_name}-outputs'
    file_name = data['name']

    gcs_input_uri = f'gs://{bucket_name}/{file_name}'
    gcs_output_uri = f'gs://{output_bucket_name}/{file_name}/'    

    client = vision.ImageAnnotatorClient()

    feature = vision.Feature(
        type_=vision.Feature.Type.DOCUMENT_TEXT_DETECTION)

    # Input
    gcs_source = vision.GcsSource(uri=gcs_input_uri)
    input_config = vision.InputConfig(
        gcs_source=gcs_source, mime_type=mime_type)

    # Output
    gcs_destination = vision.GcsDestination(uri=gcs_output_uri)
    output_config = vision.OutputConfig(
        gcs_destination=gcs_destination, batch_size=batch_size)

    async_request = vision.AsyncAnnotateFileRequest(
        features=[feature], input_config=input_config,
        output_config=output_config
    )

    operation = client.async_batch_annotate_files(
        requests=[async_request]
    )

    operation.result(timeout=420)

    create_epub(output_bucket_name, file_name)

今回はPDFを対象に文字解析を行うので DOCUMENT_TEXT_DETECTION を指定しています

feature = vision.Feature(
    type_=vision.Feature.Type.DOCUMENT_TEXT_DETECTION)

gcs_destination(出力先のバケット)と batch_size (一つのJSONファイルに何ページを入れるか)を指定しています

gcs_destination = vision.GcsDestination(uri=gcs_output_uri)
output_config = vision.OutputConfig(
    gcs_destination=gcs_destination, batch_size=batch_size)

Cloud Vision API のPDF/TIFF の解析は files:asyncBatchAnnotateで非同期に実行する必要があるので、指定しています

async_request = vision.AsyncAnnotateFileRequest(
        features=[feature], input_config=input_config,
        output_config=output_config
    )

今回はそのまま EPUB を作成していくので、 Cloud Vision API の完了を待ちます

operation.result(timeout=420)

EPUB作成

次にEPUB作成部分を見ていきます。 EPUB の作成は使用しているライブラリ aerkalov/ebooklib: Python E-book library for handling books in EPUB2/EPUB3 format - のUsageを参考にしています 

def create_epub(bucket, file_name):
    import json
    from google.cloud import storage
    from ebooklib import epub

    storage_client = storage.Client()

    prefix = f'{file_name}'

    output_bucket = storage_client.get_bucket(bucket)

    blob_list = [blob for blob in list(output_bucket.list_blobs(
        prefix=prefix
    )) if not blob.name.endswith('/')]

    book = epub.EpubBook()

    book.set_identifier('id123456')
    book.set_title(file_name)
    book.set_language('ja')

    book.add_author('Sample')

    page_no = 0
    first_chapter = None
    chpters = []
    for blob in blob_list:
        json_string = blob.download_as_string()
        ouptput = json.loads(json_string)

        for response in ouptput['responses']:
            page_no = page_no + 1
            annotation = response['fullTextAnnotation']
            chapter_file_name = f'chap_{page_no}.xhtml'
            chapter = epub.EpubHtml(title=f'Chapter{page_no}', file_name=chapter_file_name, lang='ja')
            chapter.content=u'<p>{}</p>'.format(annotation['text'])
            book.add_item(chapter)
            chpters.append(chapter)

            if not first_chapter:
                first_chapter = chapter

    book.toc = (epub.Link('chap_1.xhtml', 'Introduction', 'intro'),
                (epub.Section('Simple book'),
                chpters)
                )

    book.spine = ['nav', first_chapter]

Cloud Vision API が出力するJSONの構造はざっくり以下で、このJSONを使ってEPUBを作成していきます

{
    "responses": [ // batch_size で指定した数の配列になってる
    {
      "fullTextAnnotation": {
        "pages": {}, // 検出された文字の解析情報
        "text": "検出された文字列"
      },
      "context": {
        "uri": "gs://..." // 解析対象のPDFファイルのURI
        "pageNumber": 1 // ページ数
      }
    }
  ]
}

出力先のバケットから Cloud Vision API が出力したJSONの一覧を取得してます

output_bucket = storage_client.get_bucket(bucket)

blob_list = [blob for blob in list(output_bucket.list_blobs(
    prefix=prefix
)) if not blob.name.endswith('/')]

batch_size で指定した数だけ output に出力されたJSONの responsesの配列になっているので、その分をループしてEPUBのコンテンツを作ってます

for blob in blob_list:
    json_string = blob.download_as_string()
    ouptput = json.loads(json_string)

    for response in ouptput['responses']:
        page_no = page_no + 1
        annotation = response['fullTextAnnotation']
        chapter_file_name = f'chap_{page_no}.xhtml'
        chapter = epub.EpubHtml(title=f'Chapter{page_no}', file_name=chapter_file_name, lang='ja')
        chapter.content=u'<p>{}</p>'.format(annotation['text'])
        book.add_item(chapter)
        chpters.append(chapter)

        if not first_chapter:
            first_chapter = chapter

一時的に生成したEPUBファイルを Cloud Functions の一時領域である /tmp に保存し、それをOutput用の Cloud Storage にアップロードしています。Cloud Functions の /tmp 領域に書き込むと、メモリが消費されるのと、関数インスタンスが再利用された際にファイルが残ってしまうことがあるらしいのでご注意ください

参考:

料金  |  Cloud Functions  |  Google Cloud

Cloud Functionsのファイルシステムについて - みーのぺーじ

epub_file_name = f'{file_name}.epub'
epub_tmp_path = f'/tmp/{file_name}-{epub_file_name}'

epub.write_epub(epub_tmp_path, book, {})

epub_blob = output_bucket.blob(f'{file_name}/{epub_file_name}')
epub_blob.upload_from_filename(epub_tmp_path)

デプロイ

gcloud functions deploy pdf_to_epub \
       --runtime python39 \
       --trigger-resource ${YOUR_INPUT_BUCKET} \
       --trigger-event google.storage.object.finalize \
       --timeout=300

それぞれ以下のパラメーターを指定しています

pdf_to_epubmain.py 内の関数名 --runtime ⇒ Python のバージョン

--trigger-resource ⇒ Cloud Functions を設定するバケット

--trigger-event ⇒ Cloud Functionを発動したいトリガーを設定してます。今回はオブジェクトが作成されたタイミングで発動するように設定しています。イベントの種類については こちら を参考にしてください

--timeout => Cloud Vision API の処理結果を待つ関係で、時々 Cloud Functions のデフォルトのタイムアウトである60秒を超えるので多めに設定しています

結果

Cloud Vision API の結果は Output のバケットにJSON形式で保存されています。コード中のbatch_size で指定したページ毎に1つのファイルのJSONとしてまとまって保存されています

Pythonでpdfを画像として認識しテキストを抽出を試してみる(pyocr)

上記で使われていPDF( デジタル・ディバイド解消に向けた技術等研究開発 )を解析して出力結果を見ていきます

1ページ目

新たなICTサービスの研究開発を行う\n民間企業等の皆さんへ\n総務省\n補助金事業募集\nデジタル・ディバイド解消に向けた\n技術等研究開発\n高齢者・障害者向けICTサービスの充実を図る\n研究開発を行う企業等の取組に助成します。\nco\neee\nhttps://www.soumu.go.jp/main_sosiki/joho_tsusin/b_free/b_free03.html\nお問い合わせ先\nリサイクル適性(A\n総務省情報流通行政局情報流通振興課情報活用支援室\n〒100-8926 東京都千代田区霞が関2-1-2 中央合同庁舎第2号館\n# 03-5253-5743 FAX 03-5253-6041\nこの印刷物は、印刷用の紙へ\nリサイクルできます。\n

改行させて整形してみます

新たなICTサービスの研究開発を行う
民間企業等の皆さんへ
総務省
補助金事業募集
デジタル・ディバイド解消に向けた
技術等研究開発
高齢者・障害者向けICTサービスの充実を図る
研究開発を行う企業等の取組に助成します。
co
eee
https://www.soumu.go.jp/main_sosiki/joho_tsusin/b_free/b_free03.html
お問い合わせ先
リサイクル適性(A
総務省情報流通行政局情報流通振興課情報活用支援室
〒100-8926 東京都千代田区霞が関2-1-2 中央合同庁舎第2号館
# 03-5253-5743 FAX 03-5253-6041
この印刷物は、印刷用の紙へ
リサイクルできます。

ノイズがあるもののかなりの精度で解析できています

3ページ目

表組が使われていたり、アイコンと重なって文字が書かれていたりする、3枚目の抽出結果を見てみます

かなりの分量なので、一部抜粋して比較してみます

助成事例

研究開発
事業名
マルチメディアDAISYの自動制作・利用システムの
障害者支援研究開発

聴覚障害者向け会議支援システムの研究開発

印刷物から抽出したテキストと肉声音声を同期させ
たマルチメディアDAISYを自動制作するとともに、即
時にスマートフォン、タブレット端末での利用が可能
なシステムを実現することで、読字障がい者が必要な
時にいつでもどこでも情報の入手が可能となる。
平成27年1月、マルチメディアDAISY/テキスト
DAISY製作ソフトウェア「PLEXTALK Producer」とし
て販売開始。

PLEXTALK Producer
マルチメディアDAISY製作の流れ

ポータルサーチャンスでしが、
になりました。
plet the mentom Tepernatete
SIE
それは、
wrajore
チー:3時間からです。
メンテナンスはさような
aree Meet ant marracertener

①テキストを取り込む
インポート
エクスポート・
TXT
バックアップ!
復元ー
man)
のナンスでしたが、
HO
11
Then
Rese-thropkiyanet

表組部分に関しては問題なくテキストが抽出されています。また、本文中に掲載されているチャットの洗いサンプル画像の文字も洗いながら識別されてそうです。また、アイコンと重なった文字についても抽出できています

4ページ目

最後に縦書きもある4ページ目の結果を確認していきます

縦書き部分を一部抜粋して整形します

公募
事後評価結果報告
提案
採択
交付決定・研究開発開始
実地調査(中間)
実地調査(最終)
経理検査

きれいに見た目通りの順番(左から右)ではないですが、縦書きでもテキストの抽出はされてそうです。

EPUB

出力されたEPUBも見てみます

元のPDFのページを分けれたものの、ページとして連続してページ送りできないなどなど課題は残るものの、EPUBとして開くことができました。今後EPUBの仕様を勉強して改善していきます

最後に少しだけハマったことをメモ程度に

以下のメッセージが出てエラーになりました

You are attempting to perform an operation that requires a project id, with none configured. Please re-run gsutil config and make sure to follow the instructions for finding and entering your default project id.

以下のコマンドでデフォルトのプロジェクトを設定するといけました

gcloud config set project ${YOUR_PROJECT_ID}

Cloud Vision API APIを有効化してない時に以下のメッセージが出ました 

403 Cloud Vision API has not been used in project ${YOUR_PROJECT} before or it is disabled. Enable it by visiting

参考

ファイル内のテキストを検出する(PDF / TIFF) | Cloud Vision API | Google Cloud