taoCMS是基于php+sqlite/mysql的国内最小(100Kb左右)的功能完善的CMS管理系统

Android 接入微信扫码库,实现堪比微信的扫码效果

2022-06-07

微信二维码识别引擎移植到Android平台

对比市场上的扫码功能,微信扫描码功能无疑是最强的,现在腾讯已经将其开源并加入了opencv-contrib,成为opencv的一部分,本文主要记录讲解编译移植到Android上平台的过程,并奉上最终的结果,帮助有需要的兄弟伙。

本文Demo github地址
https://github.com/woshiwzy/opencv_wechat_qr.git

如果要解决Opencv摄像头横屏,竖屏,全屏,横竖屏自动切换,相机无法打开问题
文章地址: https://blog.csdn.net/wang382758656/article/details/106686455
代码地址:https://gitee.com/wangzy2018/Opencv42_study_demo.git

1.前言
         最近在做支付宝相关的业务,有些场景下生成的二维码内容超级长,生产的二维码点非常小,导致在某些低端设备上的低分辨率摄像头难以识别或者错误识。在做扫码功能的时候我理所当然的选择了Zxing作为解决方案,可客户的设备摄像头分辨率太低,导致产品验收困难。无奈之下又研究了zbar,发现和zxing的效果差不多,甚至更差(也可能是我找的包不对)但充其量也就是速度上的提升。好在同事发现微信的扫码已经开源并加入到Opencv contrib模块中,经过初步研究发现效果的确比原生的zxing 效果好不少。相比之下微信识别引擎能够更快,更准,弱光照环境也很不错,据说腾讯在zxing的基础上结合AI神经网络所以看起来更屌了。这里要给企鹅点赞(狗血的事,这波操作之后不能像zxing一样,同时扫二维码和条码,只能扫二维码了)。

2.使用篇
       因为工作安排的原因,我先让同事折腾了一番,无奈他的电脑是windows,在搭建环境和编译过程中痛苦的尝试了无数次,折腾几天后放弃了。好在我的电脑是Mac,整个过程显得顺利很多。 在我动手之前我研究了百度扫码,腾讯智能扫码服务,zxing,zbar。百度扫码我并没有看到效果的提升,而且百度的扫码并不专注于二维码,还需要收费,在百度云后台一顿操作之后放弃了。腾讯智能扫码服务集成了本文要说的开源技术,在扫码效果上肯定是不错的,不过智能扫码服务居然是收费SDK,而且10万人民币一个包… 另外两个则是一开始就已经尝试过了,短码内容没问题,长码,弱光照环境铁定吃瘪。经过几次尝试,在我的Mac上编译成功,先记录分享给需要的人……

3.环境,工具准备篇

  • 系统:Macos 11.2.2,
  • cmake 3.12.2,
  • java1.8,
  • apache-ant-1.9.15,
  • make 3.81,
  • opencv4.5.2和对应opencv_contrib,
  • Android SDK和ndk-bundle(这2个东西不要太旧,看起来影响不大)
  • Android Studio 4.1.1
    保持网络畅通,最好有梯子,编译过程中,会下载一些额外的文件,如果下载失败,可能会产生错误和警告(不过貌似对编译qr识别模块没啥影响,主要是其他模块的模型文件)

4.过程
      下载源码(master分支)
openCV源码:https://github.com/opencv/opencv
opencv_contrib源码:https://github.com/opencv/opencv_contrib

创建opencv_build文件夹,如下图(opencv文件夹中是opencv源码,opencv_contrib中是opencv_contrib源码,opencv_build目录是创建的空文件夹,备用)

在这里插入图片描述
4.1.打开Cmake

  • 指定source目录,就是刚刚的下载的opencv源码目录
  • 指定build目录,就是刚刚创建opencv_build目录

两个目录如下图设置
在这里插入图片描述

添加Entry
在这里插入图片描述

4.2.点击Add Entry添加

  • ANDROID_NDK 类型为PATH,填入你的NDK路径(最好填写sdk自带的ndk-bundle,免得报稀奇古怪错)
  • ANDROID_SDK PATH,填入你的SDK路径
    //我试图一次性把所有的平台的so都编译出来,但是配置的时候会报错,后来查了资料每次都只能填一个,所以各个平台要分别编译
  • ANDROID_ABI STRING,设置平台,不填默认为 armeabi-v7a
  • ANDROID_NATIVE_API_LEVEL STRING,默认API为21
  • ANT_EXECUTABLE PATH,填入ANT路径下的bin(用于java 封装,便于 AS 导入module)
  • ANDROID_STL STRING,根据需求写入c++_static或c++_shared(本次操作填入c++_shared)*

