mcpField
P
photo-to-scanned-pdf

photo-to-scanned-pdf

github.com/daymadeドキュメント1.5k299 回インストール今日更新
リポジトリ

photo-to-scanned-pdf について

署名や押印のある書類のスマートフォン写真を、スキャン風の A4 PDF にするスキルです。画像処理スクリプトに加え、内容に基づくページ順の決定と、文書全体の目視確認を組み合わせます。

主な機能

  1. 01台形補正と回転
  2. 02内容に基づくページ順の決定
  3. 03白紙と色付き用紙の別処理
  4. 04A4 PDF の作成
  5. 05コンタクトシートによる全ページ確認

活用シーン

  1. 01書類写真の PDF 化
  2. 02色付き用紙を含む書類の処理
  3. 03既存のスキャン PDF のページ差し替え

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/daymade/claude-code-skills --skill photo-to-scanned-pdf

インストール後、Claude Code で/photo-to-scanned-pdfを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
digital-signature-synthesis.md
scripts/
assemble_pdf.py
make_contact_sheet.py
photo_to_scan.py
synthesize_signature.py

詳細

写真からスキャンPDFへ

関連する2つのパイプラインです。出力の見た目は同じですが、入力が異なります。 紙の書類を撮影した写真か、署名済みのように見せるために合成署名が必要なデジタル文書です。適切に機能するパイプラインと、実行を省くと誤ったPDFを出力してしまう失敗パターンを紹介します。

どちらが必要ですか?

入力は…使用するもの
すでに署名/押印された紙の書類を撮影した写真このファイルの以下のメインパイプライン
まだ署名のないデジタル文書(docx/PDF)を手書き署名済みのように見せる必要があるreferences/digital-signature-synthesis.md
photos ──► rectify (photo_to_scan.py --raw)
       ──► ORDER BY CONTENT, detect colored paper   ← agent eyes, not filenames
       ──► enhance: noteshrink (white batch with -g │ colored pages separately,
                                after white-balance pre-pass)
       ──► assemble_pdf.py → A4 PDF
       ──► make_contact_sheet.py → READ IT, verify EVERY page   ← mandatory

役割分担:実行はスクリプトが担い、コンテンツに基づくページ順の決定と、文書全体の検証という2つの判断はあなた(エージェント)が担います。どちらも自動化できません。ファイル名が順序を正しく示すとは限らず、ページごとの抜き取り確認ではページの配置間違いを見逃すためです。デジタル署名の分岐も同じ考え方に基づき、独自の2つの判断ポイントがあります。詳しくはリファレンスファイルを参照してください。

ステップ 0 — 依存関係

which pdftoppm || brew install poppler   # contact sheet + any PDF rendering
uvx noteshrink --help | head -3          # first run builds it (~30 s)

スクリプトは uv run で実行する単一ファイルスクリプト(PEP 723)です。OpenCV/PIL/img2pdf は初回実行時に自動で解決されます。

ステップ 1 — 射影変換

uv run <skill>/scripts/photo_to_scan.py --raw --out-dir work --prefix page \
    photo1.jpg photo2.jpg ...

想定される出力:写真ごとに page_NN.jpg が1つずつ生成され、それぞれに [quad] が付いています。[FULLFRAME-fallback] というタグは、用紙の輪郭を検出できなかったことを示します(背景が複雑、ページがフレームからはみ出しているなど)。その写真を確認して、撮り直すか、トリミングされていないフレームをそのまま使うか判断してください。

スクリプトはEXIFの回転情報を内部で処理します(cv2.imread はEXIFを無視します。スマートフォンの写真は回転していることがあるため、補正に生のOpenCVを使うと、横向きのページが気付かないうちに生成されます)。

ステップ 2 — 内容に基づく順序付け、色付き用紙の検出(エージェントによる判断)

補正済みの画像をすべて読み取り(1メッセージにつき約6枚ずつ)、次の2点を記録します。

  1. ページの特定情報 — 日付、タイトル、ページ番号など、ページを区別できる情報です。 バッチで書き出された写真(WeChat、AirDrop)は、書き出し時刻のタイムスタンプが付くことがあり、多くの場合すべて1秒以内の時刻になります。そのため、ファイル名の順序は当てになりません。実際に、17枚の写真が文書の正しい順序とは完全に逆順になっていたケースがありました。それに気付けたのは、内容を読んだからです。
  2. 用紙の色 — 白か、色付き(青/黄色/ピンクの用紙)か。色付きのページはステップ 3 で別の処理をします。判別できない場合は、プログラムでサンプリングします。白紙の領域のRGB平均を求め、B > R + 25 なら青みがかった用紙です。

次に進む前に、最終的なページ順を明示的なリストとして作成します。ページを外部の台帳(請求書一覧やセッション表など)と照合する必要がある場合は、この時点でページの識別情報を照合してください。ここで見つかる欠落ページや重複ページへの対処は数秒で済みますが、納品後に見つかるとやり直しになります。

ステップ 3 — 補正(noteshrink、用紙の色ごとに分ける)

白い用紙のページ — 1つのバッチ、共通パレット:

uvx noteshrink -w -g -K -q -b ns -c "true" page_03.jpg page_01.jpg page_07.jpg ...
# inputs IN FINAL PAGE ORDER → outputs ns0000.png, ns0001.png, ... in that order
  • -w は背景を白にし、-g は1つの共通パレットを使います(ページ間でインク/スタンプの色を統一)。-K は指定された順序を保持し、-c "true" は内部のPDF処理をスキップします(PDFは自分で組み立てます)。
  • ファイル名を明示的に指定してください。 zsh は $VAR を単語に分割しないため、変数内のファイル一覧は1つの巨大な「ファイル名」として渡されます。noteshrink は出力せずに終了しますが、-q によって何も表示されません。標準出力を信用せず、ls ns0*.png で出力ファイルが存在することを確認してください。

