一、阶段目标:从“识别菜单”走向“理解菜品”

上一篇开发日志中,我完成了项目的基础框架搭建,包括 Flutter 前端、FastAPI 后端、MySQL 数据库以及前后端基础联通。那一阶段的重点,是把系统从 0 到 1 跑起来。

而这一阶段,我推进的是项目真正的核心功能:

让用户上传一张菜单图片,系统自动完成识别、翻译、结构化解析,并在前端进行可交互展示。

并且,这一阶段不只是完成了菜单列表的解析展示,我还进一步扩展了菜品详情页的二次分析能力

用户点击某一道菜后,前端会再次调用后端 API,获取该菜品更细粒度的分析信息,例如主要原材料、过敏原、口味特征、菜系类型、烹饪方法、营养信息和饮食限制等。

这意味着,系统已经不只是“把菜单翻译出来”,而是在向“智能点餐助手”真正靠近。


二、本阶段核心成果

这一阶段,我完成了项目目前最重要的一条业务链路:

菜单图片上传 → 模型解析翻译 → 菜品列表展示 → 菜品详情二次分析

具体来说,我完成了以下功能:

  • ✅ 后端接入 Qwen 多模态模型,直接解析菜单图片

  • ✅ 输出菜品名称、中文翻译、描述、价格、标签等结构化信息

  • ✅ 前端展示菜品解析结果列表

  • ✅ 前端支持点击菜品进入详情页

  • ✅ 详情页再次调用 API,获取菜品深度分析信息

  • ✅ 展示原材料、过敏原、口味特征、菜系类型、烹饪方法、营养信息等内容

  • ✅ 修复上传报错与前端解析失败问题,打通完整链路

相比上一篇“框架搭建”的阶段,这一阶段最大的不同在于:

系统已经具备了一个完整、可演示、可交互的核心业务能力。


三、本阶段最关键的实现:调用模型对菜单图片进行解析与翻译

这一阶段最重要的,不是单纯完成图片上传,而是实现了:

调用模型对菜单图片进行识别、翻译和结构化理解。

以前如果只是用传统 OCR,往往只能拿到一段原始菜单文本,信息杂乱,前端也很难直接展示。
而现在,我在后端直接调用 Qwen 多模态模型,让它完成更高层次的任务:

  • 识别菜单中的菜品内容

  • 翻译菜品名称和描述

  • 提取价格信息

  • 概括菜品标签

  • 按统一字段输出结构化结果

后端上传接口的核心逻辑如下:


@app.post("/upload")

async def upload_file(file: UploadFile = File(...)):

    contents = await file.read()

    if not file.content_type or not file.content_type.startswith("image/"):

        raise HTTPException(status_code=400, detail="请上传图片文件")

    parsed_result = parse_menu_image_with_qwen(contents, file.content_type)

    return {

        "success": True,

        "message": "解析成功",

        "items": parsed_result.get("items", [])

    }

这里有两个关键点:

  1. 上传接口不再只是接收文件,而是直接进入模型解析流程。

  2. 后端不再依赖 Tesseract OCR,而是直接调用 Qwen 多模态模型完成图片理解。

这一点对项目来说非常重要,因为它让菜单解析从“文本提取”升级成了“语义理解”。

四、结构化返回:让模型输出真正能被前端消费

为了让前端稳定展示解析结果,我将后端输出统一为标准 JSON 结构,例如:


{

  "success": true,

  "message": "解析成功",

  "items": [

    {

      "name_original": "Two-Roll Special",

      "name_zh": "双卷特惠",

      "description": "包含两种寿司卷,可选加州卷、JB卷、三文鱼卷、熟三文鱼卷、辣金枪鱼卷或蔬菜卷,附汤或沙拉。",

      "price": "$12.95",

      "tags": ["寿司", "特惠"]

    }

  ]

}

这一步看起来只是“定义了几个字段”,但实际上非常关键。

因为只有把模型输出变成稳定的结构化格式,前端才能真正完成:

  • 列表展示

  • 详情展示

  • 跳转交互

  • 后续收藏、推荐、问答等扩展