配置好后像下面这个而样子
在这里插入图片描述
4.3.配置toolchain

      点击Configure,选择Specify toolchain file for cross-compiling,点击Continue,选择对应NDK目录下的toolchain路径,点击Done。
在这里插入图片描述
在这里插入图片描述
       点击Done之后,就开始配置了,如果各项参数没有错误,结果大概是这样
在这里插入图片描述
4.4.添加opencv_contrib模块并调整参数

注意 OPENCV_EXTRA_MODULES_PATH参数和值,如果已经存在(先搜索一下)就填入值,如果不存在就添加这个参数和值,在这一步骤很难容易出问题,请make前检查一下!!!

  • OPENCV_EXTRA_MODULES_PATH,选择opencv_contrib/modules路径

在这里插入图片描述
下面2个参数直接在搜索框中搜索WITH_OPENCL出来后,勾选上就好

  • WITH_OPENCL=ON,添加移动端的并行架构支持
  • WITH_OPENCL_SVM =ON,开启共享虚拟内存

在这里插入图片描述

4.5.再次执行Configure和Generate
      先执行Configure,执行完之后,再点Generate
在这里插入图片描述
      每次都要等进度条执行完成,不要着急点下一步,中间可能会下载一些文件,甚至报错,我看了一下,是非QR模块用到的一些数据模型文件下载出错,对编译过程没有什么影响。

4.6.执行make(这里没有必要执行make install,这里没有必要执行make install,make 之前切记要检查配置的参数是否正确,有时候可能会重复或者设置的参数为空,估计是cmake bug)

      等上面2步都执行完成之后,opencv_build目录就生成了make需要的所有配置文件
接下来在shell下面进入到opencv_build目录,执行make命令后开始漫长的等待,我的电脑是Macbook Pro 15寸的老款,编译了大概1个小时左右,中间风扇转得飞起,中间可能会有一些警告,可以忽略。

4.7.注意.当编译执行到99%的时候可能会报下面的错误
在这里插入图片描述
    不要慌,其实到此刻需要的SO文件已经编译出来了,就在opencv_build/jni目录下,报错的原因是15-puzzle 项目sdk路径无法找到,不解决也可以(把opencv_android 导入到项目中直接用),要解决也很简单,你直接用Android Studio打开opencv_build/opencv_android(路径如下图)这个目录,Android Studio会自动配置sdk路径,导入之后Android Studio 自动操作之后,不再报错,现在关闭Android Studio之后,再次在opencv_build目录下执行make命令,这一次会执行得很快,并且能走到100%。
在这里插入图片描述
走到100%之后,Android Studio导入opencv_android 项目,就可以正常运行各个sample了,如果有错误得具体解决,我这里导入项目之后没有报错。到了这一步即便是报错了,解决起来也不麻烦了。

5.测试微信扫码引擎API
   初始化扫码引擎

public WeChatQRCode(String detector_prototxt_path, String detector_caffe_model_path, String super_resolution_prototxt_path, String super_resolution_caffe_model_path)

四个参数分别对应:detect.prototxt,detect.caffemodel,sr.prototxt,sr.caffemodel四个文件的路径

初始化之后就可以调用detectAndDecode方法进行检测识别

  • 方法1. points 参数可以保存二维码图形的位置,识别完成之后如果有需要可以绘制出一个方框
weChatQRCode.detectAndDecode(inputFrame.gray(), points);
  • 方法2. //如果不需要绘制方框,可以调用这个重载函数
weChatQRCode.detectAndDecode(mat)

6.测试效果,可以看到不能识别条码了
   横屏效果
在这里插入图片描述

  竖屏效果
在这里插入图片描述
   全屏效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.解决各种坑

  • 1.发现有些设备无法打开相机(而且已经赋予了打开摄像头权限)

    打开应用(初始化Opencv引擎)直接崩溃,而且看不到日志,好在我之前有玩opencv的经验,大概率的原因是Opencv的一个bug,主要原因是根据JavaCameraView大小计算相机分辨率的时候计算错误,算出一个相机不支持的分辨率导致打开相机失败。

代码所在路径

org.opencv.android.CameraBridgeViewBase#calculateCameraFrameSize

这个方法完整代码如下:

protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
    
    int calcWidth = 0;
    int calcHeight = 0;

    int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
    int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;

    for (Object size : supportedSizes) {
    
        int width = accessor.getWidth(size);
        int height = accessor.getHeight(size);
        Log.d(TAG, "trying size: " + width + "x" + height);

        if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
    
            if (width >= calcWidth && height >= calcHeight) {
    
                calcWidth = (int) width;
                calcHeight = (int) height;
            }
        }
    }
    if ((calcWidth == 0 || calcHeight == 0) && supportedSizes.size() > 0)
    {
    
        Log.i(TAG, "fallback to the first frame size");
        Object size = supportedSizes.get(0);
        calcWidth = accessor.getWidth(size);
        calcHeight = accessor.getHeight(size);
    }

    Log.e(TAG,"计算得到的摄像头分辨率:"+calcWidth+"x"+calcHeight);
    return new Size(calcWidth, calcHeight); //return new Size(1920,1080)
}

测试办法是在calculateCameraFrameSize return这里直接返回一个固定值(比如直接返回new Size(1920,1080)),看看相机能不能打开,多试验几个分辨率。大多数相机打不开的问题都是这个原因,确定是这个原因后,在supportedSizes 选择一个相对较小的分辨率返回(其实像我做的项目用于单一设备,可以写死一个值-:)。

  • 2.有些设备摄像头预览不能全屏

    (关于这个问题我早先写的一个博客,Demo中有详细的解决办法,还可以控制摄像头前后切换,横竖屏自动适应,

文章链接:
https://blog.csdn.net/wang382758656/article/details/106686455

代码地址
https://gitee.com/wangzy2018/Opencv42_study_demo.git

因为计算出来的相机分辨率不一定是手机屏幕分辨率,所有可能并没有办法铺满全屏

解决办法1.最简单的方式,修改CameraBridgeViewBase的mScale属性,

org.opencv.android.CameraBridgeViewBase#mScale

这个值是缩放最终绘制在javaCamera的bitmap大小的,(但是这里有个奇葩的大坑,mScale计算的方式有问题,我在这个文章里有说)

https://blog.csdn.net/wang382758656/article/details/106686455

需要在绘制之前重新调整mscale计算方式,如下图
在这里插入图片描述
      这个代码已经在本Demo中加上去了的,下载下来就可以看到,一般情况下你直接把mscale设置2(不行的话就加大),就铺满全屏了(实际上是绘制超过屏幕边界了,除非你想手动裁剪成和屏幕一样大的)

  • 解决办法2.用自己的ImageView显示预览图,就可以很方便的控制显示大小了(简单粗暴)

    第1步:
    从下图412行以下全部注释掉
    类路径:org.opencv.android.CameraBridgeViewBase#deliverAndDrawFrame
    Opencv自带的CameraView 就不会绘制了,但是这个VIew不要设置成InVisible或者Gone,不然拿不到预览数据
    在这里插入图片描述
    第2步:
    在你实现了CvCameraViewListener2的地方(一般是你承载JavaCameraView的Activity)onCameraFrame的方法里,把这个inputFrame的rgba()方法返回的Mat转换成Bitmap(用Utils.matToBitmap方法),然后显示到ImageVIew上去,这个ImageView盖住Opencv自带的那个JavaCameraView。然后自己根据实际情况处理显示问题,到后面就和Opencv无关了,简单粗暴,适合对Opencv了解不多的同学。

  • 3.关于竖屏预览时图像旋转90度的问题

    这个需要旋转270度给他转回去就可以了(因为二维码自带定位功能,理论上不用转也识别得出来,但是看着图像位置不对特难受),详细见demo中的代码如下
    在这里插入图片描述

  • 4.性能问题和扫描框

  • 1.使用较小的摄像头分辨率能提升速度
    (org.opencv.android.CameraBridgeViewBase#calculateCameraFrameSize 这个方法,返回一个低分辨率就行,当然还有别的办法)

  • 2.裁剪后再进行识别

    可以在扫描页面上添加一个扫描框,然后在detectAndDecode方法调用前从预览图中扣除框中的内容传入识别函数,这样就能极大的减小图片大小,大幅提升速度(一般情况下用不着),可以画一个假框放到JavaCameraView上引导用户对准即可。

源码地址:https://github.com/woshiwzy/opencv_wechat_qr.git 欢迎fork star

  看到隔壁的程序员写博客把微信二维码贴在后面据说能被大佬请喝咖啡,我也试试看,顺便大家可以再次体会一下微信的扫码功能哈哈~~~~~

在这里插入图片描述

同时欢迎讨论Opencv Android问题~~~~~~~,哎,一个双休周末没了…

类别:未分组 | 阅读:34837 | 评论:0 | 标签:

想收藏或者和大家分享这篇好文章→

公告

taoCMS发布taoCMS 3.0.2(最后更新21年03月15日),请大家速速升级,欢迎大家试用和提出您宝贵的意见建议。

捐助与联系

☟请使用新浪微博联系我☟

☟在github上follow我☟

标签云