毕设做公式识别,把 OpenCV 和深度学习串起来用,听起来像是个难啃的大题,实际拆开之后就是三条线:图像预处理、模型识别、后处理输出。每条线单独看都不算特别复杂,但合在一起非常锻炼工程能力,也很出活。这种课题特别适合当毕业设计,既能体现你懂算法,又能说明你有能力把一套完整流程落地。
这个项目解决的核心问题很简单:给一张公式图片(或者拍照拍到的纸质公式、书本截图),程序输出对应的 LaTeX 代码。应用到实际场景里,就是把论文里的公式“复制”成可编辑格式,不用再手敲一遍。适合想走计算机视觉方向、尤其是 OCR 方向的同学,也适合对图像处理加序列生成感兴趣的人参考。
1. 课题定位与方案选型
1.1 这个题的本质是什么
公式识别本质上是一个 image-to-sequence 的任务,输入是图像,输出是文本序列。但它比普通 OCR 麻烦的地方在于,公式不是线性结构的,分子分母、上标下标、根号、矩阵这些二维结构,决定了你不能像识别车牌那样按顺序读字符。
所以整个课题的核心,不只是“识别出符号”,而是“理解公式的版面结构”。举个例子,“x^2” 和 “x_2” 在图像上都是 x 后面跟一个 2,但 2 的位置决定了它是上标还是下标,输出完全不一样。这需要的不是单纯的分类能力,而是对空间位置关系的理解。
这个方向很适合毕设的原因有两层:第一,任务边界非常清晰,输入输出都有明确约定,评估指标也好量化;第二,从数据准备、模型训练到界面演示,可以覆盖一套完整的项目流程,论文里能写的东西很多,不会出现“做完没内容写”的情况。
从难度上看,它属于中等偏上。一个基础版本用 CNN 做特征提取、序列模型做解码就能跑通,不需要去卷最前沿的算法。但如果想做得漂亮,又能在注意力机制、二维位置编码、数据增强这些方向深入,天花板很高。
1.2 三条主流技术路线怎么选
这几年公式识别的主流方案大致有三类,我直接给你画个对比。
| 方案 | 核心思路 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| 传统模板匹配 / 结构分析法 | 手写规则拆解符号和结构 | 不需要训练数据 | 鲁棒性差,换字体就废 | 只适合老古董课题 |
| 字符级识别 + 结构规则 | 先识别单个字符,再用规则拼公式 | 逻辑清晰、容易解释 | 对复杂结构处理很吃力 | 简单印刷体公式 |
| 端到端序列识别 | 图像直接映射为 LaTeX 文本 | 能处理复杂公式、主流方案 | 训练成本相对较高 | 推荐作为最终方案 |
我给你的建议是:做一套端到端序列识别为主、字符级识别为辅的“组合路线”。
为什么不是直接纯端到端?因为毕设有时间限制。纯端到端模型对数据的质量和数量要求比较高,尤其是公式这种结构复杂、符号形态多的内容。如果一开始就奔着大而全的模型去,很可能训练了很久收敛效果却不理想,心态容易崩。字符级方案虽然上限低,但作为 baseline 跑起来很快,既能让论文有“实验对比”,也能在模型出问题时当兜底方案。
1.3 OpenCV 在里面是配角但必不可少
很多人一听到“深度学习 + 公式识别”,注意力就全在模型上,觉得 OpenCV 就是预处理随便写两行。这恰恰是本末倒置了。我在实际调试中的体会是:模型决定识别的天花板上限,OpenCV 决定你能不能摸到这个上限。
真实场景里拍一张公式照片,会遇到光照不均、纸面阴影、拍摄角度倾斜、公式区域粘连正文文字这些问题。如果不做处理,再好的模型上去也会被这些噪声干扰。而 OpenCV 正好负责这一层的清理工作:把公式区域定位出来、矫正透视变形、消除光照影响、切成干净的单行输入。可以说,它扮演的是“后勤保障兵”角色,看起来不起眼,但前线能不能打好仗全看它。
2. 数据集准备是真正见真章的地方
2.1 公开数据集能不能直接用
公式识别领域确实有公开数据集可用,但直接拿来训练会踩不少坑。一个问题是标注格式不统一,有些是图片配 LaTeX 标签,有些是只有渲染后的 HTML,转成 LaTeX 还得自己写解析;另一个问题是图像风格过于单一,基本是白底黑字的印刷体,跟实际场景里拍照得到的灰底、偏色、模糊图像差距很大。
另外一个非常重要的提醒:做毕设时,公开数据集的预训练模型可以拿来作为参考或者迁移学习的起点,但绝对不能直接加载人家训练好的权重做推理,然后声称这是“本项目的识别效果”。这种事情在毕业答辩时一旦被追问,很容易露馅。正确做法是:用公开模型做 baseline,在自己的数据上重新训练或微调,然后在论文里如实写出对比和改进。
2.2 用 LaTeX 生成合成数据的具体做法
对公式识别这个方向来说,合成数据是完全可行的,原因很简单:公式的 LaTeX 描述就是标注本身,天然成对。你不需要手动标注几千张图,只要会写模板就能批量生成。
我的做法是准备一批基础公式模板,覆盖算术运算、函数表达式、指数对数、分式根号、矩阵等常见类型。然后写 Python 脚本做随机组合,比如随机挑几个变量名、数字、运算符,拼出新的公式。
大致结构可以这样设计:
import random # 基础符号池 numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] variables = ['x', 'y', 'z', 'a', 'b', 'c', 'n', 'm'] operators = ['+', '-', '\\times', '\\div', '='] def generate_latex(): # 公式模板库,简单示例 templates = [ "x^{a} + y^{b}", "\\frac{a}{b}", "\\sqrt{x}", "a \\times b - c", ] template = random.choice(templates) # 用随机变量和数字替换占位符 # 返回渲染用的 latex 字符串 return template # 循环生成,每生成一条 latex,就调用渲染工具转成图片 # 保存图片文件 + 对应的 latex 文本作为标注生成数量上,我的经验是两万到五万条起步就够了,不需要贪多。关键不是数量而是覆盖度,如果你公式里全是 x、y、z 这种简单的符号组合,模型学不出复杂的结构关系。要刻意让分式、根号、上下标出现的频率足够高,才不至于训练时模型“偏科”。
渲染工具的选择上,有几种方案,核心只需要能够把 LaTeX 转成图片。不同系统环境下的渲染配置略有差异,但思路是一致的:批量渲染、批量命名、自动保存对应的 LaTeX 文本。如果遇到某些 latex 片段渲染报错,直接跳过即可,不会影响整体数据生成。
2.3 用 OpenCV 做归一化
合成数据生成出来之后,通常是很规整的渲染图,可以直接进模型。但为了让模型在真实场景下也能表现好,我建议在送入网络前用 OpenCV 做一遍标准归一化流程。
具体步骤是:读图转灰度,然后用大津阈值(Otsu)做二值化,拿到字符区域;接着用投影法找公式的上下边界,裁剪掉多余的空白;最后把所有图片的高度统一到一个固定值,比如 48 像素,宽度按原比例缩放。这样做的好处是模型输入分布一致,训练起来更平稳。
需要注意的一个坑是:不要直接把图片强行 resize 到一个正方形,那样公式的长宽比会被破坏。比如一个很宽的分数表达式,被压缩成正方形后,分子分母会叠在一起,模型根本分不清结构。所以正确做法是固定高度、动态宽度,如果宽度超过设定上限,再考虑用滑动窗口切分或者在序列端加长度惩罚。
2.4 数据增强的隐藏坑
数据增强在一般图像分类里是随便怎么用都好用,但在公式识别里要非常克制。我踩过一个典型的坑:开了随机旋转增强,角度设成 10 度,结果训练出来的模型对倾斜非常敏感,正常不倾斜的图片识别率反而下降。原因就是公式里的字符排列紧密,一旦倾斜,上标和下标就会粘连,模型被带偏了。
真正有效且安全的数据增强组合是下面这几种:
- 随机亮度扰动,模拟拍照环境的光照变化;
- 轻度高斯噪声,模拟纸张纹理;
- 高斯模糊,模拟手机对焦不准;
- 小角度旋转,控制在正负 1.5 度以内,只模拟拍摄时轻微的手抖。
记住一个原则:公式识别的数据增强幅度一定要小,以“不破坏字符结构和相对位置”为前提。你要的是让模型更鲁棒,而不是把数据变成一张张模糊的迷宫图。
3. 模型构建与训练过程实录
3.1 视觉特征提取加序列解码
模型架构上,我用的组合是 CNN 做视觉特征提取,Transformer 做解码生成 LaTeX 序列。这算是目前公式识别里非常经典的路线。
具体说,输入一张归一化后的公式图片,先进 CNN 网络提取特征图。这一步的目的,是把图像转换成一个序列化的特征表示,你可以把它理解为“把图片拆成很多小片段,每个片段对应一个向量”。然后这些特征向量加位置编码,进入 Transformer 解码器,逐个生成 LaTeX token,比如\frac、{、x、^、{、2、}这样一个个预测出来。
这里有一个对公式识别特别重要的细节:位置编码不能用一维的,要用二维的。为什么?因为公式是二维展开的。一维位置编码只知道每个 token 在序列中的先后顺序,但不知道它在图像上是上面还是下面。上标和下标在一维序列上可能只差一个位置,但二维空间位置完全不同。我在实验里对比过,加了二维位置编码之后,上下标错乱的错误率下降非常明显。
训练目标就是标准的交叉熵损失,输入图片,输出的每个位置预测下一个 token 的概率。标签就是那串 LaTeX 序列,在开头加一个起始符,结尾结束符,整个训练过程跟文本生成模型没有本质区别。
3.2 训练超参与监控指标
给出我实测下来比较稳的一组配置,可以参考:
- 输入尺寸:高度 48,最大宽度 512;
- 优化器:Adam,初始学习率 3e-4,前 5 个 epoch 做 warmup;
- 批次大小:32 或者 64,取决于显存;
- 训练轮数:30 到 50 轮,配合早停;
- 监控指标:字符错误率 CER 和公式级准确率。
CER 指的是预测的 LaTeX 序列和真实标注之间的字符级编辑距离比例,越低越好。公式级准确率要求整条序列完全一致,这个指标通常很低,很考验模型的综合能力。一般合成测试集上,CER 能到 10% 左右、公式级正确率做到 60% 到 70%,已经是一个很不错的毕设结果了。
训练过程里我建议多观察一个东西:注意力矩阵的可视化。公式识别的模型经常出现一种“注意力崩溃”现象,就是解码器在生成过程中已经跑偏了,但 loss 还在下降,因为其余样本拉低了平均值。这时候光看指标不够,要随机抽几张图,把注意力权重打出来看,如果模型在应该关注图像中分式的位置时,注意力散成一团,那就是典型的训练质量有问题。
3.3 实测效果的数据表现
我在一套大概四万条合成数据加两千条真实拍摄数据上训练,合成测试集 CER 做到 8% 左右,真实场景的 CER 大概在 20% 上下。这个差距其实很说明问题:模型还是在合成域上学得更好,真实域的泛化需要靠更多真实数据来拉近。
如果时间有限,我更推荐你优先把合成数据上的指标做漂亮,再想办法小规模收集一些真实场景数据做微调。这样整体表现在答辩演示时已经很能打了。别一上来就追求真实场景数据占大头,真实数据收集、清洗、标注的成本在毕设周期里是不可控的。
4. 从拍照到 LaTeX 的完整实操链路
4.1 用 OpenCV 把照片变成干净公式图
这一节是整个项目里最有实操价值的部分,我按顺序把完整流程写出来。
第一步,读图像后先转灰度。拍照得到的彩色图像信息量大但噪声也大,灰度化可以降维,后续处理更快。
第二步,光照补偿。直接做全局二值化在光照不均匀时效果很差,阴影区域的字符会被吞掉。我实测下来,自适应阈值比单纯的大津阈值稳定很多。它的思想是一个像素该判为黑还是白,由它周围一个小区域决定,天然能抵抗局部光照变化。代码上就是 OpenCV 的 adaptiveThreshold 函数。
第三步,公式区域定位。真实页面里公式可能和正文混在一起,用轮廓检测先拿到各种连通域,再用面积阈值过滤掉小噪点,保留类似公式尺寸的候选区域。如果图像有倾斜,还需要计算最小外接矩形,做透视变换矫正。这一步对拍摄角度不正的情况特别重要。
第四步,单行切分。公式区域内部可能有多行(比如方程组),用水平投影法统计每行像素分布,找到行与行之间的空隙,切成一条条独立的行。每一行是一个独立的识别单元。
第五步,归一化并填充。把每一行公式图高度统一缩放,两侧加一点空白,防止字符贴边。
这套流程从代码角度其实就是 OpenCV 基础操作,但每一步的目的和参数选择直接影响后续识别质量。我调试时最常调的是自适应阈值的 blockSize 参数,调大一点对粗线条更友好,调小一点对细笔画更敏感,需要根据实际拍照效果反复测试。
4.2 模型推理与后处理
前处理做完,模型推理反而是最简单的部分了。加载训练好的权重,把图片送进去,解码方式用贪心搜索或 beam search 都行。实测中 beam search 的宽度设为 3 到 5 比较合适,太大对效果提升微乎其微,但推理时间会显著增加。
解码完成后,模型输出的是原始的 LaTeX token 序列。这里不要直接当成结果,要做下面几件事:
- 检查括号是否闭合。模型偶尔会漏掉右括号,用字符计数法检查括号配对,不配对就尝试在末尾补全。
- 检查反斜杠转义是否完整。比如 \frac 后面必须有左右两个花括号参数,否则渲染不了。
- 统一空格规则。LaTeX 里空格处理比较特殊,半角空格会被忽略,所以我直接把结果里的空格统一删掉或转成 \ 这种显式控制。
这些后处理规则看起来零碎,但非常提升最终结果的可用性。很多模型生成结果看着挺对,实际粘贴到编译环境却报错,就是在这些细节上出了问题。
后处理完成后的一个重要动作:把 LaTeX 重新渲染成图片,和原始输入图片放在一起对比。这是我最常用的调试方法。文本指标会说“正确率 80%”,但你看一眼渲染图和原图,马上能发现是哪类结构识别错了。这比盯着 CER 数字猜原因高效得多。
4.3 打包成可演示的完整接口
毕设除了算法本身,最好有一个能互动的演示界面,答辩的时候效果会好很多。不必做得很复杂,用轻量 Web 框架就能搞定,比如用 FastAPI 暴露一个 /recognize 接口,接收上传图片,返回识别出的 LaTeX 代码和渲染图。
再做一个极简的前端页面,上传一张公式图片,点击识别,右侧显示 LaTeX 代码,下方显示重新渲染出来的公式图片。这样一套整链路下来,用户可以直接看到输入和输出,答辩老师也能直观感受到“拍照到结果”的完整体验。
这个接口的设计其实没有太多技术难点,核心是让前端、后端、模型推理三部分解耦。模型加载一次常驻内存,避免每次请求都要重新加载权重;输入图片先走 OpenCV 预处理,再进模型;识别结果做后处理后再返回给前端。这套结构以后想扩展成支持摄像头拍摄、批量识别,都只是加函数的事情。
5. 常见问题与排查技巧实录
5.1 高频踩坑速查表
| 现象 | 可能原因 | 排查方法与解决方案 |
|---|---|---|
| 识别结果一堆乱码 | 输入图片有背景噪点 | 检查预处理二值化;加大形态学开运算核尺寸 |
| 上标下标互相串位 | 位置编码不合适或图像压缩过狠 | 换二维位置编码;固定高度动态宽度 |
| 长公式后半段丢失 | beam search 提前终止 | 增大 beam width;检查最大序列长度设置 |
| 训练 loss 一直不降 | 学习率过大或数据顺序有偏 | 缩小学习率,开启 warmup,打乱数据 |
| 分式被拆成两个识别单元 | 单行切分错误 | 调大形态学闭运算的核宽度;改进连通域合并 |
| 真实照片识别率暴跌 | 训练只有合成数据 | 增加真实数据微调;增强亮度扰动 |
这个表里的每一项,都是我在调试过程中真实碰到的。你如果真的自己走一遍,大概率也会撞上其中一两个,直接对照着排查就行。
5.2 有时候问题根本不在模型
有段时间模型效果一直上不去,我疯狂调模型结构、换损失函数,都没用。后来仔细看了输入样本,才发现是打包数据的时候图片通道出了问题:有一批图是三通道,另一批是单通道,模型特征提取阶段的数据分布被打乱,性能当然上不去。
这种事在毕设调试里很常见。当效果不行时,先别急着怀疑模型,按顺序检查:
- 训练集和测试集的图片预处理流程是否完全一致;
- 标签序列是否有额外空格、换行符;
- 数据读取时图片通道是否统一;
- 训练时是否开了数据增强,测试时是否关了。
我给出一个个人经验:花在排查数据管线上的时间,往往比花在模型调参上的时间更有价值。数据侧的 bug 通常是系统性的,会让所有实验的效果都打折扣,你模型调得再好都补不回来。
5.3 一个小众但好用的调试习惯
训练时除了打印 loss,我还会定期把验证集的前 20 条公式渲染成图片,单独存到一个文件夹里。每天训练结束后翻一翻,能肉眼看到“昨天分式还识别错误,今天好了很多”这种变化。
这个习惯的实用之处在于,它让我能直观感受到模型在哪些结构上进步了,哪些一直没学会。如果连续十轮渲染结果都不见改善,那基本就要考虑换训练策略或者补这部分的数据了。文本指标做不到这种细颗粒度的诊断,但渲染图可以。
6. 关于论文组织和答辩展示的经验
6.1 论文主线怎么搭
毕设论文的常见问题是“做成流水账”:先写数据,再写模型,然后训练结果,最后总结。这种结构虽然没错,但很难拿高分。我建议你围绕两条主线组织内容:
一条主线是“困难与解法”。先说明公式识别的核心难点是二维结构理解,再展开你是如何通过数据设计、位置编码、后处理规则来逐一化解这些难点的。这条线的逻辑是:问题 → 方法 → 实验验证。
另一条主线是“工程实现”。把你从拍照、预处理、模型推理到最终展示的完整链路写清楚,重点突出每一环节的输入输出。这条线适合答辩演示时展示工作量。
6.2 实验设计上怎么留对比
实验部分不要只做一套方案跑一个最终结果。至少要设计两个对比:
一是不同预处理策略的效果对比。比如无预处理、全局二值化、自适应阈值三组实验,直接展示 CER 的变化。这能证明你在 OpenCV 上做的工作是有效的。
二是不同解码方式的对比。贪心搜索和 beam search 简单对比性能与耗时,这是最稳妥的补充实验。
有这两组对比,论文的实验分析部分就已经很立体了。比单独堆一组“最终效果 98%”的单一结果要有说服力得多。
6.3 答辩演示时的细节
答辩现场演示最大的敌人不是算法不行,而是环境问题。投影仪颜色偏差、现场光线、图片分辨率都会影响识别效果。我的经验是准备三个选择:第一张演示图选最稳的合成图保底,第二张选一张真实拍摄但质量尚可的图展示泛化能力,第三张留一张比较难的图用于展示模型极限。现场系统会根据状态决定要不要展示第三张。
这个领域做下来,我最深的体会是:公式识别的瓶颈从来不只是模型结构。大多数人觉得换个更强的网络,效果就自然上去了,但真正做完整个项目你会发现,数据的平衡性、预处理的稳定性、后处理规则的完整性,才是决定项目能走多远的关键。而且这些内容正好是可以写进论文的差异化工作。如果你是在准备毕设,我甚至建议不要急着调模型,先花两天时间把你手里的数据管线彻底打通,把预处理和后处理做到位,再回头看模型,会惊喜地发现有些事情已经解决了。