背景
最近搞open cv识别物体的项目,因为客户需求,最终决定使用js来实现。
捕获摄像头 是通过navigator.mediaDevices,前提是 ios 需要11以上必须是https, android到还好。
识别物体通过opencv js 。
虽然坎坎坷坷 但是第一版还是上线了 除了https和默认显示后置摄像头(Android的枚举摄像头 根据label判断是否是后置的,IOS 中的facingMode:{exact:"environment"} 是有效的),其它还算顺利。
然后 然后 opencv js 10M 这个大小比较要命。。。
opencv是通过Emscripten 将c编译成js的
所以打算ctrl+c, ctrl+v 精减下源码,再编译下,将js文件减少到500kb之内是没有问题的。
安装
安装 Emscripten 还是比较简单的
https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
git clone https://github.com/juj/emsdk.git
cd emsdk
git pull
emsdk install latest
我是windows下,安装的时候 下载LLMV的 很慢总失败,后来挂了代理 才下载成功。。。
编译
使用命令 emcc 可以把c++ 编译成js,如果你一上来就敲emcc 那么不好意思 是不存在的 先要运行下面的命令设置好当前进程的环境才能编译
C:\inno\libraries\emsdk\emsdk activate latest
C:\inno\libraries\emsdk\emsdk_env.bat
然后可以使用emcc了,说白了 每次打开cmd窗口 先要调用下上面那个命令
编译很简单
emcc main.cpp -o main.js
main.cpp
void main()
{
print("Hello World");
}
输出的main.js 内容很多,内嵌了一些基础实现,但我搜索不到“Hello World”的字样,虽然没弄明白原理但觉得可以用来加密代码
折腾了很久 最后我选用的编译选项是
emcc --bind demo.cpp -o demo.js -s WASM=0 -O3 --memory-init-file 0 --pre-js pre.js --post-js post.js
我觉得这才是实战所需要的,实在看不上-s EXPORTED_FUNCTIONS="['_main', '_myfunc']" 然后用module.ccall。。。
--bind 是指可以EMSCRIPTEN_BINDINGS来声明需要导出的类以及单个函数
-s WASM=0 不要生成web assembly 即.wasm文件,因为这里暂时用不上
-O3 优化级别是3的 默认是不会优化的
--memory-init-file 这个完全是为了填-O3的坑,因为打开优化后生成文件除了.js还有一个 .js.mem, js加载进来之后还要去加载.js.mem文件之后才能用导出的函数 这里是异步的,需要注册Module.onRuntimeInitialized这个回调才能继续,那么这个参数是不要 生成mem文件,舍弃这些麻烦。
--pre-js pre.js 和 --post-js post.js 是加在生成的js首尾的js,本来是用来做匿名的,但看生成的话 有点搞笑。。。
可以欣赏这里所有源码
变量传递
https://kripken.github.io/emscripten-site/docs/api_reference/emscripten.h.html#inline-assembly-javascript 这里讲了使用EM_XX 怎么在c++里内联js代码,然后怎么交互,c++怎么传值(数字和指针)给js,js怎么返回值(数字和指针)给c++ 写的很详细 足够了
在EMSCRIPTEN_BINDINGS中传递Uint8Array
cpp
#include <string>
#include <malloc.h>
#include <functional>
#include <emscripten/bind.h>
typedef unsigned char uchar;
class MyClass
{
public:
MyClass()
{
data = new uchar[100];
size = 100;
}
emscripten::val getData () const
{
return emscripten::val(emscripten::memory_view<uchar>(size,data));
}
private:
int size;
uchar* data;
};
EMSCRIPTEN_BINDINGS(my_class) {
emscripten::class_<MyClass>("MyClass")
.constructor()
.property("data", &MyClass::getData)
;
}
js
var my = new inno.MyClass();
var ctx = cin.getContext('2d');
my.data.set(ctx.getImageData(0, 0, w, h).data);
var imgData = new ImageData(new Uint8ClampedArray(my.data), w, h);
ctx.putImageData(imgData, 0, 0);
另一种方式
cpp
class MyClass{
public:
MyClass(int w, int h){
}
void setData(intptr_t frame4b_ptr)
{
//frame4b_ptr[0]
}
};
EMSCRIPTEN_BINDINGS(my_class) {
emscripten::class_<MyClass>("MyClass")
.constructor()
.function("update", &MyClass::updasetDatate, emscripten::allow_raw_pointers())
;
}
JS
var data = this.ctx.getImageData(0, 0, w, h);
var bytes = arrayToHeap(data.data);
my.setData(bytes.byteOffset);
data.data.set(bytes);
this.ctx.putImageData(data, 0, 0)
;
function freeArray(heapBytes) {
Module._free(heapBytes.byteOffset);
}
function arrayToHeap(typedArray) {
var numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;
var ptr = Module._malloc(numBytes);
heapBytes = Module.HEAPU8.subarray(ptr, ptr + numBytes);
heapBytes.set(typedArray);
return heapBytes;
}