webassembly+opencv前端实现图片去水印

5,984 阅读3分钟

webassembly环境搭建

下载emsdk项目,进行构建。

git clone https://github.com/juj/emsdk.git
cd emsdk 
./emsdk update 
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh 

构建openCV的webassembly版本

下载 opencv 项目

git clone https://github.com/opencv/opencv.git

构建 webassembly版本

python ./platforms/js/build_js.py build_wasm --build_wasm

**注意:**构建这里有个坑,如果本地的webassembly环境名字不一定叫  EMSCRIPTEN  ,这时就会提示该环境不存在;需要手动需要修改build_js.py文件 ,设置你本地emscripten环境地址,即令:

emscripten_dir='your emscripten path'

        构建文件为opencv/build_wasm/bin/opencv.js , wasm新型二进制代码已经被构建到了opencv.js 中,在HTML文件中直接引入。Opencv会暴露一个cv作为全局对象。我们通过cv来使用opencv的各种方法。

<script async src="opencv.js"></script>

图片去水印

        主要使用了cv.inpaint方法给图片去水印,可以参考C++的使用方法。

void inpaint( InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags );

 参数介绍:

src   输入1通道或3通道图像。 
蒙版  1通道图像。非零像素表示需要修复的区域。大小跟原图像一致
dst   输出的经过修复的图像。
半径  该算法考虑的每个修补点的圆形邻域的半径。
标志  修复算法,有两种 INPAINT_NS或INPAINT_TELEA

注意:在浏览器中使用cv.imread 方法读取图片,获取到的是四通道类型图像数据,而inpaint只支持处理一通道或三通道数据图像,所以我们还要进行下数据转换。

let dst = new cv.Mat();
let imgRGB = new cv.Mat();
let imgGrey = new cv.Mat();
let imageSource = cv.imread("img1", cv.CV_8UC3);
//转成三通道图像
cv.cvtColor(imageSource, imgRGB, cv.COLOR_BGRA2BGR);
//转成灰度图
cv.cvtColor(imageSource, imgGrey, cv.COLOR_RGBA2GRAY);

let imgMask = new cv.Mat(img1.height, img1.width, cv.CV_8UC1, new cv.Scalar(0, 0, 0, 0));
//通过阈值处理生成Mask
cv.threshold(imgGrey, imgMask, 240, 255, cv.THRESH_BINARY);
let kernel = cv.getStructuringElement(cv.MORPH_RECT, new cv.Size(3, 3));

//对Mask膨胀处理,增加Mask面积
cv.dilate(imgMask, imgMask, kernel);

cv.inpaint(imgRGB, imgMask, dst, 10, cv.INPAINT_TELEA);
cv.imshow(outputCanvas, dst);

dst.delete();
imgRGB.delete();
imgGrey.delete();
imageSource.delete();

处理前

处理后

        处理时长为30ms,效果还是可以的。但是从蒙版图片可以看出,处理区域不仅是水印部分,还包括了原图其他部分,这并不是我们想要的结果;再进行下优化,只对水印区域进行处理。

        获取到水印区域坐标,对坐标内的图像阈值处理、生成Mask。

canvesBox.onmousedown = function (e) {
    x = e.offsetX; // 鼠标落下时的X
    y = e.offsetY; // 鼠标落下时的Y
    ctx.beginPath();
    document.onmouseup = function (e) {
        console.log('onmouseup', e.offsetX, e.offsetY)
        rx = e.offsetX;//鼠标抬起时的X坐标
        ry = e.offsetY;//鼠标抬起时的Y坐标
        document.onmousemove = null;
        document.onmouseup = null;

        //获取选中区域的图像
        var dataImg = ctx.getImageData(x, y, rx - x, ry - y);
        objCanvas.width = canvesBox.width;
        objCanvas.height = canvesBox.height;
        objctx.putImageData(dataImg, x, y);
        url = canvesBox.toDataURL();
    }
}

结果

优化

        按照官方的构建方式得到的opencv.js有9M多,这对于一个网站来说实在是太大了,需要优化一下;通过修改配置文件,把用到的方法都挑出来,可以有效降低opencv.js文件大小。

  1.  打开 Opencv/platforms/js/build_js.py 把除了js和photo外,把其他用不到的依赖项都设为OFF
def get_cmake_cmd(self):
-DBUILD_opencv_photo=ON,
-DBUILD_opencv_js=ON,

2.  打开默认加载配置文件Opencv/platforms/js/build_js.config.py本次去水印功能,用到的函数并不多,这里把用到的函数挑出来,其他都删掉。

# Classes and methods whitelist
imgproc = {'': ['cvtColor','threshold','getStructuringElement', 'dilate']}
photo = {'': ['inpaint']}
white_list = makeWhiteList([imgproc, photo])

        再次构建,可以发现opencv文件缩小到了1.8M。1.8M对于一个js文件来说还是比较大的,但目前大小很难在往下缩减了,如果非要缩减,只能把opencv中inpaint函数独立出来,自己再封装成一个函数(封装方法可以参考我上一期的webassembly学习笔记)。这样实现的门槛比较高;比如,要对inpaint函数原理很了解,对C++很熟练,才有可能把它独立出来。

总结

        Webassembly的性能还是很可观的 ,但是现阶段webassembly各项插件库并不多,资料少,应用场景少,门槛还高,不利于技术的发展。Opencv.js已经算是比较成熟的webassembly库了,但是相关文档太少了,我也是踩了很多的坑才把它运用起来。希望以后能有更多的人投入到webassembly的社区中来。

参考资料:

docs.opencv.org/3.4/d7/d8b/…

www.cnblogs.com/skyfsm/p/68…