1. 项目背景与核心挑战
数学公式识别一直是计算机视觉领域的硬骨头问题。相比传统OCR(光学字符识别),公式识别面临三大核心挑战:
- 二维空间结构解析:普通文字是线性排列,而公式包含上下标、分式、根号等嵌套结构
- 符号间关系复杂:同一个符号在不同位置可能代表不同含义(如"-"可能是减号也可能是负号)
- 手写变体多样:不同人的书写习惯导致符号形态差异巨大
我去年指导的毕设项目采用"OpenCV预处理+深度学习模型"的混合方案,最终在测试集上达到92.3%的结构识别准确率。下面完整分享这个可落地的技术方案。
2. 技术方案设计
2.1 整体流程设计
项目采用典型的pipeline架构:
code复制图像输入 → OpenCV预处理 → 符号检测 → 结构分析 → LaTeX输出
关键创新点在于:
- 使用改进的MSER算法处理手写公式的模糊边缘
- 设计双分支CNN网络同时处理符号识别和空间关系
- 引入注意力机制增强对小符号(如小数点)的检测
2.2 环境配置要点
推荐使用以下环境组合:
bash复制# 基础环境
Ubuntu 20.04 LTS
Python 3.8
CUDA 11.1
# 核心库版本
opencv-python==4.5.5.64
torch==1.10.0+cu111
torchvision==0.11.1+cu111
重要提示:OpenCV必须从源码编译安装,确保包含nonfree模块(如SIFT特征支持)
编译OpenCV的完整命令:
bash复制cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules \
-D WITH_CUDA=ON \
-D ENABLE_FAST_MATH=1 \
-D CUDA_FAST_MATH=1 \
-D WITH_CUBLAS=1 \
-D OPENCV_ENABLE_NONFREE=ON ..
3. 核心实现细节
3.1 图像预处理优化
传统二值化方法(如Otsu)对手写公式效果不佳,我们改进的方案:
python复制def preprocess(img):
# 自适应光照补偿
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
l = clahe.apply(l)
lab = cv2.merge((l,a,b))
img = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
# 改进的边缘增强
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (0,0), sigmaX=3, sigmaY=3)
divide = cv2.divide(gray, blur, scale=255)
return cv2.adaptiveThreshold(divide, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
3.2 符号检测网络设计
采用改进的YOLOv4-tiny架构:
- 输入尺寸:416x416
- 锚点框聚类得到5组基准尺寸
- 添加SE注意力模块提升小目标检测
网络结构关键参数:
python复制Model(
(backbone): CSPDarknet53-tiny(
conv1: Conv2d(3, 32, kernel_size=3, stride=2)
...
)
(neck): PANet(
upsample: NearestUpsample(scale_factor=2)
...
)
(head): YOLOv4Head(
conv: Conv2d(128, 75, kernel_size=1) # 25*(classes+5)
)
)
3.3 结构关系解析算法
基于符号位置信息构建关系图:
python复制def build_relation(symbols):
graph = nx.Graph()
for i, s1 in enumerate(symbols):
graph.add_node(i, label=s1['class'])
for j, s2 in enumerate(symbols[i+1:]):
j = i + j + 1
# 计算空间关系特征
dx = s2['cx'] - s1['cx']
dy = s2['cy'] - s1['cy']
dist = np.sqrt(dx**2 + dy**2)
# 动态关系阈值
threshold = 0.5 * (s1['h'] + s2['h'])
if dist < threshold:
graph.add_edge(i, j, weight=1/dist)
return graph
4. 关键问题与解决方案
4.1 符号粘连问题
现象:手写时"+"和"="容易连笔导致检测为一个符号
解决方案:
- 形态学开运算分离轻微粘连
- 设计连通域分割算法:
python复制def split_connected(contour):
hull = cv2.convexHull(contour, returnPoints=False)
defects = cv2.convexityDefects(contour, hull)
split_points = []
for i in range(defects.shape[0]):
s,e,f,d = defects[i,0]
if d > 1000: # 深度阈值
split_points.append(f)
return cv2.split(contour, split_points)
4.2 多行公式对齐
挑战:当公式跨多行时,需要正确识别上下行关系
我们采用基于投影的方法:
- 计算垂直投影直方图
- 通过峰值检测确定行基线
- 动态调整行间距阈值:
python复制row_gap = 1.5 * median_symbol_height
5. 效果优化技巧
5.1 数据增强策略
除常规的旋转、缩放外,特别设计:
- 弹性变形模拟手写抖动
- 墨迹扩散模拟不同书写压力
- 局部模糊模拟拍照失焦
实现代码片段:
python复制class InkDiffusion:
def __call__(self, img):
h, w = img.shape[:2]
noise = np.random.randn(h,w) * 5
kernel = cv2.getGaussianKernel(11, 3)
noise = cv2.filter2D(noise, -1, kernel)
img = np.clip(img + noise, 0, 255)
return img.astype(np.uint8)
5.2 模型量化部署
为实现在树莓派等边缘设备运行,采用:
- TensorRT FP16量化
- OpenCV DNN模块优化
- 自定义算子融合
部署后模型大小从189MB压缩到23MB,推理速度提升4倍。
6. 项目扩展方向
-
复杂公式支持:当前对矩阵、多行公式的支持有限,可改进:
- 增加特殊结构检测模块
- 引入图神经网络处理复杂布局
-
在线学习:通过用户反馈持续优化模型:
python复制class OnlineLearner:
def update(self, corrected_latex):
loss = self.compute_loss(corrected_latex)
self.model.backward(loss)
if self.steps % 100 == 0:
self.optimizer.update()
- 跨平台应用:已验证可在以下平台运行:
- 树莓派4B(2fps)
- Jetson Nano(8fps)
- 安卓手机(通过ONNX Runtime)
这个项目从设计到实现共耗时4个月,最大的收获是认识到:传统图像处理与深度学习的结合往往能产生1+1>2的效果。特别是在处理像公式识别这种具有强领域特性的问题时,精心设计的预处理和后处理流程有时比单纯增大模型规模更有效。
