278 lines
11 KiB
Markdown
278 lines
11 KiB
Markdown
# 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`
|
||
|
||
### 安装依赖
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
> 依赖清单见根目录 `requirements.txt`(识别 / 数据库 / HTTP;海康 MVS 运行库需单独安装)。
|
||
|
||
## 快速启动
|
||
|
||
在项目根目录执行:
|
||
|
||
```bash
|
||
# 海康相机
|
||
python hik_camera_app.py
|
||
|
||
# USB 相机(无海康时可直接用)
|
||
python usb_camera_app.py
|
||
```
|
||
|
||
无相机时也可在界面中「选择识别图片」做离线识别。
|
||
|
||
### UI 冒烟测试(改界面后建议先跑)
|
||
|
||
改动了 `ui/` 下的组件或布局后,可以先跑一遍冒烟测试:它会真实实例化全部自绘组件、
|
||
主窗口三栏布局与全部弹窗,捕捉 Tk 运行时的参数错误,并校验旧版纯 ttk 宿主
|
||
(`core.common_classifier.BaseClassifierUI`)仍能复用同一套 Mixin。
|
||
|
||
```bash
|
||
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`)。
|
||
|
||
### 常用环境变量(数据库)
|
||
|
||
```bash
|
||
# 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(可选):
|
||
|
||
```bash
|
||
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/`。
|
||
|
||
```bash
|
||
# 启动总服务器中心 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
|
||
```
|
||
|
||
上报字段示例(仅已识别菜;未知菜只进待学习库):
|
||
|
||
```json
|
||
{
|
||
"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;绝对路径仅适合本机调试预览。
|