也就是说,这一阶段我做的不只是“把结果返回出来”,而是:

把模型生成结果转化成了前端可直接使用的数据接口。

五、前端展示:从菜单图片到菜品列表页

在 Flutter 前端,我完成了菜单解析结果的列表展示。

上传菜单图片后,前端会请求后端 /upload 接口,拿到结构化菜品数据,然后渲染为菜品卡片列表。每张卡片展示:

  • 原文菜名

  • 中文菜名

  • 描述

  • 价格

  • 标签

前端上传与解析的关键逻辑如下:


final response = await _uploadImageToBackend(

  bytes: bytes,

  filename: file.name,

);

final data = jsonDecode(response.body);

if (data['success'] == true && data['items'] != null) {

  final parsedItems = (data['items'] as List)

      .map((item) => MenuItem.fromJson(item))

      .toList();

  Navigator.of(context).push(

    MaterialPageRoute(

      builder: (_) => ResultsPage(parsedItems: parsedItems),

    ),

  );

}

同时,前端 MenuItem 模型也与后端字段保持一致:


class MenuItem {

  final String? nameOriginal;

  final String? nameZh;

  final String? description;

  final String? price;

  final List<String>? tags;

  factory MenuItem.fromJson(Map<String, dynamic> json) {

    return MenuItem(

      nameOriginal: json['name_original'],

      nameZh: json['name_zh'],

      description: json['description'],

      price: json['price']?.toString(),

      tags: json['tags'] != null ? List<String>.from(json['tags']) : null,

    );

  }

}

这一部分完成后,系统已经可以实现:

用户上传一张菜单图,前端直接展示出可读的中文菜品列表。
在这里插入图片描述

在这里插入图片描述

六、进一步扩展:菜品详情页不再只是静态展示,而是再次调用 API 进行深度分析

这一阶段我觉得最有价值的增强,不只是列表页,而是详情页的能力升级。

最开始,详情页只能展示在列表阶段已经拿到的基础信息,例如:

  • 中文名称

  • 原始英文名称

  • 价格

  • 描述

  • 标签

但这还远远不够。
因为用户在点餐时,往往更关心的是:

  • 这道菜主要是由什么做成的?

  • 有没有我过敏的成分?

  • 口味偏甜还是偏咸?

  • 属于什么菜系?

  • 是烤的、炸的还是煮的?

  • 营养特点是什么?

  • 适不适合某种饮食限制?

因此,我在后端新增了一个接口:

/dish-details:对单道菜进行深度分析

接口核心逻辑如下:


@app.post("/dish-details")

async def get_dish_details(request: dict):

    name_zh = request.get("name_zh")

    name_original = request.get("name_original")

    if not name_zh:

        raise HTTPException(status_code=400, detail="菜品中文名不能为空")

    details_result = get_dish_details_with_qwen(name_zh, name_original)

    return details_result

也就是说,当用户点击某道菜进入详情页时,系统会基于该菜品名称,再调用一次模型,让模型对这道菜进行更深入的语义分析,而不是只停留在菜单表层信息。

七、详情页的前端实现:按需加载更多菜品信息

在 Flutter 前端,详情页增加了一个“获取详细分析”按钮。点击后,前端会向 /dish-details 发送请求,请求体中包含:

  • name_zh

  • name_original

核心请求逻辑如下:


final response = await http.post(

  Uri.parse('http://127.0.0.1:8000/dish-details'),

  headers: {'Content-Type': 'application/json'},

  body: jsonEncode({

 'name_zh': widget.menuItem.nameZh,

    'name_original': widget.menuItem.nameOriginal,

  }),

);

如果返回成功,页面会展示详细分析结果。

这一步的意义非常大,因为它让详情页变成了一个按需扩展的智能分析页面,而不只是一个静态信息页。

八、详情页展示了哪些新增信息?

目前,菜品详情页已经能够展示以下深度分析内容:
在这里插入图片描述

1. 主要原材料

例如生菜、面包丁、帕玛森奶酪、凯撒沙拉酱、烤鸡肉、烤虾等。

2. 过敏原信息

