197 lines
5.3 KiB
Markdown
197 lines
5.3 KiB
Markdown
# Paper Recognizer WASM
|
||
|
||
尿检试纸图像识别模块,OpenCV C++ 编译为 WebAssembly,适用于浏览器和微信小程序。
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
├── src/
|
||
│ ├── PaperRecognizer.cpp/h # 核心识别算法
|
||
│ ├── ColorReader.cpp/h # 色块颜色读取
|
||
│ ├── PaperConfig.cpp/h # 试纸规格配置
|
||
│ ├── emsc_bridge.cpp # Emscripten 桥接(对外 API)
|
||
│ ├── Types.h # 类型定义
|
||
│ └── GlobalParams.h # 全局常量
|
||
├── web/
|
||
│ ├── paper_recognizer.js # JS 胶水代码(编译产物)
|
||
│ └── paper_recognizer.wasm # WASM 模块(编译产物)
|
||
├── build.sh # 编译脚本
|
||
├── test.js # Node.js 测试
|
||
└── README.md # 本文件
|
||
```
|
||
|
||
## 环境要求
|
||
|
||
- **Emscripten SDK**(`emsdk`),安装于 `/home/ubuntu/emsdk`
|
||
- **OpenCV** 已编译为 WASM 静态库,位于 `/home/ubuntu/opencv/build_mini_os/`
|
||
|
||
如果环境路径不同,请修改 `build.sh` 中的 `EMSDK_DIR` 和 `OPENCV_DIR`。
|
||
|
||
## 编译方法
|
||
|
||
修改 `src/` 下的代码后,执行:
|
||
|
||
```bash
|
||
cd /home/ubuntu/nanocode/web-opencv/miniappA
|
||
./build.sh
|
||
```
|
||
|
||
产物输出到 `web/` 目录:
|
||
- `web/paper_recognizer.js` — JS 胶水代码
|
||
- `web/paper_recognizer.wasm` — WASM 二进制
|
||
|
||
### 编译说明
|
||
|
||
编译使用 `-Os` 优化级别,静态链接 OpenCV core + imgproc + imgcodecs,输出单文件 WASM。
|
||
|
||
关键编译选项:
|
||
- `ALLOW_MEMORY_GROWTH=1` — 允许 WASM 内存动态增长
|
||
- `MODULARIZE=1` — 模块化导出(异步加载)
|
||
- `FILESYSTEM=0` — 不包含文件系统(减小体积)
|
||
- `DISABLE_EXCEPTION_CATCHING=1` — 禁用 C++ 异常捕获
|
||
|
||
WASM 体积:1014KB(gzip 后约 348KB)。
|
||
|
||
## 使用方法
|
||
|
||
### 加载模块
|
||
|
||
```javascript
|
||
const Module = require('./web/paper_recognizer.js');
|
||
const m = await Module();
|
||
```
|
||
|
||
浏览器中使用:
|
||
|
||
```html
|
||
<script src="paper_recognizer.js"></script>
|
||
<script>
|
||
PaperRecognizerModule().then(m => {
|
||
// m 就是模块实例
|
||
});
|
||
</script>
|
||
```
|
||
|
||
### API 说明
|
||
|
||
所有 API 通过 Module 上的函数调用,函数名带前导下划线。
|
||
|
||
#### `_recognize(data, width, height)`
|
||
|
||
执行试纸识别。
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `data` | `number` (指针) | RGBA 像素数据在 WASM 堆中的指针 |
|
||
| `width` | `number` | 图像宽度 |
|
||
| `height` | `number` | 图像高度 |
|
||
|
||
返回 `0` 成功,`-1` 失败。
|
||
|
||
**调用前需要先将图像数据拷贝到 WASM 堆:**
|
||
|
||
```javascript
|
||
const pixels = new Uint8Array(imageData); // RGBA 格式
|
||
const ptr = m._malloc(pixels.length);
|
||
m.HEAPU8.set(pixels, ptr);
|
||
|
||
const ret = m._recognize(ptr, width, height);
|
||
```
|
||
|
||
#### `_getResultJson()`
|
||
|
||
返回识别结果的 JSON 字符串指针,使用 `m.UTF8ToString(ptr)` 读取。
|
||
|
||
返回的 JSON 结构:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errorCode": 0,
|
||
"errorMessage": "",
|
||
"paperType": "14_S",
|
||
"locatorColor": 0,
|
||
"itemCount": 14,
|
||
"smallLocatorOnLeft": true,
|
||
"blurLevel": 0,
|
||
"blurScore": 0.0,
|
||
"colorBlockCenters": [{ "x": 100.0, "y": 200.0 }, ...],
|
||
"colors": [
|
||
{
|
||
"id": 0,
|
||
"r": 200.0, "g": 150.0, "b_val": 100.0,
|
||
"h": 30.0, "s": 0.5, "v": 0.8,
|
||
"l": 60.0, "a": 10.0, "b": 20.0
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### `_getAnnotatedWidth()` / `_getAnnotatedHeight()`
|
||
|
||
获取标注图像的宽度和高度(像素)。
|
||
|
||
#### `_getAnnotatedPixels()`
|
||
|
||
返回标注图像 BGRA 像素数据的指针。每像素 4 字节(B, G, R, A),总大小 = `width * height * 4`。
|
||
|
||
```javascript
|
||
const w = m._getAnnotatedWidth();
|
||
const h = m._getAnnotatedHeight();
|
||
const ptr = m._getAnnotatedPixels();
|
||
const size = w * h * 4;
|
||
const pixels = new Uint8Array(m.HEAPU8.buffer, ptr, size);
|
||
```
|
||
|
||
#### `_freeResult()`
|
||
|
||
释放内部缓存(结果 JSON、标注图像)。每次 `_recognize()` 调用后需调用一次。
|
||
|
||
### 完整调用示例
|
||
|
||
```javascript
|
||
async function processImage(imageData, width, height) {
|
||
const m = await PaperRecognizerModule();
|
||
|
||
// 拷贝图像到 WASM 堆
|
||
const dataSize = width * height * 4;
|
||
const ptr = m._malloc(dataSize);
|
||
m.HEAPU8.set(new Uint8Array(imageData), ptr);
|
||
|
||
// 识别
|
||
const ret = m._recognize(ptr, width, height);
|
||
|
||
// 读取结果
|
||
const jsonPtr = m._getResultJson();
|
||
const result = JSON.parse(m.UTF8ToString(jsonPtr));
|
||
|
||
// 读取标注图
|
||
const aw = m._getAnnotatedWidth();
|
||
const ah = m._getAnnotatedHeight();
|
||
const ap = m._getAnnotatedPixels();
|
||
const annotatedData = new Uint8Array(m.HEAPU8.buffer, ap, aw * ah * 4);
|
||
|
||
// 清理
|
||
m._freeResult();
|
||
m._free(ptr);
|
||
|
||
return { result, annotatedData, width: aw, height: ah };
|
||
}
|
||
```
|
||
|
||
### 重要说明
|
||
|
||
1. **内存管理**:`_malloc` 分配的内存必须用 `_free` 释放;`_freeResult` 必须每次 `_recognize` 后调用。
|
||
2. **输入格式**:接收 RGBA 字节数组(每像素 4 字节),R/G/B/A 各 8 位。
|
||
3. **标注图格式**:返回 BGRA 字节数组(B/G/R/A 顺序),在 JavaScript 侧绘制到 Canvas 前可能需要转换。
|
||
4. **错误处理**:`_recognize` 返回 -1 时可通过 `_getResultJson()` 查看 `errorCode` 和 `errorMessage`。
|
||
5. **模块是单例**:多次调用 `PaperRecognizerModule()` 返回同一个实例。
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
node test.js
|
||
```
|
||
|
||
测试会加载模块、用纯灰色图像调用识别,并验证所有导出函数正常。
|