笔者的团队最近接到了一个有关图像识别的需求,本来应该由后端团队提供能力,native提供容器,前端团队仅负责一些“外围的形式工作”,不过由于各种各样的原因,最后的结果变成了前端团队在原有工作基础上,承担了图像识别的能力,后端专注其他服务,于是一场图像识别的冒险就此开始。

  因为项目背景是图像识别,而笔者仅有一些本科时图形学的一些皮毛知识,自己实现想来基本不太可能了,于是一开始就准备伸出手去探索下有没有现实可用的既成方案。因为最开始的项目需求是文字识别,首先进入笔者视野的就是OCR(Optical Character Recognition),也就是光学字符识别,毕竟node具备完全的服务端能力,只需要找到库和调用库的nodejs包,实现就很简单了,不过简单查找了一下,笔者发现业界还是挺有这方面的探索的,首当其冲的就是google的开源项目tesseract-ocr,但是经过笔者的测试发现,tesseract对于印刷字的识别能力确实不错,但是笔者的项目更接近去年支付宝扫“福”字的场景,在自然光和手写字的各种干扰下,ocr整个一套方案的可行性就有待商榷了,而更让笔者吃惊的是,识别的耗时也是笔者的业务场景所不太能接受的,基于种种原因,笔者不得不放弃在OCR上的继续探索,转而寻找其他方案,笔者心想:既然识别文字做不到,那么如果是把文字抽象为图形,只是和图形做比较呢?抱着这样的思想,笔者开始将目光转向一些图片相似度比较的库,此时opencv就进入了笔者的视野,也适逢opencv在人脸识别上备受青睐,也坚定了笔者使用opencv这一方案的信心。

  方向定了之后,实施起来就快了。本应该是如此,但是笔者虽然找到了opencv binding nodejs的包,不过这个包的作者写的readme适合并不是面向笔者这个场景的,里面的api和demo也倾向于在表达opencv在人脸识别上的能力,而不是笔者所需要的图片相似度比较的能力,经过一些抹黑的探索之后,笔者在node-opencv库里找到了一个由作者实现了但并没有记录在文档中的api——ImageSimilarity,看到这个名字的时候,笔者好似终于在茫茫的C++黑夜里找到了一丝光明一般,果不其然,这个api果然是进行图片的相似度比较的,最终代码很简单:

var similarityResult = yield new Promise((resolve, reject) => {
opencv.ImageSimilarity(matrixSource, matrixTarget, function(err, dissimilarity){
resolve({dissimilarity:dissimilarity});
});
});

  经过一些简单的nodejs webservice开发之后,整个业务需求的服务就告一段落了,不过正当笔者准备测试上线的时候,却又迎来了新的问题。笔者本地使用的是mac os作为开发环境,而服务器使用的都是linux centos的操作系统,在安装opencv上也有很多不同的地方,而且还需要考虑到上线后服务器扩容的问题,在线上服务器上一台台安装环境笔者心想可不是件容易事,经过自己的整理和运维同事的沟通,最后通过rpm包的方式解决了该问题。

  想想虽然一波三折,不过事情总算是做完了的时候,笔者突然回想起了之前ocr性能堪忧的问题,于是多了个心眼,想压测下opencv的处理性能,毕竟就笔者所了解的图形学的知识来说,图形处理其实是很消耗cpu的,这和笔者之前单纯使用nodejs作为restful的场景毕竟不同,虽然本地有100ms以下的不错表现(虽然和正常的node service差距已经很大),但考虑到实际还有目标图片传输、读写、比较过程,于是笔者拿出jmeter进行了一次压力测试,结果果然令人大吃一惊:并发的请求数稍大就直接占满了CPU,而笔者所面对的业务场景的目标DAU有1200w,这种性能肯定是撑不住的!

  怎么办!即使有能力进行图片识别,但是图片识别的并发量不足依然不能满足笔者的业务需求,经过笔者的一些观察发现,最核心的耗时发生在opencv的ImageSimilarity这个方法的执行上,但是这个api对于笔者而言几乎接近于黑盒,笔者只是善意的第三调用方,而且node-opencv也只是作为对c++底层库的中间层,底层实现完全依赖c++,对笔者而言,基本短时间内就不存在了优化的余地,于是笔者只能从其他方面曲线救国了:首先,为了增加硬实力,笔者不得不才用扩容的方式,从物理上提高图片识别服务的并发处理能力;然后,因为物理的扩容并不能够解决高峰期瞬时流量过大的问题,笔者不得不采取“非常手段”,保证服务不至于崩溃,于是笔者做好容灾预案了。不过笔者还是纠结于使用怎样的容灾实现比较好:要么是显示每秒的总请求数,将会导致过载的请求直接降级处理,要么就是实施监控cpu的使用率,保证cpu正常的情况下做降级处理。两种方案取舍的关键在于哪一种能够真正反应服务器的负载状况,基于种种考虑,笔者最终选择了数量的方案(由于nodejs获取的cpu数据总是滞后,当然这只是笔者了解的情况)。最终代码如下:

    let processResult = yield new Promise((resolve, reject) => {
limiter.removeTokens(1, (err, remainingRequests) => {
if (remainingRequests <= 0) {
resolve(co(disasterRecoveryBranch.bind(this)));
} else {
resolve(co(normalBranch.bind(this)));
}
})
}); 

  到此,整个需求才算是真正告一段落。回过头来我们重新review下整个业务流程:
1、native 上传图片,调用node服务
2、nodejs 接受请求
3、将各种参数从请求体中读出(当然包括上传的图片)
4、将上传图片转换为待比较的格式,并且取出比较的源图片(当然你也可以在一开始就把它们取出来放内存里,不过由于笔者的源图片可以动态变更,所以这里的额外io并没有省)
5、调用ImageSimilarity方法,比较目标图片和源图片(容灾情况则直接会执行降级方案,然后继续往下进行)
6、通过得出的比较值,调用对应的后端服务
7、拿到后端的响应结果,经过封装后回传回客户端

  其实其中还有不少可以优化的点,最重要的是,opencv本身由于对于笔者其实是个黑匣子,这也一定程度上导致了最后的服务可用性的问题的出现。不过虽然话需要怎么说,但是反过来,笔者也庆幸能生活于这个互联网时代,能够享受如此多的时代文明之光的馈赠,更深觉还有好多好多这样早已出现,却还不曾发光的事物等待我们去发现,去链接,去让那些埋藏于浮华深处的光芒,重新发出耀眼的光辉。

【Nodejs】记一次图像识别的冒险的更多相关文章

  1. nodejs 记入

    1. vs2015 使用最新的 nodejs refer : http://josharepoint.com/2016/05/04/how-to-configure-visual-studio-201 ...

  2. [置顶] 记最近一次Nodejs全栈开发经历

    背景: 前段时间大部门下新成立了一个推广百度OCR.文字识别.图像识别等科技能力在金融领域应用的子部门.因为部门刚成立,基础设施和人力都是欠缺的.当时分到我们部门的任务是抽调一个人做新部门主站前端开发 ...

  3. linux系统下nodejs安装过程随记

    首先下载适合的版本.这里我使用的是node v.10.36 先介绍编译安装的详细过程. 下载该版本: wget http://nodejs.org/dist/v0.10.36/node-v0.10.3 ...

  4. 一次使用NodeJS实现网页爬虫记

    前言 几个月之前,有同事找我要PHP CI框架写的OA系统.他跟我说,他需要学习PHP CI框架,我建议他学习大牛写的国产优秀框架QeePHP. 我上QeePHP官网,发现官方网站打不开了,GOOGL ...

  5. [记]Windows 系统下设置Nodejs NPM全局路径

    Windows下的Nodejs npm路径是appdata,担心安装的node_modules越来越多,导致C盘满,所以参考别人的博文,将node_modules安装的默认目录修改一下. 参考Wind ...

  6. nodejs随记04

    aes加密 资料 简介; 例子; process 改变工作目录: process.chdir(path); 路径计算 例子 获取调用执行所在文件地址 function getCaller() { tr ...

  7. nodejs随记03

    文件操作 文件系统的操作 fs.readFile(filename, [options], callback) fs.writeFile(filename, data, [options], call ...

  8. nodejs随记02

    Basic认证 检查报文头中Authorization字段,由认证方式和加密值构成: basic认证中,加密值为username:password,然后进行Base64编码构成; 获取username ...

  9. nodejs随记01

    EventEmitter var stream = require('stream'); var Readable = stream.Readable; //写入类(http-req就是),初始化时会 ...

随机推荐

  1. COJ 1163 乘法逆元的求解

    乘法逆元就是求一个 a/b = c(mod m)在已知a%m , b%m 的条件下 求c的解 #include <cstdio> #include <cstring> usin ...

  2. 无权二分图最大匹配 HDU2063 匈牙利算法 || Hopcroft-Karp

    参考两篇比较好的博客 http://www.renfei.org/blog/bipartite-matching.html http://blog.csdn.net/thundermrbird/art ...

  3. 树形DP 树的最小支配集,最小点覆盖与最大独立集

    最小支配集: 从V中选取尽量少的点组成一个集合,让V中剩余的点都与取出来的点有边相连. (点) 最小点覆盖: 从V中选取尽量少的点组成一个集合V1,让所有边(u,v)中要么u属于V1,要么v属于V1 ...

  4. UVA 129_ Krypton Factor

    题意: 一个字符串含有两个相邻的重复的子串,则称这个串为容易的串,其他为困难的串,对于给定n,l,求出由前l个字符组成的字典序第n小的困难的串. 分析: 按字典序在字符串末尾增加新的字符,并从当前字符 ...

  5. CSS+Jquery实现QQ分组列表

    实现效果图如下: 说明: 1.css隐藏分组下的好友内容: 2.Jquery实现点击分组项事件,实现好友内容的显示和隐藏: 3.样式1,可展开多个分组:样式2,只能有一个分组展开: 源码: <! ...

  6. WCF - 自定义绑定

    自定义绑定 当系统提供的某个绑定不符合服务的要求时,可使用 CustomBinding 类.所有绑定都是从绑定元素的有序集构造而来的.自定义绑定可以从一组系统提供的绑定元素生成,也可以包含用户定义的自 ...

  7. 关于ping以及TTL的分析

    首先介绍一下ping这个工具 ping [目标] 的意思就是向目标发送几个数据包,之后假设目标接受到一个数据包.那么目标就会向发送ping的主机返回一个数据包 比方上图.我ping了百度的server ...

  8. String类的四个默认成员函数

    优化版的拷贝构造函数,先创建一个暂时实例tmp,接着把tmp._ptr和this->_ptr交换,因为tmp是一个局部变量.程序执行到该函数作用域外,就会自己主动调用析构函数.释放tmp._pt ...

  9. nginx进程和实时控制

    原文地址:http://nginx.com/resources/admin-guide/processes-and-runtime-control/ Processes and Runtime Con ...

  10. Flex 页面启动事件

    事件启动顺序 容器Preinitialize=>子组件preinitialize=>子组件initialize=>childAdd=>initialize =>子组件cr ...