色付き用紙のページ — ホワイトバランスの前処理を行い、個別に処理:

uv run <skill>/scripts/photo_to_scan.py --out-dir work --prefix wb colored_photo.jpg   # no --raw
uvx noteshrink -w -g -K -q -b nc -c "true" work/wb_01.jpg

このように分ける必要がある失敗パターンが2つあります(どちらも、このルールができる前にバグとして実際に発生しました)。

  1. 色付きページを -g のバッチに含めると、文書全体に悪影響が出る — 用紙の色が共通パレットに取り込まれ、白いページに色付きの影やアーティファクトが発生します。
  2. 色付き用紙にnoteshrinkだけを使うと、背景は白くなっても前景の色かぶりは残る — 青い用紙に黒インクで撮影された文字は青紫色に見え、赤いスタンプはえんじ色に見えます。photo_to_scan.py のデフォルトモード(--raw 以外)では、最初に用紙の色を除去するため、インクは黒に、スタンプは赤に戻ります。

ステップ 4 — 組み立て

uv run <skill>/scripts/assemble_pdf.py --out scanned.pdf \
    ns0000.png ns0001.png nc0000.png ns0002.png ...   # FINAL page order

期待される出力:OK scanned.pdf (N pages, ~0.05 MB/page)。エッジクロップ(デフォルトでは 200 dpi で上部 24px/左右 12px)により、補正処理でページの縁に入り込む机の表面の細片を取り除きます。文書の余白はそれよりはるかに大きいです。

ステップ 5 — 文書全体を確認する(必須)

uv run <skill>/scripts/make_contact_sheet.py scanned.pdf --out contact.png

contact.png を読み込み、すべてのページを確認してください:識別情報の並びが完全かつ正しいこと(各日付/タイトルがあるべき位置にあり、重複や欠落がないこと)、色味の異なるページがないこと、スタンプ/署名があることを確認します。その後、1~2 ページをフル解像度で読み、テキストの鮮明さを確認します。

毎回、文書全体を確認する理由:このスキルの元となったセッションで発生した、リリース済みの不具合にまつわる 2 つの事例です —

  • ページ差し替えタスクで、新しいページが間違ったスロットに書き込まれました(コピーコマンドのオフバイワンエラーにより、隣のページが気付かれないまま上書きされました)。差し替えたスロットのページごとの確認には合格しましたが、上書きされた隣のページはユーザーによって初めて発見されました。
  • パレット汚染の不具合(ステップ 3 #1)により、編集対象ではないページまで色味が変わりました。編集したページだけを確認していたため、見逃されました。

コストの差は歴然としています。コンタクトシートなら 1 回の Read で済みますが、納品した PDF に間違ったページがあれば、やり直しになり、信頼も失います。「変更したページは確認しました」は、検証ではありません。

既存のスキャン済み PDF でページを差し替える

ページごとの補正済み PNG(ns*/nc*)を作業用ファイルとして保持します。ページ k を差し替えるには、新しい写真にステップ 1~3 を実行して、そのページの PNG を上書きし、ステップ 4~5 を再実行します。番号付きスロットにコピーする際は、対応関係に注意してください。写真の順序を逆にした場合、スロット番号もずれます。スロットは、写真バッチ内での位置ではなく、ページの内容に基づく識別情報から判断してください。そのうえで、ステップ 5 の全体確認によって、実際に間違いを防止できます。

トラブルシューティング

症状原因/対処法
出力が「スキャンしたように見えない」— グレーのかすみ、ぼやけた文字レベル/カーブ/除算による補正を手作業で行っています。やめてください。noteshrink に切り替える前に、実際のユーザーから 2 回却下されました(背景サンプリングとパレット量子化によって、白地がフラットなスキャンらしい見た目になります)。
白いページの影に色が付いているカラー用紙のページが -g バッチに含まれていました。白いページだけのバッチを再実行してください(ステップ 3)。
カラー用紙のページで、インクが青/紫に見え、スタンプがえんじ色に見えるnoteshrink にカラー用紙のページを生のまま渡しました。ホワイトバランスの前処理(--raw を付けずに photo_to_scan.py を実行)を挿入してください。
noteshrink が出力もエラーも返さないzsh で引用符なしのシェル変数を使ってファイルリストを渡したため、単語分割が行われていないか、パスに空白が含まれています。ファイル名を明示して渡し、ls ns0*.png を確認してください。
ページが横向き/上下逆さま上流のどこかで EXIF が無視されたか、四角形の検出結果が横向きになっています。photo_to_scan.py は EXIF を補正し、縦向きに回転します。上下逆さまのページは自動では判別できないため、ステップ 2 で確認し、元の写真を回転させてください。
写真に [FULLFRAME-fallback] と表示される用紙の輪郭を検出できませんでした(テーブルとのコントラストが低い、ページが切れているなど)。暗い背景で撮り直すか、フルフレームを受け入れてエッジクロップに頼ってください。
PDF のページ端に細い暗い帯がある補正処理で机の表面が入り込みました。assemble_pdf.py で --crop-top/--crop-side の値を大きくしてください。
PDF のページの順序が間違っているファイル名の順序を前提としていました。順序はステップ 2 の内容確認で決まり、noteshrink(-K)と assemble_pdf.py に明示的に渡されます。

コメント