0%

基于shell的数学公式识别程序

最开始是因为复习高数的时候想要做笔记,就需要写各种各样的公式,这里就有很好用的工具Mathpix Snip,专业,高效,优雅。

然后它突然告诉我次数不够了。

emm,突然不免费了,而且价格也有点不近人情。

后来过了几天我拿C#用腾讯的接口实现了个备用的,再后来就是切换到ubuntu,电脑文件在装系统的时候丢失了。

那就再用shell写个吧,我这么想到。

这其实算是个回忆归档的记录吧,当初完成的时候碍于精力就搁置了,然后有时间了就拿出来拾掇拾掇。

最开始是打算写出个自用的,可是种种原因最后变成了对比体验。

整篇文章以记录为主,许多查得到细节就不一一展开了

分析

让我自己去实现一个OCR程序肯定是不行的,得从头学起,然后训练,最后精度肯定不得行。占用了相当的时间——我还得考研呐。

所以去找第三方接口用是更实际点的思路(实际上在之前以我的知识储备一直不知道有这么条路走),然后我大概找了这么几个方案。

腾讯云百度智能云讯飞开放平台,以及业界领先Mathpix Snip——让我又爱又恨。

开发

腾讯云

腾讯云的帮助文档写的不错,还特地开发了保姆级别的交互教程API Explorer,填进去参数,选择一个主流的开发语言,自动生成代码。

不过我最中意的还是它的命令行工具:tccli,既然都换成ubuntu了,不好好练练shell怎么能行。况且,借助shell强大的生态,和其他程序形成联动的话,就能省下很多精力。

先安装

1
pip install tccli

通过tccli的帮助文档确定了tccli的使用方式:tccli [options] <command> <subcommand> [<subcommand> ...] [parameters],先选择想要调用的应用类型,然后给出参数。

一步步确定后就知道需要调用的是ocr中的FormulaOCR应用,在配置完成后只需要一个重要参数--ImageBase64,然后系统自带的就有 base64 工具,直接调用就行。

返回的数据是一串json,不过它也自带了解析器,通过--filter参数调整就行,最后的话就是通过这么一行命令完成调用

1
texts=$(tccli ocr FormulaOCR --ImageBase64 $imgbase64 --filter FormulaInfos[*].DetectedText)

然后再加上截图,base64编码,提示以及逻辑,就形成了完整的shell程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# !/bin/sh

# 指定截图保存位置
imgpath=/tmp/tmpimg.png

# 截屏
gnome-screenshot -a -f $imgpath

# 没有截屏的话就不识别了
if [ -f "$imgpath" ]
then

# 截图采用64位编码
imgbase64=$(base64 -w 0 $imgpath)

# 删除截图
rm $imgpath

# 调用接口获取识别结果
texts=$(tccli ocr FormulaOCR --ImageBase64 $imgbase64 --filter FormulaInfos[*].DetectedText)

# 删除换行符(估计是参数的关系引号也给删了)
texts=$(echo $texts | xargs echo -n)

# 删除首尾字符‘[’与‘]’
texts=${texts:1:-1}

