js image 获取rgb_OpenCV.js:在浏览器中实现计算机视觉任务

本文经授权,根据Intel软件工程师Dmitry Kurtaev的文章翻译整理。

原文地址 

https://habr.com/ru/company/intel/blog/437600/

英译文地址 

https://opencv.org/opencv4arts-draw-my-city-vincent/

OpenCV是一个有20年历史的开源计算机视觉库,而且一直在持续不断开发。20岁,正是探索世界奥妙的年龄。有没有什么基于OpenCV的项目可以让人们的生活更美好、让大家开心呢?在思考和探索OpenCV新模块时,我想到了可以用OpenCV创建一些具有强烈视觉效果的应用。看到这些应用,人们的反应首先是惊叹,然后才意识到“哦,这就是计算机视觉”。本文首先来做一个风格迁移的实验,然后介绍此处理过程的核心以及相对OpenCV来说还很新的OpenCV.js,即JavaScript的OpenCV。

c6288d76d476c43b299215b2ef371312.png

风格迁移

首先要抱歉的告诉并不看好深度学习的读者,深度神经网络将是本文的一个重点,因为它的确很有效。OpenCV并不提供训练神经网络的功能,但是可以通过OpenCV应用已经训练好的网络模型。这里以CycleGan为例。感谢作者提供的免费模型,让我们能将苹果的图片转换为橙子,马的图片转换为斑马,卫星图像转换为普通地图,冬天的照片转换为夏天。虽然训练是将冬天转换为夏天,但训练会产生两个方向的生成器,所以利用模型也可以把夏天的照片转换为冬天。所以这里要用CycleGan作为例子。这里我使用可以把照片转换为画家的作品的神经网络模型,也就是转换为梵高、莫奈、塞尚或者浮世绘风格的图画。所以需要四个网络模型。在训练模型的时候,并不是使用画家的某一幅作品,而是其大量的作品。这样训练得到的模型会按照画家的绘画风格进行风格迁移,而不是按照某幅作品形式进行迁移。 OpenCV.js

OpenCV是一个用C++实现的库,但其大部分函数都可以自动生成wrappers。目前OpenCV主线版本支持Python和Java的wrapper。此外,也有自定义的Go和PHP实现。如果你在实际应用使用其它语言wrapper,也欢迎告诉我。OpenCV.js是2017年谷歌编程之夏(Google Summer of Code)的一个项目。跟其它语言不同,OpenCV.js并不是对原始方法函数的封装,它是用Emscripten使用LLVM和Clang完全编译生成的。它可以将C或C++的程序转换成一个能在浏览器运行的.js文件。
例如

#include 

int main(int argc, char** argv) {

std::cout <"Hello, world!" <std::endl;

return 0;

}


编译成asm.js

emcc main.cpp -s WASM=0 -o main.js


然后在浏览器中打开

html>

<html>

<head>
  <script src="main.js" type="text/javascript">script>
head>

html>

1f09203b0c58574effaf44dacb586324.png

OpenCV.js可以用下面的方式与你的工程联系起来(nightly build)

<script src="https://docs.opencv.org/master/opencv.js" type="text/javascript">script>

另外还需要一个用JavaScript手动实现的读取图像的库

<script src="https://docs.opencv.org/master/utils.js" type="text/javascript">script>

上传图像

对于OpenCV.js,图像可以从canvas或者img元素中读入。在用户从硬盘选定图像并点击上传后,辅助函数addFileInputHandler可以自动将图像上传到指定的canvas元素。
var utils = new Utils('');
utils.addFileInputHandler('fileInput', 'canvasInput');

var img = cv.imread('canvasInput');

其中

<input type="file" id="fileInput" name="file" accept="image/*" />

<canvas id="canvasInput" >canvas>

需要指出的是,img是一个4通道的RGBA图像,与cv::imread默认的读入图像文件后产生一幅3通道的BGR图像是不同的。这点在导入其它语言的算法时要特别注意。

然后,只需调用imshow函数即可以显示图像。imshow的参数是指定的canvas(RGB或GRBA)。

cv.imshow("canvasOutput", img);

算法

整个处理过程其实就是神经网络的前向计算。我们不需要知道网络内部发生了什么,只需提供正确的输入,然后正确的解析预测(网络输出)即可。这个例子的输入是一个四维浮点矩阵,元素值范围为[-1,1]。这四个维度分别是:图片的数量、图像的通道、图像的高、图像的宽,用NCHW来表示。这个四维矩阵叫做blob,是一个二值量。预处理首先要将其转换成OpenCV格式的图像,即图像通道是交错的,元素值范围为[0,255]。后处理是预处理的反向过程,即将网络输出元素值范围为[-1,1]的NCHW blob转换到[0, 255]区间的无符号整型。

f19679a452b7cb99f40bd79bd96ddbcb.png

人眼看到的Nizhny Novgorod Kremlin的图片块

