中文 PDF 课件 → Markdown 学习笔记
把课程 PDF 课件转化为结构化、可直接复习的中文 Markdown 笔记;图例(流程图、结构图、示意图等)裁剪为完整图片,嵌入对应知识点下方。
适用条件
| 条件 | 说明 |
|---|---|
| PDF 有文本层 | 文本提取依赖 pdfplumber;纯扫描件需先 OCR,不适用本流程 |
| 图例由图形对象构成 | 位图(images)或矢量(curves/lines/rects)均可,两者裁剪路径不同,见第 3 步 |
| 页面尺寸动态读取 | 常见 960×540、720×405、1280×720,用 page.width/height 读取,不要硬编码 |
环境准备
python3 -m venv .venv
# Linux / macOS
.venv/bin/pip install -r requirements.txt
# Windows (PowerShell)
# .venv\Scripts\pip install -r requirements.txt
依赖共三个:pdfplumber(文本与图形对象坐标)、pypdfium2(整页渲染)、Pillow(裁剪与像素统计)。不需要 numpy,逐行/逐列像素统计用 PIL 的 histogram() 即可。
工作流
- [ ] 1. 提取文本 + 渲染页面 + 诊断固定角标
- [ ] 2. 挑选图例页
- [ ] 3. 裁剪图例(figures.json 驱动)
- [ ] 4. 目视复核裁剪结果
- [ ] 5. 撰写 Markdown 笔记
- [ ] 6. 校验图片引用
- [ ] 7. 清理中间文件
第 1 步:提取文本 + 渲染页面
python scripts/extract_and_render.py 课件.pdf -o _tmp --scale 3 --diagnose-deco
产出:
_tmp/text_raw.txt:每页文本 +images/curves/rects/lines计数,计数高的页大概率含图例;_tmp/pages/pNN.png:整页渲染图(scale=3,保证裁剪后仍清晰);--diagnose-deco:找出"在多数页面相同坐标重复出现"的图形对象(页眉校名、页脚校徽这类固定角标),直接输出可粘进figures.json各项deco字段的 bbox 列表。
PPT 型课件通常有两个固定角标(页眉 + 页脚),诊断结果要全部填入 deco;漏填会使 auto 模式的裁剪边界一路扩到页边。
PPT 类课件的文本抽取顺序常常错乱(标题与正文穿插),需结合渲染图判断真实版面。
第 2 步:挑选图例页
只截真图。以下情况不截:
- 纯文字页(包括带项目符号的要点罗列);
- 与正文文字完全重复的内容;
- 目录页、章节分隔页、参考文献页(除非含图)。
值得截的:流程图、结构图、语法树、状态机、时序图、架构图、示意图、带标注的代码对比图、表格化的映射关系图。
同一张图在多页重复出现时只截一次;同一页有多个独立图例时分开裁剪保存,不要合并成一张。
第 3 步:裁剪图例
核心原则:宁可截大,不可截破。裁剪过紧导致标题、箭头、边框、图注被切掉,是最常见的返工原因。不要用手工比例坐标直接下刀。
先区分图例类型,再选对应模式。判据:打开该页 page.images,看是否存在面积占比 >10% 的单张大图(PPT 导出型课件典型 15%~40%):
- 有 → 整块嵌入位图,用
mode: "bbox",直接取该图例自身的 image bbox(加 2~4pt 内边距); - 没有,图形由大量小对象组成 → 矢量线条 + 文字标签绘制,用
mode: "auto"。
编写裁剪配置(完整示例见 examples/figures.example.json),然后执行:
python scripts/crop_figures.py 课件.pdf figures.json -o figures --scale 3
[
{"page": 19, "name": "fig_b", "mode": "auto", "margin": 40,
"deco": [[17.8, 20.2, 103.4, 60.7], [599.3, 368.4, 697.0, 399.6]]},
{"page": 3, "name": "fig_a", "mode": "bbox", "box": [173, 68, 524, 221]},
{"page": 29, "name": "fig_c", "mode": "bbox", "box": [58, 206, 650, 386],
"masks": [
{"box": [608.5, 366, 650, 386], "fill": "bg"},
{"box": [601, 382, 608.5, 386], "fill": [0, 112, 192]}
]}
]
mode: "auto" 的算法
实现见 scripts/crop_figures.py 的 auto_bbox(),确定性流程:
- 种子 bbox = 全部图形对象的联合(images/curves/lines/rects 全部纳入,不只 images——树图、自动机、流程图常由矢量线条 + 文字标签绘制)。并入前排除三类对象:
deco中的固定角标;- 面积 >70% 页面的大背景框;
- 通栏装饰线(一维 ≤8pt 且另一维横跨 >60% 页面,如标题下划线、页脚色带)。装饰线面积接近 0,面积阈值挡不住,一旦并入会把裁剪框拉到接近整页,必须在求并集之前剔除。
- 迭代吸收相邻文字:与 bbox 膨胀 6pt 相交的 word 全部并入,循环到不动点(图例旁注、轴标、箭头文字在这一步保住)。
- 外扩 30~40pt 缓冲,clamp 到页面边界;通栏装饰线与不重叠的角标作为护栏压住外扩边界(把角标排除出种子框并不能阻止它进入最终裁剪范围)。角标与图例重叠时脚本打印
[警告],改用masks局部遮盖。 - 截断词吸收到不动点:任何 word 与裁剪框相交但未被完全包含,则并入并外扩 4pt,循环直到零截断。只做一轮不够——扩张边界后可能切到新的词。
无图像查看能力时,上述程序化保证(矢量全包含 + 文字零截断)即完整性依据;能目视时仍应逐张复核(第 4 步)。
mode: "bbox" 的边界确定
整块位图不要套用"联合 + 40pt 外扩":外扩会把邻近的公式小图框进来,而公式是 image 不是 word,截断词校验管不到它,结果图里出现半个公式。
精确切边用墨迹扫描(第 6 步 --ink / --col):逐行/逐列统计暗像素,找出图例墨迹的真实起止位置,再与邻近文字的行区间比对。例如:图例墨迹止于 330.3pt,下方图注 word 的 top 是 339.4pt,则取 332pt 作下边界,既完整又不带半个字。
masks:角标压住图例时的局部遮盖
固定校徽可能压住图例一角。不要整体裁窄(会切掉图例另一侧的独立图元),改为局部遮盖:
- 遮盖矩形不得进入图例本体,否则会啃掉边框。先用列扫描量出图例本体右边界与校徽墨迹起始列(例如方框右边框在 607.0~608.0pt、校徽墨迹起于 608.3pt,则遮盖从 608.5pt 起),确认遮盖区内没有图例内容;
fill: "bg"取图中出现次数最多的颜色作为页面背景色(这类课件背景常是(242, 242, 242)而非纯白,比固定采样某个像素稳);- 校徽压在彩色方框边框上时,用
fill: [r, g, b]以该边框色补一小块,视觉上自然融合; - 遮盖后复扫该区域,剩余深色像素应能确认属于图例本身(如箭头笔尖),否则继续修。
命名
figures/fig_<语义名>.png,英文小写下划线,名字体现图例内容。
第 4 步:目视复核裁剪结果
当前工具链能查看图片时(能看图时用满这一能力,成本远低于返工):
- 先看整页渲染图(
_tmp/pages/pXX.png)确认图例与邻近文字的真实版面关系; - 逐张打开裁剪结果,确认:四周无半个字、无断掉的边框/箭头、无混入的校徽或正文残片;
- 顺带核准公式:这类课件的公式多为图片,纯文本抽取常丢失或乱序(
∑结构散架、下标错位),关键公式必须对照渲染页确认后再写入笔记; - 发现问题按
bbox模式重裁或用masks遮盖,不要带问题交付。
第 5 步:撰写 Markdown 笔记
结构与风格要求:
- 一级标题:
课程名 · 第X章 章节名 · 学习笔记; - 开头用
>引用块注明来源与笔记定位,附目录(锚点链接); - 标题层级合理,章节编号清晰;
- 优先引用课件原文(中文)概括,不要擅自改写术语;
- 重点、难点加粗,并用
>引用块附加解释与注解("难点提示 / 注"); - 善用表格做对比归纳;
- 图例放在对应知识点正下方,用相对路径
; - 章末附「重点回顾与易混淆点」与「课后自检清单」(
- [ ]形式)。
第 6 步:校验图片引用
python scripts/check_figures.py 笔记.md # 路径与可加载性校验
python scripts/check_figures.py 笔记.md --edges # 四边墨迹检测(推荐,必跑)
第一句校验笔记引用的图片全部存在且能被 PIL 加载(0 缺失才通过),并列出 figures/ 下未被引用的孤儿文件。
--edges 是客观完整性判据,不能只靠目视复核:它检查每张图四边最外 3px 内是否有墨迹,四边全 0 才算干净。边缘有墨迹 = 要么混入了页外元素(正文列、校徽、页脚色带),要么图例内容被裁掉了。
⚠️ 为什么必须跑:看图工具(Read 等)可能返回缓存的旧图——重新裁剪后立刻看图,看到的可能仍是上一版,于是把已修好的图误判为"还有问题"、或把没修好的图误判为"已经好了"。
--edges直接读磁盘像素,不受缓存影响。
墨迹扫描(bbox 模式定边界、masks 遮盖后复查时使用):
python scripts/check_figures.py 笔记.md --ink figures/fig_a.png --y0 69 --scale 3 # 逐行:找图例墨迹下界
python scripts/check_figures.py 笔记.md --col figures/fig_a.png --x0 58 --scale 3 # 逐列:找右边界 / 校徽起始列
用 --ink 找上下边界时,要扫出"连续空白行区间"再取边界值,不要凭肉眼估——正文行之间、图注与正文之间通常都只有 4~8pt 的空白带。定位步骤:先扫一遍打印逐行墨迹,找到 dark=0 的连续行区间,取该空白带偏上的位置作为 y1(保住图注、避开正文)。
第 7 步:清理中间文件
删除 _tmp/ 等全部中间产物,只保留最终 Markdown 与 figures/。不要删除用户的历史产出或原始课件,那些不是临时文件。
常见问题
| 问题 | 处理 |
|---|---|
| 裁剪过紧、内容截断 | 用 auto 模式的零截断算法;宁可截大,不可截破 |
| 只按 images bbox 裁,矢量图被切掉大半 | 种子框必须纳入 curves/lines/rects |
| 裁剪框被拉到接近整页 | 通栏装饰线混入种子框(面积接近 0,面积阈值挡不住);auto 模式已自动剔除 |
| 裁剪边界扩到页边、混入校徽/页眉 | deco 未填全;重跑 --diagnose-deco,通常有两个角标 |
| 外扩把邻近公式小图截进来半个 | 整块位图型图例不要套联合 + 外扩,改用 bbox 模式 |
| 图例底部与下方正文重叠 1~2pt,裁出半个字 | 用逐行墨迹扫描找图例墨迹真实下界,与正文 word 的 top 比对后定边界 |
| 遮盖矩形压进图例本体、啃掉边框 | 遮盖只能从图例本体边界之外起笔,先用列扫描量出确切列 |
| 吸收只做一轮,扩张后又切到新词 | 吸收必须循环到不动点 |
| PPT 文本抽取顺序错乱、公式丢失 | 结合渲染图判断版面,关键公式对照渲染页核准 |
| 截了纯文字页或与正文重复的图 | 执行第 2 步"只截真图"标准 |
| 图片路径用了绝对路径 | 用 figures/xxx.png 相对路径,便于笔记整体移动 |
| 只靠目视复核、被看图工具的缓存旧图骗了 | 重裁后立刻看图可能看到上一版 → 必须跑 check_figures.py --edges 做客观检测 |
| 裁剪框底部混进页脚色带(横贯页面的彩色带) | 页脚带通常在 y ≥ 399.5pt(页高 405)→ y1 取 ≤ 398;量墨迹时也要排除 y≥399 的行 |
图注与正文行分不清,y1 取错带进半行正文 |
逐行墨迹统计找连续空白行区间再取边界;两者之间常只有 4~8pt 空白,不能凭肉眼估 |
| 测量 ROI 给窄了,读数贴在 ROI 边上却当成真边界 | 读数与 ROI 边界相差 <2pt 说明还没测到真边界,放宽 ROI 重测 |
忘了清理 _tmp/ |
第 7 步强制检查 |
仓库结构
.
├── SKILL.md # 本文件:完整工作流(agent 加载此文件即可工作)
├── README.md # 面向人的介绍与快速开始
├── requirements.txt # pdfplumber / pypdfium2 / Pillow
├── examples/
│ └── figures.example.json # 裁剪配置示例(auto / bbox / masks)
└── scripts/
├── extract_and_render.py # 第 1 步:抽文本 + 图形统计 + 整页渲染 + 角标诊断
├── crop_figures.py # 第 3 步:配置驱动的图例裁剪
└── check_figures.py # 第 6 步:引用校验 + 墨迹扫描