例如乳制品、贝类、小麦等。

3. 口味特征

我把模型返回的口味特征做成了可视化进度条,包括:

每项以 0~10 分表示,用户可以更直观看出菜品风味倾向。

4. 菜系类型

例如西餐、日式等。

5. 烹饪方法

例如烤、炸、煮等。

6. 营养信息

例如高蛋白、低脂、富含维生素和矿物质等。

7. 饮食限制

例如是否适合素食、是否含麸质、是否含海鲜等。

这部分内容已经明显超出了普通“菜单翻译”的范围,更接近一个真正的智能菜品理解系统。

九、开发过程中遇到的两个典型问题

虽然这篇日志的重点是“实现菜单解析与详情分析”,但在联调过程中,也遇到了两个必须解决的问题。

1. Flutter 上传图片时偶发 400

问题原因是前端最开始把图片类型写死成了 image/jpeg,上传 PNG、WebP 等图片时,后端可能识别为 application/octet-stream,进而校验失败。

后来我改成了根据文件后缀动态设置 MIME 类型:


late MediaType mediaType;

if (filename.toLowerCase().endsWith('.png')) {

  mediaType = MediaType('image', 'png');

} else if (filename.toLowerCase().endsWith('.webp')) {

  mediaType = MediaType('image', 'webp');

} else {

  mediaType = MediaType('image', 'jpeg');

}

这样之后,上传稳定性就明显提高了。

2. 前端解析后端返回数据失败

在这里插入图片描述

另一个问题是前端一开始没有适配后端的 JSON 结构,尤其是:

  • 外层的 {success, message, items}

  • 字段名 name_original、name_zh

后来通过调整前端解析逻辑和 MenuItem.fromJson() 映射,问题得到解决。

这两个问题本身不是本阶段的最终目标,但确实是把整条链路打通必须跨过去的环节。

十、这一阶段真正完成了什么?

我真正打通了菜单图片解析、翻译、前端展示,以及菜品详情二次分析这整条业务链路。

也就是说,系统现在已经可以做到:

用户上传菜单图片

→ 后端调用 Qwen 解析和翻译菜单

→ 前端展示菜品列表

→ 用户点击菜品进入详情页

→ 前端再次调用 API

→ 后端基于菜品名称返回更深入的分析信息

→ 前端完成详细可视化展示

这已经不是单一接口调通,而是一个完整的交互闭环。

本阶段我主要完成的工作

  • 设计并实现菜单图片上传流程

  • 后端接入 Qwen 多模态模型解析菜单图片

  • 设计菜品结构化返回格式

  • 完成 Flutter 菜品列表页展示

  • 实现详情页跳转与基础信息展示

  • 新增 /dish-details 接口,支持菜品深度分析

  • 完成详情页对二次分析结果的展示与可视化

  • 修复上传类型错误和前端 JSON 解析问题

  • 打通从“图片输入”到“详情分析”的完整流程

十一、当前系统能力总结

截至目前,系统已经具备以下能力:

前端

  • 上传菜单图片

  • 展示菜品列表

  • 展示菜品详情

  • 详情页按需获取更多分析信息

  • 可视化展示口味特征、原材料、过敏原等内容

后端

  • 接收菜单图片

  • 调用 Qwen 多模态模型解析菜单

  • 翻译并结构化输出菜品信息

  • 提供单道菜深度分析接口

  • 返回更细粒度的语义信息

整体流程

  • 菜单图片输入

  • 模型解析与翻译

  • 菜品结构化输出

  • 前端列表展示

  • 菜品详情二次分析

  • 结果可视化呈现

这意味着,项目已经具备了一个比较完整的“智能菜单理解与点餐辅助”雏形。

十二、下一步计划

接下来,我会继续在这一基础上做增强,主要包括:

  • 提高复杂菜单图片的解析准确率

  • 优化模型返回结果的一致性和稳定性

  • 增加菜品推荐与用户偏好联动

  • 加入 AI 问答能力,例如“这道菜适合减脂吗”“有没有不含海鲜的替代选择”

  • 进一步完善收藏、推荐和个性化点餐功能

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