PDF(Portable Document Format)は、WYSIWYG(What You See is What You Get)形式ではありません。
プラットフォームに依存せず、基盤となるオペレーティングシステムやレンダリングエンジンに依存しないように開発されました。
これを実現するために、PDFはプログラミング言語のようなもので操作できるように構築されており、結果を得るためには一連の命令と操作に依存することになります。
実際、PDFはスクリプト言語であるPostScriptをベースにしています。
PostScriptは、デバイスに依存しない最初のページ記述言語でした。
このガイドでは、PDF文書の読み取り、操作、生成に特化したPythonライブラリであるborbを使用します。
borbは、低レベルモデル(正確な座標やレイアウトにアクセスできる)と高レベルモデル(余白や位置などの正確な計算をレイアウトマネージャーに委ねることができる)の両方を提供します。
>
このガイドでは、スキャンしたPDF文書に光学式文字認識(OCR)を適用する方法について説明します。
borbのインストール
borbはGitHubのソースからダウンロードするか、pip
経由でインストールすることができます。
$ pip install borb
“私のPDF文書にはテキストがありません!”
これは、あらゆるプログラミングフォーラムやヘルプデスクで最も古典的な質問の1つです。
という質問です。
gt; 「私の文書にはテキストが含まれていないようです。
助けてください。
という質問です。
または
>
> “あなたのテキスト抽出コードサンプルは、私の文書では動作しません。
答えは、「スキャナに嫌われている」と同じくらい簡単なことが多いです。
これがうまくいかない文書のほとんどは、本質的に見栄えのする画像であるPDF文書です。
PDFを構成するのに必要なメタデータはすべて含まれていますが、そのページは、物理的な紙をスキャンして作成された、単なる大きな(多くの場合低品質の)画像に過ぎません。
その結果、これらの文書にはテキストレンダリングの指示がありません。
そして、ほとんどのPDFライブラリはそれらを扱うことができないでしょう。
しかし、borb
は、OCRのサポートを内蔵しており、このような場合に適用することができます。
このセクションでは、OCRAsOptionalContentGroup
という特別な EventListener
の実装を使用します。
このクラスは tesseract
(というより pytesseract
) を使用して、 Document
に対して OCR (光学式文字認識) を実行します。
>
> PythonでのOCRについてもっと知りたい方は、PyTesseractを使った簡単な光学式文字認識ガイドをご覧ください!
をご覧ください。
認識されたテキストは、各ページに特別な「レイヤー」として再挿入されます(PDFでは「オプショナル コンテンツ グループ」と呼ばれます)。
これでコンテンツが復元されたので、いつものトリック(SimpleTextExtraction
)で期待通りの結果が得られます。
まず、いくつかのテキストを含む PIL 画像を作成するメソッドを作成します。
この画像はその後PDFに挿入されます。
画像の作成
import typing
from pathlib import Path
from PIL import Image as PILImage # Type: ignore [import]
from PIL import ImageDraw, ImageFont
def create_image() -> PILImage:
# Create new Image
img = PILImage.new("RGB", (256, 256), color=(255, 255, 255))
# Create ImageFont
# CAUTION: you may need to adjust the path to your particular font directory
font = ImageFont.truetype("/usr/share/fonts/truetype/ubuntu/UbuntuMono-B.ttf", 24)
# Draw text
draw = ImageDraw.Draw(img)
draw.text((10, 10),
"Hello World!",
fill=(0, 0, 0),
font=font)
# Return
return img
では、この画像を使ってPDFを作成してみましょう。
この画像はメタデータを含んでいないため、解析することはできませんが、スキャンした文書を表現しています。
import typing
# New imports
from borb.pdf.canvas.layout.image.image import Image
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
from borb.pdf.canvas.layout.text.paragraph import Paragraph
from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
# Main method to create the document
def create_document():
# Create Document
d: Document = Document()
# Create/add Page
p: Page = Page()
d.append_page(p)
# Set PageLayout
l: PageLayout = SingleColumnLayout(p)
# Add Paragraph
l.add(Paragraph("Lorem Ipsum"))
# Add Image
l.add(Image(create_image()))
# Write
with open("output_001.pdf", "wb") as pdf_file_handle:
PDF.dumps(pdf_file_handle, d)
出来上がったドキュメントは次のようなものになります。
このドキュメントのテキストを選択すると、一番上の行だけが実際にテキストであることがすぐにわかります。
残りの部分はテキスト付きの画像 (作成した画像) です。
では、この文書にOCRを適用し、実際のテキストを重ね合わせて、解析可能な状態にしてみましょう。
# New imports
from pathlib import Path
from borb.toolkit.ocr.ocr_as_optional_content_group import OCRAsOptionalContentGroup
from borb.toolkit.text.simple_text_extraction import SimpleTextExtraction
def apply_ocr_to_document():
# Set up everything for OCR
tesseract_data_dir: Path = Path("/home/joris/Downloads/tessdata-master/")
assert tesseract_data_dir.exists()
l: OCRAsOptionalContentGroup = OCRAsOptionalContentGroup(tesseract_data_dir)
# Read Document
doc: typing.Optional[Document] = None
with open("output_001.pdf", "rb") as pdf_file_handle:
doc = PDF.loads(pdf_file_handle, [l])
assert doc is not None
# Store Document
with open("output_002.pdf", "wb") as pdf_file_handle:
PDF.dumps(pdf_file_handle, doc)
この処理により、PDFに追加のレイヤーが作成されたことがわかります。
このレイヤーは “OCR by borb” という名前で、レンダリング命令 borb
が Document
に再挿入されています。
このレイヤーの可視性を切り替えることができます(デバッグするときに便利です)。
borbがPostScriptレンダリングコマンドを再挿入して、”Hello World!”が`Document’にあることを確認していることがわかります。
このレイヤーをもう一度非表示にしてみましょう。
>
OCRはヒューリスティックであることを覚えておいてください。
位置とマッチしたテキストが100%正しいとは限りません。
それは仕方のないことです。
通常、レイヤーを非表示(ただし選択可能)にしておくと、元画像はその場にあり、その近似画像を選択/コピーすることができます。
これで(レイヤーを隠したままでも)、テキストを選択できるようになりました。
そして、SimpleTextExtraction
を適用すると、Document
にあるすべてのテキストを取得できるはずです。
# New imports
from borb.toolkit.text.simple_text_extraction import SimpleTextExtraction
def read_modified_document():
doc: typing.Optional[Document] = None
l: SimpleTextExtraction = SimpleTextExtraction()
with open("output_002.pdf", "rb") as pdf_file_handle:
doc = PDF.loads(pdf_file_handle, [l])
print(l.get_text_for_page(0))
def main():
create_document()
apply_ocr_to_document()
read_modified_document()
if __name__ == "__main__":
main()
これで印刷されます。
Lorem Ipsum
Hello World!
すごい!
結論
このガイドでは、PDF文書にOCRを適用する方法を学び、スキャンした文書が検索可能で、将来の処理に備えることができることを確認しました。