这脚本的核心就一句话:把那些健身教练手写的、鬼画符一样的饮食记录单,自动变成 Excel 表格。我受够了每天晚上对着一堆破纸手动录入,效率低还容易错,关键是,这他妈本来就不该是人干的活。
最开始想得太简单了,以为调个 Tesseract 的 API 就完事了。结果第一批扫描件扔进去,识别率不到 30%。那些教练,有的用圆珠笔,有的用水笔,纸张是那种最便宜的拍纸簿,扫描仪扫出来背景全是灰的,字迹还经常洇开。这根本不是 OCR 问题,这是图像预处理问题。我花了整整三天,就折腾 OpenCV。灰度化、二值化、自适应阈值、降噪、形态学操作(开运算闭运算)全试了一遍。最后发现,针对这种低质量扫描件,用高斯模糊去噪再加一个局部自适应阈值(cv2.adaptiveThreshold 用 cv2.ADAPTIVE_THRESH_GAUSSIAN_C),效果比全局阈值好得多。但参数调起来真是要命,窗口大小、常数 C,差一点结果就天差地别。我写了个循环,批量测试不同参数组合,用识别出来的数字和字母的准确率作为评估指标,才算找到一个勉强能用的配置。
但这才刚开始。版面分析才是噩梦。单据格式不统一,有的项目横着写,有的竖着列。Tesseract 的 –psm 模式从 1 到 13 我全试了,自动布局分析(–psm 3)在复杂情况下就是一坨屎。最后没办法,我根据扫描件的固定物理尺寸(因为用的是统一模板),手动用像素坐标去划分 ROI(感兴趣区域)。比如客户姓名永远在左上角 100×50 的框里,日期在右上角,下面的食物列表是一个固定高度的区域。我用 cv2.rectangle 画出这些区域,分别裁剪出来,再单独扔给 Tesseract 识别,并且对每个区域指定不同的 –psm 模式。姓名和日期用 –psm 7(单行文本),食物列表用 –psm 6(统一区块的文本)。这招土,但有效。
识别出来的文本清洗又是坑。教练们缩写五花八门,“鸡胸肉”写成“鸡胸”,“200g”写成“200克”甚至“二百克”。我建了一个映射字典,把常见的错误拼写和缩写映射回标准词条。数字单位处理用了正则表达式,匹配“数字+单位(g/ml/克/毫升)”的模式,然后统一转换成纯数字。最头疼的是手写数字“7”和“1”、“5”和“6”经常分不清,我只能在后处理逻辑里加了一些启发式规则,比如在重量字段里,如果识别结果是“1”但上下文像是一个较大的值(比如前后都是几百),就尝试纠正为“7”。
全部流程用 Python 的 concurrent.futures 搞成多线程,一个线程处理图像预处理,一个线程跑 OCR,一个线程做文本清洗和入库。本地跑起来,处理一张单子从原来的手动3分钟,压缩到10秒以内,准确率从看心情提升到95%以上。我把输出直接怼进一个预设好公式的 Excel,总热量、蛋白质、碳水、脂肪自动算好,还能按客户生成周报图表。
做完了看着自动生成的报表,一点成就感都没有,只觉得累。技术栈七拼八凑(OpenCV, Tesseract, Pandas, openpyxl),全是脏活累活,没有任何“优雅”可言。但这就是2020年的我,困在管理交付的泥潭里,只能用这种极客式的、自虐般的自动化,来抢夺一点点属于自己的时间,和一点点对混乱局面的控制感。健身教练们觉得这工具神了,问我怎么弄的。我懒得解释,只说“写了个小脚本”。他们永远不会知道,为了对付他们那些破纸,我几乎重新学了一遍计算机视觉的入门课。这脚本比任何健身 App 都好,不是因为技术多先进,而是因为它诞生于一种深刻的、具体的、令人窒息的痛点,并且是用最硬的、最不讨巧的方式,把问题砸开了。














