Files
nanocode/urinecheck/docs/cpp-json-recognition-analysis.md
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

335 lines
11 KiB
Markdown
Raw Permalink 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.
# C++ 尿检项目 JSON 数据格式识别逻辑分析
> 基于 `/home/ubuntu/nanocode/urinecheck/UrineCheck/` 源码分析
> 分析日期:2026-05-13
---
## 1. 概述
C++ 尿检项目支持两种输入模式:
- **图片模式**:传入试纸图片(JPG/PNG),从图像中定位色块并识别
- **JSON数据模式**:直接传入已提取的色块颜色数据(H,S,V,L,A,B),跳过图像处理直接进行ML推理
本文档专注于 **JSON数据模式** 的完整处理流程。
---
## 2. 入口函数
### 2.1 `ProcessJsonDataRequest` (新版接口,匹配txt文件格式)
**位置**:`urinecheck_class.cpp:2052`
**输入JSON格式**:
```json
{
"extend": {
"color_14i": 2, // 试纸颜色类型(1=绿, 2=红, 3=蓝4联, 5=蓝14联...)
"method": 1, // 1=干化学, 2=胶体金
"type": 2 // 可选,优先级高于color_14i
},
"data": [
[ // 第1帧(多帧采集,通常2-5帧)
{"id": 0, "value": "7.62,146.43,37.76,23.22,135.71,134.46"},
{"id": 1, "value": "24.73,38.34,155.7,159.45,126.1,138.93"},
...
{"id": 13, "value": "36.38,100.93,155.55,157.81,113.17,158.8"}
],
[...] // 第2帧
]
}
```
**value字段含义**:6个逗号分隔的浮点数,对应:
`H, S, V, L, A, B`
| 位置 | 符号 | 含义 | 取值范围 |
|------|------|------|----------|
| 0 | H | HSV 色相 | 0-360(但OpenCV存储为0-180) |
| 1 | S | HSV 饱和度 | 0-255 |
| 2 | V | HSV 明度 | 0-255 |
| 3 | L | LAB 亮度 | 0-255 |
| 4 | A | LAB a通道 | 0-255 |
| 5 | B | LAB b通道 | 0-255 |
> **注意**:txt文件中的value数据已经是 **原始手机端提取的像素颜色值**,与C++图片模式中 `GetHsvLabVs()` 提取的格式一致。这些值在C++中未经白平衡处理就直接送入ML模型。
### 2.2 `analJsonData` (旧版接口)
**位置**:`urinecheck_class.cpp:746`
旧版使用不同的data结构——`data` 是一个对象而非数组:
```json
{
"data": {
"data0": [{"id": 0, "value": "..."}, ...],
"data1": [{"id": 0, "value": "..."}, ...]
}
}
```
旧版的 `id` 直接对应试纸上的物理位置(position),而新版 `id` 就是色块索引。
---
## 3. 完整处理流程
```
ProcessJsonDataRequest(jsonBody)
│
├── 1. 解析JSON
│ └── 提取 data 数组、extend 对象
│
├── 2. 确定试纸类型
│ ├── extend.type 优先
│ ├── extend.color_14i 其次
│ └── paperType = paperType值 (1-20)
│ X = paperType - 1 (用于查表索引)
│
├── 3. 转换项目类型
│ └── projectId = getProjectType(paperType)
│ ├── 1 (绿) → projectType=10
│ ├── 2,5 (红) → projectType=2
│ ├── 3,4 (蓝) → projectType=1
│ └── ...
│
├── 4. 逐帧处理 ────────────────────────────┐
│ for each frame in data: │
│ │ │
│ │ 4a. 解析每帧颜色数据 │
│ │ mapData = { │
│ │ id: [H,S,V,L,A,B] // 6个float │
│ │ } │
│ │ │
│ │ 4b. 调用 ML 推理 │
│ │ frameResult = │
│ │ urineCheck14.Get14Results( │
│ │ mapData, X) │
│ │ │
│ │ 4c. 收集结果 │
│ │ results[position].push_back(idx) │
│ └────────────────────────────────────────┘
│
├── 5. 多帧投票
│ └── lastResult = CountResultsModal(results)
│ 对每个position,取多帧结果中出现次数最多的index
│
├── 6. 关联修正
│ ├── PRO/MAU 修正 (见 §6.2)
│ └── SG/PH 修正 (见 §6.3)
│
├── 7. 索引映射输出
│ ├── itemsStatIds[X][position] → 输出id
│ └── itemsResultIdxMapTbl[X][position][rawIndex] → 输出index
│
└── 8. 构建输出JSON
└── {"score":"100","status":"0","data":[{id, index}, ...]}
```
---
## 4. 核心ML推理:`UrineCheck14::Get14Results(mapData, papertypeIndex)`
**位置**:`UrineCheck14.cpp:1041`
### 4.1 算法流程
```
for each model in rtsArray[papertypeIndex]:
① 从 mapData 取出该 position 的6个颜色值
② 数据不足6个 → 跳过
③ GetModelInstance(papertypeIndex, position) → 获取 RTrees 模型
④ 模型为空 → 跳过
⑤ 构造 float data[6] = {H, S, V, L, A, B}
⑥ p = GetPredictResultVal(data)
└── RTrees::predict(1×6 Mat) → 浮点数预测值
⑦ 获取该 position 对应的结果索引函数
⑧ pi = resultIndexFunc(data, p)
└── 大部分直接 round(p),部分有HSV修正
⑨ results[position] = pi
```
### 4.2 ML模型配置
模型配置从 `/usr/local/Models/ml.json` 加载。
**ml.json 结构**:
```json
{
"ml": [
{
"name": "glu", // 检测项目名
"model": [
{
"positon": 0, // 色块位置(0-based)
"id": 35, // 输出id
"filepath": "/usr/local/Models/glu_0.data",
"algorithm": 0, // 0=Red, 1=Green/Blue
"indexmap": "0,1,2,3,4" // rawIndex → outputIndex映射
},
{
"positon": 3,
"filepath": "/usr/local/Models/glu-rts-M1.data",
...
}
]
},
...
]
}
```
**关键设计**:
- 同一个检测项目(如glu)在不同试纸类型(绿/红/蓝)上可能位于不同 position
- 同一position在不同试纸类型上使用不同的 RTrees 模型文件
- 总共支持 21 种试纸类型(MAX_PAPER_TYPE=21),每种最多15个色块(MAX_PAPER_BLOCK=15)
### 4.3 `GetPredictResultVal`
```cpp
float UrineCheck14::GetPredictResultVal(float* sample) {
cv::Mat sampleMat(1, 6, CV_32FC1, sample);
return rtsPtr->predict(sampleMat); // OpenCV RTrees 预测
}
```
输入:6维特征向量 (H,S,V,L,A,B)
输出:浮点数预测值(代表颜色等级,如 0=阴性, 1=弱阳, 2=中阳...)
---
## 5. 各检测项目的索引修正函数
所有修正函数都先调用 `round(p)` 取四舍五入整数,然后根据 HSV 值进行二次修正。
### 5.1 红色试纸(algorithm=0)修正逻辑
| 项目 | 修正函数 | 修正规则 |
|------|----------|----------|
| **PRO (蛋白质)** | `GetRedProResultIndex` | 若 ret>0 且 (H<25 或 H>110) → 强制返回0 |
| **MA (微量白蛋白)** | `GetRedMauResultIndex` | 若 ret>0 且 (H<30 或 H>115) → 强制返回0 |
| **KET (酮体)** | `GetRedKetResultIndex` | 若 ret>1 且 (45<H<145) → 强制返回0 |
| **NIT (亚硝酸盐)** | `GetRedNitResultIndex` | 若 ret>0 且 (40<H<140) → 强制返回0 |
| **URO (尿胆原)** | `GetRedUroResultIndex` | 若 ret>0 且 (25<H<150) → 强制返回0 |
| **BLD (潜血)** | `GetRedBldResultIndex` | 直接 `round(p)`,无修正 |
| **VC (抗坏血酸)** | `GetRedVcResultIndex` | 直接 `round(p)`,无修正 |
| **BIL (胆红素)** | `GetRedBilResultIndex` | 直接 `round(p)`,无修正 |
| **GLU (葡萄糖)** | `GetRedGluResultIndex` | 直接 `round(p)`,无修正 |
| **CRE (肌酐)** | `GetRedCreResultIndex` | 直接 `round(p)`,无修正 |
| **Ca (尿钙)** | `GetRedUcaResultIndex` | 直接 `round(p)`,无修正 |
| **SG (比重)** | `GetRedSgResultIndex` | 直接 `round(p)`,无修正 |
| **pH** | `GetRedPhResultIndex` | 直接 `round(p)`,无修正 |
| **WBC (白细胞)** | `GetRedWbcResultIndex` | 直接 `round(p)`,无修正 |
### 5.2 绿色/蓝色试纸(algorithm>0)修正逻辑
大部分直接 `round(p)`,无额外修正。
---
## 6. 关联修正规则
### 6.1 配置来源
`positionsPro[X]`、`positionsMau[X]`、`positionsSg[X]`、`positionsPh[X]` 从 `ml.json` 中提取,记录了各试纸类型上 PRO/MAU/SG/pH 对应的色块位置。
### 6.2 PRO(蛋白质)与 MAU(微量白蛋白)关联
```
maxMau = (X > 0) ? 3 : 2 // 红色3级,其他2级
情况1: mauval == 0
→ proVal = 0 // MA阴性时PRO必定阴性
情况2: proVal == 0 且 mauval == maxMau
→ mauval = maxMau - 1 // PRO阴性时MA不能满级
情况3: proVal >= 1
→ mauval = maxMau // PRO阳性时MA设为满级
```
### 6.3 SG(比重)与 pH 关联
```
maxSg = (X > 0) ? 6 : 5 // 红色6级,其他5级
条件: projectType < 20 且 phVal >= 2 且 sgVal < maxSg
→ sgVal += 1 // 在特定pH下上调比重一级
```
---
## 7. 索引映射(indexmap)
### 7.1 映射表结构
`itemsResultIdxMapTbl[MAX_PAPER_TYPE][MAX_PAPER_BLOCK][MAX_ALG_TAG]`
三维查表:`[试纸类型][色块位置][算法原始输出index]` → 最终输出index
### 7.2 indexmap示例
```json
"indexmap": "0,1,2,3,4"
```
含义:算法输出的 index=0 映射为输出0,index=1 映射为输出1...
```json
"indexmap": "5,6,2,3,4"
```
含义:算法输出的 index=0→5, index=1→6, index=2→2...
**用途**:不同试纸类型可能使用不同的等级映射,通过 indexmap 灵活配置。
---
## 8. 输出JSON格式
```json
{
"score": "100",
"status": "0",
"message": "OK",
"imgid": 0,
"project_id": 2,
"paper_id": 2,
"versioninfo": "...",
"data": [
{"id": 35, "index": 1}, // 葡萄糖: 弱阳性
{"id": 36, "index": 0}, // 白细胞: 阴性
{"id": 37, "index": 0}, // 亚硝酸盐: 阴性
...
]
}
```
**data数组**:每个元素包含 `id`(项目ID,如35=GLU)和 `index`(等级,0=阴性)。
---
## 9. 新旧JSON格式差异总结
| 特性 | 旧版 (analJsonData) | 新版 (ProcessJsonDataRequest) |
|------|---------------------|-------------------------------|
| 入口函数 | `analJsonData` | `ProcessJsonDataRequest` |
| data结构 | `{"data0": [...], "data1": [...]}` | `[[...], [...]]` (直接数组) |
| 试纸类型 | 从 `project_id` 或 `extend` 推断 | 从 `extend.type` 或 `extend.color_14i` |
| seekey | `id` 是物理位置 | `id` 是色块索引(0-13) |
| 数据格式 | 整数<br>(如 `"7,146,37,23,135,134"`) | 浮点数<br>(如 `"7.62,146.43,37.76..."`) |
| 对应txt | 不匹配当前txt | **匹配** txt文件格式 |
| pro/mau修正 | 在analJsonData内联 | 在ProcessJsonDataRequest内联 |
---
## 10. 关键文件清单
| 文件 | 功能 |
|------|------|
| `urinecheck_class.cpp:2052-2217` | `ProcessJsonDataRequest` - 新版JSON处理入口 |
| `urinecheck_class.cpp:746-1078` | `analJsonData` - 旧版JSON处理 |
| `urinecheck_class.cpp:54-172` | `initconfig()` - 加载 ml.json 配置 |
| `UrineCheck14.cpp:1041-1104` | `Get14Results(mapData)` - JSON数据的ML推理 |
| `UrineCheck14.cpp:1107-1176` | `Get14Results(mat, pts)` - 图片数据的ML推理 |
| `UrineCheck14.cpp:32-230` | `initconfig()` - 加载模型文件和注册结果函数 |
| `UrineCheck14.cpp:1195-1199` | `GetPredictResultVal` - RTrees预测 |
| `UrineCheck14.cpp:1413-1746` | Red系列结果索引修正函数 |