5.3 KiB
5.3 KiB
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/ 下的代码后,执行:
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)。
使用方法
加载模块
const Module = require('./web/paper_recognizer.js');
const m = await Module();
浏览器中使用:
<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 堆:
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 结构:
{
"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。
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() 调用后需调用一次。
完整调用示例
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 };
}
重要说明
- 内存管理:
_malloc分配的内存必须用_free释放;_freeResult必须每次_recognize后调用。 - 输入格式:接收 RGBA 字节数组(每像素 4 字节),R/G/B/A 各 8 位。
- 标注图格式:返回 BGRA 字节数组(B/G/R/A 顺序),在 JavaScript 侧绘制到 Canvas 前可能需要转换。
- 错误处理:
_recognize返回 -1 时可通过_getResultJson()查看errorCode和errorMessage。 - 模块是单例:多次调用
PaperRecognizerModule()返回同一个实例。
测试
node test.js
测试会加载模块、用纯灰色图像调用识别,并验证所有导出函数正常。