笔者的团队最近接到了一个有关图像识别的需求,本来应该由后端团队提供能力,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. noip模拟赛 区间

    分析:要遍历所有的区间,肯定是枚举左端点,然后再枚举右端点.关键是怎么高效地求区间&,|,一般而言是用前缀和的,但是&,|不满足区间可减性,所以可以考虑线段树?这道题不带修改操作,用线 ...

  2. hdu 1698区间延迟更新

    #include<stdio.h> #define N 100100 struct node { int x,y,yanchi; }a[N*4];//注意数组范围 void build(i ...

  3. Spring boot data JPA数据库映射关系 : @OneToOne,@OneToMany,@ManyToMany

    问题描述 在利用Spring boot data JPA进行表设计的时候,表对象之间经常存在各种映射关系,如何正确将理解的映射关系转化为代码中的映射关系是关键之处. 解决办法 概念理解 举例:在公司的 ...

  4. TCP/IP学习笔记(3)----IP,ARP,RARP协议

    把这三个协议放到一起学习是因为这三个协议处于同一层(网络层协议),ARP协议用来找到目标主机的Ethernet网卡Mac地址,IP则承载要发送的消息.数据链路层可以从ARP得到数据的传送信息,而从IP ...

  5. 在GNS3下使用Cisco SDM 的教程

    安装步骤: 1..先安装jre-6u17-windows-i586se (最新版的)如图: 点击安装,直到安装完成. © 2.安装SDM2.5中文版SDM-V25 如图 : 出现欢迎安装向导,点击下一 ...

  6. [GraphQL] Query Local and Remote Data in Apollo Link State

    In this lesson, you will learn how to query local and remote data in Apollo Link State in the same c ...

  7. 爸爸和儿子的故事带你理解java线程

    今天回想线程方面的知识,发现一个非常有意思的小程序.是用来说明多线程的以下贴出来分享下,对刚開始学习的人理解线程有非常大的帮助 爸爸和儿子的故事 <span style="font-f ...

  8. JavaSE入门学习5:Java基础语法之keyword,标识符,凝视,常量和变量

    一keyword keyword概述:Java语言中有一些具有特殊用途的词被称为keyword.keyword对Java的编译器有着特殊的意义.在程 序中应用时一定要谨慎. keyword特点:组成k ...

  9. form 表单序列化 serialize

    在开发中有时需要在js中提交form表单数据,就需要将form表单进行序列化. jquery提供的serialize方法能够实现. $("#searchForm").seriali ...

  10. Linux下,PHP的SESSION不起作用的问题

    改动SESSION目录的权限就能够了. 先找到SESSION目录, 然后 chmod -R 777 /var/lib/php/session 假设没有此目录,则新建此目录 mkdir -R 777 / ...