ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从文本层体检到公式OCR:初中数学北师大版PDF处理指南

从文本层体检到公式OCR:初中数学北师大版PDF处理指南 简介《初中数学北师大版.pdf》是一份以北师大版初中数学教材为蓝本的知识梳理资料面向初中生、数学教师及备考复习者帮助快速把握七至九年级的整体知识框架。资源包仅含1个PDF文件大小约10KB内容以目录式章节结构呈现覆盖七年级的丰富的图形世界、有理数及其运算、字母表示数、平面图形、一元一次方程八年级的整式运算、平行线与相交线、生活中的数据、概率、三角形、变量关系、生活中的轴对称以及九年级的勾股定理、实数、图形平移与旋转、一元一次不等式、分解因式、分式、相似图形等核心知识点。每个章节还细化了课题学习比如“制成一个尽可能大的无盖长方体”“制作视力表”“设计遮阳棚”等既能作为教师备课时的章节导航也方便学生按索引进行阶段性复习。目前该资源已有497人学习适合用来对照北师大版教材快速定位知识点构建完整的初中数学知识网络。1. 拿到「初中数学北师大版.pdf」后先别急着翻开看老师从区教研组拿到的整套教材、学校共享盘里的旧版课本、机构资料库里躺了许久的课件 PDF——这类文件名很规矩的「初中数学北师大版.pdf」内部往往一点都不规矩。有的整页是扫描图有的复制出来全是乱码有的公式在页面上印得清清楚楚程序里却找不到对应的字符。问题出在 PDF 的渲染层和文本层脱节而数学教材恰恰是排版最复杂的那类。下面先把文本版和扫描版的判定讲清再分别处理公式、目录、表格和插图最后给一条能直接跑的批处理管线。适合教研系统开发、题库建设工程师和需要批量清洗 PDF 的内容运营直接照搬。2. 先给「初中数学北师大版.pdf」做体检文本层厚度决定路线拿任何教材 PDF 干活第一步永远是体检而不是直接打开试读。阅读器能渲染的内容和程序能抽取的内容是两回事文字版 PDF 有一层看不见的文本层扫描版没有有文本层的也会因为字体嵌入方式怪异而抽取失败。北师大版初中数学这种公式密集、图文混排的教材扫描页、公式乱码、带线表格三类问题常常同时出现在一本书里先看清楚症状再开药方。2.1 pdfinfo 看页数与加密状态先确认文件没坏体检第一件事是用 PDF 元数据工具看文件基本信息。poppler-utils 自带的 pdfinfo 一个命令就能拿完pdfinfo 初中数学北师大版.pdf | head -n 20 # 只看前20行避免元数据过长重点看四个字段。Pages 确认页数与封面标注一致防止下载不完整Page size 看是 A4 还是 16 开后面截图裁剪的坐标体系依赖它Encrypted 为 yes 时PyMuPDF 打开后要调 doc.authenticate(密码) 才能渲染内容Producer 字段如果出现 Scanner、Paper Capture 之类的字样基本可以断定这份 PDF 是扫描产物直接跳到 OCR 路线。提示macOS 上用 brew install poppler 装这套工具Ubuntu 系用 apt install poppler-utilsWindows 用户可以直接绕开 pdfinfo用下一节的 PyMuPDF 完成同样判断。2.2 用 PyMuPDF 逐页统计字符量扫描版一眼看穿pdfinfo 只能看整体页面级别的体检要用 Python。PyMuPDF 的页面对象提供 get_text(text)它返回 PDF 内部文本层里的字符串扫描页没有文本层返回空串或者极少字符。逐页数一遍字符量整本书是哪种货色就清楚了import fitz doc fitz.open(初中数学北师大版.pdf) char_counts [] for page in doc: text page.get_text(text).strip() char_counts.append(len(text)) # 空页或纯图片页接近0 total len(doc) thin sum(1 for n in char_counts if n 50) # 50是薄页阈值 print(f总页数: {total}, 文本低于50字符的页: {thin}) print(f薄页占比: {thin / total:.1%})这段代码有三个细节值得说。get_text 的 mode 参数默认是 text返回纯文本字符串strip 是为了去掉空页可能带有的空白字符否则空页会被数成 0 或 1判断失真。阈值为什么取 50教材页面哪怕只有章节标题和三行正文字符数也远超过 100低于 50 的页面要么是整页图片要么是只有页码的空白页在教材场景里都算薄页。把总页数和薄页占比打出来一份 PDF 的文本层厚度分布就呈现在眼前了。2.3 薄页占比三档分流文字版、混合版、扫描版各有打法拿到薄页占比后按下面这张表分流薄页占比判定处理路线0% - 10%文字版 PDF直接文本抽取公式按字体定位特殊处理10% - 60%混合型 PDF逐页判定薄页单独走 OCR60% - 100%扫描版 PDF整页渲染后走 OCR公式单独识别文字版不是免检产品。有些 PDF 的文本层是出版时由 OCR 工具自动生成的字符量正常但公式区域错字成堆。体检时顺手抽一页打印前 200 个字符看到类似「□」「堒」这种替换符密集出现说明文本层质量不可靠公式区域按第三章的图片方案处理。另外注意页面旋转体检循环里加上 page.rect.width 和 height 的比较宽大于高说明页面是横向的先去 page.set_rotation(0) 纠正否则后面 OCR 的文字方向会整体错掉。3. 数学公式不是普通文字这套教材 PDF 的三种抽取手段教材 PDF 里的公式跟普通段落文字完全不是一类东西。LaTeX 编译出的 PDF公式是排版引擎用数学字体画出来的字形Word 另存的 PDF公式往往是 OMML 渲染后的矢量图形扫描版的公式干脆就是像素。三种形态对应三种抽取路径先搞清楚手头是哪一种再决定工具这是处理数学教材 PDF 最关键的判断。3.1 文字版里公式乱码get_text(rawdict) 和字体名定位法文字版 PDF 的公式乱码绝大多数源于数学字体。CMSY、CMMI、MTExtra、STIX 这类数学字体在 PDF 里经常用私有编码区映射字符阅读器能结合字体程序正确渲染但 get_text 拿到的码点对应不上 Unicode复制出来就成了乱码。别急着修字库我的做法是把公式当图片处理。用 rawdict 模式拿到每个 span 的字体名和坐标import fitz doc fitz.open(初中数学北师大版.pdf) page doc[10] raw page.get_text(rawdict) math_fonts {CMSY, CMMI, MTExtra, MSAM, STIX} target_spans [] for block in raw[blocks]: for line in block.get(lines, []): for span in line.get(spans, []): font span[font] if any(mf in font for mf in math_fonts): # 包含匹配兼容CMSY10这类带尺寸后缀的字体名 target_spans.append(span)rawdict 返回的层级是 block → line → spanspan 里有 font 字体名和 bbox 坐标框。这里用包含关系而非全等匹配是因为很多数学字体名带前缀或尺寸后缀比如 CMSY10 表示 10 磅的 CMSY。这一步的价值在于不用肉眼翻页找公式代码直接告诉你哪些坐标区域属于数学字体接下来只需要按 span 的 bbox 裁剪成图片让公式识别模型处理绕开乱码字符的还原难题。3.2 扫描页公式 OCRpix2tex 转 LaTeXTesseract 只认中文扫描版本没有文本层公式只能靠识别。Tesseract 对中文正文的识别很稳但对分式、根号、上下标几乎无能为力所以标准分工是中文段落交给 Tesseract裁剪出的公式区域交给 pix2tex 这个 LaTeX-OCR 模型。pip install pix2tex # 安装 LaTeX-OCR 推理环境 pix2tex /tmp/formula_crop.png识别结果是一段 LaTeX 源码比如 \frac{x}{2}\sqrt{3} 这样的结构。这段源码可以直接放进 LaTeX 文档编译也可以转成 MathML 渲染在网页里。pix2tex 的输入只接受公式截图不接受整页图片所以使用前必须把公式区域裁出来这是整个流程里最需要人工介入的一步。裁剪工具不限上一节的坐标框、下一节的 xref 抠图、或者任何标框工具都行唯一硬性要求是截图里别带中文注释模型对中文标签会输出奇怪的结构。3.3 混合版里按 xref 抠公式图去重后逐个识别混合型 PDF 里公式经常以嵌入图片的形式出现直接按图片对象抠出来最快import fitz doc fitz.open(初中数学北师大版.pdf) page doc[15] seen set() for img in page.get_images(fullTrue): xref img[0] if xref in seen: continue # 同一xref只处理一次避免跨页重复图 seen.add(xref) info doc.extract_image(xref) with open(fpage16_{xref}.{info[ext]}, wb) as f: f.write(info[image])get_images(fullTrue) 返回的元组里第一个元素 xref 是图片在 PDF 对象流里的编号同一张图在多个页面重复引用时 xref 相同所以用 seen 集合去重避免输出目录里堆满重复几何图。extract_image 返回的 dict 里ext 是图片扩展名image 是原始字节。注意一个坑这里取到的是嵌入的原始图片如果页面上对图片做了缩放旋转原始图可能和显示效果不一致要求所见即所得时改用 page.get_pixmap(cliprect)rect 取页面显示坐标。三种手段的适用场景可以这样选场景手段准确率成本文字版特殊数学字体字体名定位 裁剪 span高低扫描版公式密集pix2tex 逐块转 LaTeX中等需要一定 CPU/GPU 时间混合版公式是嵌入图按 xref 抠图后批量识别高低实操建议公式识别不要追求一次到位。先把公式图归档批量跑完 pix2tex 后把输出的 LaTeX 渲染成缩略图抽查 20 个统计可编译率。公式转 LaTeX 出错是常态翻车率控制在 15% 以内就可以交付把 20 个里出错的人工重识成本比逐个人工录入低一个数量级。4. 抽目录、习题表、几何图把整本数学 PDF 变成可检索数据公式之外教材 PDF 最有复用价值的骨架是目录、习题框和几何插图。北师大版初中数学每章末尾都有「随堂练习」「习题」「复习题」区块这些内容在排版上常常是表格或带边框的文本框抽取策略跟正文完全不同。把这三类结构抽干净整本 PDF 就能变成一个可检索、可引用的资源库。4.1 get_toc() 提取章节书签没有书签就按标题正则兜底PyMuPDF 直接读 PDF 内嵌书签一行代码拿到整本书的章节层级import fitz doc fitz.open(初中数学北师大版.pdf) toc doc.get_toc() if not toc: import re toc [] for pno in range(len(doc)): first_line doc[pno].get_text(text).strip().split(\n)[0] m re.search(r^(第[一二三四五六七八九十百\d]章)\s*(.*)$, first_line) if m: toc.append((1, m.group(1) m.group(2), pno 1)) for level, title, page in toc: print( * (level - 1) f{title} → 第 {page} 页)get_toc 返回 (层级, 标题, 页码) 三元组列表level 从 1 开始1 是章、2 是节打印时按层级缩进就是一棵目录树。很多扫描版 PDF 没内嵌书签get_toc 返回空列表这时用正则兜底取每页第一行文本匹配「第一章 有理数」这类模式。正则里的 [一二三四五六七八九十百\d] 覆盖中文数字和阿拉伯数字两种章号写法\s*(.*)$ 把章标题剩余部分列出来。兜底匹配会在空白页和奇数页页码上误报所以拿到 toc 后建议人工扫一眼前 10 条确认。4.2 pdfplumber 抽习题表格vertical_strategy 先定线条策略习题框和答案表在 PDF 里经常是带边框的表格。pdfplumber 的 extract_tables 是这类抽取的标准工具但策略参数直接决定成败import pdfplumber with pdfplumber.open(初中数学北师大版.pdf) as pdf: page pdf.pages[20] table_settings { vertical_strategy: lines, horizontal_strategy: lines, } tables page.extract_tables(table_settings) for row in tables[0]: cleaned [(cell or ).replace(\n, ) for cell in row] print( | .join(cleaned))extract_tables 接受一个 dict 参数。vertical_strategy 和 horizontal_strategy 有 lines、text、explicit 三种取值教材习题表通常画了完整表格线用 lines 最稳text 策略在没有网格线时靠字符间距推断列位置遇到数学公式这种字符分布不规律的内容容易对齐错explicit 是手动指定表格区域坐标适合版式完全固定的试卷。读出来的 row 里跨行合并单元格的位置是 None代码里用 (cell or ) 统一处理成空字符串避免后续拼接报错。vertical_strategy适用情况主要风险lines有完整表格线的习题框无线表格直接漏检text无表格线排版公式干扰列对齐explicit版式固定的答题卡坐标对不上就全错4.3 几何图按像素尺寸过滤函数图像单独归档教材插图的质量差异很大几何题的坐标系图、立体图是有价值的资源装饰用的分隔线、页码旁的小图标、背景纹理是噪音。按像素尺寸过滤是最省力的清洗手段import fitz doc fitz.open(初中数学北师大版.pdf) for pno in range(20, 30): for img in doc[pno].get_images(fullTrue): xref img[0] pix fitz.Pixmap(doc, xref) if pix.width 120 or pix.height 120: continue # 过滤装饰性小图 pix.save(ffigures/p{pno 1}_{xref}.png)Pixmap(doc, xref) 直接按图片编号构建位图对象width 和 height 是图片的像素尺寸。120 这个阈值来自经验教材里几何坐标图一般 300 像素以上装饰图通常只有几十像素低于 120 的图基本没有复用价值。归档命名用「页码 xref」双段页号方便回溯到教材位置xref 保证同图跨页时能识别出重复。如果想按章节归档把第 4.1 节抽出的 toc 页号范围映射到页码上图文件名前加章节前缀即可。5. 一条管线跑完「初中数学北师大版.pdf」再用三个指标验收前面几章的代码可以单独跑但整本教材动辄一两百页手工分段处理不现实。最后一步是把体检、抽取、OCR 串成一条可重复执行的管线保存成 pdf_pipeline.py 直接运行输出一份带页签和目录的 Markdown。5.1 端到端脚本逐页判定文本层薄页自动 OCR#!/usr/bin/env python3 import fitz import os import subprocess PDF_PATH 初中数学北师大版.pdf OUT_DIR output DPI 200 # 渲染分辨率200是速度与精度的折中 def ocr_image(png_path: str) - str: res subprocess.run( [tesseract, png_path, stdout, -l, chi_sim], capture_outputTrue, textTrue, checkFalse, ) return res.stdout.strip() def main(): os.makedirs(OUT_DIR, exist_okTrue) doc fitz.open(PDF_PATH) lines [## 目录] for level, title, page in doc.get_toc(): lines.append( * (level - 1) f- {title} (第 {page} 页)) for pno in range(len(doc)): text doc[pno].get_text(text).strip() if len(text) 50: # 薄页判定低于50字符走OCR pix doc[pno].get_pixmap(dpiDPI) png os.path.join(OUT_DIR, fp{pno 1}.png) pix.save(png) text ocr_image(png) lines.append(f\n!-- 第 {pno 1} 页 --\n{text}) out_path os.path.join(OUT_DIR, result.md) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) print(f完成: {OUT_DIR}/result.md共 {len(doc)} 页) if __name__ __main__: main()参数上有三个点值得说。DPI 取 200 是速度与精度的常规折中值只认正文 150 就够要连着下标一起识别出来就提到 300tesseract 的 -l chi_sim 指定简体中文语言包缺了就 apt install tesseract-ocr-chi-sim。目录部分直接用了 get_toc扫描版没书签时要补上第 4.1 节的正则兜底。整本书串行跑可能要半小时想提速就把循环体放进 ProcessPoolExecutor每个 worker 单独 fitz.open因为 fitz 对象不能跨线程共享。5.2 用三个指标验收10 分钟内出结论管线跑完先验收再交付。文字错误率随机抽 3 页OCR 文本与原书对比错误字符占 1% 以内算合格重点盯数字和英文字母这两类是 OCR 最容易错的位置。公式可编译率从公式归档里抽 20 张图把 pix2tex 输出的 LaTeX 逐条送进 KaTeX 渲染成功渲染率低于 85% 就回头查裁剪区域是否混入正文。表格结构完整率拿 pdfplumber 输出与原表对比列数列数不齐的先查 vertical_strategy 是否用了 lines再看表头是否跨页。5.3 三处高频翻车点改法都在一行内双栏页文字乱序get_text(text) 默认按内部流顺序返回双栏教材经常左栏读一半跳右栏把调用改成 page.get_text(text, sortTrue) 按坐标重排即可。旋转页 OCR 横过来处理前先看 page.rotation 和 page.rect 宽高比rotation 非 0 的页面先 set_rotation(0) 再渲染顺序错一步后面全白跑。公式图与插画混同归档按第 4.3 节的尺寸阈值分目录公式单独放 formula/ 目录避免几何图被批量喂给 pix2tex 刷出一堆无效 LaTeX。本文还有配套的精品资源点击获取
返回列表