Android进阶之路Android开发经验谈Android开发

媲美微信的二维码识别库

2019-07-19  本文已影响8人  森码

1. 为什么要做这个库?

相信大家在平常的生活中,如果遇到扫码的场景第一个想到的应该就是微信了,可以说微信使用二维码打开了移动互联网的另一扇大门,而微信在扫码的体验上也及其优秀,本该有一定要求的扫码过程,在经过微信的优化之后,让用户在使用的过程中拥有了一种『随意性』,像拍一张照片一样简单,像发一句消息随意,像摆弄一件玩具一样有趣。

有了这样的『标杆』存在,大家在潜意识里面好像都有了标准,你们的扫一扫为什么不好用?为什么要识别这么久?甚至我对准了也识别不出来?摆在我们面前的是各种用户的不满,解决这些问题就成了我们必须要面对的情况。

2. 选型

二维码处理,绕不开的就是ZXing和ZBar了,ZXing作为老牌的识别库已经"孵化"出了包括js、Python、C++、PHP等各个语言的lib,同时Android版本也一直在更新,但是ZBar作为C的处理者,上次的更新已经是7年前了。为了让二维码的识别尽量的快,并且对图像处理有更多的可能性,考量之后我们选用了更具活力的zxing-cpp,选用了它来作为我们的底层处理库。

3. 相机的处理

原始图像的获取至关重要,倘若这一步走不好,其他的处理再好也无尽于是,对于从来没有接触这一领域的自己来说,踏遍Android相机的坑不知要花多少时间,好在已经有优秀的开源库,这里特别感谢BGAQRCode-Android的开源库,操纵摄像头的一些重要功能,比如自动对焦、触摸对焦、放大缩小等都已经具备,自己也只是在其之上做了一些小改进,比如GroupView的改进、加入传感器对焦、线程池处理,有了这些之后,我们就可以开始处理数据了。

4. 相机数据处理

Android的相机获取到的数据并非我们平常认为的RGB数据,而是视频采集中的经常使用的NV21格式即YUV,所以在获取到这些数据之后是无法直接使用的。

  1. 格式转变
    要转格式首先我们先要了解NV21在内存中是什么样子的。
    YUV420
    不同于我们平常的图片格式,比如png的图片,图片由一个一个像素点构成,400 * 800的图片就有320000个像素,每一个像素对应一个ARGB,即4个字节,分别表示(透明度,红色色值,绿色色值,蓝色色值),就是我们平常见到的(0,255,255,255)一个像素的内存是连在一起的,但是YUV不同于我们『认知』上的格式,这3个数值分别代表的是(明亮度,色度,浓度),一个很有意思的知识是:YUV的发明是由于彩色电视与黑白电视的过渡时期
    YUV的组成
    有了一个大概的了解之后,我们就可以把摄像头的数据转化为我们需要的数据,其实只要根据公式来推倒就可以了,但是了解原理能让我们更好的理解。
    YUV->RGB
  2. 算法优化

    对于二维码来说它是一个黑白组成的点,并不需要色彩斑斓的图片,一张灰度图或许是更好的选择,一个YUV转化为RGB的算法要把所有数据循环,还有各种转换操作,无疑是一种浪费,所以一个更好的选择是直接转化为灰度图。 灰度图.jpg
   for (int i = top; i < height + top; ++i) {
            srcIndex += left;
            for (int j = left; j < left + width; ++j, ++desIndex, ++srcIndex) {
                p = data[srcIndex] & 0xFF;
                pixels[desIndex] = 0xff000000 | p << 16 | p << 8 | p;
            }
            srcIndex += margin;
        }
  1. 图片裁剪
    通常二维码识别的界面都有一个『框』,这个框可不是可有可无的,它不仅能告诉用户我们正在扫描,二维码应该放在这里面,在我们处理时更能成倍的提高效率,在测试的过程中,裁剪和没有经过裁剪的图片处理一般要相差4-5倍的时间,一张600 * 600的图片识别要50-80ms;而一张完整照片,比如1920 * 1080的图片,通常要经过200ms以上的时间处理,如果所有的图片都不经过截取,那么想要提升整体的识别效率是很困难的,加上我们上面的灰度转换,完全可以合二为一得到质量很高的原图像。


    截取图.jpg

    截取后的图像只有原图像的1/5,更利于我们去处理数据,至此我们的前期准备就已经做完了。

