微信二维码扫描功能移植到Android平台

微信二维码识别引擎移植到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问题~~~~~~~,哎,一个双休周末没了…

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值