Go to file
wanghanbo e0a4f85f59 去除用秒判断,通过帧率判断,菜名不一样,但像同盘,就过滤 2026-10-14 15:12:28 +08:00
.idea commit 2026-09-28 10:09:23 +08:00
camera Sync production client code: live preview overlay and related updates. 2026-09-29 11:03:42 +08:00
central_server 识别菜品上传接口 2026-10-12 09:55:55 +08:00
core 「手」「空盘」直接丢弃,不裁剪、不识别、不上报, 2026-10-14 15:09:14 +08:00
dao 去除用秒判断,通过帧率判断,菜名不一样,但像同盘,就过滤 2026-10-14 15:12:28 +08:00
dish_server 「手」「空盘」直接丢弃,不裁剪、不识别、不上报, 2026-10-14 15:09:14 +08:00
docs ui更新,增加主食类别,未识别图片也上传,增加上报无法识别样本接口 2026-10-13 16:10:23 +08:00
models commit 2026-09-28 10:09:23 +08:00
service ui更新,增加主食类别,未识别图片也上传,增加上报无法识别样本接口 2026-10-13 16:10:23 +08:00
tools ui更新,增加主食类别,未识别图片也上传,增加上报无法识别样本接口 2026-10-13 16:10:23 +08:00
ui 去除用秒判断,通过帧率判断,菜名不一样,但像同盘,就过滤 2026-10-14 15:12:28 +08:00
.gitignore 主食判断,菜品菜单判断 2026-10-13 10:21:12 +08:00
README.md commit 2026-09-28 10:09:23 +08:00
best.pt commit 2026-09-28 10:09:23 +08:00
hik_camera_app.py 「手」「空盘」直接丢弃,不裁剪、不识别、不上报, 2026-10-14 15:09:14 +08:00
requirements.txt Sync production client code: live preview overlay and related updates. 2026-09-29 11:03:42 +08:00
start_main.vbs Sync production client code: live preview overlay and related updates. 2026-09-29 11:03:42 +08:00
start_usb.bat Sync production client code: live preview overlay and related updates. 2026-09-29 11:03:42 +08:00
usb_camera_app.py ui更新,增加主食类别,未识别图片也上传,增加上报无法识别样本接口 2026-10-13 16:10:23 +08:00
菜品识别.bat Sync production client code: live preview overlay and related updates. 2026-09-29 11:03:42 +08:00

README.md

Dish_Recognition 菜品识别

食堂场景下的菜品识别客户端:YOLO 检测餐盘区域 → ConvNeXt 特征匹配认菜 → 结果上报业务 API。
支持海康工业相机 / USB 相机,模板库使用 MySQL 总库 + SQLite 本地缓存。

功能概览

  • 拍照或选图识别,多盘自动裁剪
  • 菜品模板学习 / 删除 / 阈值调节
  • 未知菜即时确认学习,或进入待学习库稍后处理
  • 识别结果上报(默认本地 mock,可切真实 API)
  • 多基地模板同步:本地读 SQLite,变更写入 MySQL 后再拉取刷新;支持手动「立即同步」、上传队列查看与失败重试

目录结构

Dish_Recognition/
├── hik_camera_app.py          # 入口:海康相机
├── usb_camera_app.py          # 入口:USB 相机
├── best.pt                    # YOLO 餐盘检测权重(需放在项目根目录)
│
├── ui/                        # 界面
│   ├── classifier_ui.py       # 公共主界面(三栏:模板库 / 主区 / 结果栏)
│   ├── pending_learning_ui.py # 待学习库 / 新菜确认弹窗
│   ├── widgets.py             # 自绘组件:圆角卡片、胶囊按钮、徽章、Toast 等
│   └── theme.py               # 设计令牌:COLORS / FONTS / RADIUS / SPACING
│
├── core/                      # 识别核心
│   ├── common_classifier.py   # ConvNeXt 特征提取与模板匹配
│   ├── detect_plate_process.py# YOLO 检盘与标注可视化
│   └── pending_learning.py    # 待学习持久化
│
├── camera/                    # 海康相机封装 + MvImport SDK
├── central_server/            # 总服务器中心 API(单独拷贝部署)
├── service/                   # 工位机上报客户端等
├── dao/                       # 数据库连接、模板仓储、同步
├── models/                    # 表模型
├── tools/                     # 杂项脚本 + UI 冒烟测试
│
├── api_mock_results/          # mock 模式下的识别上报 JSON
├── media_store/               # 工位机 mock 时的备用目录(可选)
├── cutting/                   # 裁剪的餐盘图
└── detect/                    # 带标注的识别预览图

根目录只保留两个启动入口;业务代码按模块分目录。

环境要求

  • Windows + Python 3.10+(推荐)
  • 海康入口需安装 MVS 运行库,默认 DLL:
    C:\Program Files (x86)\Common Files\MVS\Runtime\Win64_x64\MvCameraControl.dll
  • 项目根目录放置 best.pt

