# 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 ``` ### 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 ``` 测试会加载模块、用纯灰色图像调用识别,并验证所有导出函数正常。