# 双斜杠替换为单斜杠
texts=${texts//'\\'/'\'}

# 发送到剪贴板
echo -n $texts | xclip -selection c

# 弹出提示消息
notify-send "公式识别成功:$texts"

fi

对了,xclip 需要单独安装一下,识别结果直接放到剪贴板中是最舒服的。

emm,识别效果最后统一说吧。

百度智能云

在之前使用天若OCR通用文字识别的时候知道它的接口是百度,然后就觉得会靠谱,也试试。

百度的逻辑会复杂一点,需要先申请个token,再拿这个token去申请服务,而且token还是有期限的,就忍不住的想要吐槽。

然后都是http调用,就很自然的给出了bash脚本

1
2
3
4
5
# 获取token
curl -i -k 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=【百度云应用的AK】&client_secret=【百度云应用的SK】'

# 调用服务
curl -i -k 'https://aip.baidubce.com/rest/2.0/ocr/v1/formula?access_token=【调用鉴权接口获取的token】' --data 'image=【图片Base64编码,需UrlEncode】' -H 'Content-Type:application/x-www-form-urlencoded'

emm,对,因为是走的是http,所以在base64编码后又来了个urlencode,还不错有自带的 urlencode 工具。

它的token有效期是三十天整,那我的思路就是每月一号或者十五号申请一次token,保存在文件里,然后截个图识别。

就得要两段代码

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

day=$(date "+%d")

# 每月1号以及15号更新一次token
if [[ $day == 1 || $day == 15 ]]
then
token=$(curl -k "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=$BAIDUCLOUD_API_KEY&client_secret=$BAIDUCLOUD_SECRET_KEY" | jq .access_token)
token=${token:1:-1}
echo -n $token > /home/frankchen/.baidu_access_token
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/sh

# 指定截图保存位置
imgpath=/tmp/tmpimg.png

# 截屏
gnome-screenshot -a -f $imgpath

# 没有截屏的话就不识别了
if [ -f "$imgpath" ]
then

# 截图采用64位编码
imgbase64=$(base64 -w 0 $imgpath)

# 再经过一次urlencode
imgbase64=$(urlencode $imgbase64)

# 删除截图
rm $imgpath

# 获取access_token
access_token=$(cat /home/frankchen/.baidu_access_token)

# 调用接口获取识别结果
texts=$(curl -k "https://aip.baidubce.com/rest/2.0/ocr/v1/formula?access_token=$access_token" --data "image=$imgbase64" -H 'Content-Type:application/x-www-form-urlencoded' | jq .words_result | jq .[].words)

# 删除换行符(估计是参数的关系引号也给删了)
texts=$(echo $texts | xargs echo -n)

# 发送到剪贴板
echo -n $texts | xclip -selection c

# 弹出提示消息
notify-send "公式识别成功:$texts"

fi

显而易见的使用了和腾讯那份类似的大框架,不过虽然都返回一个json,但是百度这边不提供解析的,需要自己从中拿。就需要额外安装一个 jq ,我这个jq参数写的比较丑。

然后识别结果同样放在最后吐槽。

讯飞开放平台

讯飞给我的感觉就很暧昧,我看了它的演示结果后决定试一试,但是随之而来的一系列绑定,强迫使用以及相对不如之前好的文档就emm

讯飞的调用规则比较复杂,参数也更加多,返回的json也更复杂,于是我决定直接白嫖讯飞提供的demo。

emm,白嫖同样是脚本语言的python3的demo,然后再在shell里调用——这点就是我特别喜欢shell的原因。

调用讯飞python3的demo命令:

1
texts=$(python3 WebITRTeach.py | jq '.data.region[] | select( has("recog") == true)'.recog.content)

json解析规则肉眼可见的复杂

全部的shell代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# !/bin/sh

# 指定截图保存位置
imgpath=/tmp/tmpimg.png

# 截屏
gnome-screenshot -a -f $imgpath

# 没有截屏的话就不识别了
if [ -f "$imgpath" ]
then

# 调用接口获取识别结果
texts=$(python3 WebITRTeach.py | jq '.data.region[] | select( has("recog") == true)'.recog.content)

# 删除截图
rm $imgpath

# 合并
texts=$(echo $texts | xargs echo -n)

# 删除‘ifly-latex-begin’
texts=${texts//'ifly-latex-begin'/''}

# 删除‘ifly-latex-end’
texts=${texts//'ifly-latex-end'/''}

# 双斜杠替换为单斜杠
texts=${texts//'\\'/'\'}

# 发送到剪贴板
echo -n $texts | xclip -selection c

# 弹出提示消息
notify-send "公式识别成功:$texts"

fi

python3的代码就需要稍微改动一点点就可以了,而且确实有点长,就不放出来了。

Mathpix Snip

如果能用的话我就不会去考虑上边那三个了,信用卡着实劝退。

对比

实际上考研时高数笔记上大多数的公式都是我一个一个手打的,然后我现在从中拿出来几个做下对比测试。

我用妈咪说MommyTalk制作的LaTeX公式编辑器来代替Mathpix Snip的测试结果(毕竟它确实调用的Mathpix Snip接口)

对于个人开发者的个人使用来说,调用次数,频率什么的都完全满足个人使用需求,对比没有实际意义,因此只对识别结果进行对比。

简单组

1
2
3
4
5
6
7
8
9
10
11
% 腾讯:
\sin ( \alpha - 3 ) = \sin \cos z \sin z \sin 5

% 百度:
sn(a+)=sinacos+cosasin

% 讯飞:
\sin ( \alpha + \beta )= \sin \alpha \cos \beta + \cos \alpha \sin \beta \_ ---- -

% Mathpix Snip:
\sin (\alpha+\beta)=\sin \alpha \cos \beta+\cos \alpha \sin \beta

中等组

1
2
3
4
5
6
7
8
9
10
11
% 腾讯:
\arcsin ( \alpha ) = \frac { \triangle B \sin \gamma } { 1 } = \arcsin z

% 百度:
t x = 1 5 0 ^ { 2 }

% 讯飞:
\tan ( \alpha + \beta )= \frac {\tan \alpha +\tan \beta }{1-\tan \alpha \tan \beta } \_ . \_ \bot

% Mathpix Snip:
\tan (\alpha+\beta)=\frac{\tan \alpha+\tan \beta}{1-\tan \alpha \tan \beta}

复杂组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% 腾讯
\arcsin \cos 2 \arcsin 2 a \cos 2 \alpha = a ^ { 2 } ) ( \sqrt { \beta ^ { 2 } + \beta ^ { 2 } } ) ( \sqrt { 2 0 2 } ) y \perp y

% 百度

% 讯飞
f(x,y,z)dx+Q(x,y,z)dy+R(x,y,z)dz= \int _ {\frac {1}{2}} |_ {|} | \frac {dz}{P} dzdx dxdy ---- - C. \frac {\cos \alpha }{a^ {3}} \cos \beta

% Mathpix Snip:
\oint_{L} P(x, y, z) d x+Q(x, y, z) d y+R(x, y, z) d z=\iint_{\Sigma}\left|\begin{array}{ccc}
d y d z & d z d x & d x d y \\
\frac{\partial}{\partial x} & \frac{\partial}{\partial y} & \frac{\partial}{\partial z} \\
P & Q & R
\end{array}\right|=\iint_{\Sigma}\left|\begin{array}{ccc}
\cos \alpha & \cos \beta & \cos \gamma \\
\frac{\partial}{\partial x} & \frac{\partial}{\partial y} & \frac{\partial}{\partial z} \\
P & Q & R
\end{array}\right| d S

结论

腾讯和百度搞得我心里洼凉洼凉的。

腾讯这边总是能给我识别出奇奇怪怪的公式出来,一度让我以为我记错了识别的公式。

百度这边我怀疑只是通用OCR改了个名字,完全是欺诈吧,而且调用速度还是这之间最慢的,调用次数还最少。

讯飞勉勉强强可以一战,不尽如人意,但比腾讯百度靠谱多了,还更快。

Mathpix Snip永远滴神!

尾声

其实我也试过别的接口,例如更实用的通用OCR接口,都表现十分好。但是就公式识别这一细分领域来讲,Mathpix Snip是真的棋无对手,国内的表现堪忧。

我是暂时不再有公式识别的需要了,而且也已经足够熟练,就对识别的需求不是很强烈,估计最多Mathpix Snip的学生额度就差不多了,也就暂时不考虑想办法申请信用卡进行Mathpix Snip的开发测试了。

这次的调用之旅让我一度有开发一个完整界面出来的想法,就和我之前提到的天若一样,但是用QT写,就可以很好的跨平台使用。这应该算得上是一个毕设级别的作品,但是我已经有 别的想法,就只能搁置了。可能什么时候再次有空了,会闲下心来做一做吧。

就这样。