拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python+OCR:口算题卡自动识别与批改实战

很多朋友看到“python 口算外挂”这种组合第一反应就是这东西肯定又是拿来刷小猿口算对战榜、秒杀小朋友的大杀器。说实话我当初刷到这个话题也是这么想的但真把一个能跑通的 Python 工具写完之后我的想法变了。这个项目真正值钱的地方不在于“炸鱼”那点快感而在于它是一条特别典型的自动化链路截屏取图、图像预处理、OCR 识别、表达式清洗、自动求值、结果输出。你能在这条链路上摸到 Python 图像处理、AI 模型调用、文本解析的完整实操做完之后还能秒懂不少更高阶项目的套路。这个项目入门的门槛非常低你只需要会 Python 基础语法、能 pip 装包就能照着一步步复现。我不打算教你暗戳戳去搞在线对战刷分那部分我先泼冷水放在后面细说。我这里要讲的是从“一张口算题卡”到“自动化批改结果”的完整实现方案并且把我在调参、踩坑、优化过程中总结的实战经验全部摊开讲。你要是对 OCR、AI Agent、自动化测试这些方向感兴趣这个小项目就是一个特别合适的第一站。1. 项目背景与核心思路1.1 小猿口算这类 App 的“痛点”在哪小猿口算、作业帮口算这类应用这几年在家长圈里几乎是标配。孩子做完一页口算题拍照上传系统自动批改或者直接用 App 里的在线口算练习模式跟全国小朋友实时 PK。平时看着很方便但如果只是想快速验证答案、批量给孩子整理错题一题一题手动拍照、识别、对照那就是纯体力活。真正让我决定动手写这个 Python 项目的是一次周末实测。孩子班里老师布置了 50 张口算题卡每张 20 题我老婆在边上拿手机拍照批改改了 20 分钟还没改完。我随口说一句“这个可以写脚本自动做”半个小时后我打开电脑发现 OCR 库已经能把一张口算题的算式、数字、加减乘除符号全部读成文本剩下的事就是算出来然后标记对错。所以这个项目的本质不是真的要做一个“外挂”去炸鱼而是把一个高频重复场景口算题识别、解析、批改、答案生成用 Python AI 能力自动化。它既能跑成一个独立小工具也是一条能摸到图像识别、OCR、表达式求值、自动化操作这些技术点的完整链路。1.2 为什么选 Python AI 组合选 Python 几乎是必然。理由很朴素生态最全试错成本最低。OCR 有现成库截图有 mss/PIL模拟点击有 pyautogui后端逻辑几十行就能写完。如果换成 C 或 Java光是摄像头/截屏图像的预处理、OpenCV 的环境配置就能劝退大部分人。AI 在这里起到的作用是替代过去“模板匹配”式的死办法。早期做这类识别工具很多开发者会用颜色阈值 数字模板匹配把 0-9 和加减乘除符号做成模板在截图上做卷积匹配。这套方案的缺点很明显换个颜色、换个字体、换了背景花纹模板就要重做属于一次性产品。引入 AI 后OCR 和图像识别模型已经见过海量不同类型的字体、背景、排版鲁棒性高了好几个量级。你不需要知道“模型到底是怎么认出这个 6 的”你只需要把图片质量处理好传给模型它返回文本。1.3 先泼一盆冷水别把“炸鱼”当成目标标题里写了“炸鱼”但我要先泼一盆冷水。小猿口算这类 App 的在线 PK 模式本质上是一个带积分、排行榜、竞技匹配系统的产品用自动化脚本去刷题、秒答很容易触发风控轻则封号重则被当成作弊样本公示。而且这种行为的核心问题是它破坏了其他小朋友的练习体验也违背了家长和老师用这个 App 的初衷。我把这个项目定位成“AI 口算题自动识别与批改工具”所有截图均来自本地测试图片不接入真实在线对战。你可以把它用于三件事批改纸质/导出题卡、研究 OCR 在真实小字号数字场景的表现、给自动化测试提供模拟答题数据。技术上代码完全可以迁移到自动答题场景但部署到线上对战平台前请想清楚后果。本文只讲技术链路不提供任何针对真实在线平台的风控对抗方案。2. 整体技术架构与方案选型2.1 五大核心模块拆解整个程序如果画成串行流程是五步取图、预处理、OCR 识别、算式解析与计算、结果输出/回填。第一模块是截屏或读图。至少要支持三种输入本地图片文件、当前屏幕截图、剪贴板图片。为了调试方便我默认先做本地图片后面再加 mss 实时截屏。这个顺序很重要——你先把单张图片的链路跑通再考虑实时抓屏否则出了问题你根本分不清是截图环节坏了还是识别环节坏了。第二模块是图像预处理。这一步容易被人忽略但恰恰是决定 OCR 准确率的关键。原始截图往往带有背景色、水印、噪点、坐标线数字可能被描边或阴影干扰。需要做灰度化、二值化、去噪、适当放大。我多次实测下来预处理效果好坏比换一个更大参数的 OCR 模型影响更明显。第三模块是 OCR 识别。负责把图像里的“23 45 ”连符号带数字一起变成字符串。可选方案有 Tesseract、PaddleOCR、RapidOCR以及直接调大模型 API。我最终主力用的是 PaddleOCR因为它在中文和印刷体算式上表现稳定pip 安装方便CPU 推理也能接受。第四模块是算式解析。OCR 拿到的字符串往往不规范可能有空格、把“”识别成字母 t、把“x”识别成“×”把“÷”识别成“/”。这里需要做文本清洗、符号映射再用表达式求值逻辑算出结果。为了安全我没有直接使用 eval而是用操作数栈和运算符栈手写了一个简单的四则运算解析器。第五模块是结果输出。输出可以是一次打印也可以是写入 CSV还可以是用 pyautogui 自动点击。既然定位是批改和辅助我建议先做 CSV 和终端输出自动点击留到彻底理解风险后再考虑。2.2 OCR 方案对比其实 OCR 库特别多但适合口算题这种场景的不多。我这里给个朴素对比表都是我自己实测过的感受不是跑基准测试那种结论。方案安装难度识别中文识别算式/数字CPU 推理速度备注Tesseract低一般需要 chi_sim 语言包数字不错符号容易带多余空格较快老牌适合纯英文/数字但算式符号处理弱PaddleOCR中好好能保留版面顺序中等一次约 0.1-0.3s中文印刷体识别稳依赖稍微重RapidOCR中好好较快OnnxRuntime 推理部署体积小大模型 API视觉低需网络极好极好还能理解语义网络延迟高适合批量离线不适合毫秒级成本高对小猿口算这类以数字、加减乘除为主的场景我最推荐 PaddleOCR 或 RapidOCR。如果对延迟和部署体积敏感RapidOCR 更轻如果已经有 Paddle 环境PaddleOCR 更省事。Tesseract 只建议当成备胎特别是当你的图片里有“小明今天做了几道题”这种中文说明文字时Tesseract 的默认分词会把中英文混排搞得很乱。2.3 为什么不直接截图模板匹配非要上 AI有朋友问过口算题就是 0-9 和加减乘除符号模板匹配难道不行吗行但只对“一张不变的题卡”行。你只要换一次字体比如老师打印的字变成了楷体、圆体模板匹配就崩了。再加上现在 App 里的口算题背景有渐变、卡通动画、干扰线模板匹配的阈值参数能让你调一晚上。AI 模型不一样。PaddleOCR 的文字检测和识别是分开的检测模块找到“哪里是文字区域”识别模块把区域内容转成字符。它看到的是大量形态各异的数字和符号后学到的统计规律所以遇到稍微歪一点、亮点、阴影覆盖仍然能认出来。我们不需要碰模型训练直接用预训练模型就行。这就是“开箱即用的 AI”在这个项目里最大的价值。3. 核心模块实现细节3.1 屏幕截图与图像预处理先给一个最小可用的截图实现。我推荐用 mss它比 PIL.ImageGrab 在 Windows 上更稳多显示器场景下也不会错位。import mss import cv2 import numpy as np with mss.mss() as sct: monitor sct.monitors[1] # 主屏幕 screenshot sct.grab(monitor) img np.array(screenshot) img cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) cv2.imwrite(debug_screen.png, img)这段代码做的事情很简单抓主屏转成 OpenCV 的 BGR 格式存成调试图。实际应用中不要截整屏一定要截取目标区域。一是减少 OCR 的搜索范围二是避免无关文字干扰识别。区域截取可以改成monitor {top: 100, left: 100, width: 800, height: 600}如果你已经有一张题卡图片就直接cv2.imread读取。后面所有预处理都基于这张图。预处理我总结了一个黄金链路灰度化 - 去噪 - 二值化 - 放大。代码是这样的def preprocess_image(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯去噪去掉细小噪点口算题背景常见。 blurred cv2.GaussianBlur(gray, (3, 3), 0) # 自适应阈值二值化突出文字区域。 binary cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 31, 15 ) # 放大 1.5~2 倍小数字识别率提升明显。 scale 2.0 resized cv2.resize(binary, None, fxscale, fyscale, interpolationcv2.INTER_LINEAR) return resized自适应阈值这里两个参数最关键blockSize31C15。blockSize是计算阈值的邻域大小越大越能应对光照渐变C是从均值里减掉的常数越大越容易把浅色像素变成白色背景文字边缘保留越粗。这个组合是我在一批深浅不同背景的题卡上试出来的不同屏幕下你需要微调。这里有个很容易踩的坑cv2.THRESH_BINARY_INV会把文字变成白色、背景变成黑色这是 OCR 更常见的输入格式。如果你发现 OCR 识别率很低可以先反过来用THRESH_BINARY试试一部分版本对深底白字的输入更敏感。调试方法很简单把binary保存出来看一眼就行。3.2 用 OCR 把题卡变成文本PaddleOCR 的使用方式很直白from paddleocr import PaddleOCR ocr PaddleOCR( use_angle_clsTrue, langch, show_logFalse ) result ocr.ocr(preprocessed.png, clsTrue) for line in result: for word_info in line: # 坐标信息位置置信度 box word_info[0] text word_info[1][0] score word_info[1][1] print(text, score)这里要注意use_angle_cls方向分类器对文字倒置、旋转的场景必须开。但口算题大多数是横平竖直的开不开影响不大。如果图片方向正常开了反而增加一点耗时。我习惯在正式跑大批量图片之前先拿 5 张典型图片分别开关这个参数测一遍看识别结果哪个更稳。langch是因为题卡里可能有“姓名”“班级”这样的汉字。如果只识别纯数字和运算符可以试试langen在某些版本里对纯数字效果更干净。建议你两个都跑一遍选择识别结果更稳定的那个。PaddleOCR 返回的结构是[box, (text, score)]box是四个角的坐标。这个信息很有用我们可以根据坐标判断题目顺序从左到右、从上到下排序避免 OCR 把两行题混在一起。这个小细节等后面批量批改 50 张题卡时你就知道有多重要了。如果直接把所有文本拼一起顺序完全错乱结果对不上题号。3.3 算式清洗与安全求值OCR 出来的文本通常长这样2345 12 × 8 72 ÷ 9? 35x 2 直接拿去做计算一定会出问题。第一个问题全角、半角、空格、特殊符号混在一起。第二个问题×、x、X、*都能表示乘号÷和/需要统一。第三个问题OCR 偶尔把、、?等干扰符号留在字符串尾部。清洗规则可以总结成这样统一加减乘除符号把×、x、X、*都映射成*把÷映射成/减号可能是-、或—。半角/全角统一中文括号转成英文半角()。去掉空格和 OCR 的占位符。过滤掉非算式字符只保留数字、运算符、括号、小数点。一个简单但有效的清洗函数import re def clean_expr(text): text text.replace( , ).replace(\u3000, ) text text.replace(×, *).replace(x, *).replace(X, *) text text.replace(÷, /) text text.replace(, -).replace(—, -) text text.replace(, ().replace(, )) text text.replace(, ).replace(, ).replace(?, ) # 过滤掉非算式字符保留数字、运算符、括号、小数点 text re.sub(r[^0-9\-*/().], , text) return text第二个问题求值。很多人图方便直接eval(expr)。我强烈建议不要在生产代码里这么干因为如果 OCR 结果里意外出现恶意字符串或者未来把输入源换成不可信文本eval就是命令执行漏洞。这个项目虽然只是本地跑但养成安全习惯很重要。我手写了一个只支持四则运算和括号的简易解析器def apply_op(nums, ops): right nums.pop() left nums.pop() op ops.pop() if op : nums.append(left right) elif op -: nums.append(left - right) elif op *: nums.append(left * right) elif op /: # 口算题的除数一般不为 0但防御一下没坏处 if abs(right) 1e-9: nums.append(float(inf)) else: nums.append(left / right) def precedence(op): if op in (, -): return 1 if op in (*, /): return 2 return 0 def parse_expr(s): s s.replace( , ) ops [] nums [] i 0 while i len(s): ch s[i] if ch.isdigit() or ch .: j i while j len(s) and (s[j].isdigit() or s[j] .): j 1 nums.append(float(s[i:j])) i j continue if ch in -*/: while ops and precedence(ops[-1]) precedence(ch): apply_op(nums, ops) ops.append(ch) i 1 elif ch (: ops.append(ch) i 1 elif ch ): while ops and ops[-1] ! (: apply_op(nums, ops) ops.pop() i 1 else: i 1 while ops: apply_op(nums, ops) return nums[0] if nums else None这个实现就是维护两个栈一个数字栈一个运算符栈。遇数字直接压栈遇运算符先比较优先级括号单独处理。这样完全避开eval对负数、括号、加减乘除都支持。缺点是如果要支持乘方、取余、三角函数还得继续扩展但对口算题来说足够。第三个问题计算精度。口算题一般不会出现小数但一旦出现除法比如10/4浮点数会得到2.5没问题如果是1/3你会得到0.3333333333333333。建议在最终结果上做一次格式化def fmt_result(value): if value is None or value float(inf): return None if abs(value - round(value)) 1e-9: return int(round(value)) return round(value, 6)这样既保留整数题目的干净结果又避免小数题目出现巨长尾巴。3.4 结果输出与自动回填思路最小可用版本输出到终端和 CSV 就够了。import csv def save_results(items, filenameresults.csv): with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([算式, 识别内容, 计算结果, 识别置信度]) writer.writerows(items)utf-8-sig是为了让 Excel 打开 CSV 不乱码这个细节很实用。如果你是做错题整理建议多加一列“正确/错误”后面按True/False过滤就行。如果要做到“识别后自动点击答题”常见做法是pyautoguiimport pyautogui pyautogui.click(x, y) pyautogui.write(68, interval0.05)但这部分我只作为技术研究写出来。真实在线场景里App 的控件不一定能通过写屏操作响应而且很容易被风控识别成非人类操作。如果你只是想练手建议先在本地测试一个模拟输入框不要对着真实对战平台操作。4. 端到端实测从截图到答案的完整链路4.1 单题识别过程详解为了说清楚整条链路我用一张自己拼的测试题卡跑一遍。这张题卡我故意做了三种处理纯白底黑字、淡黄色底带卡通边框、手机拍摄的实拍照片。你会发现同一套代码在不同输入下的表现差距很大这也是为什么预处理不能一刀切。测试输入是四道题21 37 48 ÷ 6 9 × 7 (15 5) - 8 OCR 输出经过clean_expr之后分别得到2137 48/6 9*7 (155)-8然后解析器计算结果58 8.0 63 12这里有个值得注意的细节48/6得到8.0格式化后变成8但如果你不格式化直接跟题卡上写的8做字符串比较会得到False导致错判。这就是我前面为什么要写fmt_result。4.2 准确率与耗时数据我拿 200 道混合口算题做了个粗略测试真实情况是这样的输入类型识别准确率解析正确率平均耗时每题高清截图白底97% 左右100%清洗后0.15s淡黄色背景93% 左右100%0.18s手机实拍照片85% 左右95% 左右0.25s这里的“识别准确率”指 OCR 文本没出错的比例“解析正确率”指清洗后表达式能被正确求值的比例。在实拍照片里倾斜、反光、透视变形是最大的敌人OCR 偶尔把6认成0或者把4认成9。要解决需要加透视矫正、直方图均衡化或者直接换大模型视觉 API 做语义校验。耗时方面PaddleOCR 的 CPU 推理大约占了大头。如果你追求实时性可以用 RapidOCR 或开启 ONNX Runtime 的 GPU 加速。实测 RapidOCR 在同样图片上大约能快 30%-40%但识别准确率在某些花哨背景上略低属于可以接受的权衡。4.3 提升识别准确率的四个技巧第一个技巧是放大图片。OCR 对太小的数字非常敏感建议把包含算式的区域放大到每个数字至少 20×20 像素。最简单的方式就是用cv2.resize双倍放大。放大倍数不是越高越好超过 3 倍只会增加推理耗时对准确率帮助很小。第二个技巧是自适应阈值的参数不要乱调。如果你把C设成 5可能背景噪点全变黑了如果设成 30浅色文字可能直接消失。稳妥做法是先保存二值化中间结果肉眼确认文字清晰、背景干净再批量跑 OCR。这一步你调好的参数基本可以复用到同类型题卡上属于一次投入长期收益。第三个技巧是后处理时用“预计答案范围”做校验。口算题的答案通常不会超过四位数。如果计算结果巨大说明 OCR 大概率把运算符认错了。比如21 37被认成21 370和题面结构矛盾可以标记为低置信度人工复核。你可以定义一个is_plausible(expr, result)函数把位数、正负、结果范围都判断一遍。第四个技巧是同一张图跑两遍 OCR一遍正向一遍旋转 180 度或 90 度然后把两遍结果中置信度更高的一边保留。这个在手机实拍场景特别有用因为实拍经常出现照片方向被系统自动旋转的问题。代价是耗时翻倍所以我只在“关键题卡”上启用。5. 常见问题与排查实录5.1 识别结果全是乱码大概率是图像预处理没做好或者 OCR 语言包没对上。先打印中间二值化图片看看文字是否清晰。如果文字断断续续就把blockSize调小一点、C调大一点。如果文字变成黑块说明阈值太高把C调小。中文语言包没有下载PaddleOCR 首次运行会提示失败。解决办法是手动下载对应模型放到~/.paddleocr/目录或者检查网络后重试。这个坑在离线环境特别常见建议第一次运行前先确认模型能正常加载。5.2 加减乘除符号被识别错x和×经常被 OCR 当成字母x÷被当成或/-被当成一一字。清洗函数里一定要做统一映射。另外我建议把识别置信度低于 0.8 的题目单独打印出来人工看一遍不要直接进入批改结果。这个人工复核步骤能挡住 80% 的隐性错误。5.3 计算优先级出错如果你选择用简单正则替换后调eval优先级是不会有问题的。但如果你像我一样手写解析器一定要测一下1 2 * 3这类题确认乘法先于加法。还有一个隐含问题题面里的被清洗掉后表达式末尾如果残留?或正常情况下已经被正则过滤。如果发现结果不对劲建议在parse_expr里加一个print把最终输入的字符串打出来对比下一步结果。5.4 程序运行很慢慢的根源通常是 OCR 初始化模型加载和整张大图推理。解决方向只用一次PaddleOCR()不要每次识别前都新建实例。如果你放在循环里创建每张图都要重新加载模型慢到你怀疑人生。把识别区域裁剪到只包含题目区域。开use_gpuFalseCPU 模式下 PaddleOCR 也能接受。如果还需要更快换 RapidOCR。5.5 批改结果对不上题号这是批量场景最常见的坑。OCR 按检测顺序返回文本但如果题卡是两栏布局OCR 可能先读右栏再读左栏导致结果错位。解决办法是利用box坐标排序按y坐标分组再按x坐标排序。实际操作里你先对所有文本框按中心点的y值从小到大排把同一行的题归到一组如果两个文本框的y差距小于一定阈值比如 10 像素就认为它们属于同一行。然后再对每一行按x值排序。这样几乎不会错。6. 这个项目的边界技术很有趣但别越线6.1 在线对战场景的风险小猿口算这类 App 有在线 PK 模式号称能和全国的小朋友实时比赛。用自动化脚本去抢答、秒答本质上是利用了脚本速度远高于人类的物理极限。这类行为一旦被系统识别账号轻则限制登录重则设备被封禁。更重要的是你赢下的每一局对手可能都是真实的小学生。这种“炸鱼”行为一旦被家长发现后果不只是技术问题而是教育问题。我见过一些开发者在这个项目上继续魔改加自动点击、加随机延迟、加模拟滑动想尽办法躲过风控。技术上确实越做越深但方向已经偏了。你不会希望自己的孩子在一款学习 App 里遇到一个永远秒答的机器人那既不公平也毫无练习价值。6.2 值得推荐的正向用法我的建议是重新包装这个项目定位成三个方向第一家长批改助手。本地导入题卡图片自动生成对错清单和错题集。这比肉眼一张张看高效得多。尤其是在老师布置大量打印版口算题的场景这个工具能省下大量时间。第二OCR 在数学公式识别上的技术验证。口算题是公式识别的一个简化子集你后面可以扩展成识别分数、根号、竖式。把这道链路跑通你就理解了文字检测、文字识别、后处理的基本套路。第三自动化测试的模拟数据源。如果你在开发数学类应用可以用这套程序生成大量模拟答题记录方便做压测、做算法验证不用手工伪造几百条 JSON 数据。6.3 如果要往更深的 AI 链路上走这类项目最好的延伸方向是引入一个“题目理解”环节。比如你说“我要连续识别 10 页题卡并且按题型统计错误分布”传统的 OCR 程序做不到但你可以接一个大模型把 OCR 得到的文本发给模型让它返回结构化 JSON包含题型、算式、答案、难度。这样一来你等于做了一次 AI Agent 的入门实践工具负责“看”模型负责“理解”代码负责“整理”。这其实也是 2025 年前后很多“AI 辅助办公”类应用的通用架构。自动识别、语义理解、结构化输出、批量执行四个环节层层递进。你这个口算识别工具相当于亲手实现了最前面的“自动识别”环节后面的环节都是同一个模式的扩展。7. 我踩过的一些坑和最后想说的话这个项目虽然看起来简单但我踩的坑一点不少。刚开始我用 Tesseract 固定阈值二值化识别数字还可以一旦画面里出现一条装饰线文字就被切成几截。后来换成自适应阈值 PaddleOCR才算稳下来。还有一个细节很多截图里“答案”区域不是空白而是有淡灰色占位字或底色需要把区域单独裁出来用更高倍率放大再识别。这些经验写出来只有几句话真正调的时候花了我两个晚上。我个人的体会是Python 项目的价值不在于复杂度而在于“一条链路能不能完整闭环”。这个项目从截图、预处理、OCR、解析、计算到输出每个环节之间都是靠数据格式衔接的图片 - 文本 - 表达式 - 数字结果。搞懂这个链路以后很多自动化脚本的套路你都能举一反三。最后再分享一个小技巧所有中间结果都要落盘。我习惯在每个环节保存一份 debug 文件比如01_screen.png、02_binary.png、03_ocr_result.txt。这样出问题时你只需要往前回溯而不是盲猜。问题排查比跑通功能花的时间更多中间结果就是你排查问题时的 roadmap。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门