Dish_Recognition/README.md

278 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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;绝对路径仅适合本机调试预览。