Files
nanocode/urinecheck/docs/urinecheck-plan.md
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

460 lines
18 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.
# 尿检试纸颜色识别功能 - 改造方案
> 创建时间:2026-05-03
> 基于:urinecheckcpp C++ 代码 (鸿蒙版) → OpenCV Python 重写
> 状态:方案讨论中,待确认后动工
---
## 一、现有 C++ 代码理解
### 1.1 试纸结构
```
┌─────────────────────────────────────────────────────────────────┐
│ 试纸物理结构 │
│ │
│ ┌────────┐ ┌──┐ │
│ │ 大定位块 │ [0][1][2][3][4][5][6][7][8][9][10][11][12][13] │ 小定位块 │
│ │ (10/15mm)│ ←── 14个反应色块 ──→ │ (3mm) │
│ └────────┘ └──┘ │
│ │
│ 定位块颜色: 红/绿/蓝 (两个定位块颜色必须相同) │
│ 大定位块: 10mm×5mm (S型, 宽高比2.0) 或 15mm×5mm (L型, 宽高比3.0) │
│ 小定位块: 3mm×5mm (宽高比≈1.667) │
│ 色块间距: 3.5个单位 (每单位2mm = 7mm间距) │
│ 第一个色块位置: 2.25个单位 (距离小定位块边缘4.5mm) │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 核心算法流程(7步)
| 步骤 | 名称 | 关键操作 |
|------|------|---------|
| 1 | 图像预处理 | BGR/HSV/Gray转换, 高斯滤波(5×5) |
| 2 | 定位块检测 | HSV颜色分割(红/绿/蓝) → 形态学(CLOSE+OPEN, 9×9核) → 轮廓查找; 备选Canny边缘 |
| 3 | 候选筛选+配对 | 面积/宽高比/矩形度过滤 → Y坐标聚类 → 遍历配对验证(颜色一致+RGB距离<180+面积比±20%+Y差<4倍高度) |
| 4 | 清晰度评估 | Laplacian方差法, 分级: 清晰>300 / 轻微>100 / 严重<100 |
| 5 | 杂物检测 | 试纸高度≥30px, 候选数量≤15 |
| 6 | 规格匹配 | distRatio(定位块距离/试纸高度) + areaRatio → 6种规格之一 |
| 7 | 色块位置计算 | 边缘中心点 + 切片插值(slicesNum=50, firstPos=2.25, step=3.5) → 14个中心点 |
### 1.3 颜色读取
- **白平衡**: 灰度世界算法(各通道均值缩放到相同目标值)
- **采样**: 圆形掩码(半径3px)取均值
- **输出**: RGB + HSV(H:0-360) + LAB(L:0-100, a/b:-128-127)
- **顺序**: 始终从靠近小定位块的色块开始编号(id=0)
---
## 二、改造方案
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 新增页面: /admin/urinecheck │ │
│ │ ├── 图片上传 (拖拽/点击, 支持 jpg/png) │ │
│ │ ├── 上传预览 (显示原图) │ │
│ │ ├── 识别按钮 (调用后端API) │ │
│ │ ├── 标注图展示 (色块中心红圈标记) │ │
│ │ └── 结果输出框 (14项颜色值表格) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ HTTP POST /api/urinecheck │
├─────────────────────────────────────────────────────────┤
│ Backend (FastAPI) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 新增路由: routers/urinecheck.py │ │
│ │ ├── POST /api/urinecheck/analyze │ │
│ │ │ 接收: multipart/form-data (image file) │ │
│ │ │ 返回: JSON { colors, annotated_image_url, ... } │ │
│ │ └── GET /api/urinecheck/result/{id} │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ 调用 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 算法模块: urine_recognizer.py (新建) │ │
│ │ ├── class UrineStripRecognizer │ │
│ │ ├── class ColorReader │ │
│ │ ├── class PaperConfig │ │
│ │ └── 全局参数常量 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 2.2 文件清单
```
personal-blog/
├── backend/
│ ├── routers/
│ │ └── urinecheck.py # 新增: API路由
│ ├── urine_recognizer.py # 新增: 核心识别算法 (Python重写)
│ ├── app.py # 修改: 注册新路由
│ └── requirements.txt # 已满足: opencv-python已安装
├── frontend/src/
│ ├── pages/admin/
│ │ └── UrineCheck.jsx # 新增: 尿检识别页面
│ ├── components-preline/
│ │ └── Sidebar.jsx # 修改: 添加导航项
│ ├── App.jsx # 修改: 添加路由
│ └── api/
│ └── urinecheck.js # 新增: API调用封装
└── docs/
└── urinecheck-plan.md # 本文件
```
### 2.3 Python 重写的关键数据结构
```python
from dataclasses import dataclass, field
from enum import Enum
import cv2
import numpy as np
class LocatorColor(Enum):
UNKNOWN = 0
RED = 1
GREEN = 2
BLUE = 3
class BigLocatorType(Enum):
SHORT = 0 # 10mm
LONG = 1 # 15mm
class PaperSpatialType(Enum):
UNKNOWN = 0
PST_14_S = 1
PST_14_L = 2
PST_11_S = 3
PST_11_L = 4
PST_8_S = 5
PST_8_L = 6
@dataclass
class LocatorInfo:
rotated_rect: cv2.RotatedRect = None
center: tuple = (0, 0)
area: float = 0
width: float = 0
height: float = 0
color: LocatorColor = LocatorColor.UNKNOWN
is_big: bool = False
avg_color: tuple = (0, 0, 0)
@dataclass
class ColorResult:
id: int = 0
r: float = 0; g: float = 0; b: float = 0
h: float = 0; s: float = 0; v: float = 0
l: float = 0; a: float = 0; b_val: float = 0 # LAB
@dataclass
class RecognitionResult:
success: bool = False
error_code: int = -1
error_message: str = ""
paper_type: PaperSpatialType = PaperSpatialType.UNKNOWN
locator_color: LocatorColor = LocatorColor.UNKNOWN
item_count: int = 0
color_blocks: list = field(default_factory=list) # list of ColorResult
annotated_image: np.ndarray = None # 标注图 (BGR)
blur_level: int = 0 # 0=清晰, 1=轻微, 2=严重
blur_score: float = 0
```
### 2.4 全局参数 (直接平移 C++ GlobalParams)
```python
# 颜色误差
COLOR_DISTANCE_THRESHOLD = 180.0
# 面积/距离/角度误差
AREA_ERROR_RATIO = 0.20
DISTANCE_ERROR_RATIO = 0.20
ANGLE_ERROR_DEGREE = 15.0
# 定位块宽高比
BIG_LOCATOR_RATIO_SHORT = 2.0 # 10mm: 10/5
BIG_LOCATOR_RATIO_LONG = 3.0 # 15mm: 15/5
SMALL_LOCATOR_RATIO = 1.667 # 5/3
# 面积比
AREA_RATIO_SHORT = 3.33 # 10/3
AREA_RATIO_LONG = 5.0 # 15/3
# 采样
MIN_SAMPLE_PIXELS = 25
DEFAULT_SAMPLE_RADIUS = 3
# 轮廓过滤
MIN_CONTOUR_AREA = 100.0
MAX_AREA_RATIO = 0.3
MIN_RECTANGULARITY = 0.70
# 定位块面积限制
MIN_SMALL_LOCATOR_AREA = 100.0
MAX_SMALL_LOCATOR_AREA = 8000.0
MIN_BIG_LOCATOR_AREA = 200.0
MAX_BIG_LOCATOR_AREA = 15000.0
```
### 2.5 试纸配置 (14联S型/L型)
```python
PAPER_CONFIGS = {
PaperSpatialType.PST_14_S: {
"items_num": 14, "slices_num": 50,
"first_pos": 2.25, "step": 3.5, "dist_times": 21.3,
"wh_big_ratio": 2.0
},
PaperSpatialType.PST_14_L: {
"items_num": 14, "slices_num": 50,
"first_pos": 2.25, "step": 3.5, "dist_times": 21.8,
"wh_big_ratio": 3.0
},
}
```
### 2.6 API 设计
**POST /api/urinecheck/analyze**
请求:
```
multipart/form-data
- file: 试纸图片 (jpg/png)
```
成功响应 (200):
```json
{
"success": true,
"paper_type": "PST_14_S",
"locator_color": "RED",
"item_count": 14,
"blur_level": 0,
"blur_score": 450.5,
"colors": [
{
"id": 0,
"rgb": {"r": 200, "g": 180, "b": 150},
"hsv": {"h": 36.0, "s": 64.0, "v": 200.0},
"lab": {"l": 75.0, "a": 5.0, "b": 18.0}
},
// ... 共14项
],
"annotated_image_url": "/uploads/urinecheck/annotated_xxx.jpg"
}
```
失败响应:
```json
{
"success": false,
"error_code": 2,
"error_message": "No valid locator pair found"
}
```
**GET /api/urinecheck/result/{filename}** — 获取标注图
### 2.7 错误码体系 (与C++一致)
| 错误码 | 含义 |
|--------|------|
| 0 | 成功 |
| 1 | 未找到轮廓/候选不足 |
| 2 | 未找到有效定位块配对 |
| 3 | 定位块颜色不一致/未知 |
| 4 | 试纸规格未匹配 |
| 5 | 色块位置计算失败 |
| 6 | 检测到多条试纸 |
| 7 | 输入图像为空 |
| 9 | 试纸过小(杂物) |
| 10 | 场景过于杂乱 |
---
## 三、实施步骤
### 第1步: 编写 Python 核心算法模块
**文件**: `backend/urine_recognizer.py`
完整平移 C++ `PaperRecognizer` + `ColorReader` + `PaperConfig` 逻辑:
1. `class GlobalParams` — 所有常量
2. `class PaperConfig` — 试纸配置字典
3. `class ColorReader` — 白平衡 + 圆形采样 + HSV/LAB转换
4. `class UrineStripRecognizer` — 核心7步流程:
- `preprocess_image()`
- `detect_locators()` — 包括 HSV分割、轮廓查找、备选Canny
- `filter_locator_candidates()` — 多级过滤 + Y聚类 + 配对验证
- `match_paper_spec()` — distRatio/areaRatio 匹配
- `calculate_color_block_centers()` — 边缘中心 + 切片插值
- `draw_annotations()` — 色块红圈标记
- `recognize()` — 主入口
**注意事项**:
- Python OpenCV 中 `cv2.findContours` 返回2个值(OpenCV 4.x),与C++ 不同
- 颜色空间: C++ BGR ↔ Python BGR 一致,无需转换
- 旋转矩形的 `.points()` / `.boundingRect()` 函数签名略有不同
- 移除所有鸿蒙日志宏(PR_LOG_*),改用 Python `logging` 模块
- 移除 `getPaperImg()` / `savePaperImg()` — 博客项目不需要RGBA格式
### 第2步: 编写后端 API 路由
**文件**: `backend/routers/urinecheck.py`
- `POST /api/urinecheck/analyze`
- 接收上传的图片文件
- 保存到 `/tmp/urinecheck_<uuid>.jpg` (方便清理)
- 调用 `UrineStripRecognizer.recognize()`
- 如果成功,保存标注图到 `backend/uploads/urinecheck/`
- 返回 JSON 结果
- `GET /api/urinecheck/result/{filename}` — 静态文件服务(标注图)
### 第3步: 注册路由
**修改** `backend/app.py`:
```python
from routers import urinecheck
app.include_router(urinecheck.router)
```
### 第4步: 编写前端页面
**文件**: `frontend/src/pages/admin/UrineCheck.jsx`
UI布局 (Preline风格,参考Images.jsx):
```
┌──────────────────────────────────────────────┐
│ 尿检识别 │
│ 上传尿常规试纸图片,自动识别14项颜色值 │
├──────────────────┬───────────────────────────┤
│ 上传区域 │ 结果输出区 │
│ ┌─────────────┐ │ ┌─────────────────────┐ │
│ │ 📤 拖拽或点击 │ │ │ 定位块颜色: RED │ │
│ │ 上传试纸图片 │ │ │ 试纸规格: 14联S型 │ │
│ │ jpg/png │ │ │ 清晰度: 清晰 (450) │ │
│ └─────────────┘ │ ├─────────────────────┤ │
│ │ │ # | RGB | HSV │ │
│ ┌─────────────┐ │ │ 0 | 200,180,150 | ... │ │
│ │ [预览图] │ │ │ 1 | ... │ │
│ │ │ │ │ ... │ │
│ │ │ │ │ 13| ... │ │
│ └─────────────┘ │ └─────────────────────┘ │
├──────────────────┴───────────────────────────┤
│ 标注图展示区 (色块中心红圈标记) │
│ ┌─────────────────────────────────────────┐ │
│ │ [标注后的试纸大图] │ │
│ └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
功能交互:
1. 用户上传图片 (拖拽或点击)
2. 图片立即显示预览(本地预览,无需等待)
3. 用户点击"开始识别"按钮
4. 前端调用 `POST /api/urinecheck/analyze`
5. 结果显示在右侧:
- 基本信息(定位块颜色、试纸类型、清晰度)
- 14项颜色值表格(RGB + HSV + LAB)
- 标注图展示
### 第5步: 修改导航
**文件**: `frontend/src/components-preline/Sidebar.jsx` — 在 `navItems` 中添加:
```javascript
{ path: '/admin/urinecheck', label: '尿检识别', icon: TestTubeIcon },
```
### 第6步: 修改路由
**文件**: `frontend/src/App.jsx` — 添加:
```jsx
import { UrineCheck } from './pages/admin/UrineCheck'
// ...
<Route path="/admin/urinecheck" element={<PrivateRoute><UrineCheck /></PrivateRoute>} />
```
### 第7步: 测试验证
使用 `urinecheckcpp/images/` 下的测试图片验证:
```bash
curl -X POST http://localhost:8889/api/urinecheck/analyze \
-F "file=@test1.png"
```
验证:
- 定位块正确识别
- 色块数量 = 14
- 颜色值合理 (RGB在0-255范围)
- 标注图生成正确
---
## 四、已确认的决定
1. ✅ **全部6种规格都支持** — 8联(S/L)、11联(S/L)、14联(S/L)
2. ✅ **输出中添加检测项目名称** — 14/11/8项都有对应的中文名称
3. ✅ **不需要标注图** — 只需颜色数据,移除 `draw_annotations()`
4. ✅ **不存数据库** — 每次临时识别,不上传文件到 uploads 目录
5. ✅ **上传图片存 /tmp** — 使用 `tempfile` 自动清理
6. ✅ **前端 Preline UI 风格** — 与现有管理后台一致
---
## 五、检测项目名称映射
### 14联 (id=0 靠近小定位块)
| id | 项目 | id | 项目 |
|----|------|----|------|
| 0 | 微量白蛋白(MA) | 7 | 比重(SG) |
| 1 | 钙(Ca) | 8 | 潜血(BLD) |
| 2 | 肌酐(CRE) | 9 | pH |
| 3 | 抗坏血酸(VC) | 10 | 蛋白质(PRO) |
| 4 | 葡萄糖(GLU) | 11 | 尿胆原(URO) |
| 5 | 胆红素(BIL) | 12 | 亚硝酸盐(NIT) |
| 6 | 酮体(KET) | 13 | 白细胞(LEU) |
### 11联
| id | 项目 | id | 项目 |
|----|------|----|------|
| 0 | 抗坏血酸(VC) | 6 | pH |
| 1 | 葡萄糖(GLU) | 7 | 蛋白质(PRO) |
| 2 | 胆红素(BIL) | 8 | 尿胆原(URO) |
| 3 | 酮体(KET) | 9 | 亚硝酸盐(NIT) |
| 4 | 比重(SG) | 10 | 白细胞(LEU) |
| 5 | 潜血(BLD) | | |
### 8联
| id | 项目 | id | 项目 |
|----|------|----|------|
| 0 | 葡萄糖(GLU) | 4 | 潜血(BLD) |
| 1 | 胆红素(BIL) | 5 | pH |
| 2 | 酮体(KET) | 6 | 蛋白质(PRO) |
| 3 | 比重(SG) | 7 | 白细胞(LEU) |
> ⚠️ 注意:项目名称根据常见尿试纸排列推定,不同品牌/批次可能顺序不同。
> 建议对照实际试纸包装确认顺序。代码中的映射在 `urine_recognizer.py` 的 `ITEM_NAMES_*` 中定义。
---
## 六、技术风险与注意事项
| OpenCV 版本差异 | Python OpenCV API与C++有细微差异 | 已在代码审查中标注关键差异点 |
| 颜色分割阈值 | 不同光照下HSV阈值需要微调 | 直接复用C++验证过的阈值 |
| 性能 | Python可能比C++慢 | 单张图片秒级处理,可接受 |
| 图片格式 | 用户可能上传超大图片 | 限制最大10MB,压缩到合理尺寸 |
| systemd 写入限制 | 如遇ReadWritePaths限制 | 确保 /tmp 和 backend/uploads/ 可写 |
---
**请蔡叔叔审阅以上方案,确认后我开始写代码。有问题随时指出。**