拍摄菜单照片,识别菜品和价格,选中菜品自动算总价,适配聚餐AA制快速算账

2 阅读10分钟

设计一个“聚餐AA制智能算账助手”。这个程序利用计算机视觉(CV)和Python,通过拍摄菜单照片,自动识别菜品和价格,并允许用户选择自己点的菜,最后自动计算每个人需要支付的金额,非常适合朋友聚会、公司团建等AA制场景。

  1. 实际应用场景描述

在朋友聚餐或公司团建时,点完菜后结账时往往面临一个难题:

  • 算账慢:大家点了不同的菜,需要回忆和统计每个人吃了什么,手动计算很费时。
  • 算错账:手动计算容易出错,导致有人多付或少付,影响心情。
  • AA制不精确:虽然名义上是AA,但点菜时可能有人多点了硬菜,简单的“总额/人数”并不公平。
  • 尴尬:在桌上逐一询问、记录每个人的消费,场面可能略显尴尬。

我们的程序可以解决这些问题:

  1. 拍张照:在服务员下单后,用手机或电脑给菜单拍张照。

  2. 自动识别:程序自动识别出所有菜名和对应的价格。

  3. 快速点选:在程序界面上,大家可以快速勾选自己点的菜。

  4. 自动算账:程序自动计算总金额,并生成每个人(或每道菜)的账单,实现精确的AA制或多退少补。

  5. 引入痛点

  • 痛点1:信息获取难 - 从纸质或电子菜单上快速、准确地获取所有菜品和价格信息,对计算机来说是一个挑战。
  • 痛点2:用户交互 - 如何让用户(尤其是不太懂技术的朋友)能简单、直观地选择自己点的菜。
  • 痛点3:计算逻辑 - 处理各种AA制情况,如“整单均摊”、“按人点菜均摊”、“部分人共吃某道菜”等。
  • 痛点4:环境适应性 - 照片可能因为光线、角度、字体等问题,导致识别率下降。
  1. 核心逻辑讲解

  2. 图像输入:用户通过程序界面上传或拍摄菜单照片。

  3. 图像预处理:对图像进行灰度化、二值化、去噪等操作,为OCR做准备。

  4. 光学字符识别 (OCR):使用PaddleOCR等强大的开源库,从预处理后的图像中提取出所有的文字及其坐标位置。

  5. 菜品价格解析:

    • 遍历OCR识别出的每一行文字。
    • 使用正则表达式匹配可能的“菜品名 + 价格”的模式(例如: "宫保鸡丁 ¥38" 或 "Fish & Chips $12.99")。
    • 将匹配成功的菜品名和价格存入一个列表。
  6. 用户界面 (UI):使用 "tkinter" 创建一个简单的桌面应用。界面包含:

    • 一个区域显示上传的菜单图片。
    • 一个列表显示识别出的菜品和价格,旁边有复选框供用户选择。
    • 一个输入框用于输入用餐人数。
    • “计算账单”按钮。
    • 一个区域显示最终的结算结果。
  7. 账单计算:

    • 当用户点击“计算账单”时,程序会收集所有被选中的菜品的价格总和。
    • 根据用户选择的AA模式(例如:均摊总价、按人头计算),计算出每个人应付的金额。
  8. 结果展示:清晰地展示每个人的应付金额,并可以生成简单的文本账单,方便分享。

  9. 代码模块化

我们将项目组织为以下模块,保持代码清晰和可维护性。

aa_calculator/ ├── main.py // 程序主入口,创建UI ├── ocr_processor.py // OCR处理模块 ├── bill_calculator.py // 账单计算逻辑模块 └── requirements.txt // 项目依赖

  1. 关键代码实现 (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

  1. 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 中进一步开发。
  1. 核心知识点卡片

知识点 说明与应用 PaddleOCR 一个优秀的开源OCR库,支持多语言,能准确识别印刷体文字,是本项目实现“拍菜单”功能的核心。 Tkinter Python的标准GUI库,用于快速构建桌面应用的用户界面,如按钮、列表、输入框等。 OpenCV 强大的计算机视觉库,用于图像的读取、预处理(如灰度化、二值化),为OCR提供更好的输入。 Pillow (PIL) 另一个图像处理库,与Tkinter结合得更好,用于在UI中显示图像。 模块化设计 将OCR、计算、UI分别放在不同文件中,使代码结构清晰,易于维护和扩展。 正则表达式 (Regex) 用于在OCR识别出的大段文本中,精准地提取出“菜品名”和“价格”这两个关键信息。 事件驱动编程 通过响应用户的点击、选择等事件来驱动程序运行,是GUI应用的基础。

  1. 总结

这个“聚餐AA制智能算账助手”项目,成功地将计算机视觉 (CV) 和桌面应用开发结合起来,解决了一个非常实际的生活痛点。

  • 创新点:将传统的“手动算账”过程,通过“拍照-识别-点选-计算”的自动化流程,极大地提升了效率和准确性。
  • 技术价值:项目涵盖了从图像输入、预处理、信息提取到用户交互和结果展示的完整数据处理链条,是信息技术实用创新实践的优秀案例。
  • 可扩展性:
    • 可以增加对不同货币的支持。
    • 可以实现更复杂的AA逻辑,比如“谁点了什么就谁付钱”。
    • 可以开发一个后端,让用户能够保存和分享账单记录。
    • 甚至可以开发一个移动端APP,让用户直接用手机拍照使用。

利用AI高效解决实际问题,如果你觉得这个工具好用,欢迎关注我!