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. 04组装 A4 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

两个相关流程,最终呈现相同,起点不同: 纸质文档的手机照片,或需要添加合成签名、让它看起来像已签署的数字文档。以下介绍有效的流程,以及跳过后会导致 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

分工:脚本负责执行;你(Agent)负责两个判断步骤——基于内容排列页面顺序,以及核验整份文档。这两步都无法自动化:文件名会误导顺序,而逐页抽查会漏掉位置错误。数字签名分支也遵循相同理念,其中有自己的两个判断环节——参见参考文件。

步骤 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,并标记为 [quad]。标记为 [FULLFRAME-fallback] 表示未找到纸张轮廓(背景杂乱、页面被截断)——查看该照片并决定:重新拍摄,还是接受未裁剪的画面。

脚本会在内部处理 EXIF 旋转(cv2.imread 会忽略 EXIF;手机照片带有旋转信息——如果直接用原始 OpenCV 图像进行校正,页面会悄无声息地变成横着的)。

步骤 2 — 按内容排序并检测彩色纸张(Agent 判断)

阅读每张校正后的图像(每条消息批量处理约 6 张),并记录两项信息:

  1. 页面标识——日期、标题、页码,以及任何能区分页面的信息。批量导出的照片(微信、AirDrop)会带有导出时的时间戳,通常所有照片的时间都只相差一秒以内——文件名顺序毫无意义。实际案例:17 张照片的顺序恰好与文档顺序完全相反;只有阅读内容才能发现这一点。
  2. 纸张颜色——白色还是彩色(蓝色/黄色/粉色纸张)?彩色页面在步骤 3 中要采用不同流程。如果不确定,可通过程序采样:取空白区域的 RGB 平均值;B > R + 25 ⇒ 纸张偏蓝。

继续之前,先将最终页面顺序整理成明确列表。如果页面需要与外部登记表(发票清单、场次表等)对应,现在就核对页面标识——此时发现缺页/重复页只需几秒;交付后才发现则需要返工。

步骤 3 — 增强(noteshrink,按纸张颜色拆分)

白纸页面——统一批处理,使用全局调色板:

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 使用一个全局调色板(确保所有页面上的墨迹/印章颜色一致),-K 保持给定顺序,-c "true" 跳过其内部 PDF 步骤(由我们自行组装)。
  • 显式传入文件名。 zsh 不会对 $VAR 进行按空格拆分——变量中的文件列表会被当成一个巨大的“文件名”传入,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

以下两种不同的失败模式要求将两者分开处理(规则制定前,这两种情况都曾作为 bug 交付):

  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 页的文字清晰度。

为什么每次都要检查整个文档的每一页:这个技能源自本次会话,期间出现过两个已交付内容中的错误案例——

  • 一项页面替换任务把新页面写入了错误的槽位(复制命令中有一个差一错误),悄无声息地覆盖了相邻页面。被替换槽位的逐页检查通过了;被覆盖的邻页只有用户发现了。
  • 调色板污染错误(步骤 3 #1)给未编辑的页面也染了色。只检查编辑过的页面没能发现问题。

成本差异是绝对的:联系表 = 一次 Read;交付的 PDF 中有错误页面 = 重做 + 失去信任。“我验证了修改过的页面”不等于验证。

替换现有扫描 PDF 中的页面

将每页增强后的 PNG(ns*/nc*)保留为工作文件集。要替换第 k 页:将新照片按步骤 1–3 处理,覆盖该页的 PNG,然后重新运行步骤 4–5。复制到编号槽位时要注意映射关系——照片顺序被反转时,槽位编号也会变化;应根据页面的内容身份确定槽位,绝不能根据它在照片批次中的位置来确定。之后,步骤 5 的完整检查才是真正的保障。

故障排除

现象原因/解决方法
输出“看起来不像扫描件”——灰蒙蒙的,文字发虚你手动调了色阶/曲线/除法增强。不要这样做——切换到 noteshrink 之前,真实用户拒绝了两次尝试(背景采样 + 调色板量化才是生成纯白扫描效果的关键)。
白色页面的阴影带有色调-g 批次中包含了彩色纸张页面。重新运行仅处理白色页面的批次(步骤 3)。
彩色页面上的墨迹呈蓝色/紫色,印章呈栗色noteshrink 直接处理了彩色页面。插入白平衡预处理步骤(运行 photo_to_scan.py,不带 --raw)。
noteshrink 没有输出,也没有报错在 zsh 下通过未加引号的 shell 变量传入文件列表(不会进行单词拆分),或者路径中包含空格。传入明确的文件名;检查 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。

评论

支持 Markdown