はじめに
pdfから色のついた部分を識別し、その部分を全て白くするコードをPythonで作りました。
本をスキャンするとき、スキャンアプリが自動で指などを消してくれるのですが、うまく消えないことがあります。今まではこれを無視するか、自分で白塗りしていました。しかし、このくらいのことなら自動化できるよな、と思いやってみることにしました。
コード全文
以下がコードの全文です。もしも使いたい方がいたら自由にコピペして使ってください。実行するとファイルの選択画面が開くので、白塗りしたいファイルを選択するとよいです。
コード全文
import os
from pdf2image import convert_from_path
import cv2
import numpy as np
from PIL import Image
from tkinter import filedialog, Tk
from tqdm import tqdm
from PyPDF2 import PdfReader
def process_pdf(pdf_path):
# まずPyPDF2でPDFを読み込み、各ページのサイズ(ポイント単位)を取得
reader = PdfReader(pdf_path)
pages_dimensions = []
for i in range(len(reader.pages)):
page = reader.pages[i]
media_box = page.mediabox
# 幅・高さはポイント単位(1pt = 1/72インチ)
width_pt = float(media_box.width)
height_pt = float(media_box.height)
# 300dpiでのピクセル数に変換: ピクセル数 = (ポイント / 72) * 300
width_px = int(width_pt / 72 * 300)
height_px = int(height_pt / 72 * 300)
pages_dimensions.append((width_px, height_px))
# PDFを画像に変換
images = convert_from_path(pdf_path, dpi=300)
processed_images = []
# tqdmで進行状況を表示
for i, img in tqdm(enumerate(images), total=len(images), desc="ページ処理中"):
# PIL画像をNumPy配列に変換
img_np = np.array(img)
# 各ピクセルのRGBの最大値と最小値の差を計算し、閾値より大きければ色味があると判断
diff = np.max(img_np, axis=-1) - np.min(img_np, axis=-1)
threshold = 30 # この閾値で色味の有無を判定
mask = diff > threshold
# 色味があると判定されたピクセルを白に置換
img_np[mask] = [255, 255, 255]
# 変換前の各ページのサイズ(ピクセル単位)を取得(ページ毎に異なる場合にも対応)
original_size = pages_dimensions[i] if i < len(pages_dimensions) else img.size
# NumPy配列からPIL画像に戻し、元のサイズにリサイズ
processed_img = Image.fromarray(img_np)
processed_img = processed_img.resize(original_size, Image.LANCZOS)
processed_images.append(processed_img)
# Tkinterダイアログで出力先PDFの保存先を指定
output_path = filedialog.asksaveasfilename(defaultextension=".pdf",
filetypes=[("PDF files", "*.pdf")])
if output_path:
# PILのsave()でPDFに保存する際、dpiパラメータを指定することでDPI情報を付与
processed_images[0].save(output_path, save_all=True,
append_images=processed_images[1:], dpi=(300, 300))
print(f"PDFが保存されました: {output_path}")
else:
print("保存先が指定されませんでした。")
if __name__ == "__main__":
# Tkinterのルートウィンドウを非表示にしてファイル選択ダイアログを表示
root = Tk()
root.withdraw()
pdf_path = filedialog.askopenfilename(title="PDFファイルを選択",
filetypes=[("PDF files", "*.pdf")])
if pdf_path:
process_pdf(pdf_path)
else:
print("PDFファイルが選択されませんでした。")
コードの説明とかこだわりポイントとか
色の判定
色の判定は以下の部分で行なっています。
# 各ピクセルのRGBの最大値と最小値の差を計算し、閾値より大きければ色味があると判断
diff = np.max(img_np, axis=-1) - np.min(img_np, axis=-1)
threshold = 30 # この閾値で色味の有無を判定
mask = diff > threshold
白黒灰色の場合、RGBのそれぞれの値が等しくなるので、RGBの数値間の差が大きければ「色」があると判定するようにしました。thresholdの値を変更することで色の判定の強さを変更することができます。本当は指を認識することができたらよかったかもしれないですが、白黒の本に割り切り、このようにしました。
PDFの大きさ
PDFには解像度以外にも大きさが設定されています。元のPDFと同じ大きさになるようにしました。
(もともと大きさは気にせず作ったんですが、GoodNoteに入れたときに「これでは不便だ」と思い追加しました)
おわりに
「ChatGPTに聞けばすぐだろ」と思い作り始めたんですが、なんだかんだで2,3時間かかってしまいました。微妙なところをなおしたり、起動修正をしたり。当初は指を認識して消そうかと思ったんですが、あまりうまくいかず色を排除する方向に起動修正しました。色の判定方法は私が提案しています。
今回に限っていえば、自分で白塗りした方が早かったんですが、これで今度から楽ちんです。めでたしめでたし。