zoukankan      html  css  js  c++  java
  • 【Nodejs】记一次图像识别的冒险

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

  • 相关阅读:
    【C++】资源管理
    【Shell脚本】逐行处理文本文件
    【算法题】rand5()产生rand7()
    【Shell脚本】字符串处理
    Apple iOS产品硬件参数. 不及格的程序员
    与iPhone的差距! 不及格的程序员
    iPhone游戏 Mr.Karoshi"过劳死"通关. 不及格的程序员
    XCode V4 发布了, 苹果的却是个变态. 不及格的程序员
    何时readonly 字段不是 readonly 的?结果出呼你想象!!! 不及格的程序员
    object file format unrecognized, invalid, or unsuitable Command 不及格的程序员
  • 原文地址:https://www.cnblogs.com/mfoonirlee/p/6869130.html
Copyright © 2011-2022 走看看