设计一个“聚餐AA制智能算账助手”。这个程序利用计算机视觉(CV)和Python,通过拍摄菜单照片,自动识别菜品和价格,并允许用户选择自己点的菜,最后自动计算每个人需要支付的金额,非常适合朋友聚会、公司团建等AA制场景。
1. 实际应用场景描述
在朋友聚餐或公司团建时,点完菜后结账时往往面临一个难题:
* 算账慢:大家点了不同的菜,需要回忆和统计每个人吃了什么,手动计算很费时。
* 算错账:手动计算容易出错,导致有人多付或少付,影响心情。
* AA制不精确:虽然名义上是AA,但点菜时可能有人多点了硬菜,简单的“总额/人数”并不公平。
* 尴尬:在桌上逐一询问、记录每个人的消费,场面可能略显尴尬。
我们的程序可以解决这些问题:
1. 拍张照:在服务员下单后,用手机或电脑给菜单拍张照。
2. 自动识别:程序自动识别出所有菜名和对应的价格。
3. 快速点选:在程序界面上,大家可以快速勾选自己点的菜。
4. 自动算账:程序自动计算总金额,并生成每个人(或每道菜)的账单,实现精确的AA制或多退少补。
2. 引入痛点
* 痛点1:信息获取难 - 从纸质或电子菜单上快速、准确地获取所有菜品和价格信息,对计算机来说是一个挑战。
* 痛点2:用户交互 - 如何让用户(尤其是不太懂技术的朋友)能简单、直观地选择自己点的菜。
* 痛点3:计算逻辑 - 处理各种AA制情况,如“整单均摊”、“按人点菜均摊”、“部分人共吃某道菜”等。
* 痛点4:环境适应性 - 照片可能因为光线、角度、字体等问题,导致识别率下降。
3. 核心逻辑讲解
1. 图像输入:用户通过程序界面上传或拍摄菜单照片。
2. 图像预处理:对图像进行灰度化、二值化、去噪等操作,为OCR做准备。
3. 光学字符识别 (OCR):使用PaddleOCR等强大的开源库,从预处理后的图像中提取出所有的文字及其坐标位置。
4. 菜品价格解析:
* 遍历OCR识别出的每一行文字。
* 使用正则表达式匹配可能的“菜品名 + 价格”的模式(例如:
"宫保鸡丁 ¥38" 或
"Fish & Chips $12.99")。
* 将匹配成功的菜品名和价格存入一个列表。
5. 用户界面 (UI):使用
"tkinter" 创建一个简单的桌面应用。界面包含:
* 一个区域显示上传的菜单图片。
* 一个列表显示识别出的菜品和价格,旁边有复选框供用户选择。
* 一个输入框用于输入用餐人数。
* “计算账单”按钮。
* 一个区域显示最终的结算结果。
6. 账单计算:
* 当用户点击“计算账单”时,程序会收集所有被选中的菜品的价格总和。
* 根据用户选择的AA模式(例如:均摊总价、按人头计算),计算出每个人应付的金额。
7. 结果展示:清晰地展示每个人的应付金额,并可以生成简单的文本账单,方便分享。
4. 代码模块化
我们将项目组织为以下模块,保持代码清晰和可维护性。
aa_calculator/
├── main.py // 程序主入口,创建UI
├── ocr_processor.py // OCR处理模块
├── bill_calculator.py // 账单计算逻辑模块
└── requirements.txt // 项目依赖
5. 关键代码实现 (Python)
"ocr_processor.py" (OCR处理模块)
# ocr_processor.py
import cv2
import numpy as np
from paddleocr import PaddleOCR
import re
# 初始化PaddleOCR,使用中文简体模型
# use_angle_cls=True 用于纠正图片旋转,use_gpu=False 表示不使用GPU
ocr = PaddleOCR(use_angle_cls=True, lang='ch', use_gpu=False)
def preprocess_image(image_path):
"""
对图像进行预处理,以提高OCR识别率。
"""
img = cv2.imread(image_path)
# 转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 可选:使用高斯模糊去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 可选:自适应阈值二值化,增强文字对比度
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
return thresh
def extract_menu_items(image_path):
"""
从菜单图片中提取菜品和价格。
"""
# 可以直接使用原图,也可以传入预处理后的图像
# processed_img = preprocess_image(image_path)
# result = ocr.ocr(processed_img, cls=True)
# 直接使用原图进行OCR
result = ocr.ocr(image_path, cls=True)
menu_items = []
price_pattern = re.compile(r'([\u4e00-\u9fa5]+[^¥]*)\s*¥?(\d+\.?\d*)')
if not result or not result[0]:
print("OCR未能识别到任何文字。")
return []
for line in result[0]:
text = line[1][0]
box = line[0]
# 尝试匹配菜品和价格
match = price_pattern.search(text)
if match:
dish_name = match.group(1).strip()
price = float(match.group(2))
menu_items.append({'name': dish_name, 'price': price})
print(f"识别到菜品: {dish_name}, 价格: {price}")
else:
# 对于无法匹配价格的行,可以选择性地打印出来进行分析
# print(f"无法解析的行: {text}")
pass
return menu_items
"bill_calculator.py" (账单计算模块)
# bill_calculator.py
class BillCalculator:
def __init__(self, menu_items):
self.menu_items = menu_items
def calculate_total_for_selected(self, selected_indices):
"""计算选中菜品的总价"""
total = 0
for index in selected_indices:
if 0 <= index < len(self.menu_items):
total += self.menu_items[index]['price']
return total
def calculate_split_bill(self, selected_indices, num_people):
"""计算人均费用"""
total = self.calculate_total_for_selected(selected_indices)
if num_people <= 0:
return 0
return total / num_people
"main.py" (程序主入口)
# main.py
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import ocr_processor
import bill_calculator
class AAApp:
def __init__(self, root):
self.root = root
self.root.title("聚餐AA制智能算账助手")
self.root.geometry("800x700")
self.menu_items = []
self.selected_items = []
self.setup_ui()
def setup_ui(self):
# --- 顶部:图片上传 ---
top_frame = tk.Frame(self.root)
top_frame.pack(pady=10)
btn_upload = tk.Button(top_frame, text="上传菜单照片", command=self.upload_image)
btn_upload.pack()
self.image_label = tk.Label(top_frame)
self.image_label.pack()
# --- 中部:菜品列表 ---
middle_frame = tk.Frame(self.root)
middle_frame.pack(pady=10, padx=10, fill="both", expand=True)
tk.Label(middle_frame, text="请从下方选择您点的菜品:", font=('Helvetica', 12)).pack(anchor='w')
canvas = tk.Canvas(middle_frame)
scrollbar = ttk.Scrollbar(middle_frame, orient="vertical", command=canvas.yview)
self.scrollable_frame = tk.Frame(canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
self.item_vars = [] # 用于存储每个菜品的IntVar
# --- 底部:计算和结果显示 ---
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(pady=10, fill="x", padx=10)
input_frame = tk.Frame(bottom_frame)
input_frame.pack(fill="x", pady=5)
tk.Label(input_frame, text="用餐人数:").pack(side="left")
self.num_people_entry = tk.Entry(input_frame, width=5)
self.num_people_entry.insert(0, "1")
self.num_people_entry.pack(side="left", padx=5)
btn_calculate = tk.Button(bottom_frame, text="计算我的账单", command=self.calculate_bill, font=('Helvetica', 14, 'bold'))
btn_calculate.pack(pady=10)
self.result_label = tk.Label(bottom_frame, text="", font=('Helvetica', 12), fg="blue")
self.result_label.pack()
def upload_image(self):
filepath = filedialog.askopenfilename()
if not filepath:
return
# 显示图片
try:
img = Image.open(filepath)
img.thumbnail((400, 400))
photo = ImageTk.PhotoImage(img)
self.image_label.config(image=photo)
self.image_label.image = photo # keep a reference!
except Exception as e:
messagebox.showerror("错误", f"无法打开图片: {e}")
return
# 进行OCR识别
self.result_label.config(text="正在识别菜单,请稍候...")
self.root.update_idletasks() # 更新UI,显示提示文字
self.menu_items = ocr_processor.extract_menu_items(filepath)
if not self.menu_items:
self.result_label.config(text="未能从图片中识别到任何菜品,请换一张更清晰的图片。")
return
# 清空旧的列表
for widget in self.scrollable_frame.winfo_children():
widget.destroy()
self.item_vars = []
# 填充新的菜品列表
for i, item in enumerate(self.menu_items):
var = tk.IntVar()
chk = tk.Checkbutton(self.scrollable_frame, text=f"{item['name']} - ¥{item['price']}", variable=var)
chk.pack(anchor='w')
self.item_vars.append(var)
self.result_label.config(text=f"识别成功!共找到 {len(self.menu_items)} 道菜品。请勾选您点的菜。")
def calculate_bill(self):
if not self.menu_items:
messagebox.showwarning("警告", "请先上传菜单照片!")
return
try:
num_people = int(self.num_people_entry.get())
if num_people <= 0:
raise ValueError
except ValueError:
messagebox.showerror("错误", "请输入有效的用餐人数!")
return
selected_indices = [i for i, var in enumerate(self.item_vars) if var.get() == 1]
if not selected_indices:
messagebox.showinfo("提示", "您还没有选择任何菜品哦。")
return
calculator = bill_calculator.BillCalculator(self.menu_items)
my_share = calculator.calculate_split_bill(selected_indices, num_people)
my_total = calculator.calculate_total_for_selected(selected_indices)
result_text = f"您共消费: ¥{my_total:.2f}\n{num_people}人AA,您需支付: ¥{my_share:.2f}"
self.result_label.config(text=result_text)
if __name__ == "__main__":
root = tk.Tk()
app = AAApp(root)
root.mainloop()
"requirements.txt" (项目依赖)
paddlepaddle
paddleocr
opencv-python
Pillow
6. README.md
# 聚餐AA制智能算账助手 (AA Calculator Assistant)
一个基于Python的桌面应用程序,旨在通过拍照识别菜单,帮助用户快速、准确地计算聚餐AA制账单。
## ✨ 主要功能
* **拍照识别菜单**:上传菜单照片,自动识别菜品名称和价格。
* **一键勾选菜品**:直观的界面,轻松勾选每位参与者消费的菜品。
* **智能计算账单**:自动计算总金额和个人分摊金额。
* **简单易用**:图形化界面,操作简单,无需专业知识。
## 🛠️ 安装与使用
### 前提条件
* 确保你的电脑已安装 Python (推荐 Python 3.7+)。
* 拥有一个可用的网络连接以下载依赖库。
### 安装步骤
1. **克隆或下载本项目**到本地。
2. **打开终端或命令提示符**,导航到项目根目录 (`aa_calculator/`)。
3. **安装所需依赖库**:
bash
pip install -r requirements.txt
> **注意**:首次运行 `paddleocr` 时会自动下载所需的模型文件,请确保网络通畅。这个过程可能需要几分钟。
### 运行程序
在项目根目录下,运行以下命令启动程序:
bash
python main.py
随后,一个图形化的应用程序窗口将会打开。
### 使用方法
1. 点击 **“上传菜单照片”** 按钮,选择一张清晰的菜单照片。
2. 程序会自动识别菜品并显示在列表中。
3. 勾选你自己点的菜品旁边的复选框。
4. 在 **“用餐人数”** 输入框中输入聚餐总人数。
5. 点击 **“计算我的账单”** 按钮,程序会立即显示你需要支付的金额。
## 🧩 技术栈
* **编程语言**: Python
* **图形界面 (UI)**: Tkinter
* **图像处理**: OpenCV, Pillow
* **核心识别 (OCR)**: PaddleOCR (百度飞桨开源的OCR工具)
* **计算逻辑**: 自定义Python类
## ⚠️ 注意事项
* **识别准确率**:OCR的准确率受图片质量、光线、字体等因素影响。如果识别结果不理想,请尝试提供更清晰、角度更正的图片。
* **环境配置**:`paddleocr` 在第一次运行时会自动下载模型,请耐心等待。
* **AA制模式**:目前程序实现的是“整单均摊”模式,即所有点选的菜品总价除以人数。更复杂的分摊逻辑(如按人点菜、按份数均摊)可以在 `bill_calculator.py` 中进一步开发。
7. 核心知识点卡片
知识点 说明与应用
PaddleOCR 一个优秀的开源OCR库,支持多语言,能准确识别印刷体文字,是本项目实现“拍菜单”功能的核心。
Tkinter Python的标准GUI库,用于快速构建桌面应用的用户界面,如按钮、列表、输入框等。
OpenCV 强大的计算机视觉库,用于图像的读取、预处理(如灰度化、二值化),为OCR提供更好的输入。
Pillow (PIL) 另一个图像处理库,与Tkinter结合得更好,用于在UI中显示图像。
模块化设计 将OCR、计算、UI分别放在不同文件中,使代码结构清晰,易于维护和扩展。
正则表达式 (Regex) 用于在OCR识别出的大段文本中,精准地提取出“菜品名”和“价格”这两个关键信息。
事件驱动编程 通过响应用户的点击、选择等事件来驱动程序运行,是GUI应用的基础。
8. 总结
这个“聚餐AA制智能算账助手”项目,成功地将计算机视觉 (CV) 和桌面应用开发结合起来,解决了一个非常实际的生活痛点。
* 创新点:将传统的“手动算账”过程,通过“拍照-识别-点选-计算”的自动化流程,极大地提升了效率和准确性。
* 技术价值:项目涵盖了从图像输入、预处理、信息提取到用户交互和结果展示的完整数据处理链条,是信息技术实用创新实践的案例。
* 可扩展性:
* 可以增加对不同货币的支持。
* 可以实现更复杂的AA逻辑,比如“谁点了什么就谁付钱”。
* 可以开发一个后端,让用户能够保存和分享账单记录。
* 甚至可以开发一个移动端APP,让用户直接用手机拍照使用。
利用AI高效解决实际问题,如果你觉得这个工具好用,欢迎关注我!