笔者的团队最近接到了一个有关图像识别的需求,本来应该由后端团队提供能力,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本身由于对于笔者其实是个黑匣子,这也一定程度上导致了最后的服务可用性的问题的出现。不过虽然话需要怎么说,但是反过来,笔者也庆幸能生活于这个互联网时代,能够享受如此多的时代文明之光的馈赠,更深觉还有好多好多这样早已出现,却还不曾发光的事物等待我们去发现,去链接,去让那些埋藏于浮华深处的光芒,重新发出耀眼的光辉。

最新文章

  1. .NET面试题系列[7] - 委托与事件
  2. Python: open和codecs.open
  3. 3D几何变换
  4. Angularjs路由需要了解的那点事
  5. firefox中flash经常崩溃
  6. ajax 提交數據
  7. spring日记------部署环境、写demo
  8. java编程acm基础
  9. 联想Z470安装10.11懒人版成功!!特此分享!!
  10. Servlet &amp; JSP - Decorating Requests and Responses
  11. python: pandas模块
  12. Archlinux 的U盘自动装载(三)udevil
  13. JavaSE(六)包装类、基本类型和字符串之间的转换、==和equals的区别
  14. SVM分类器实现实例
  15. 【javascript】随机颜色
  16. MyBatis :Insert (返回主键、批量插入)
  17. 认识Nginx,理解原理和功能
  18. Spring整合jedis 集群模式
  19. XSS 跨站脚本攻击 的防御解决方案
  20. ASP.NET获取文件名,后缀名

热门文章

  1. 582. Kill Process
  2. 基于端口的信息探测-portscan-1.0
  3. java生成xml
  4. MySQL架构优化实战系列4:SQL优化步骤与常用管理命令
  5. PyMySQL - Python3 MySQL数据库连接 - 基本操作
  6. Django学习系列之captcha 验证码插件
  7. Boss OpenCart 商城自适应主题模板 ABC-0012-01
  8. Creo二次开发—内存处理
  9. COCOS学习笔记--即时动作ActionInstant
  10. C#项目的生成事件及批处理文件