安装依赖

pip install -r requirements.txt

依赖清单见根目录 requirements.txt(识别 / 数据库 / HTTP;海康 MVS 运行库需单独安装)。

快速启动

在项目根目录执行:

# 海康相机
python hik_camera_app.py

# USB 相机(无海康时可直接用)
python usb_camera_app.py

无相机时也可在界面中「选择识别图片」做离线识别。

UI 冒烟测试(改界面后建议先跑)

改动了 ui/ 下的组件或布局后,可以先跑一遍冒烟测试:它会真实实例化全部自绘组件、 主窗口三栏布局与全部弹窗,捕捉 Tk 运行时的参数错误,并校验旧版纯 ttk 宿主 (core.common_classifier.BaseClassifierUI)仍能复用同一套 Mixin。

python tools/ui_smoke_test.py

脚本会自动为缺失的重依赖(torch / torchvision / ultralytics)打桩, 因此不装 YOLO 与深度学习框架也能运行。全部通过时输出 全部通过, 失败时会打印具体组件名与完整堆栈。

主流程

拍照 / 选图
  → YOLO 检测餐盘并裁剪
  → 与本地 SQLite 模板特征比对
  → 达阈值:输出菜名;未达:新菜确认弹窗 / 待学习库
  → 结构化结果上报 API(mock 或真实)

学新菜 / 删模板时:优先写 MySQL → 再拉回 SQLite;离线则写入本地 upload_queue,联网后补传。

数据库说明

层级 用途 主要表
MySQL(总服务器) 权威数据,多基地共享 dish_categories、global_templates、bases、app_settings 等
SQLite(本地) 识别缓存,默认 dao/dish_templates.db 同类镜像 + upload_queue
  • 对比认菜只依赖 feature_blob 向量,不依赖本机图片路径。
  • image_url / thumbnail_url 若存绝对路径,本机可预览;跨基地展示需改为可访问 URL。
  • 旧版 ~/ImageClassifierTemplates/templates_data.json 会在首次启动时自动迁入数据库(备份为 .bak)。

常用环境变量(数据库)

# MySQL
set DISH_DB_NAME=goods
set CARC_CRAWLER_DB_HOST=127.0.0.1
set CARC_CRAWLER_DB_PORT=3306
set CARC_CRAWLER_DB_USER=root
set CARC_CRAWLER_DB_PASSWORD=123456

# SQLite 路径(可选)
set DISH_SQLITE_PATH=D:\path\to\dish_templates.db

# 本机基地(学菜写入 source_base_id;结果 API 默认也用此编码)
# 建议填写管理系统返回的 code,例如:上海路基地-1788059884828-e55vu
set DISH_BASE_CODE=BASE_01
set DISH_BASE_NAME=一号食堂
set DISH_BASE_REGION=上海

# 管理系统基地同步(GET + X-API-Key)
set DISH_MGMT_API_URL=https://cpsb.ruimeida.cc/api/public/bases
set DISH_MGMT_API_KEY=dish_a4f7a1287198179fdc6a6571aa14064c2afc6338c6730e2b668ac5be15bdf769
set DISH_MGMT_SYNC_ON_START=1

# 当天排餐(启动拉取,界面不展示;识别比对不在菜单→待学习)
set DISH_MGMT_MEAL_ON_START=1
set DISH_BASE_NAME=上海路基地

# 设备状态定时上报
set DISH_DEVICE_CODE=SETTLE_01
set DISH_DEVICE_NAME=一号结算机
set DISH_DEVICE_STATUS_INTERVAL=60
set DISH_DEVICE_STATUS_ENABLED=1

# 识别结果上报到管理系统(默认已指向此地址)
set DISH_RESULT_API_MOCK=0
set DISH_RESULT_API_URL=https://cpsb.ruimeida.cc/api/public/recognition-orders
# 图片上传仍可用本机 central_server(管理系统公开接口一般不提供 upload)
set DISH_MEDIA_UPLOAD_URL=http://127.0.0.1:9000/api/dish/media/upload

# 同步
set DISH_SYNC_ON_START=1
set DISH_SYNC_INTERVAL=300

每个客户端设置不同的 DISH_BASE_CODE(对齐管理系统 code)。启动时会:

  1. 用 X-API-Key 拉取管理系统基地列表并 upsert 到 MySQL bases
  2. 按 DISH_BASE_CODE 查找本机基地(找不到才本地注册)
  3. 学到的模板带上对应 source_base_id;模板同步时镜像全部基地并刷新心跳

详见 docs/第三方接口文档.md。界面状态栏显示基地编码与总库在线情况。

