PythonでPDFファイルのサムネイル画像を作る

PythonでPDFファイルのサムネイル画像を作ってみました。

目次

  1. 動機
  2. 環境
  3. 手順
  4. コード
  5. コードの説明

動機

ローカルのNASにウェブサーバーを立ててPDFファイルをブラウザで読めるようにしているのですが、そのローカルのウェブページにPDFのサムネイルが貼ってあると便利だろうなと思い立ちまして、作ってみました。

環境

  • Python3

  • Poppler

  • pdf2imageモジュール

  • opencv-python(cv2)モジュール

  • Pillowモジュール

  • Windows10

手順

  1. pdf2image(Poppler)でPDFの1ページ目を画像に変換する

  2. 画像をOpenCVでクロップする(余白を削除する)

  3. クロップした画像をサムネイルサイズに縮小して出力する

コード

from pdf2image import convert_from_path
import numpy as np
import cv2
import os.path

THUMBNAIL_SIZE = 200
PDF_RESOLUTION = 300
PRE_CROP_AREA_UPPER = 400
PRE_CROP_AREA_LOWER = 400
PRE_CROP_AREA_LEFT = 200
PRE_CROP_AREA_RIGHT = 200

def read_pdf(file_path):
    # PDFを画像に変換
    pages = convert_from_path(file_path, dpi=PDF_RESOLUTION, last_page=1)
    # 最初のページをPIL.Imageからcv2用のndarrayに変換
    top_page = np.array(pages[0], dtype=np.uint8)
    page_img = cv2.cvtColor(top_page, cv2.COLOR_RGB2BGR)
    return page_img

def make_thumbnail(page_img):
    # ヘッダーやフッターを削除するためのクロップ
    precrop_img = page_img[PRE_CROP_AREA_UPPER:page_img.shape[0]-PRE_CROP_AREA_LOWER, PRE_CROP_AREA_LEFT:page_img.shape[1]-PRE_CROP_AREA_RIGHT]
    # 画像の2値化
    gray_img = cv2.cvtColor(precrop_img, cv2.COLOR_BGR2GRAY)
    ret_, bw_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
    # 輪郭の抽出
    conts, h_ = cv2.findContours(bw_img, cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_NONE)
    # すべての輪郭を含むボックスの計算
    x_min = precrop_img.shape[0]
    y_min = precrop_img.shape[1]
    x_max = 0
    y_max = 0
    for c in conts:
        r = cv2.boundingRect(c)
        if r[0] < y_min:
            y_min = r[0]
        if r[1] < x_min:
            x_min = r[1]
        if r[0]+r[2]-1 > y_max:
            y_max = r[0]+r[2]-1
        if r[1]+r[3]-1 > x_max:
            x_max = r[1]+r[3]-1
    # ボックスに従ってクロップ
    crop_img = precrop_img[x_min:x_max, y_min:y_max]
    # サムネイル画像の大きさの計算
    max_len = max(crop_img.shape[0], crop_img.shape[1])
    shrink_ratio = THUMBNAIL_SIZE / float(max_len)
    width = round(crop_img.shape[1]*shrink_ratio)
    height = round(crop_img.shape[0]*shrink_ratio)
    # クロップした画像のリサイズ
    shrink_img = cv2.resize(src=crop_img, dsize=(width,height), interpolation=cv2.INTER_AREA)
    return shrink_img

def main():
    fn = 'test.pdf'
    # PDFファイルから最初のページを画像として取り出す
    page = read_pdf(fn)
    # 画像からサムネイルの生成
    thumbnail = make_thumbnail(page)
    # 出力
    (fn_root, e_) = os.path.splitext(os.path.basename(fn))
    cv2.imwrite(fn_root + '_thumbnail.jpg', thumbnail)

if __name__ == '__main__':
    main()

コードの説明

こういうPDFファイルのサムネイル画像を生成してみます。

元のPDF

まず、pdf2image.convert_from_path()を使ってPDFファイルを画像に変換します。 pdf2image.convert_from_path()については、PDFを画像に変換する投稿を参照ください。

変換された画像は、PIL.Imageのリストになります。Pillowの画像形式ですね。

ここではサムネイル画像の生成にOpenCVを使いますので、Pillowの画像形式からcv2の画像形式に変換します。NumPyのarray()にImageを渡すと、ndarrayができます。このデータがRGB形式になっているので、cv2cvtColor()でBGR形式に変換します。(PDFファイルからの変換なので、ここでは透過は考慮していません。)

元のPDFファイルには、ページ番号が入っています。ページ番号はサムネイルには不要ですので、サムネイル作成前にヘッダーやフッター部分を削除します。

この際のクロップの量は、適当に決め打ちしました。

プリクロップ

そして、画像をグレースケールに変換し、さらに白黒(2値)にします。画像の2値化については、2値化についての記事を参照してください。

2値化

画像の2値化をしたら、その画像を使って輪郭の抽出をします。輪郭の抽出の仕方については、輪郭の抽出についての記事を参照してください。

輪郭が各オブジェクト(文字や図)に対してそれぞれ抽出されますので、すべての輪郭を含むボックスを計算する必要があります。そこでcv2.boundingRect()ですべての輪郭の外接矩形を計算して、全ての矩形の中から最小と最大の座標を求めます。そうすると、全ての輪郭を含むボックスが得られます。cv2.boundingRect()については、外接矩形を求める記事を参照してください。

ボックスを計算できたら、その座標を使って画像をクロップします。

クロップ

そして、クロップした画像を縮小します。

サムネイル

これなら、文字だけでリンクするよりは良さそうです。

公開日

広告