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

18 KiB
Raw Blame History

尿检试纸颜色识别功能 - 改造方案

创建时间: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 重写的关键数据结构

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)

# 颜色误差
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型)

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):

{
  "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"
}

失败响应:

{
  "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:

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 中添加:

{ path: '/admin/urinecheck', label: '尿检识别', icon: TestTubeIcon },

第6步: 修改路由

文件: frontend/src/App.jsx — 添加:

import { UrineCheck } from './pages/admin/UrineCheck'
// ...
<Route path="/admin/urinecheck" element={<PrivateRoute><UrineCheck /></PrivateRoute>} />

第7步: 测试验证

使用 urinecheckcpp/images/ 下的测试图片验证:

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/ 可写 |


请蔡叔叔审阅以上方案,确认后我开始写代码。有问题随时指出。