同步体验(界面)

  • 顶栏基地徽章旁有 同步 入口;左侧有 立即同步 / 同步详情
  • 立即同步:上传本地队列(离线学菜/删菜)→ 从 MySQL 全量刷新 SQLite
  • 同步详情:查看待上传 / 失败队列,可 重试失败
  • 徽章文案示例:BASE_01 · 在线 16:20:03 · 队2/败1(有积压时显示)
  • 后台定时同步也会顺带 flush 上传队列(不再只 pull);默认每 300 秒(可用环境变量 DISH_SYNC_INTERVAL 调整)

手动迁移旧 JSON(可选):

python dao/migrate_json_templates.py

识别记录

每次选图/拍照识别(含上报)会写入表 recognition_records:

  • 本地 SQLite 必写(dao/dish_templates.db)
  • MySQL 可用时同步写入总库
  • 字段含:request_id、基地编码、原图/标注图、菜数/未知数、摘要、完整 dishes JSON

界面左侧「识别记录」可查看最近记录,双击看详情。

识别结果 API

工位机上报见 service/result_api.py。总服务器单独目录 central_server/(可整夹拷走部署)。

协议:先传图,再上报。 source_image / labeled_image / crop_image 必须是 HTTP URL。

默认发往菜品管理系统:POST https://cpsb.ruimeida.cc/api/public/dish/recognize/result(带 X-API-Key)。
设 DISH_RESULT_API_MOCK=1 时不发 HTTP,只写本地 media_store/ + api_mock_results/。

# 启动总服务器中心 API(推荐:进入独立目录)
cd central_server
python start.py
# 或项目根:python -m central_server
# 或双击 central_server/start.bat

# 模拟:只写本地 media_store + JSON
set DISH_RESULT_API_MOCK=1

# 对接菜品管理系统(默认)
set DISH_RESULT_API_MOCK=0
set DISH_RESULT_API_URL=https://cpsb.ruimeida.cc/api/public/recognition-orders
set DISH_MGMT_API_KEY=你的密钥

# 或对接本地中心服务
set DISH_RESULT_API_URL=http://127.0.0.1:9000/api/dish/recognize/result
# 可选;不设则由上报地址推导;指向管理系统时会回落到本机 upload
set DISH_MEDIA_UPLOAD_URL=http://127.0.0.1:9000/api/dish/media/upload
set DISH_MEDIA_PUBLIC_BASE=http://127.0.0.1:9000/media
set DISH_RESULT_BASE_ID=BASE_01

上报字段示例(仅已识别菜;未知菜只进待学习库):

{
  "request_id": "REQ_...",
  "base_id": "BASE_LOCAL_01",
  "capture_time": "2026-09-08 09:22:41",
  "source_image": "http://127.0.0.1:9000/media/BASE_LOCAL_01/REQ_.../source_xxx.jpg",
  "labeled_image": "http://127.0.0.1:9000/media/BASE_LOCAL_01/REQ_.../labeled_xxx.jpg",
  "dish_count": 1,
  "dishes": [
    {
      "index": 1,
      "name": "土豆丝",
      "crop_image": "http://127.0.0.1:9000/media/BASE_LOCAL_01/REQ_.../crop_xxx.jpeg"
    }
  ]
}

界面结构

主窗口为三栏布局,顶栏常驻状态与入口,底部为状态条:

顶栏   品牌区 │ 相机 / API / 基地 徽章 │ 同步 · 设置
左侧栏 模板库:搜索框 → 菜品卡片列表 → 待学习库 / 识别记录 / 立即同步
主区   识别控制(选图·拍照 + 阈值) → 模板学习 → 预览(自适应撑满)
结果栏 本次识别结果:逐盘菜名 + 相似度 + 状态标签
状态条 单行结果摘要(识别中显示旋转动效)
  • 视觉令牌集中在 ui/theme.py,改配色 / 字号 / 圆角只需改这一处。
  • 组件集中在 ui/widgets.py,新增了 Tooltip、Toast、SearchBox、Chip、StatTile、EmptyState 等;原有类的构造签名保持兼容。
  • 鼠标滚轮由主界面统一接管,指针在哪个滚动区就滚哪个。

界面操作摘要

主界面为结算机布局:左已点清单、右预览+总价、底栏绿按钮。

操作 说明
拍照识别 / 选图识别 检盘 → 认菜 → 写入已点清单并上报
已点清单 缩略图 / 菜名 / 数量± / 单价 / 小计 / 删除
清空 清空本次已点(不影响模板库)
设置 → 模板学习 / 模板库 学菜、删模板、待学习库、识别记录、同步
设置 → 识别与上报 阈值、API 模式、本地单价、同步入口
新菜确认弹窗 识别后当场命名学习

本地模板图片与待学习图片目录:~/ImageClassifierTemplates/(含 pending_images/、缩略图等)。

当前范围说明

  • 业务目标:识别菜品并发送给 API;界面可展示本地单价与数量,不做真实支付结算。
  • 跨基地模板图展示:总库建议存 URL;绝对路径仅适合本机调试预览。