ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从PyMuPDF版面分析到公式识别:机械能守恒检测卷结构化解析

从PyMuPDF版面分析到公式识别:机械能守恒检测卷结构化解析 简介这份 PDF 是面向高中物理必修二学生的《机械能守恒定律》章节检测卷及答案解析适合单元自测、期末复习与错题订正使用。题目围绕动能、重力势能与弹性势能的相互转化延伸到嫦娥五号轨道变轨、弹簧压缩与弹回、汽车恒定功率启动、传送带多消耗电能等典型情景并配有选择题与填空题的逐项辨析和计算过程。资源包仅含 1 个 PDF 文件大小约 548KB下载后可直接打印或在线查阅答案部分对易错选项、机械能守恒条件以及变力做功的处理给出了较细致的说明。目前已有 64 人学习下载可作为课堂检测的补充材料也适合教师备课选题、学生对照解析梳理能量观点强化从受力分析到能量守恒的综合建模能力。1. 从一份《机械能守恒定律》检测卷 PDF 说起期末前两周教务群里转得最多的文件名字往往长得离谱上海上海市实验学校西校高中物理必修二第八章《机械能守恒定律》检测(答案解析).pdf。学校、册次、章节、题型、答案解析全塞进文件名里。麻烦也跟着来了——这类试卷基本是排版软件导出的双栏 PDF题干、插图、选择题选项、参考答案区混排在同一页直接复制出来公式全散架搜「动能定理」搜不到想按知识点重新组卷只能人工拆。把它变成可检索、可结构化、能自动生成解析的数据是一次很典型的文档智能工程版面分析决定切分边界公式识别决定知识点能不能对上解析模板决定后续能不能批量复用最后还得靠数值验算把质量兜住。下面按「解析版面—识别公式—生成解析—校验质量」四步讲一套能在本地跑通的方案环境只需要 Python 和一个能装包的解释器。2. 用 PyMuPDF 解析检测卷版面题干、选项与答案区的切分一份《机械能守恒定律》检测卷的信息密度很高正文是双栏题干里有上下标和希腊字母旁边夹着斜面、单摆、弹簧的矢量图末尾还有一个独立的「参考答案与解析」区块。用纯文本抽取比如某些在线转换工具拿到的是一锅粥因为丢掉了坐标和字号这两个最关键的版面信号。这一章先把版面拆干净。2.1 用 PyMuPDF 拿到文本块、坐标和字号第一件事不是写正则而是把每个 span 的坐标、字号、字体都打印出来看一眼。get_text(dict)会保留这些信息这是后面所有切分规则的输入。import fitz # PyMuPDFpip install pymupdf PATH 上海上海市实验学校西校高中物理必修二第八章《机械能守恒定律》检测(答案解析).pdf doc fitz.open(PATH) for pno, page in enumerate(doc): w, h page.rect.width, page.rect.height blocks page.get_text(dict)[blocks] for b in blocks: if b.get(type) ! 0: # type0 是文本块1 是图片块受力图、纸带图 print(pno, IMAGE, [round(v) for v in b[bbox]]) continue for line in b[lines]: for span in line[spans]: # bbox 是 (x0, y0, x1, y1)页面坐标系原点在左上角 print(pno, round(span[size], 1), span[font], [round(v, 1) for v in span[bbox]], repr(span[text]))跑完这一步通常能直接看出三件事正文正文字号集中在同一个值比如 10.5标题和「参考答案」这类区段头会明显更大或加粗页眉页脚字号偏小且 y 坐标贴边。span[bbox]里的 x0 则决定它属于左栏还是右栏。图片块单独标出来很重要物理卷里的机械能守恒题几乎都配图题干文字和图的相对位置就是判断「这题属于哪一小问」的依据。2.2 双栏、页眉页脚与题号的切分规则拿到坐标后规则可以写得非常直白。先把页面上所有 span 按栏分组页面中线取w/2x0 w/2的进左栏其余进右栏如果某个 span 横跨中线x0 w/2 x1且宽度超过页宽的三成判定为通栏标题单独成组。分组后再按 y0 排序双栏顺序就正确了。页眉页脚和题号的判定可以固化到一张表里避免每次都凭感觉调阈值版面元素判断依据处理动作页眉校名、章节名y0 页高 × 0.06 且字号小于正文丢弃不计入题干页脚 / 页码y1 页高 × 0.94丢弃大题号匹配^[一二三四五六]、新建大题分组记录题型小题号匹配^\d{1,2}[.、]新建题目对象选项匹配^[A-D][.、]或四个 A/B/C/D 位于同一行追加到当前题目的选项列表答案区起点文本命中「参考答案」「答案解析」「答案」切换 section 标记为answer配图block type 1且与题干 y 区间重叠挂到当前题目的figures字段关键点是「状态机」思路从左到右、从上到下扫一遍 span遇到小题号就开一个新题目遇到选项就追加遇到答案区标记就把后续内容全部归到答案池。页码这类噪声在扫描阶段直接丢比事后清洗省事得多。2.3 答案区定位与题干对齐答案区通常有两种形态一种是「1. B 2. D 3. ACD」的选择题答案速查表另一种是逐题的「解析」。速查表按题号对齐用正则(\d{1,2})[.、]?\s*([A-D]{1,4})一把梭即可逐题解析则要看它有没有复述题号。我一般会做一次双向对齐校验解析里出现的题号集合和题干区解析出的题号集合取对称差。差集非空就说明切分漏了题或者串了题——常见原因是跨栏题干被切到了两栏或者某道题的图把文字块打散了。import re def align_answers(stems: dict, answers: dict) - None: stems/answers 均为 {题号: 内容}打印对不上的题号 missing sorted(set(stems) - set(answers), keyint) # 有题干没答案 extra sorted(set(answers) - set(stems), keyint) # 有答案没题干 if missing: print(缺答案的题号, missing) # 通常要去 2.2 表格里放宽题号正则 if extra: print(多出的答案, extra) # 多半是解析里把分数、页码误认成题号这一步做完一份试卷就变成了{题号: {题干, 选项, 图, 答案, 解析}}的结构后面所有处理都在这个结构上做不再碰 PDF。3. 机械能守恒公式的识别与归一化让 E_k、ΔE_p 不再散架题干拆出来了但内容不能直接入库。物理卷里v²、E_k、ΔE_p、mgh、kg·m/s²这些东西在 PDF 内部是靠字体和位置拼出来的纯文本抽取会把上标压回基线v²变v2E_k变Ek。知识点打标全靠字符串匹配公式一散标签就全错。这一章解决公式保真问题。3.1 为什么纯文本抽取保不住 WmghPDF 里没有「公式」这个对象只有一堆带坐标的字形。上标是字号更小、y 坐标更靠上的独立 span分数线是一条细长的矩形或者一张小图根号常常是图片。这意味着三件事同时发生上下标层级丢失、分数结构丢失、根号直接变图片。实践中不用死磕「还原成排版级 LaTeX」够用就行。真正影响检索和打标的是三样东西物理量符号E_k、E_p、ΔE、幂次v²、h³、单位J、N/m、m/s²。把这三样归一化剩下的交给人工在解析模板里校对。另外要处理的是字形兼容问题。同一份卷子里Δ可能以三种码位出现U0394、U2206、以及某些字体下的私有区字形×和·也常混用。统一做一次 Unicode 规范化能省掉后面无数个「明明看着一样却匹配不上」的坑。3.2 公式区域检测与 LaTeX 归一化公式区域检测不需要上模型。一个足够好用的启发式是同一个文本块内如果出现「字号不一致的相邻 span」且高频命中物理量符号表就判定为公式区走归一化管线其余走普通文本清洗。import re import unicodedata # 先做 Unicode 规范化压掉同形异码 def clean(s: str) - str: s unicodedata.normalize(NFKC, s) return (s.replace(\u2206, \u0394) # 增量符号统一成 Δ .replace(\u00b7, \u00b7) # 中点保持 .replace(\u2212, -)) # 减号统一成 ASCII 连字符 # 物理量写法 - 规范 LaTeX顺序敏感长的放前面 RULES [ (r\bE\s*[kK]\b, rE_{k}), # 动能 (r\bE\s*[pP]\b, rE_{p}), # 重力势能 / 弹性势能 (r\u0394\s*E\s*[pP], r\Delta E_{p}), # 势能变化量 (r\bW\s*\u5408\b, rW_{\u5408}), # 合外力做功 (r\b([vhmg])\s*\^?\s*2\b, r\1^{2}), # v2 / v^2 - v^{2} ] def normalize_formula(s: str) - str: s clean(s) for pat, rep in RULES: s re.sub(pat, rep, s) return re.sub(r\s{2,}, , s).strip()规则表按「先长后短」排列很关键如果把E_p的规则写在\Delta E_p前面ΔEp会先被拆成ΔE_{p}结果变成ΔE_{p}而不是带\Delta的整体语义上就差了一层。NFKC规范化能顺带把全角括号、全角数字、全角减号统一掉中文试卷里这类字符非常多。单位单独处理因为它们决定数值换算对不对原始写法规范写法说明JJ能量机械能守恒题的核心单位m/s^2、m/s2m/s²重力加速度kg·m/s²kg·m/s²与 N 等价保留原样便于对照题干N/mN/m弹簧劲度系数cm、mcm / m实验题用 cm计算题用 m必须显式标注注意单位混用是物理题解析出错的第一大来源。打点计时器纸带实验用厘米机械能守恒计算用米流水线里必须带上单位字段不能只存数字。3.3 符号映射与打标前置归一化之后知识点打标的准确率取决于符号层是否统一。我一般维护一份小映射表把卷面里出现过的所有物理量别名收敛到唯一符号再交给打标规则。这样即使出题老师这次写E_k、下次写Ek、再下次写「动能」落到库里都是同一个标记。打标规则本身用正则集合就够不需要模型命中「只有重力做功」「只有重力和弹力做功」「机械能守恒」归到主知识点命中「动能定理」「合外力做功」归到相邻知识点命中「打点计时器」「纸带」「光电门」归到实验类。经验上一份 20 题的章节检测卷人工过一遍打标规则大概十分钟之后这个章节的所有卷子都能复用。4. 从题干到解析答案一条可复现的生成流水线前两章把内容变成了结构化数据这一章把数据变成能直接发出去的「检测卷 答案解析」。整条流水线的关键是解析不能靠自由生成必须靠模板 数值验算。物理计算的每一步都有确定的公式模板化之后的解析比自由文本更可靠也更容易批量校对。4.1 题干切分与知识点打标先定义数据模型题目对象要能承载小问、选项、配图、答案、解析和标签。from dataclasses import dataclass, field import re dataclass class Question: no: str stem: str options: list field(default_factorylist) sub: list field(default_factorylist) # 小问如 (1)(2)(3) figures: list field(default_factorylist) answer: str analysis: str tags: list field(default_factorylist) TAG_RULES { 机械能守恒: [r机械能守恒, r只有重力做功, r只有重力和弹力做功], 动能定理: [r动能定理, r合外力做功, rW\s*\s*\u0394E], 重力势能: [r重力势能, rmgh, rE_\{p\}], 弹性势能: [r弹性势能, r弹簧, rk\s*x], 实验探究: [r打点计时器, r纸带, r光电门, r刻度尺], } def tag_stem(stem: str) - list: return [t for t, pats in TAG_RULES.items() if any(re.search(p, stem) for p in pats)]小问切分用^\((\d)\)这个模式注意中文卷子里还有1全角括号的写法NFKC之后会统一成半角所以正则只写半角即可。小题号如果跨栏断行靠 2.2 的栏分组保证顺序不要用纯 y 坐标排序否则右栏的内容会插到左栏中间。4.2 解析模板与数值校验解析生成分两类。选择题走「选项辨析」模板把正确选项的物理依据写成一句话再把最常见的错误选项对应的错误物理模型点出来。计算题走「步骤模板」列出研究对象、受力与做功分析、列机械能守恒方程、代入求解、检查合理性。无论哪类数值都必须过一遍验算函数这是整个流水线里最值钱的一环。def check_conservation(m, h1, v1, h2, v2, g9.8, tol1e-3): 校验一段过程是否满足机械能守恒E1 E2 m 单位 kgh 单位 mv 单位 m/sg 默认 9.8 返回 (是否守恒, E1, E2) e1 m * g * h1 0.5 * m * v1 ** 2 e2 m * g * h2 0.5 * m * v2 ** 2 ok abs(e1 - e2) tol * max(abs(e1), 1e-9) return ok, e1, e2 # 例m1kg 从 h5m 自由下落求落地速度g 取 9.8 import math v math.sqrt(2 * 9.8 * 5) # 约 9.899 m/s print(check_conservation(1, 5, 0, 0, v)) # (True, 49.0, 49.0)tol用相对误差而不是绝对误差因为不同题目的能量数量级差很多单摆题可能是 0.5 J滑块题可能是 500 J。相对误差取 1e-3正好能容忍答案四舍五入到两位有效数字带来的偏差又不会把真的算错的题放过去。如果卷面给出 g10不少初中衔接题会这么写调用时显式传入g10别改默认值否则同一批卷子里两种取值会互相污染。4.3 批量导出与检索索引结构化的最后一步是入库让它可搜、可组卷、可追溯来源试卷。用 SQLite 就够FTS5 负责全文检索。-- 主表一道题一行paper 字段记录来源试卷文件名 CREATE TABLE questions ( id INTEGER PRIMARY KEY, paper TEXT NOT NULL, no TEXT NOT NULL, stem TEXT NOT NULL, answer TEXT, analysis TEXT, tags TEXT, -- 逗号分隔如 机械能守恒,重力势能 UNIQUE(paper, no) ); -- 全文索引覆盖题干和解析组卷时按关键词召回 CREATE VIRTUAL TABLE q_fts USING fts5( stem, analysis, tags, contentquestions, content_rowidid, tokenizeunicode61 ); -- 按知识点查题召回所有机械能守恒题按来源卷分组 SELECT paper, no, substr(stem, 1, 40) FROM questions WHERE tags LIKE %机械能守恒% ORDER BY paper, CAST(no AS INTEGER);UNIQUE(paper, no)这一条约束能在重复解析同一份 PDF 时自动挡住脏数据比在应用层判重更省心。tokenizeunicode61对中文按字切分虽然不如专门的中文分词精确但对「机械能守恒」「动能定理」这类固定术语的召回已经够用如果卷子里术语写法多变可以在入库前把 tags 也写进 FTS 表的 tags 列靠标签召回比靠原文召回稳。导出环节建议同时产出两份东西一份给人看的 Word/PDF题干在前、答案解析在后题号严格对应一份给机器用的 JSON。两份用同一个id出问题时能直接顺着 id 回查原始页码和坐标。5. 自动验算与抽检把机械能守恒检测卷的解析质量管住结构化做完真正的风险才露头错一道题的答案整份卷子的解析都得返工。质量把控靠两件事——数值验算和定点抽检。5.1 用守恒关系做交叉验算凡是带数值的机械能守恒题都应该能用check_conservation复算一遍。做法是从题干里抽出所有数值和单位转成标准单位后代入公式把计算结果和卷面给出的答案比对。选择题如果选项里只有一个值能通过验算那这个选项的答案基本可以自动确认如果有多个值都能通过说明题目本身缺条件需要人工介入。import re NUM re.compile(r(-?\d(?:\.\d)?)\s*(m/s|m|kg|cm|J)) def extract_quantities(text: str) - dict: 从题干里抠出带单位的量统一换算成 m/s、m、kg、J out {} for val, unit in NUM.findall(text): v float(val) if unit cm: v, unit v / 100.0, m # 厘米一律换算成米 out.setdefault(unit, []).append(v) return out这个抽取器很粗糙但只要在解析模板里预先把变量名和单位绑好比如h1一定对应「高度」「距地面」这类词准确率就能上一个台阶。不建议在这一步上大模型规则错了能一眼看出来模型错了很难定位。5.2 抽检清单与回退路径全自动跑完之后按下面这张表做一次抽检每一条都对应一个明确的回退位置出问题不用从头查。抽检项抽样比例判错标准回退动作题号连续性100%缺号、重号、跳号回到 2.2 检查页眉丢失与题号正则公式渲染10%出现裸露的v2、Ek回到 3.2 补公式映射规则单位一致100%同一题内 cm 与 m 混用检查 4.2 的单位换算入口答案覆盖100%有题干无答案回到 2.3 检查答案区定位数值验算100%守恒等式相对误差 1e-3人工复核题干条件是否完整分值合计100%各题分值之和不等于卷面满分回到 2.2 检查大题分组最后补一个实操技巧把g的取值、单位制、以及每道题用到的公式编号一起写进 JSON 的元数据里别只存答案。同一份上海上海市实验学校西校的章节检测卷下次改版重排时只要版面结构没大改第 2 章的切分规则和第 3 章的公式映射都能直接复用只有 4.2 的数值验算需要重新跑一遍。遇到解析里出现两个不同的g值时先查 3.3 的符号映射表再回头确认题干给出的重力加速度取 9.8 还是 10这一条能挡掉大半的数值对不上。本文还有配套的精品资源点击获取
返回列表