fe0a61fdca1ba1c938cce033f5e0f996.png

OpenCV中的交错表示

4e3ab0125ade9f25ed7cab571cea9020.png

按颜色平面的表示

所以,OpenCV.js读取和输出图片的整个过程为:
imread -> RGBA -> BGR [0, 255] -> NCHW [-1, 1] -> [network]
[network] -> NCHW [-1, 1] -> RGB [0, 255] -> imshow

    为什么不直接将交错的RGBA图像直接输入网络,然后返回交错的RGB图像呢?为什么还要进行额外的变换和归一化?这是因为神经网络要求输入数据有一定的分布。在这个例子中,训练模型时使用的是这种数据格式,所以在应用模型的时候也需要使用相同的输入。

实现

    这个例子使用的模型是一个二进制文件,所以需要先上传到本地文件系统。

var net;
var url = 'style_vangogh.t7';
utils.createFileFromUrl('style_vangogh.t7', url, () => {
  net = cv.readNet('style_vangogh.t7');
});

这里,url是此文件的完整链接。在这个例子中,我是把文件上传到当前HTML页面。当然也可以使用原始链接,只是下载的时间会长一点。

从canvas读入图像,然后将其从RGBA转换到BGR

var imgRGBA = cv.imread('canvasInput');
var imgBGR = new cv.Mat(imgRGBA.rows, imgRGBA.cols, cv.CV_8UC3);
cv.cvtColor(imgRGBA, imgBGR, cv.COLOR_RGBA2BGR);

创建4D blob。blobFromImage函数将图像类型转换为浮点型,并做归一化。然后进行网络的前向计算。

var blob = cv.blobFromImage(imgBGR, 1.0 / 127.5,  // multiplier
                            {width: imgBGR.cols, height: imgBGR.rows},  // dimensions
                            [127.5, 127.5, 127.5, 0]);  // subtraction of the average 
net.setInput(blob);
var out = net.forward();

模型的输出结果再转换到要求的图像类型,元素值范围[0,255]。

// Normalization of values from interval [-1, 1] to [0, 255]
var outNorm = new cv.Mat();
out.convertTo(outNorm, cv.CV_8U, 127.5, 127.5);

// Creation of an interleaved image from the planar blob
var outHeight = out.matSize[2];
var outWidth = out.matSize[3];
var planeSize = outHeight * outWidth;

var data = outNorm.data;
var b = cv.matFromArray(outHeight, outWidth, cv.CV_8UC1, data.slice(0, planeSize));
var g = cv.matFromArray(outHeight, outWidth, cv.CV_8UC1, data.slice(planeSize, 2 * planeSize));
var r = cv.matFromArray(outHeight, outWidth, cv.CV_8UC1, data.slice(2 * planeSize, 3 * planeSize));

var vec = new cv.MatVector();
vec.push_back(r);
vec.push_back(g);
vec.push_back(b);
var rgb = new cv.Mat();
cv.merge(vec, rgb);

// Result rendering
cv.imshow("canvasOutput", rgb);
目前,OpenCV.js还是一种半自动的模式,即不是所有的模块和方法都可以获得JavaScript相应签名。例如,对于dnn模块需要用下面的list来定义有效的函数:
dnn = {'dnn_Net': ['setInput', 'forward'],
       '': ['readNetFromCaffe', 'readNetFromTensorflow',
            'readNetFromTorch', 'readNetFromDarknet',
            'readNetFromONNX', 'readNet', 'blobFromImage']}

上面说到的对于输出的转换,即将blob分成3个通道然后再合并为一幅图像,其实可以用imagesFromBlob函数来实现。但是目前它还没有添加进上面的list,也许你可以来完成这个功能,成为你对OpenCV的第一个贡献?:)

结论

可以到我的GitHub( https://dkurtaev.github.io/opencv4arts)查看上面风格迁移的例子,你也可以测试代码。每次使用新的图像时,建议刷新一下页面,否则会影响后面的处理。另外,处理过程不是很快,最好改变一下图像的尺寸。在写这篇文章并选择一幅合适的示例图片时,我偶然发现了一张我朋友照的Nizhny Novgorod Kremlin的照片。我建议你在实验时也使用你喜欢的景点的照片,也许你还可以在评论里告诉我一些关于她的趣事。       

cac8c222cfffdbffb84eac61d631f6cc.png

OpenCV中国团队官方推荐和认证OpenCV&AI在线课程

长按右侧QR码进入课程533a4f687e1b8581b631e7a1858086d3.png

OpenCV中国团队于2019年9月由深圳市人工智能与机器人研究院支持成立,非营利目的,致力于OpenCV的开发、维护和推广工作。

获取OpenCV最新动态,长按91074a410199b60348a9f9de83af62c6.png关注我们

346bdacac6ff841b9129d2d2de42b5d6.png

98162e977df90fcef5b83d8973113dd1.png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值