This commit is contained in:
Ubuntu
2026-07-03 16:58:28 +08:00
commit 32b43f85bd
6240 changed files with 1987678 additions and 0 deletions
@@ -0,0 +1,335 @@
# 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系列结果索引修正函数 |