a
This commit is contained in:
@@ -0,0 +1,765 @@
|
||||
# 鸿蒙尿检试纸识别算法 - 技术方案
|
||||
|
||||
> 文档创建时间:2026-03-31
|
||||
> 最后更新:2026-04-03
|
||||
> 版本:v2.1
|
||||
> 状态:已实现并集成到 NAPI,包含清晰度评估和杂物检测
|
||||
|
||||
---
|
||||
|
||||
## 一、技术架构
|
||||
|
||||
### 1.1 整体架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 鸿蒙上层 (ArkTS/TS) │
|
||||
│ ↓ N-API │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ napi_init.cpp │
|
||||
│ └── SayHello() 入口函数 │
|
||||
│ ├── 解析输入参数 (ArrayBuffer) │
|
||||
│ ├── 调用核心算法 │
|
||||
│ └── 封装返回结果 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 核心算法模块 │
|
||||
│ ├── PaperRecognizer (试纸识别器) │
|
||||
│ │ ├── 定位块检测 │
|
||||
│ │ ├── 试纸规格匹配 │
|
||||
│ │ └── 色块位置计算 │
|
||||
│ ├── ColorReader (颜色读取器) │
|
||||
│ │ ├── 白平衡处理 │
|
||||
│ │ └── 颜色采样与转换 │
|
||||
│ └── Config (配置模块) │
|
||||
│ ├── PaperConfig (试纸规格配置) │
|
||||
│ └── GlobalParams (全局误差参数) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ OpenCV 库 │
|
||||
│ ├── imgproc (图像处理) │
|
||||
│ ├── core (核心数据结构) │
|
||||
│ └── imgcodecs (图像编解码) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
entry/src/main/cpp/
|
||||
├── docs/ # 文档目录
|
||||
│ ├── 沟通日志.md
|
||||
│ ├── 技术方案.md
|
||||
│ └── 详细设计文档.md # 新增:详细设计文档
|
||||
├── images/ # 测试图片(27张)
|
||||
├── reference/ # 参考代码(老代码)
|
||||
│ ├── ImgLocateDryChemistry.cpp # 老代码-参考
|
||||
│ ├── ImageLocation.cpp
|
||||
│ └── ...
|
||||
├── test/ # Windows测试程序
|
||||
│ ├── test_main.cpp # 测试入口
|
||||
│ ├── build/ # 构建目录
|
||||
│ └── CMakeLists.txt # 测试构建配置
|
||||
├── napi_init.cpp # N-API 入口(已集成新代码)
|
||||
├── napi_init_old.cpp # N-API 备份(老代码)
|
||||
├── PaperRecognizer.h # 试纸识别器头文件
|
||||
├── PaperRecognizer.cpp # 试纸识别器实现(核心)
|
||||
├── ColorReader.h # 颜色读取器头文件
|
||||
├── ColorReader.cpp # 颜色读取器实现
|
||||
├── PaperConfig.h # 试纸配置定义
|
||||
├── PaperConfig.cpp # 试纸配置实现
|
||||
├── Types.h # 类型定义
|
||||
└── CMakeLists.txt # 鸿蒙构建配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、核心模块设计
|
||||
|
||||
### 2.1 类型定义 (Types.h)
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// 试纸联数
|
||||
enum class BlockItems {
|
||||
BI_14 = 14,
|
||||
BI_11 = 11,
|
||||
BI_8 = 8
|
||||
};
|
||||
|
||||
// 大定位块类型
|
||||
enum class BigLocatorType {
|
||||
BLT_SHORT = 0, // 10mm
|
||||
BLT_LONG = 1 // 15mm
|
||||
};
|
||||
|
||||
// 定位块颜色分类
|
||||
enum class LocatorColor {
|
||||
UNKNOWN = 0,
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3
|
||||
};
|
||||
|
||||
// 试纸空间类型
|
||||
enum class PaperSpatialType {
|
||||
PST_UNKNOWN = 0,
|
||||
PST_14_S = 1, // 14联, 10mm大定位块
|
||||
PST_14_L = 2, // 14联, 15mm大定位块
|
||||
PST_11_S = 3, // 11联, 10mm大定位块
|
||||
PST_11_L = 4, // 11联, 15mm大定位块
|
||||
PST_8_S = 5, // 8联, 10mm大定位块
|
||||
PST_8_L = 6 // 8联, 15mm大定位块
|
||||
};
|
||||
|
||||
// 图像清晰度等级(新增)
|
||||
enum class BlurLevel {
|
||||
BLUR_NONE = 0, // 清晰
|
||||
BLUR_SLIGHT = 1, // 轻微模糊
|
||||
BLUR_SEVERE = 2 // 严重模糊
|
||||
};
|
||||
|
||||
// 定位块信息
|
||||
struct LocatorInfo {
|
||||
cv::RotatedRect rotatedRect; // 旋转矩形
|
||||
cv::Point2f center; // 中心点
|
||||
double area; // 面积
|
||||
double width; // 宽度(长边)
|
||||
double height; // 高度(短边)
|
||||
LocatorColor color; // 颜色
|
||||
bool isBig; // 是否大定位块
|
||||
cv::Scalar avgColor; // 平均RGB颜色
|
||||
};
|
||||
|
||||
// 色块颜色结果
|
||||
struct ColorResult {
|
||||
int id; // 编号(从靠近小定位块开始)
|
||||
double h, s, v; // HSV值
|
||||
double l, a, b; // LAB值
|
||||
double r, g, b_val; // RGB值
|
||||
};
|
||||
|
||||
// 识别结果
|
||||
struct RecognitionResult {
|
||||
bool success; // 是否成功
|
||||
int errorCode; // 错误码
|
||||
std::string errorMessage; // 错误信息
|
||||
PaperSpatialType paperType; // 试纸类型
|
||||
LocatorColor locatorColor; // 定位块颜色
|
||||
int itemCount; // 色块数量
|
||||
std::vector<cv::Point2f> colorBlockCenters; // 色块中心点
|
||||
std::vector<ColorResult> colors; // 颜色结果
|
||||
cv::Mat annotatedImage; // 标注后的图片
|
||||
bool smallLocatorOnLeft; // 小定位块是否在左边
|
||||
BlurLevel blurLevel; // 图像清晰度等级(新增)
|
||||
double blurScore; // 清晰度分数(新增)
|
||||
};
|
||||
```
|
||||
|
||||
### 2.2 全局参数 (GlobalParams.h)
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
namespace GlobalParams {
|
||||
// ========== 颜色误差 ==========
|
||||
// RGB欧式距离阈值,判断两个定位块颜色是否相同
|
||||
const double COLOR_DISTANCE_THRESHOLD = 20.0;
|
||||
|
||||
// ========== 面积误差 ==========
|
||||
// 面积误差比例(10%)
|
||||
const double AREA_ERROR_RATIO = 0.10;
|
||||
|
||||
// ========== 距离误差 ==========
|
||||
// 距离误差比例(10%)
|
||||
const double DISTANCE_ERROR_RATIO = 0.10;
|
||||
|
||||
// ========== 角度误差 ==========
|
||||
// 角度误差(度)
|
||||
const double ANGLE_ERROR_DEGREE = 15.0;
|
||||
|
||||
// ========== 定位块尺寸比例 ==========
|
||||
// 大定位块(10mm)宽高比:10/5 = 2.0
|
||||
const double BIG_LOCATOR_RATIO_SHORT = 2.0;
|
||||
// 大定位块(15mm)宽高比:15/5 = 3.0
|
||||
const double BIG_LOCATOR_RATIO_LONG = 3.0;
|
||||
// 小定位块宽高比:5/3 ≈ 1.667(高度是长边)
|
||||
const double SMALL_LOCATOR_RATIO = 1.667;
|
||||
|
||||
// ========== 面积比例 ==========
|
||||
// 大定位块(10mm)与小定位块面积比:10/3 ≈ 3.33
|
||||
const double AREA_RATIO_SHORT = 3.33;
|
||||
// 大定位块(15mm)与小定位块面积比:15/3 = 5.0
|
||||
const double AREA_RATIO_LONG = 5.0;
|
||||
|
||||
// ========== 采样参数 ==========
|
||||
// 最小采样像素数
|
||||
const int MIN_SAMPLE_PIXELS = 25;
|
||||
// 默认采样半径
|
||||
const int DEFAULT_SAMPLE_RADIUS = 3;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 试纸配置 (PaperConfig)
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include <map>
|
||||
|
||||
// 单个试纸规格配置
|
||||
struct PaperConfig {
|
||||
PaperSpatialType type; // 试纸类型
|
||||
BlockItems blockItems; // 联数
|
||||
BigLocatorType locatorType; // 大定位块类型
|
||||
int itemsNum; // 反应色块数量
|
||||
int slicesNum; // 最小分割单位数量
|
||||
double firstPos; // 第一个色块位置倍数
|
||||
double step; // 步长倍数
|
||||
double distTimes; // 定位块中心距离/试纸高度
|
||||
double whBigRatio; // 大定位块宽高比
|
||||
double whSmallRatio; // 小定位块宽高比
|
||||
};
|
||||
|
||||
// 试纸配置管理器
|
||||
class PaperConfigManager {
|
||||
public:
|
||||
static PaperConfigManager& getInstance();
|
||||
|
||||
const PaperConfig* getConfig(PaperSpatialType type) const;
|
||||
PaperSpatialType matchConfig(double distRatio, double areaRatio) const;
|
||||
|
||||
private:
|
||||
PaperConfigManager();
|
||||
void initConfigs();
|
||||
|
||||
std::map<PaperSpatialType, PaperConfig> configs_;
|
||||
};
|
||||
```
|
||||
|
||||
### 2.4 试纸识别器 (PaperRecognizer)
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "PaperConfig.h"
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
class PaperRecognizer {
|
||||
public:
|
||||
PaperRecognizer();
|
||||
~PaperRecognizer();
|
||||
|
||||
// 主识别函数
|
||||
RecognitionResult recognize(const cv::Mat& image);
|
||||
|
||||
private:
|
||||
// ===== 第一步:图像预处理 =====
|
||||
void preprocessImage(const cv::Mat& image);
|
||||
|
||||
// ===== 第二步:定位块检测 =====
|
||||
bool detectLocators();
|
||||
void findCandidateContours();
|
||||
bool filterLocatorCandidates();
|
||||
bool validateLocatorPair();
|
||||
|
||||
// ===== 第三步:颜色判断 =====
|
||||
LocatorColor determineLocatorColor(const cv::Mat& roi);
|
||||
double calculateColorDistance(const cv::Scalar& c1, const cv::Scalar& c2);
|
||||
|
||||
// ===== 第四步:规格匹配 =====
|
||||
bool matchPaperSpec();
|
||||
|
||||
// ===== 第五步:色块位置计算 =====
|
||||
void calculateColorBlockCenters();
|
||||
|
||||
// ===== 第六步:绘制标注 =====
|
||||
void drawAnnotations();
|
||||
|
||||
// ===== 辅助函数 =====
|
||||
double calculateAspectRatio(const cv::RotatedRect& rect);
|
||||
bool isBigLocator(const cv::RotatedRect& rect, double height);
|
||||
|
||||
private:
|
||||
cv::Mat originalImage_; // 原图
|
||||
cv::Mat processImage_; // 处理用图像
|
||||
cv::Mat hsvImage_; // HSV图像
|
||||
|
||||
LocatorInfo leftLocator_; // 左定位块
|
||||
LocatorInfo rightLocator_; // 右定位块
|
||||
bool leftIsBig_; // 左边是否是大定位块
|
||||
|
||||
std::vector<cv::Point> centers_; // 色块中心点
|
||||
const PaperConfig* config_; // 匹配的配置
|
||||
|
||||
RecognitionResult result_; // 结果
|
||||
};
|
||||
```
|
||||
|
||||
### 2.5 颜色读取器 (ColorReader)
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
class ColorReader {
|
||||
public:
|
||||
ColorReader();
|
||||
~ColorReader();
|
||||
|
||||
// 执行颜色读取
|
||||
std::vector<ColorResult> readColors(
|
||||
const cv::Mat& image,
|
||||
const std::vector<cv::Point>& centers,
|
||||
bool smallLocatorOnLeft
|
||||
);
|
||||
|
||||
private:
|
||||
// 白平衡处理
|
||||
void applyWhiteBalance(cv::Mat& image);
|
||||
|
||||
// 采样颜色
|
||||
cv::Scalar sampleColor(const cv::Mat& image, const cv::Point& center);
|
||||
|
||||
// 颜色空间转换
|
||||
void convertColorSpaces(const cv::Scalar& bgr,
|
||||
double& h, double& s, double& v,
|
||||
double& l, double& a, double& b);
|
||||
|
||||
// 调整顺序(始终从靠近小定位块开始)
|
||||
std::vector<ColorResult> adjustOrder(
|
||||
std::vector<ColorResult>& colors,
|
||||
bool smallLocatorOnLeft
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、核心算法流程
|
||||
|
||||
### 3.1 主流程图
|
||||
|
||||
```
|
||||
输入图像
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 1. 图像预处理 │
|
||||
│ - 颜色空间转换 │
|
||||
│ - 高斯滤波 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 2. 轮廓检测 │
|
||||
│ - 二值化 │
|
||||
│ - findContours│
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 3. 定位块筛选 │
|
||||
│ - 面积过滤 │
|
||||
│ - 宽高比过滤 │
|
||||
│ - 矩形度过滤 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 4. 定位块配对 │
|
||||
│ - 面积比验证 │
|
||||
│ - 颜色一致性 │
|
||||
│ - 距离验证 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ 候选数=2? │
|
||||
└────┬────┘
|
||||
│
|
||||
┌────┼────┐
|
||||
Yes No
|
||||
│ │
|
||||
▼ ▼
|
||||
继续 返回错误
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 5. 规格匹配 │
|
||||
│ - 计算距离比 │
|
||||
│ - 匹配PaperType│
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 6. 色块位置计算 │
|
||||
│ - 计算斜率 │
|
||||
│ - 计算各中心点 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 7. 颜色读取 │
|
||||
│ - 白平衡 │
|
||||
│ - 采样颜色 │
|
||||
│ - HSV/LAB转换 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 8. 结果封装 │
|
||||
│ - 绘制标注 │
|
||||
│ - JSON格式 │
|
||||
└─────────────────┘
|
||||
│
|
||||
▼
|
||||
返回结果
|
||||
```
|
||||
|
||||
### 3.2 定位块检测算法
|
||||
|
||||
```
|
||||
输入: 二值化图像
|
||||
输出: 2个定位块信息
|
||||
|
||||
Step 1: findContours 获取所有轮廓
|
||||
Step 2: 遍历轮廓,计算几何特征
|
||||
- 面积: contourArea()
|
||||
- 最小外接矩形: minAreaRect()
|
||||
- 宽高比: max(w,h) / min(w,h)
|
||||
- 矩形度: area / (w * h)
|
||||
|
||||
Step 3: 过滤轮廓
|
||||
- 面积范围: [minArea, maxArea]
|
||||
- 宽高比: 大定位块 ≈ 2.0 或 3.0, 小定位块 ≈ 1.667
|
||||
- 矩形度: > 0.85
|
||||
|
||||
Step 4: 分组配对
|
||||
- 按面积分组: 大/小
|
||||
- 验证面积比: 大/小 ≈ 3.33 或 5.0
|
||||
- 验证颜色一致性: RGB欧式距离 < 20
|
||||
|
||||
Step 5: 验证距离
|
||||
- 两定位块中心距离
|
||||
- 与PaperConfig中distTimes匹配
|
||||
|
||||
返回: 两个定位块信息
|
||||
```
|
||||
|
||||
### 3.3 色块位置计算算法
|
||||
|
||||
```cpp
|
||||
// 伪代码
|
||||
void calculateColorBlockCenters() {
|
||||
// 1. 获取两个定位块中心点
|
||||
Point p1 = leftLocator_.center;
|
||||
Point p2 = rightLocator_.center;
|
||||
|
||||
// 2. 计算方向向量和距离
|
||||
Point vec = p2 - p1;
|
||||
double totalDist = norm(vec);
|
||||
Point unitVec = vec / totalDist;
|
||||
|
||||
// 3. 计算基准单位(像素/毫米)
|
||||
// 试纸高度 = 两定位块高度的均值
|
||||
double paperHeight = (leftLocator_.height + rightLocator_.height) / 2;
|
||||
|
||||
// 4. 根据 PaperConfig 计算每个色块位置
|
||||
double avgDist = totalDist / config_->slicesNum;
|
||||
double firstOffset = avgDist * config_->firstPos;
|
||||
double stepOffset = avgDist * config_->step;
|
||||
|
||||
// 5. 计算各色块中心点
|
||||
Point firstCenter = p1 + unitVec * firstOffset;
|
||||
for (int i = 0; i < config_->itemsNum; i++) {
|
||||
Point center = firstCenter + unitVec * (stepOffset * i);
|
||||
centers_.push_back(center);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 白平衡算法
|
||||
|
||||
参考 `reference/ColorCorrection.cpp`:
|
||||
|
||||
```cpp
|
||||
void applyWhiteBalance(cv::Mat& image) {
|
||||
// 1. 计算各通道均值
|
||||
Scalar sumChannels = sum(image);
|
||||
int totalPixels = image.rows * image.cols;
|
||||
double avgB = sumChannels[0] / totalPixels;
|
||||
double avgG = sumChannels[1] / totalPixels;
|
||||
double avgR = sumChannels[2] / totalPixels;
|
||||
|
||||
// 2. 计算目标均值(三通道平均)
|
||||
double targetAvg = (avgB + avgG + avgR) / 3.0;
|
||||
|
||||
// 3. 各通道缩放校正
|
||||
std::vector<Mat> channels(3);
|
||||
split(image, channels);
|
||||
|
||||
channels[0] = channels[0] * (targetAvg / avgB);
|
||||
channels[1] = channels[1] * (targetAvg / avgG);
|
||||
channels[2] = channels[2] * (targetAvg / avgR);
|
||||
|
||||
merge(channels, image);
|
||||
image.convertTo(image, CV_8UC3);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、PaperConfig 参数表
|
||||
|
||||
### 4.1 支持的试纸规格
|
||||
|
||||
| 类型 | 联数 | 大定位块 | itemsNum | slicesNum | firstPos | step | distTimes | whBigRatio |
|
||||
|------|------|----------|----------|-----------|----------|------|-----------|------------|
|
||||
| PST_14_S | 14 | 10mm | 14 | 50 | 2.25 | 3.5 | 21.3 | 2.0 |
|
||||
| PST_14_L | 14 | 15mm | 14 | 50 | 2.25 | 3.5 | 21.8 | 3.0 |
|
||||
| PST_11_S | 11 | 10mm | 11 | 39 | 2.25 | 3.5 | 17.6 | 2.0 |
|
||||
| PST_11_L | 11 | 15mm | 11 | 39 | 2.25 | 3.5 | 17.6 | 3.0 |
|
||||
| PST_8_S | 8 | 10mm | 8 | 29 | 2.25 | 3.5 | 12.9 | 2.0 |
|
||||
| PST_8_L | 8 | 15mm | 8 | 29 | 2.25 | 3.5 | 13.4 | 3.0 |
|
||||
|
||||
### 4.2 试纸物理尺寸参考
|
||||
|
||||
以 PST_14_S (14联,10mm大定位块) 为例:
|
||||
- 小定位块:3mm × 5mm
|
||||
- 大定位块:10mm × 5mm
|
||||
- 反应色块:14个 × (5mm × 5mm)
|
||||
- 间隔:15个 × 2mm
|
||||
- 总长度:3 + 14×5 + 15×2 + 10 = 3 + 70 + 30 + 10 = 113mm
|
||||
- 定位块中心距离:113 - 1.5 - 5 = 106.5mm
|
||||
- distTimes = 106.5 / 5 = 21.3
|
||||
|
||||
---
|
||||
|
||||
## 五、测试计划
|
||||
|
||||
### 5.1 测试环境
|
||||
|
||||
- 操作系统:Windows
|
||||
- 编译器:MSVC 或 MinGW
|
||||
- OpenCV:D:\opencv(官方预编译库)
|
||||
- 测试框架:自定义简单测试框架
|
||||
|
||||
### 5.2 测试图片
|
||||
|
||||
| 文件名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| 11green_1.png | 11联绿色 | 设计参考图 |
|
||||
| 11green_2.png | 11联绿色 | 摄像头采集 |
|
||||
| 14blue_1.png | 14联蓝色 | 设计参考图 |
|
||||
| 14blue_2.png | 14联蓝色 | 摄像头采集 |
|
||||
| 14red_1.png | 14联红色 | 设计参考图 |
|
||||
| 14red_2.png | 14联红色 | 摄像头采集 |
|
||||
| 65686153160b075c3d7eb019f9eecdb4.jpg | 实拍 | 实际采集 |
|
||||
| be3af2fa7372464a98172a91fa52adb8.jpg | 实拍 | 实际采集 |
|
||||
| ... | ... | 其他实拍图片 |
|
||||
|
||||
### 5.3 测试输出
|
||||
|
||||
每个测试图片生成以下输出:
|
||||
|
||||
1. **标注图片**:`{filename}_annotated.jpg`
|
||||
- 在定位块周围画红框
|
||||
- 在色块中心画红圈(半径5像素)
|
||||
- 标注色块编号
|
||||
|
||||
2. **颜色数据文件**:`{filename}_colors.txt`
|
||||
```
|
||||
Paper Type: PST_14_S
|
||||
Locator Color: RED
|
||||
Item Count: 14
|
||||
Status: SUCCESS
|
||||
|
||||
Color Results:
|
||||
[0] HSV(12.34, 56.78, 90.12) LAB(23.45, 67.89, 12.34) RGB(255, 128, 64)
|
||||
[1] HSV(...)
|
||||
...
|
||||
```
|
||||
|
||||
3. **还原模拟图**:`{filename}_restored.jpg`
|
||||
- 200px × 1000px
|
||||
- 试纸居中
|
||||
- 定位块颜色按识别结果
|
||||
- 色块颜色按读取结果
|
||||
|
||||
### 5.4 测试用例
|
||||
|
||||
| 用例ID | 描述 | 预期结果 |
|
||||
|--------|------|----------|
|
||||
| TC001 | 14联红色试纸 | 正确识别PST_14_S/R,返回14个颜色值 |
|
||||
| TC002 | 14联蓝色试纸 | 正确识别PST_14_S/B,返回14个颜色值 |
|
||||
| TC003 | 11联绿色试纸 | 正确识别PST_11_S/G,返回11个颜色值 |
|
||||
| TC004 | 8联试纸 | 正确识别PST_8_x,返回8个颜色值 |
|
||||
| TC005 | 15mm大定位块 | 正确识别PST_14_L等L型规格 |
|
||||
| TC006 | 倾斜试纸 | 正确处理倾斜,色块位置准确 |
|
||||
| TC007 | 小定位块在右 | 颜色顺序正确(从右往左读,结果从左往右排)|
|
||||
| TC008 | 无效图片 | 返回错误码,不崩溃 |
|
||||
|
||||
---
|
||||
|
||||
## 六、实现步骤
|
||||
|
||||
### 阶段一:基础框架(Day 1)
|
||||
|
||||
1. 创建文件结构
|
||||
2. 实现 Types.h 类型定义
|
||||
3. 实现 GlobalParams.h 全局参数
|
||||
4. 实现 PaperConfig 配置模块
|
||||
|
||||
### 阶段二:核心算法(Day 2-3)
|
||||
|
||||
1. 实现 PaperRecognizer
|
||||
- 图像预处理
|
||||
- 轮廓检测
|
||||
- 定位块筛选与配对
|
||||
- 规格匹配
|
||||
- 色块位置计算
|
||||
|
||||
2. 实现 ColorReader
|
||||
- 白平衡处理
|
||||
- 颜色采样
|
||||
- 颜色空间转换
|
||||
|
||||
### 阶段三:测试程序(Day 4)
|
||||
|
||||
1. 创建 test/ 目录
|
||||
2. 实现测试入口和运行器
|
||||
3. 配置 CMakeLists.txt(链接 D:\opencv)
|
||||
4. 编写测试输出逻辑
|
||||
|
||||
### 阶段四:调试优化(Day 5)
|
||||
|
||||
1. 运行测试,查看结果
|
||||
2. 调整误差参数
|
||||
3. 优化识别率
|
||||
4. 处理边界情况
|
||||
|
||||
### 阶段五:NAPI 集成(已完成 ✅)
|
||||
|
||||
1. ✅ 修改 napi_init.cpp 的 SayHello 函数
|
||||
2. ✅ 更新 CMakeLists.txt(移除老文件,添加新文件)
|
||||
3. ✅ 编译 HAP 成功
|
||||
4. ✅ 实现白平衡处理
|
||||
5. ✅ 实现自适应 ROI 颜色采样
|
||||
6. ✅ 实现试纸裁剪保存(getPaperData)
|
||||
|
||||
### 阶段六:优化迭代(已完成 ✅)
|
||||
|
||||
1. ✅ **颜色显示修复**:BGRA→BGR 颜色空间转换修复
|
||||
2. ✅ **规格匹配优化**:调整 distRatio 范围,添加缓冲区间
|
||||
3. ✅ **颜色一致性验证**:配对定位块必须颜色相同
|
||||
4. ✅ **宽高比过滤**:最大宽高比限制4.0,容差收紧至0.35
|
||||
5. ✅ **清晰度评估**:Laplacian方差法检测图像模糊(新增)
|
||||
6. ✅ **杂物检测**:试纸高度<30px或候选>15个时拒绝识别(新增)
|
||||
7. ✅ **NAPI参数扩展**:返回locatorColor、paperType、blurLevel、blurScore
|
||||
|
||||
---
|
||||
|
||||
## 七、编译与运行
|
||||
|
||||
### 7.1 测试程序编译
|
||||
|
||||
```bash
|
||||
# Windows PowerShell
|
||||
cd E:\Qoder\urologist\entry\src\main\cpp\test
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOpenCV_DIR=D:\opencv\build
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
### 7.2 运行测试
|
||||
|
||||
```bash
|
||||
# 运行单个图片测试
|
||||
.\Release\test_urine_analyzer.exe ..\..\images\14red_1.png
|
||||
|
||||
# 运行批量测试
|
||||
.\Release\test_urine_analyzer.exe ..\..\images\
|
||||
```
|
||||
|
||||
### 7.3 查看结果
|
||||
|
||||
```bash
|
||||
# 结果输出在 test_output/ 目录
|
||||
cd ..\..\test_output
|
||||
dir
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、风险与对策
|
||||
|
||||
| 风险 | 影响 | 对策 |
|
||||
|------|------|------|
|
||||
| 光照变化大 | 定位块识别失败 | 增加颜色判断的鲁棒性,提供可调参数 |
|
||||
| 试纸倾斜过大 | 色块位置计算偏差 | 使用斜率补偿计算 |
|
||||
| 图片质量差 | 轮廓检测失败 | 预处理增强(滤波、形态学操作)|
|
||||
| 颜色相近 | 定位块配对错误 | 多重验证条件(面积、距离、颜色)|
|
||||
|
||||
---
|
||||
|
||||
## 九、附录
|
||||
|
||||
### A. SayHello 返回值结构
|
||||
|
||||
```javascript
|
||||
{
|
||||
"byteLength": 123456, // 图像数据长度
|
||||
"buffer": ArrayBuffer, // 图像数据(RGBA格式)
|
||||
"width": 1920, // 图像宽度
|
||||
"height": 1080, // 图像高度
|
||||
"strLength": 567, // 颜色字符串长度
|
||||
"strBuffer": ArrayBuffer, // 颜色JSON字符串
|
||||
"locatorColor": 1, // 定位块颜色:0=未知,1=红,2=绿,3=蓝(新增)
|
||||
"paperType": 1, // 试纸类型:1=PST_14_S,2=PST_14_L,...(新增)
|
||||
"blurLevel": 0, // 清晰度等级:0=清晰,1=轻微模糊,2=严重模糊(新增)
|
||||
"blurScore": 450.5, // 清晰度分数(Laplacian方差值,新增)
|
||||
"statusCode": 0 // 状态码:0成功,其他失败
|
||||
}
|
||||
```
|
||||
|
||||
### B. 状态码定义
|
||||
|
||||
| 状态码 | 含义 | 说明 |
|
||||
|--------|------|------|
|
||||
| 0 | 成功 | 识别成功,返回完整结果 |
|
||||
| 1 | 未检测到定位块 | 没有找到符合条件的定位块轮廓 |
|
||||
| 2 | 定位块颜色识别失败 | 定位块颜色无法识别(非红/绿/蓝)|
|
||||
| 3 | 定位块配对失败 | 未找到一大一小的有效配对 |
|
||||
| 4 | 试纸规格匹配失败 | distRatio 不在任何规格范围内 |
|
||||
| 5 | 色块位置计算失败 | 内部计算错误 |
|
||||
| 6 | 检测到多条试纸 | 图片中存在多条试纸,需单独拍摄 |
|
||||
| 7 | 图像处理错误 | 图像格式或处理异常 |
|
||||
| 8 | 输入图像为空 | 传入的图像数据为空 |
|
||||
| 9 | 试纸太小/杂物 | 试纸高度<30px,可能是杂物干扰(新增)|
|
||||
| 10 | 场景杂乱 | 候选定位块>15个,场景过于杂乱(新增)|
|
||||
| 100 | 未知错误 | 其他未分类错误 |
|
||||
|
||||
### C. 色块编号规则
|
||||
|
||||
**核心原则:编号 0 始终是靠近小定位块的色块**
|
||||
|
||||
无论试纸如何放置(水平、垂直、旋转),返回的颜色数据中:
|
||||
- `id=0` 始终是小定位块旁边的第一个反应色块
|
||||
- `id=1,2,3...` 依次远离小定位块
|
||||
|
||||
**实现方式**:
|
||||
- `calculateColorBlockCenters()` 始终从小定位块边缘开始计算
|
||||
- 方向向量始终从小定位块指向大定位块
|
||||
- 这样 `centers_[0]` 始终靠近小定位块
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
@@ -0,0 +1,301 @@
|
||||
# 鸿蒙算法改造 - 需求沟通日志
|
||||
|
||||
> 文档创建时间:2026-03-31
|
||||
> 参与方:用户 & AI助手
|
||||
|
||||
---
|
||||
|
||||
## 一、项目背景
|
||||
|
||||
### 1.1 项目概述
|
||||
- **项目名称**:鸿蒙尿检试纸识别算法改造
|
||||
- **改造范围**:仅限于 `E:\Qoder\urologist\entry\src\main\cpp` 目录下的 C++ 代码
|
||||
- **技术栈**:C++ + OpenCV,通过 N-API 与鸿蒙上层交互
|
||||
|
||||
### 1.2 核心功能
|
||||
1. 从上层 NAPI 接收图片
|
||||
2. 试纸定位:根据定位块特征找到定位块,匹配 PaperConfig 规格
|
||||
3. 获取反应色块的中心点位置列表
|
||||
4. 读取每个化学反应色块的颜色值(HSV、LAB)
|
||||
|
||||
---
|
||||
|
||||
## 二、第一轮需求澄清(2026-03-31)
|
||||
|
||||
### Q1:定位块的识别特征
|
||||
|
||||
**问题**:
|
||||
- 定位块颜色范围(HSV边界值)是什么?
|
||||
- 定位块形状特征?最小/最大面积限制?
|
||||
- 如何区分定位块和反应色块?
|
||||
|
||||
**用户答复**:
|
||||
- 定位块颜色使用 RGB 分量比例判断,不用 HSV 边界值(效率考虑)
|
||||
- 定位块有两种:
|
||||
- 小定位块:3mm × 5mm(宽×高)
|
||||
- 大定位块:10mm × 5mm 或 15mm × 5mm
|
||||
- 定位块是矩形,反应色块是正方形(5mm × 5mm)
|
||||
- **关键点**:反应色块可能是白色,通过 `findContours` 可能找不到轮廓
|
||||
- 定位块在试纸两端,中间是反应色块
|
||||
|
||||
### Q2:定位失败的处理
|
||||
|
||||
**问题**:
|
||||
- 找不到两个定位块时返回什么错误码?
|
||||
- 找到多个候选定位块如何选择?
|
||||
|
||||
**用户答复**:
|
||||
- 反应色块和定位块是对齐在一条线上的
|
||||
- 找不到两个定位块 → 返回错误(参考现有 SayHello 代码)
|
||||
- 找到多于2个定位块 → 直接返回错误,不做进一步处理
|
||||
|
||||
### Q3:试纸角度处理
|
||||
|
||||
**问题**:
|
||||
- 最大倾斜角度?
|
||||
- 是否需要图像旋转校正?
|
||||
|
||||
**用户答复**:
|
||||
- 不限制倾斜角度
|
||||
- **不做图像旋转处理**,通过几何公式和斜率计算位置
|
||||
|
||||
---
|
||||
|
||||
## 三、第二轮需求澄清(2026-03-31)
|
||||
|
||||
### Q4:slicesNum 计算方式
|
||||
|
||||
**问题**:
|
||||
- "2毫米"如何转换?是否用定位块高度作为基准?
|
||||
|
||||
**用户答复**:
|
||||
- 这是比例问题,生产单位(毫米)需转换为像素比例
|
||||
- 例如:10mm×5mm 的定位块在图像中可能是 100px×50px,则放大了10倍
|
||||
- PaperConfig 中的值需要乘以这个比例因子
|
||||
|
||||
### Q5:firstPos 和 step 的单位
|
||||
|
||||
**问题**:
|
||||
- 这两个值是以"2毫米"还是"最小分割单位"为单位?
|
||||
|
||||
**用户答复**:
|
||||
- firstPos = 2.25 表示:(2 + 2.5) / 2 = 2.25
|
||||
- 2mm = 定位块边缘到第一个反应色块的距离
|
||||
- 2.5mm = 反应色块中心到边缘的距离(5mm色块的一半)
|
||||
- 本质上 firstPos 和 step 是倍数关系
|
||||
- 计算时基于像素,不是毫米
|
||||
|
||||
### Q6:白平衡算法
|
||||
|
||||
**问题**:
|
||||
- 使用 `Whitebalance` 还是 `WhitebalancePR`?
|
||||
- 白平衡参考点如何选取?
|
||||
|
||||
**用户答复**:
|
||||
- 参考 `reference` 目录下的代码
|
||||
- **白平衡基于整个图片进行**
|
||||
|
||||
### Q7:颜色返回顺序
|
||||
|
||||
**问题**:
|
||||
- 小定位块在右边时,是否从右往左编号?
|
||||
|
||||
**用户答复**:
|
||||
- **颜色选取顺序始终从靠近小定位块的反应色块开始**
|
||||
- 无论试纸怎么摆放
|
||||
|
||||
### Q8:采样区域大小
|
||||
|
||||
**问题**:
|
||||
- 5毫米对应多少像素?
|
||||
- 是否需要动态计算采样半径?
|
||||
|
||||
**用户答复**:
|
||||
- 最少取 25 个像素的均值
|
||||
- 可以尝试动态计算半径
|
||||
|
||||
### Q9:SayHello 返回值格式
|
||||
|
||||
**问题**:
|
||||
- 返回值是否完全保持一致?
|
||||
- strBuffer 格式?
|
||||
|
||||
**用户答复**:
|
||||
- **完全一致**,上层代码不改
|
||||
- strBuffer 格式:`{"id":0,"value":"h,s,v,l,a,b"}`
|
||||
|
||||
### Q10:测试代码运行环境
|
||||
|
||||
**问题**:
|
||||
- Windows 独立测试程序?
|
||||
- 是否支持命令行参数?
|
||||
|
||||
**用户答复**:
|
||||
- Windows 独立命令行程序
|
||||
- 告知如何运行测试程序
|
||||
|
||||
### Q11:误差范围参数
|
||||
|
||||
**问题**:
|
||||
- 具体有哪些误差参数?
|
||||
|
||||
**用户答复**:
|
||||
|
||||
| 参数 | 范围 | 说明 |
|
||||
|------|------|------|
|
||||
| 颜色误差 | RGB欧式距离 ≤ 20 | 判断两个定位块颜色是否相同 |
|
||||
| 面积误差 | 10% | 上下限范围 |
|
||||
| 距离误差 | 10% | 上下限范围 |
|
||||
| 角度误差 | 15° | 倾斜判断 |
|
||||
|
||||
- 这些参数用全局变量定义,方便调节
|
||||
|
||||
### Q12:需要支持的试纸规格
|
||||
|
||||
**问题**:
|
||||
- 支持哪些规格?
|
||||
|
||||
**用户答复**:
|
||||
- **只支持 14联、11联、8联**
|
||||
- 每种联数支持两种大定位块(10mm 和 15mm)
|
||||
- 共 6 种 PaperConfig
|
||||
|
||||
### Q13:PaperConfig 参数
|
||||
|
||||
**问题**:
|
||||
- 其他规格参数如何计算?
|
||||
|
||||
**用户答复**:
|
||||
- 参考现有 `initPaperConfig` 函数
|
||||
- 只实现 14、11、8 联的支持代码
|
||||
|
||||
---
|
||||
|
||||
## 四、第三轮需求澄清(2026-03-31)
|
||||
|
||||
### Q14:图片格式和来源
|
||||
|
||||
**问题**:
|
||||
- 是否支持 NV21/YUV 格式?
|
||||
- 测试程序是否需要支持 NV21?
|
||||
|
||||
**用户答复**:
|
||||
- **不需要支持其他格式**
|
||||
- 测试只用 PNG/JPG
|
||||
- 没有 NV21 测试数据
|
||||
|
||||
### Q15:ROI 区域
|
||||
|
||||
**问题**:
|
||||
- 是否需要从大图自动搜索试纸位置?
|
||||
|
||||
**用户答复**:
|
||||
- 输入已经是裁剪好的 ROI
|
||||
- `11green_1.png` 等文件大小与实际传递的图片差不多
|
||||
- 裁剪来源:`savePaperImg` 函数产生的外包矩形
|
||||
|
||||
### Q16:白色反应色块的处理
|
||||
|
||||
**问题**:
|
||||
- 白平衡后白色色块的 RGB 如何解释?
|
||||
|
||||
**用户答复**:
|
||||
- 澄清:反应色块可能颜色很浅,findContours 可能找不到轮廓
|
||||
- **核心原则**:化学反应色块的位置通过两个定位块**计算得出**,与反应色块本身的位置无关
|
||||
- 计算的位置可能与实际中心点有偏差
|
||||
|
||||
### Q17:定位块的亮度变化
|
||||
|
||||
**问题**:
|
||||
- 深红 vs 浅红如何判断?
|
||||
|
||||
**用户答复**:
|
||||
- 深红、浅红都是红色 → R分量 > G、B分量
|
||||
- **增加欧式距离判断**:
|
||||
- 如果两个定位块一个是"深红"、一个是"浅红"
|
||||
- 欧式距离可能超过20 → 判定为不符合要求
|
||||
|
||||
### Q18:颜色输出格式确认
|
||||
|
||||
**问题**:
|
||||
- HSV 和 LAB 精度?
|
||||
|
||||
**用户答复**:
|
||||
- **保留2位小数点**
|
||||
|
||||
### Q19:还原图片的格式
|
||||
|
||||
**问题**:
|
||||
- 图片格式?分辨率?
|
||||
|
||||
**用户答复**:
|
||||
- 格式:JPG
|
||||
- 分辨率示例(14项红色试纸):
|
||||
- 试纸高度:50像素
|
||||
- 小定位块:30px宽 × 50px高
|
||||
- 反应色块:50px × 50px
|
||||
- 大定位块:100px宽
|
||||
- 间隔:20像素
|
||||
- 总图:200px高 × 1000px宽,试纸居中
|
||||
|
||||
---
|
||||
|
||||
## 五、最终需求确认
|
||||
|
||||
### 5.1 核心业务规则
|
||||
|
||||
| 规则 | 内容 |
|
||||
|------|------|
|
||||
| 定位块识别 | RGB分量比例判断 + 欧式距离验证(≤20)|
|
||||
| 试纸规格 | 仅支持 14联、11联、8联,各有大小定位块两种 |
|
||||
| 颜色顺序 | 始终从靠近小定位块的色块开始编号 |
|
||||
| 采样方式 | 以计算的中心点取至少25像素均值 |
|
||||
| 白平衡 | 基于整图进行 |
|
||||
|
||||
### 5.2 全局误差参数
|
||||
|
||||
```cpp
|
||||
// 颜色误差:RGB欧式距离阈值
|
||||
const double COLOR_DISTANCE_THRESHOLD = 20.0;
|
||||
|
||||
// 面积误差:10%
|
||||
const double AREA_ERROR_RATIO = 0.10;
|
||||
|
||||
// 距离误差:10%
|
||||
const double DISTANCE_ERROR_RATIO = 0.10;
|
||||
|
||||
// 角度误差:15度
|
||||
const double ANGLE_ERROR_DEGREE = 15.0;
|
||||
```
|
||||
|
||||
### 5.3 返回值格式
|
||||
|
||||
- 保持与现有 SayHello 完全一致
|
||||
- strBuffer JSON格式:`{"id":0,"value":"h,s,v,l,a,b"}`
|
||||
- 颜色值保留2位小数
|
||||
|
||||
### 5.4 测试要求
|
||||
|
||||
- Windows 独立命令行程序
|
||||
- 测试图片:`images/` 目录下的 PNG/JPG 文件
|
||||
- 输出:
|
||||
1. 识别成功/失败状态
|
||||
2. 标注定位块和色块中心的红圈图片
|
||||
3. 颜色值文本文件(HSV、LAB、RGB)
|
||||
4. 还原生成的试纸模拟图片(JPG,200×1000像素)
|
||||
|
||||
---
|
||||
|
||||
## 六、沟通结论
|
||||
|
||||
**需求已完全澄清,双方达成一致意见。**
|
||||
|
||||
下一步:
|
||||
1. AI 编写技术方案文档
|
||||
2. 逐步实现代码
|
||||
3. 编写测试程序
|
||||
4. 验证测试结果
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
@@ -0,0 +1,748 @@
|
||||
# 尿检试纸识别系统 - 详细设计文档
|
||||
|
||||
> 文档创建时间:2026-03-30
|
||||
> 最后更新:2026-04-03
|
||||
> 版本:v1.1
|
||||
> 适用范围:PaperRecognizer、ColorReader、PaperConfig、NAPI接口
|
||||
|
||||
---
|
||||
|
||||
## 一、系统概述
|
||||
|
||||
### 1.1 功能描述
|
||||
|
||||
尿检试纸识别系统用于从拍摄的试纸图片中自动识别:
|
||||
1. **定位块**:试纸两端的红色/绿色/蓝色定位块
|
||||
2. **试纸规格**:根据定位块特征判断8/11/14联及S/L型
|
||||
3. **反应色块位置**:计算每个反应色块的精确中心坐标
|
||||
4. **颜色数据**:读取每个反应色块的HSV+LAB颜色值
|
||||
|
||||
### 1.2 设计原则
|
||||
|
||||
- **模块化**:识别器、颜色读取器、配置管理器独立设计
|
||||
- **鲁棒性**:多级过滤、多重验证、容错处理
|
||||
- **可维护性**:清晰的代码结构、详细的注释
|
||||
- **可扩展性**:支持新增试纸规格
|
||||
|
||||
### 1.3 支持的试纸规格
|
||||
|
||||
| 类型 | 联数 | 大定位块 | 面积比 | 距离比范围 |
|
||||
|------|------|----------|--------|-----------|
|
||||
| PST_14_S | 14 | 10mm | ~3.33 | 19.5-23.5 |
|
||||
| PST_14_L | 14 | 15mm | ~5.0 | 20.0-24.0 |
|
||||
| PST_11_S | 11 | 10mm | ~3.33 | 15.9-19.2 |
|
||||
| PST_11_L | 11 | 15mm | ~5.0 | 16.2-19.6 |
|
||||
| PST_8_S | 8 | 10mm | ~3.33 | 11.6-14.1 |
|
||||
| PST_8_L | 8 | 15mm | ~5.0 | 12.0-14.6 |
|
||||
|
||||
---
|
||||
|
||||
## 二、核心模块设计
|
||||
|
||||
### 2.1 PaperRecognizer(试纸识别器)
|
||||
|
||||
#### 2.1.1 类图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ PaperRecognizer │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ - originalImage_: cv::Mat // 原图 │
|
||||
│ - processImage_: cv::Mat // 处理用图像 │
|
||||
│ - hsvImage_: cv::Mat // HSV图像 │
|
||||
│ - grayImage_: cv::Mat // 灰度图像 │
|
||||
│ - locator1_: LocatorInfo // 定位块1 │
|
||||
│ - locator2_: LocatorInfo // 定位块2 │
|
||||
│ - bigLocator_: LocatorInfo* // 大定位块指针 │
|
||||
│ - smallLocator_: LocatorInfo* // 小定位块指针 │
|
||||
│ - centers_: vector<Point2f> // 色块中心点 │
|
||||
│ - config_: PaperConfig* // 匹配的配置 │
|
||||
│ - result_: RecognitionResult // 识别结果 │
|
||||
│ - paperImg_: cv::Mat // 裁剪的试纸图 │
|
||||
│ - candidateCount_: size_t // 候选数量(新增) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ + recognize(image): RecognitionResult │
|
||||
│ + getPaperImg(): cv::Mat │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ - preprocessImage(image) │
|
||||
│ - detectLocators(): bool │
|
||||
│ - filterLocatorCandidates(contours): bool │
|
||||
│ - validateLocatorPair(): bool │
|
||||
│ - groupCandidatesByY(candidates): vector<vector<*>> │
|
||||
│ - findValidLocatorPairInGroup(group): LocatorPair │
|
||||
│ - determineLocatorColorByCenter(rect): LocatorColor │
|
||||
│ - matchPaperSpec(): bool │
|
||||
│ - calculateColorBlockCenters() │
|
||||
│ - drawAnnotations() │
|
||||
│ - savePaperImg() │
|
||||
│ - assessLocatorBlur(gray, locator, score): BlurLevel│ // 清晰度评估(新增)
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 2.1.2 主流程
|
||||
|
||||
```cpp
|
||||
RecognitionResult PaperRecognizer::recognize(const cv::Mat& image) {
|
||||
// Step 1: 图像预处理
|
||||
preprocessImage(image);
|
||||
|
||||
// Step 2: 定位块检测(包含多试纸处理)
|
||||
if (!detectLocators()) {
|
||||
return result_; // 返回错误
|
||||
}
|
||||
|
||||
// Step 3: 颜色判断(在 validateLocatorPair 中完成)
|
||||
|
||||
// Step 3.5: 清晰度评估(新增)
|
||||
double bigScore, smallScore;
|
||||
BlurLevel bigBlur = assessLocatorBlur(grayImage_, bigLocator_->rotatedRect, bigScore);
|
||||
BlurLevel smallBlur = assessLocatorBlur(grayImage_, smallLocator_->rotatedRect, smallScore);
|
||||
result_.blurLevel = max(bigBlur, smallBlur);
|
||||
result_.blurScore = min(bigScore, smallScore);
|
||||
|
||||
// Step 3.6: 杂物检测(新增)
|
||||
if (bigLocator_->height < 30.0) { // 试纸高度太小
|
||||
result_.errorCode = 9;
|
||||
return result_;
|
||||
}
|
||||
if (candidateCount_ > 15) { // 候选太多,场景杂乱
|
||||
result_.errorCode = 10;
|
||||
return result_;
|
||||
}
|
||||
|
||||
// Step 4: 规格匹配
|
||||
if (!matchPaperSpec()) {
|
||||
return result_;
|
||||
}
|
||||
|
||||
// Step 5: 计算色块中心点
|
||||
calculateColorBlockCenters();
|
||||
|
||||
// Step 6: 绘制标注
|
||||
drawAnnotations();
|
||||
|
||||
// Step 7: 保存试纸图片(NAPI用)
|
||||
savePaperImg();
|
||||
|
||||
result_.success = true;
|
||||
return result_;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.1.3 定位块检测流程
|
||||
|
||||
```
|
||||
输入: 原图
|
||||
输出: 两个定位块信息 (bigLocator_, smallLocator_)
|
||||
|
||||
1. 图像预处理
|
||||
- 颜色空间转换: BGR -> HSV, GRAY
|
||||
- 高斯滤波: 5x5 核
|
||||
|
||||
2. 轮廓检测
|
||||
- 二值化: THRESH_OTSU
|
||||
- findContours: RETR_EXTERNAL
|
||||
|
||||
3. 候选过滤
|
||||
for each contour:
|
||||
- 面积过滤: 100 < area < 10000
|
||||
- 正方形过滤: aspectRatio 不在 [0.9, 1.1]
|
||||
- 宽高比过滤:
|
||||
- 大定位块: 2.0 ± 0.5 或 3.0 ± 0.5
|
||||
- 小定位块: 1.667 ± 0.5
|
||||
- 计算颜色(延迟到最后)
|
||||
|
||||
4. Y坐标聚类(多试纸处理)
|
||||
- 按 Y坐标排序候选
|
||||
- 相邻候选 Y差 < 3*平均高度 -> 同组
|
||||
- 得到多个组
|
||||
|
||||
5. 组内配对验证
|
||||
for each group:
|
||||
- 找一大一小配对
|
||||
- 验证面积比: ~3.33 或 ~5.0
|
||||
- 验证颜色一致: RGB欧式距离 < 阈值
|
||||
- 验证 Y坐标相近: 差 < 阈值
|
||||
|
||||
6. 结果判定
|
||||
- validPairs.size() == 1: 成功
|
||||
- validPairs.size() > 1: errorCode = 6 (多条试纸)
|
||||
- validPairs.size() == 0: errorCode = 1 (未检测到)
|
||||
```
|
||||
|
||||
#### 2.1.4 定位块颜色判断
|
||||
|
||||
```cpp
|
||||
LocatorColor PaperRecognizer::determineLocatorColorByCenter(
|
||||
const cv::RotatedRect& rotatedRect) {
|
||||
|
||||
// 获取旋转矩形中心30%区域的颜色
|
||||
cv::Scalar avgColor = getCenterRegionColor(rotatedRect);
|
||||
|
||||
double r = avgColor[2];
|
||||
double g = avgColor[1];
|
||||
double b = avgColor[0];
|
||||
|
||||
// RGB 主判断
|
||||
double maxChannel = std::max({r, g, b});
|
||||
|
||||
// 红色: R最大且优势明显
|
||||
if (r == maxChannel && r > g + 20 && r > b + 20) {
|
||||
return LocatorColor::RED;
|
||||
}
|
||||
|
||||
// 绿色: G最大
|
||||
if (g == maxChannel && g > r + 10 && g > b + 10) {
|
||||
return LocatorColor::GREEN;
|
||||
}
|
||||
|
||||
// 蓝色: B最大且优势明显(降低阈值处理深蓝)
|
||||
if (b == maxChannel && b > r + 15 && b > g + 15) {
|
||||
return LocatorColor::BLUE;
|
||||
}
|
||||
|
||||
// HSV 辅助判断
|
||||
// ... 省略 HSV 计算细节
|
||||
|
||||
return LocatorColor::UNKNOWN;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.1.5 色块位置计算
|
||||
|
||||
**核心原则:`centers_[0]` 始终靠近小定位块**
|
||||
|
||||
```cpp
|
||||
void PaperRecognizer::calculateColorBlockCenters() {
|
||||
// 判断小定位块位置
|
||||
result_.smallLocatorOnLeft = !locator1_.isBig;
|
||||
|
||||
// 计算定位块边缘中心点
|
||||
cv::Point2f bigEdgeCenter, smallEdgeCenter;
|
||||
|
||||
// 情况1: 小定位块在左边
|
||||
if (result_.smallLocatorOnLeft) {
|
||||
// 方向向量: 从小指向大
|
||||
double dx = bigLocator_->center.x - smallLocator_->center.x;
|
||||
double dy = bigLocator_->center.y - smallLocator_->center.y;
|
||||
// ...
|
||||
// smallEdgeCenter = 小定位块右边缘中心
|
||||
// bigEdgeCenter = 大定位块左边缘中心
|
||||
}
|
||||
// 情况2: 小定位块在右边
|
||||
else {
|
||||
// 方向向量: 仍然从小指向大(反向计算)
|
||||
double dx = bigLocator_->center.x - smallLocator_->center.x;
|
||||
double dy = bigLocator_->center.y - smallLocator_->center.y;
|
||||
// ...
|
||||
// smallEdgeCenter = 小定位块左边缘中心
|
||||
// bigEdgeCenter = 大定位块右边缘中心
|
||||
}
|
||||
|
||||
// 计算步长
|
||||
double effectiveDist = distance(bigEdgeCenter, smallEdgeCenter);
|
||||
double avgDist = effectiveDist / config_->slicesNum;
|
||||
double firstOffset = avgDist * config_->firstPos;
|
||||
double stepOffset = avgDist * config_->step;
|
||||
|
||||
// 始终从小定位块边缘开始计算
|
||||
for (int i = 0; i < config_->itemsNum; ++i) {
|
||||
double offset = firstOffset + stepOffset * i;
|
||||
cv::Point2f center = smallEdgeCenter + offset * unitVector;
|
||||
centers_.push_back(center);
|
||||
}
|
||||
// 结果: centers_[0] 靠近小定位块
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 ColorReader(颜色读取器)
|
||||
|
||||
#### 2.2.1 类设计
|
||||
|
||||
```cpp
|
||||
class ColorReader {
|
||||
public:
|
||||
// 读取颜色(可选择是否应用白平衡)
|
||||
std::vector<ColorResult> readColors(
|
||||
const cv::Mat& image,
|
||||
const std::vector<cv::Point2f>& centers,
|
||||
bool smallLocatorOnLeft,
|
||||
bool applyWB = true // 白平衡开关
|
||||
);
|
||||
|
||||
private:
|
||||
// 白平衡处理(灰度世界算法)
|
||||
void applyWhiteBalance(cv::Mat& image);
|
||||
|
||||
// 计算自适应ROI大小
|
||||
int calculateAdaptiveROI(double locatorHeight);
|
||||
|
||||
// 采样颜色
|
||||
cv::Scalar sampleColor(const cv::Mat& image,
|
||||
const cv::Point2f& center,
|
||||
int roiSize);
|
||||
};
|
||||
```
|
||||
|
||||
#### 2.2.2 白平衡算法
|
||||
|
||||
```cpp
|
||||
void ColorReader::applyWhiteBalance(cv::Mat& image) {
|
||||
// 灰度世界算法
|
||||
cv::Scalar avgScalar = cv::mean(image);
|
||||
double avgGray = (avgScalar[0] + avgScalar[1] + avgScalar[2]) / 3.0;
|
||||
|
||||
std::vector<cv::Mat> channels;
|
||||
cv::split(image, channels);
|
||||
|
||||
// 各通道缩放
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
double scale = avgGray / avgScalar[i];
|
||||
channels[i] = channels[i] * scale;
|
||||
}
|
||||
|
||||
cv::merge(channels, image);
|
||||
image.convertTo(image, CV_8UC3);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.3 自适应ROI
|
||||
|
||||
```cpp
|
||||
// ROI大小: 定位块高度的0.6倍
|
||||
// 范围限制: 5x5 到 15x15 像素
|
||||
int calculateAdaptiveROI(double locatorHeight) {
|
||||
int halfSize = static_cast<int>(locatorHeight * 0.6);
|
||||
halfSize = std::max(5, std::min(15, halfSize));
|
||||
return halfSize * 2 + 1; // 11, 13, ..., 31
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 PaperConfig(试纸配置)
|
||||
|
||||
#### 2.3.1 配置参数
|
||||
|
||||
```cpp
|
||||
struct PaperConfig {
|
||||
PaperSpatialType type; // 试纸类型
|
||||
BlockItems blockItems; // 联数
|
||||
BigLocatorType locatorType; // 大定位块类型
|
||||
int itemsNum; // 反应色块数量
|
||||
int slicesNum; // 最小分割单位数量
|
||||
double firstPos; // 第一个色块位置倍数
|
||||
double step; // 步长倍数
|
||||
};
|
||||
```
|
||||
|
||||
#### 2.3.2 规格匹配
|
||||
|
||||
```cpp
|
||||
PaperSpatialType PaperConfigManager::matchConfig(
|
||||
double distRatio, double areaRatio) const {
|
||||
|
||||
// distRatio = 定位块中心距离 / 试纸高度
|
||||
// areaRatio = 大定位块面积 / 小定位块面积
|
||||
|
||||
// S型 (10mm大定位块, areaRatio < 4.0)
|
||||
if (areaRatio < 4.0) {
|
||||
if (distRatio > 19.5 && distRatio < 23.5) return PST_14_S;
|
||||
if (distRatio > 15.9 && distRatio < 19.2) return PST_11_S;
|
||||
if (distRatio > 11.6 && distRatio < 14.1) return PST_8_S;
|
||||
}
|
||||
// L型 (15mm大定位块, areaRatio >= 4.0)
|
||||
else {
|
||||
if (distRatio > 20.0 && distRatio < 24.0) return PST_14_L;
|
||||
if (distRatio > 16.2 && distRatio < 19.6) return PST_11_L;
|
||||
if (distRatio > 12.0 && distRatio < 14.6) return PST_8_L;
|
||||
}
|
||||
|
||||
return PST_UNKNOWN;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、NAPI 接口设计
|
||||
|
||||
### 3.1 接口列表
|
||||
|
||||
| 函数名 | 功能 | 输入 | 输出 |
|
||||
|--------|------|------|------|
|
||||
| sayHello | 试纸识别 | ArrayBuffer(YUV), width, height, format | Object |
|
||||
| getPaperData | 获取裁剪试纸图 | 无 | Object |
|
||||
| add | 测试用 | num1, num2 | sum |
|
||||
| analysisImage | 测试用 | PixelMap | PixelMap |
|
||||
|
||||
### 3.2 sayHello 详细设计
|
||||
|
||||
#### 3.2.1 输入参数
|
||||
|
||||
```javascript
|
||||
// ArkTS 调用示例
|
||||
let result = nativeModule.sayHello(
|
||||
yuvBuffer, // ArrayBuffer: NV21格式的YUV数据
|
||||
width, // number: 图像宽度
|
||||
height, // number: 图像高度
|
||||
format // number: 格式代码 (8)
|
||||
);
|
||||
```
|
||||
|
||||
#### 3.2.2 处理流程
|
||||
|
||||
```cpp
|
||||
static napi_value SayHello(napi_env env, napi_callback_info info) {
|
||||
// 1. 解析参数
|
||||
napi_get_arraybuffer_info(env, args[0], &inputBuffer, &length);
|
||||
napi_get_value_int32(env, args[1], &width);
|
||||
napi_get_value_int32(env, args[2], &height);
|
||||
|
||||
// 2. YUV -> RGBA
|
||||
cv::Mat yuv_mat(height + height/2, width, CV_8UC1, inputBuffer);
|
||||
cv::cvtColor(yuv_mat, t, cv::COLOR_YUV2RGBA_NV21);
|
||||
cv::rotate(t, rgb_mat, cv::ROTATE_90_CLOCKWISE);
|
||||
|
||||
// 3. 调用识别器
|
||||
cvtColor(rgb_mat, bgra_mat, COLOR_RGBA2BGRA);
|
||||
RecognitionResult recResult = g_paperRecognizer.recognize(bgra_mat);
|
||||
|
||||
// 4. 处理结果
|
||||
if (recResult.success) {
|
||||
// 读取颜色(白平衡)
|
||||
// 自适应ROI采样
|
||||
// 生成JSON字符串
|
||||
}
|
||||
|
||||
// 5. 封装返回对象
|
||||
napi_create_object(env, &object);
|
||||
napi_set_named_property(env, object, "byteLength", ...);
|
||||
napi_set_named_property(env, object, "buffer", ...);
|
||||
napi_set_named_property(env, object, "width", ...);
|
||||
napi_set_named_property(env, object, "height", ...);
|
||||
napi_set_named_property(env, object, "strLength", ...);
|
||||
napi_set_named_property(env, object, "strBuffer", ...);
|
||||
napi_set_named_property(env, object, "statusCode", ...);
|
||||
|
||||
return object;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.3 输出格式
|
||||
|
||||
```javascript
|
||||
{
|
||||
byteLength: 123456, // 标注图像数据长度
|
||||
buffer: ArrayBuffer, // 标注图像数据 (RGBA)
|
||||
width: 1920, // 图像宽度
|
||||
height: 1080, // 图像高度
|
||||
strLength: 567, // 颜色JSON字符串长度
|
||||
strBuffer: ArrayBuffer, // 颜色JSON字符串
|
||||
statusCode: 0 // 状态码
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.4 颜色JSON格式
|
||||
|
||||
```json
|
||||
[
|
||||
{"id":0, "value":"52.0,255.0,179.0,163.0,-3.0,68.0"},
|
||||
{"id":1, "value":"42.0,58.0,175.0,172.0,0.0,16.0"},
|
||||
...
|
||||
]
|
||||
// value格式: H,S,V,L,A,B
|
||||
```
|
||||
|
||||
### 3.3 getPaperData 详细设计
|
||||
|
||||
```cpp
|
||||
static napi_value GetPaperData(napi_env env, napi_callback_info info) {
|
||||
// 获取裁剪后的试纸图片(RGBA格式)
|
||||
cv::Mat paperMat = g_paperRecognizer.getPaperImg();
|
||||
|
||||
// 封装返回
|
||||
napi_create_object(env, &object);
|
||||
napi_set_named_property(env, object, "byteLength", paperMat.total()*4);
|
||||
napi_set_named_property(env, object, "buffer", ...);
|
||||
napi_set_named_property(env, object, "width", paperMat.cols);
|
||||
napi_set_named_property(env, object, "height", paperMat.rows);
|
||||
|
||||
return object;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、数据流程图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 上层应用 (ArkTS) │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ 摄像头采集 │ → NV21数据 │
|
||||
│ └────────────────┘ │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│ N-API
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ napi_init.cpp │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ SayHello │ │
|
||||
│ │ 1. 解析NV21 → RGBA │ │
|
||||
│ │ 2. 调用 PaperRecognizer │ │
|
||||
│ │ 3. 读取颜色 (ColorReader) │ │
|
||||
│ │ 4. 封装返回结果 │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PaperRecognizer │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 图像预处理 │→│ 定位块检测 │→│ 规格匹配 │→│ 色块计算 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ 高斯滤波 轮廓检测 distRatio 中心点坐标 │
|
||||
│ 颜色转换 多试纸处理 areaRatio │
|
||||
│ 颜色判断 │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ColorReader │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 白平衡 │→│ ROI采样 │→│ 颜色转换 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ 灰度世界 自适应大小 HSV + LAB │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 返回结果 │
|
||||
│ { │
|
||||
│ statusCode: 0, │
|
||||
│ buffer: 标注图像(RGBA), │
|
||||
│ strBuffer: [{id:0, value:"H,S,V,L,A,B"}, ...] │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、错误处理
|
||||
|
||||
### 5.1 错误码定义
|
||||
|
||||
```cpp
|
||||
// napi_init.cpp
|
||||
// ========== 错误码定义 ==========
|
||||
// 0 - 成功
|
||||
// 1 - 未检测到定位块
|
||||
// 2 - 定位块颜色识别失败
|
||||
// 3 - 定位块配对失败(一大一小)
|
||||
// 4 - 试纸规格匹配失败
|
||||
// 5 - 色块位置计算失败
|
||||
// 6 - 检测到多条试纸
|
||||
// 7 - 图像处理错误
|
||||
// 8 - 输入图像为空
|
||||
// 9 - 试纸太小/杂物(试纸高度<30px)
|
||||
// 10 - 场景杂乱(候选定位块>15个)
|
||||
// 100 - 未知错误
|
||||
// ===============================
|
||||
```
|
||||
|
||||
### 5.2 错误处理策略
|
||||
|
||||
| 错误场景 | 处理方式 | 返回内容 |
|
||||
|----------|----------|----------|
|
||||
| 未检测到定位块 | 返回原图 + statusCode=1 | buffer=原图, strBuffer=空 |
|
||||
| 多条试纸 | 返回原图 + statusCode=6 | 提示用户单独拍摄 |
|
||||
| 规格不匹配 | 返回原图 + statusCode=4 | 可能是非标准试纸 |
|
||||
| 试纸太小/杂物 | 返回原图 + statusCode=9 | 提示用户靠近拍摄 |
|
||||
| 场景杂乱 | 返回原图 + statusCode=10 | 提示用户简化背景 |
|
||||
| 其他错误 | 返回原图 + statusCode=100 | 检查日志 |
|
||||
|
||||
---
|
||||
|
||||
## 六、关键算法参数
|
||||
|
||||
### 6.1 定位块检测参数
|
||||
|
||||
```cpp
|
||||
// 面积过滤
|
||||
const double MIN_LOCATOR_AREA = 100;
|
||||
const double MAX_LOCATOR_AREA = 10000;
|
||||
|
||||
// 宽高比
|
||||
const double BIG_LOCATOR_RATIO_SHORT = 2.0; // 10mm
|
||||
const double BIG_LOCATOR_RATIO_LONG = 3.0; // 15mm
|
||||
const double SMALL_LOCATOR_RATIO = 1.667;
|
||||
const double ASPECT_RATIO_TOLERANCE = 0.35; // 收紧容差(原为0.5)
|
||||
const double MAX_ASPECT_RATIO = 4.0; // 最大宽高比限制(新增)
|
||||
|
||||
// 面积比
|
||||
const double AREA_RATIO_SHORT = 3.33; // 10mm大定位块
|
||||
const double AREA_RATIO_LONG = 5.0; // 15mm大定位块
|
||||
const double AREA_RATIO_TOLERANCE = 1.0;
|
||||
|
||||
// 颜色距离阈值
|
||||
const double COLOR_DISTANCE_THRESHOLD = 20.0;
|
||||
|
||||
// 杂物检测阈值(新增)
|
||||
const double MIN_PAPER_HEIGHT = 30.0; // 最小试纸高度(像素)
|
||||
const size_t MAX_CANDIDATE_COUNT = 15; // 最大候选定位块数量
|
||||
```
|
||||
|
||||
### 6.2 清晰度评估参数(新增)
|
||||
|
||||
```cpp
|
||||
// Laplacian 方差法阈值
|
||||
const double BLUR_THRESHOLD_NONE = 300; // >300: 清晰
|
||||
const double BLUR_THRESHOLD_SLIGHT = 100; // 100-300: 轻微模糊
|
||||
// <100: 严重模糊
|
||||
const double BLUR_ROI_EXPAND_RATIO = 0.15; // ROI外扩15%
|
||||
```
|
||||
|
||||
### 6.3 颜色判断阈值
|
||||
|
||||
```cpp
|
||||
// RGB 阈值
|
||||
const int RED_MARGIN = 20; // R > G+20 && R > B+20
|
||||
const int GREEN_MARGIN = 10; // G > R+10 && G > B+10
|
||||
const int BLUE_MARGIN = 15; // B > R+15 && B > G+15 (降低处理深蓝)
|
||||
|
||||
// HSV 范围
|
||||
// 红色: H < 10 或 H > 170
|
||||
// 绿色: 35 < H < 85
|
||||
// 蓝色: H >= 95
|
||||
```
|
||||
|
||||
### 6.4 自适应ROI参数
|
||||
|
||||
```cpp
|
||||
// ROI大小 = 定位块高度 × 0.6
|
||||
// 范围: 5×5 到 15×15 像素
|
||||
// 对应 ROI: 11×11 到 31×31 像素
|
||||
```
|
||||
|
||||
### 6.5 清晰度评估算法(新增)
|
||||
|
||||
**算法**:Laplacian 方差法
|
||||
|
||||
**步骤**:
|
||||
1. 提取定位块 ROI(外扩15%)
|
||||
2. 计算 Laplacian 算子
|
||||
3. 计算方差(标准差的平方)
|
||||
4. 根据阈值判断清晰度等级
|
||||
|
||||
**优势**:
|
||||
- 只在定位块局部区域计算,不受图像大小影响
|
||||
- 计算量小,实时性好
|
||||
- 对运动模糊敏感
|
||||
|
||||
---
|
||||
|
||||
## 七、性能优化
|
||||
|
||||
### 7.1 已实现的优化
|
||||
|
||||
1. **早期过滤**
|
||||
- 正方形轮廓早期排除(宽高比0.9-1.1)
|
||||
- 面积过小/过大早期排除
|
||||
|
||||
2. **延迟计算**
|
||||
- 颜色计算延迟到候选确定后
|
||||
- 从N次减少到2次
|
||||
|
||||
3. **位置排序策略**
|
||||
- 按X坐标排序,只取最左最右
|
||||
- 中间轮廓不参与定位
|
||||
|
||||
### 7.2 内存管理
|
||||
|
||||
- 使用引用避免不必要的拷贝
|
||||
- 及时释放中间变量
|
||||
- `cv::Mat` 使用 `clone()` 显式拷贝
|
||||
|
||||
---
|
||||
|
||||
## 八、测试与验证
|
||||
|
||||
### 8.1 Windows测试程序
|
||||
|
||||
```bash
|
||||
# 编译
|
||||
cd entry/src/main/cpp/test/build
|
||||
cmake --build . --config Debug
|
||||
|
||||
# 运行
|
||||
.\bin\Debug\urine_test.exe ..\..\images\14red_1.png
|
||||
```
|
||||
|
||||
### 8.2 输出文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| *_annotated.jpg | 标注图(定位块框+色块圈+编号)|
|
||||
| *_paper.jpg | 裁剪的试纸图 |
|
||||
| *_colors.txt | 颜色结果文本 |
|
||||
| *_restored_no_wb.jpg | 无白平衡还原图 |
|
||||
| *_restored_with_wb.jpg | 有白平衡还原图 |
|
||||
| *_colors_no_wb.json | 无白平衡颜色JSON |
|
||||
| *_colors_with_wb.json | 有白平衡颜色JSON |
|
||||
|
||||
### 8.3 鸿蒙HAP构建
|
||||
|
||||
```powershell
|
||||
$env:DEVECO_SDK_HOME = "D:\DevEco Studio\sdk"
|
||||
cd E:\Qoder\urologist
|
||||
& "D:\DevEco Studio\tools\hvigor\bin\hvigorw.bat" assembleHap --mode module -p product=default --no-daemon
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、附录
|
||||
|
||||
### A. 文件依赖关系
|
||||
|
||||
```
|
||||
napi_init.cpp
|
||||
├── PaperRecognizer.h
|
||||
│ ├── Types.h
|
||||
│ ├── PaperConfig.h
|
||||
│ └── ColorReader.h
|
||||
│ └── Types.h
|
||||
└── OpenCV
|
||||
```
|
||||
|
||||
### B. CMakeLists.txt 配置
|
||||
|
||||
```cmake
|
||||
add_library(
|
||||
entry
|
||||
SHARED
|
||||
napi_init.cpp
|
||||
PaperRecognizer.cpp
|
||||
PaperConfig.cpp
|
||||
ColorReader.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(entry PUBLIC
|
||||
libace_napi.z.so
|
||||
libhilog_ndk.z.so
|
||||
libpixelmap_ndk.z.so
|
||||
${OpenCV_LIBS}
|
||||
)
|
||||
```
|
||||
|
||||
### C. 版本历史
|
||||
|
||||
| 版本 | 日期 | 变更说明 |
|
||||
|------|------|----------|
|
||||
| v1.0 | 2026-03-31 | 初始设计 |
|
||||
| v2.0 | 2026-03-30 | NAPI集成完成,多试纸处理,色块编号修正 |
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
Reference in New Issue
Block a user