はじめに
複数ページにわたる資料を手作業でスクリーンショットしてまとめる作業が、面倒だと感じていた。1ページずつキャプチャして、ファイルを整理して、PDFに変換して——単純な繰り返しなのに時間がかかる。
「これは自動化できるはずだ」と思い、Pythonで作ることにした。技術的な学習も兼ねて、pyautogui・OpenCV・Pillow を組み合わせたツールを開発した。
最終的には、キーを1回押すだけで自動的にページを送りながらキャプチャし続け、最終ページを検出したら自動でPDFを生成して終了する、という仕組みが完成した。
ツールの概要
コマンドラインからページ送りの方向(左 or 右)を指定して起動する。3秒以内に対象ウィンドウをクリックすると、あとは自動でキャプチャが進み、最終ページに達したらPDFが生成される。
↓
② 3秒以内に対象ウィンドウをクリック
↓
③ スクリーンショットを撮影・保存
↓
④ 前ページとの差分を比較
↓
⑤ 変化なしが一定回数続いたら最終ページと判断
↓
⑥ 保存した画像をまとめてPDF生成 → 完了
# 使い方(右向きページ送りの場合)
python auto_capture.py right
# 左向きページ送りの場合
python auto_capture.py left
一番の工夫:ページ終端の自動検出
このツールで一番考えたのが、「最終ページをどう判断するか」だった。単純にページ数を指定する方法もあるが、それでは毎回設定が必要になる。
採用したのは、前後ページの画像差分を比較する方法だ。最終ページに達するとページ送りをしても画面が変わらなくなる。この「変化なし」が一定回数続いたら終端と判断して処理を止める。
def diff_ratio(a, b):
diff = cv2.absdiff(a, b)
return np.sum(diff) / (diff.size * 255)
# 差分が閾値未満なら「変化なし」としてカウント
if diff < CHANGE_THRESHOLD:
no_change_count += 1
if no_change_count >= NO_CHANGE_LIMIT:
print("最終ページと判断 → 終了")
break
gray = cv2.GaussianBlur(gray, (7, 7), 0)
gray = cv2.resize(gray, (800, 450))
ハマったポイント:prev_gray = gray.copy()
実装の途中、「毎回同じページと判定されて止まらない」というバグが発生した。
原因は、Pythonの参照渡しの挙動だった。prev_gray = gray と書くと、変数が同じオブジェクトを指してしまう。その後 gray が更新されると prev_gray も同時に書き変わってしまうため、常に「前回と今回が同じ」という判定になっていた。
# NG:参照渡しになってしまう
prev_gray = gray
# OK:値をコピーして独立させる
prev_gray = gray.copy() # ← これが超重要
.copy() を付けることで独立したコピーが作られ、前のページの情報が正しく保持されるようになった。NumPy配列を扱う際に覚えておくべき基本的な挙動だが、実際にバグとして踏んで初めて腹落ちした。
使用技術
| 技術 | 用途 |
|---|---|
| Python | メイン言語 |
| pyautogui | スクリーンショット・キー操作の自動化 |
| OpenCV (cv2) | 画像の差分比較・前処理(ブラー・リサイズ) |
| Pillow (PIL) | 画像のPDF変換・保存 |
| NumPy | 画像データの数値演算 |
おわりに
手作業で繰り返していた単純な作業が、コードを書くことで完全に自動化できた。実際に動かして使えるものが出来上がる体験は、学習のモチベーションになる。
prev_gray.copy() のような「実際にバグを踏んで学んだ知識」は、ドキュメントを読むだけでは得られないものだと感じている。作って、動かして、壊して、直す——この繰り返しが一番の近道だと思っている。