1. 项目概述:聚餐算账的智能解决方案
每次朋友聚餐最头疼的就是最后算账环节。七八个人围着账单,有人用手机计算器按来按去,有人掏出纸币找零,还有人喊着"我那份饮料没喝应该少算5块"...作为组织过上百场饭局的社牛,我开发了一套基于图像识别的聚餐AA算账工具,用手机拍下菜单照片就能自动识别菜品和价格,勾选各自消费的条目后秒出分摊金额。实测在10人聚餐场景下,算账时间从平均15分钟压缩到30秒,再也没人抱怨"算错账"了。
这个方案的核心在于将计算机视觉技术与消费场景深度结合。不同于通用的OCR识别,我们针对菜单特有的版式(菜品名+价格栏)和餐饮行业术语做了专项优化。整套系统包含三个关键模块:高精度菜单图像识别引擎、消费条目勾选界面、多人AA算法核心。下面我就从技术选型到落地细节完整分享实现过程。
2. 技术架构与核心模块
2.1 图像识别方案选型
经过对比测试三种主流方案后,我们最终采用组合式识别策略:
传统OCR方案(Tesseract)
- 优点:开源免费,部署简单
- 痛点:对中文菜单识别率仅68%,特别是手写体价格识别完全失效
- 测试数据:100张菜单样本,价格字段错误率高达42%
云端API方案(某大厂OCR服务)
- 优点:印刷体中文识别率95%+
- 痛点:按次收费(0.01元/次),网络依赖性强
- 隐藏成本:需要额外开发价格字段定位逻辑
定制化CV模型(最终方案)
- 基于YOLOv5训练专用检测模型
- 数据集:收集2000+张真实菜单标注数据
- 效果:菜品名识别率92%,价格字段98%
- 创新点:引入价格符号(¥¥$)强化检测
关键建议:如果刚开始尝试,建议先用PaddleOCR+自定义字典方案过渡,准确率能达到85%左右且完全免费。
2.2 数据结构设计
识别后的数据需要结构化存储以便后续计算。我们设计了三层数据结构:
class MenuItem: def __init__(self): self.name = "" # 菜品名称 self.price = 0.0 # 单价 self.coordinates = [] # 图片中位置坐标 self.is_selected = False # 是否被用户选中 class DiningBill: def __init__(self): self.items = [] # 所有菜品列表 self.diners = {} # 参与人及所选菜品 # 示例:{"张三":[item1,item3], "李四":[item2]}这种设计支持两种计算模式:
- 经典AA制(总价均分)
- 精确AA制(按实际消费计算)
3. 实现细节与避坑指南
3.1 图像预处理技巧
实测发现90%的识别错误源于图像质量。经过大量测试总结出最佳处理流程:
光源补偿(关键步骤)
- 问题:餐厅暖光灯造成色偏
- 方案:用cv2.createCLAHE()做自适应直方图均衡化
- 效果:文字对比度提升300%
透视校正
# 自动检测菜单四角 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) corners = cv2.goodFeaturesToTrack(gray, 4, 0.01, 10)文字增强
- 使用深度学习超分模型Real-ESRGAN
- 特别提升小字号价格数字的清晰度
3.2 价格字段特殊处理
价格识别有三大陷阱:
- 小数点被识别为句点(12.5 → 125)
- 货币符号粘连(¥120 → ¥120)
- 促销价干扰(原价¥88 现价¥68)
我们的解决方案:
def price_sanitize(text): # 处理小数点 text = text.replace('.', '。').replace('。', '.') # 提取数字 amount = re.findall(r'\d+\.?\d*', text) # 验证逻辑 if len(amount) == 1 and float(amount[0]) < 1000: return float(amount[0]) return 0.04. 实际应用案例
以某次8人火锅聚餐为例:
拍摄菜单获取原始数据:
- 肥牛卷 ¥48
- 毛肚 ¥56
- ...(共22个菜品)
参与者勾选:
- 张三:肥牛卷、虾滑、啤酒
- 李四:毛肚、羊肉、酸梅汤
- ...(其他6人各选3-5个菜品)
系统自动计算:
- 总消费:¥842
- 传统AA:每人¥105.25
- 精确AA:
- 张三应付:¥78(少付27.25)
- 李四应付:¥129(多付23.75)
实测数据:使用精确AA模式后,85%的参与者认为分摊更公平,尤其适合有素食者或不饮酒的饭局。
5. 常见问题排查
Q1:识别结果出现乱码怎么办?
- 检查图片是否反光
- 尝试手动框选识别区域
- 临时解决方案:手动输入错误条目
Q2:多人同时勾选会冲突吗?
- 采用乐观锁机制:
function updateSelection(itemId, userId) { const item = findItem(itemId); if(!item.locked) { item.locked = true; item.selectedBy = userId; } }
Q3:如何应对合菜(大份分摊)场景?
- 特殊标记"共享菜品"
- 设置分摊人数
- 算法自动按人数均分该菜品金额
开发过程中最意外的收获是发现了很多餐厅的"定价心理学":价格尾数8的出现频率高达76%(比如48、58、88),这反而帮助我们提升了数字识别准确率。现在每次朋友聚会,当大家为算账犯难时,我就默默掏出手机拍下菜单——这种用技术解决生活痛点的成就感,比省下的算账时间更让人愉悦。