void ImageUtil::convertNV21ToGrayAndScale(int left, int top, int width, int height, int rowWidth,
                                          const jbyte *data, int *pixels) {
    int p;
    int desIndex = 0;
    int bottom = top + height;
    int right = left + width;
    int srcIndex = top * rowWidth;
    int marginRight = rowWidth - right;
    for (int i = top; i < bottom; ++i) {
        srcIndex += left;
        for (int j = left; j < right; ++j, ++desIndex, ++srcIndex) {
            p = data[srcIndex] & 0xFF;
            pixels[desIndex] = 0xff000000u | p << 16u | p << 8u | p;
        }
        srcIndex += marginRight;
    }
}
5. 二维码识别
  1. 解析二维码
    有了充足的准备,二维码的识别已经是水到渠成的事情了,根据转化好的数据,生成HybridBinarizer对象,通过MultiFormatReader即可解析,但是这个扫码的距离实在不能让人满意,我们常用的扫一扫通常都会有一个放大的操作,而这个操作是扫码优化中至关重要的一步。

  2. 放大优化
    想要进一步的优化我们就得更进一步的研究二维码了,二维码的生成细节和原理二维码(QR code)基本结构及生成原理有详细的解释,这里我们发现左上、左下、右上三个位置探测图形,在二维码的解码过程中,其实是分几个步骤的,首先就是要定位这个二维码确认其位置,然后才能取出里面的数据,而这个定位的点就是这三个。在距离二维码较远时,可能无法解析出完整的数据,但是却能定位这个二维码,通过定位点的信息,我们可以进行放大的操作,从而获取到更精确的图像数据,更有利于我们解析。

    二维码结构
    /**
     * 没有查询到二维码结果,但是能基本能定位到二维码,根据返回的数据集,检验是否要放大
     *
     * @param result 二维码定位信息
     */
    void tryZoom(BarcodeReader.Result result) {
        int len = 0;
        float[] points = result.getPoints();
        if (points.length > 3) {
            float point1X = points[0];
            float point1Y = points[1];
            float point2X = points[2];
            float point2Y = points[3];
            float xLen = Math.abs(point1X - point2X);
            float yLen = Math.abs(point1Y - point2Y);
            len = (int) Math.sqrt(xLen * xLen + yLen * yLen);
        }
        handleAutoZoom(len);
    }
  1. 与微信的对比
    微信的扫一扫可以说是秒级的处理,特别是在iOS的设备上,有时候我们都没对准二维码就能获取到数据,更不可思议的是它好像没有距离的限制。经过我们的优化之后,我们的二维码可以在50cm内解析出来,但是与微信想比还是差了太远,我们需要更好的处理图像数据,来定位二维码。
6. OpenCV
识别距离
二维码的识别中,距离 是一个非常关键的制约条件,通常在30cm-40cm内是一定可以识别出来的,但是超过这个距离获取到的图像就会比较模糊,如果摄像头的分辨率不高识别率也会下降,如果超过这个阈值,识别算法就只能定位数据而无法解析数据,比如B点,这里我们加入自动放大可以解决,但是超过这个距离呢?我们就需要手机移动了。如果有一种方案,可以像在B点时一样,虽然无法获取到数据但是可以得到二维码的位置、大小呢?要做到这个,OpenCV是一个不二之选。

说到图像处理,我们大致有两套方案,方案一:处理图像数据,获取图像轮廓,算法检测二维码位置。方案二:机器学习,直接定位二维码。

两者其实都是可行的,只是在难易度方面的差异,我们首先尝试了机器学习的方案,奈何自己学的还比较浅,收集到的样本数据也不够,训练出来的模型也不太理想,比如一个没有二维码的画面会检查出好几个,又比如有的时候又要离的特别近才能识别出来,这又违背了我们的本意。所以我选择了方案一,虽然听起来没那么高大上了,但是在实际的测试中也完全能达到预期水平。

当图像即无法解析出数据也无法定位到二维码时,我们采用OpenCV去处理图像。因为之前已经进行过灰度处理了,这里可以直接进行Canny化,然后执行findContours方法获取轮廓信息,之后过滤轮廓信息,判断点与点之间的距离,得到二维码的位置信息。(以上的过程看似简单,其实进行了很多尝试,包括二值化,毛边去除、调节亮度、对比度处理,直接获取点信息等等,这里感谢https://blog.csdn.net/jia20003/article/details/77348170
https://blog.csdn.net/zwx1995zwx/article/details/79171979
的图像过滤算法,作为一个图像处理的门外汉真的学到很多)

canny化之后的图像 识别二维码轮廓 定位二维码轮廓,红色框框是自动生成的

拿到这些信息之后,我们就可以遵循在B点时的处理逻辑,直接放大图像获取数据。(你可能会想为什么不直接截取图像,这样就不用费时费力再进行一轮识别,其实这里也想到过,但是得到的数据精度丢失实在太多,我尝试用微信去识别截取得到的二维码,微信也无法检测出来,这样的处理对于简单的二维码或许可行,但是对于稍微复杂的二维码或者我们所要解决的问题来说是远远不够的)

7. 成果

在加入OpenCV之后,我们的识别距离扩大了一倍,得到的效果比预期的还要好。
识别效果展示

8. 待完善的功能
  1. aar过大,因为有OpenCV的加入,aar文件有7.7M。
  2. 不支持生成二维码(将会在近期加入)。
  3. 扩展性、可定制性不够,这个可以慢慢加入。
  4. 有时候会放的太大,试试缩小功能。

注: 不同手机识别效率其实不尽相同,摄像头越好,识别效率越高。

源码地址
欢迎Star,欢迎Fork,欢迎和我一起开发。

9. 感谢

zxing-cpp
BGAQRCode-Android

在不到一个月的时间里完成了主要功能(主要是自己在业余时间完成的),感谢那些无私奉献的博主之余也感受到开源的便利和伟大,我能做的也只是用开源来回馈各位。最后以牛顿的一句话来结尾吧,“我之所以站得高,是因为我站在巨人的肩膀上。”

上一篇 下一篇

猜你喜欢

热点阅读