Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

5.3 KiB
Raw Permalink Blame History

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 };
}

重要说明

  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() 返回同一个实例。

测试

node test.js

测试会加载模块、用纯灰色图像调用识别,并验证所有导出函数正常。