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

对于Android的扫码库,我们平时都会使用ZXing或者ZBar来实现。

但是实际情况是,对于一些环境恶劣的情况下,比如 眩光、昏暗、有污渍等情况下,很难被识别。

即使是在普通情况下,扫码的识别速度、识别率依旧不甚满意的。

为了满足业务需要,故我们需要找到更好的扫码库。

微信开源的扫码库,就是其中一个不错的选择。

微信的扫码库,基于开源引擎ZXing,结合计算机视觉和深度学习技术,深度优化改造的高性能扫码库。

拥有 基于CNN的二维码检测和增强,更鲁棒的定位点检测,更详细的特性可以看基于CNN的微信二维码引擎OpenCV开源!

接入微信扫码库

接入微信扫码库,需要接入OpenCV库,接入OpenCV库有以下几种方式

  • 接入OpenCV的Java SDK包,这样可以直接在Java调用OpenCV的大部分方法,这种方法最简单
  • 使用OpenCV sdk 提供的C++头文件与.so动态库、.a静态库,自己封装jni,这样可以100%使用openCV的接口
  • 通过openCV的源码,重新编译成Android sdk库,这样能只编译生成自己需要的功能,文件比较小,缺点是编译环境啥的比较难搞。

关于OpenCV不同接入方式,详情请看 Android 接入 OpenCV库的三种方式

这里,我直接用方法一 + 方法二的形式,首先我们需要下载OpenCV的SDK包,附上下载地址
Android 接入微信扫码库,实现堪比微信的扫码效果_第1张图片

这里选择的是OpenCV 4.5.2版本,选择android进行下载

下载完成解压后,我们得到了如下所示的文件
Android 接入微信扫码库,实现堪比微信的扫码效果_第2张图片

这里的sdk文件夹,就是我们需要的OpenCV的Java SDK包了。

接着,我们新建一个项目,选择Native C++
Android 接入微信扫码库,实现堪比微信的扫码效果_第3张图片

Android 接入微信扫码库,实现堪比微信的扫码效果_第4张图片

接着,我们导入这个Java SDK

Android 接入微信扫码库,实现堪比微信的扫码效果_第5张图片

接着,在app的gradle中,依赖这个library

implementation project(':sdk')

新建一个WeChatQRCode的扫码类,这里有三个jni接口

  • WeChatQrCode : 初始化
  • detectAndDecode : 发现并解析
  • delete : 销毁
class WeChatQRCode {
    
    val nativeObjAddr: Long

    private constructor(addr: Long) {
        nativeObjAddr = addr
    }

    constructor(
        detector_prototxt_path: String,
        detector_caffe_model_path: String,
        super_resolution_prototxt_path: String,
        super_resolution_caffe_model_path: String
    ) {
        nativeObjAddr = WeChatQRCode(
            detector_prototxt_path,
            detector_caffe_model_path,
            super_resolution_prototxt_path,
            super_resolution_caffe_model_path
        )
    }

    fun detectAndDecode(img: Mat, points: List<Mat>): List<String> {
        val points_mat = Mat()
        val retVal = detectAndDecode(
            nativeObjAddr, img.nativeObj, points_mat.nativeObjAddr
        )
        Converters.Mat_to_vector_Mat(points_mat, points)
        points_mat.release()
        return retVal
    }

    @Throws(Throwable::class)
    protected fun finalize() {
        delete(nativeObjAddr)
    }

    external fun WeChatQRCode(
        detector_prototxt_path: String,
        detector_caffe_model_path: String,
        super_resolution_prototxt_path: String,
        super_resolution_caffe_model_path: String
    ): Long


    external fun detectAndDecode(
        nativeObj: Long,
        img_nativeObj: Long,
        points_mat_nativeObj: Long
    ): List<String>


    external fun delete(nativeObj: Long)

}

在app模块的cpp文件夹中,添加头文件

  • 复制OpenCV-android-sdk\sdk\native\jni\include下的opencv2文件夹到cpp文件夹下
  • 复制 wechat_qrcode.hpp到cpp/opencv2文件夹下
  • 复制opencv_contrib/modules/wechat_qrcode/src/下的文件到cpp目录下

同时,我们在native-lib.cpp中,实现jni方法

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define CONSTRUCTOR(ENV, CLS) ENV->GetMethodID(CLS, "", "(I)V")
#define ARRAYLIST(ENV) static_cast(ENV->NewGlobalRef(ENV->FindClass("java/util/ArrayList")))
#define LIST_ADD(ENV, LIST) ENV->GetMethodID(LIST, "add", "(Ljava/lang/Object;)Z")

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "NativeLib", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "NativeLib", __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO  , "NativeLib", __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN  , "NativeLib", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , "NativeLib", __VA_ARGS__)


using namespace std;
using namespace cv;


/// throw java exception
#undef throwJavaException
#define throwJavaException throwJavaException_wechat_qrcode

static void throwJavaException(JNIEnv *env, const std::exception *e, const char *method) {
    std::string what = "unknown exception";
    jclass je = 0;

    if (e) {
        std::string exception_type = "std::exception";

        if (dynamic_cast(e)) {
            exception_type = "cv::Exception";
            je = env->FindClass("org/opencv/core/CvException");
        }

        what = exception_type + ": " + e->what();
    }

    if (!je) je = env->FindClass("java/lang/Exception");
    env->ThrowNew(je, what.c_str());

    LOGE("%s caught %s", method, what.c_str());
    (void) method;        // avoid "unused" warning
}


void vector_Mat_to_Mat(std::vector &v_mat, cv::Mat &mat) {
    int count = (int) v_mat.size();
    mat.create(count, 1, CV_32SC2);
    for (int i = 0; i < count; i++) {
        long long addr = (long long) new Mat(v_mat[i]);
        mat.at >(i, 0) = Vec(addr >> 32, addr & 0xffffffff);
    }
}


jobject vector_string_to_List(JNIEnv *env, std::vector &vs) {

    static jclass juArrayList = ARRAYLIST(env);
    static jmethodID m_create = CONSTRUCTOR(env, juArrayList);
    jmethodID m_add = LIST_ADD(env, juArrayList);

    jobject result = env->NewObject(juArrayList, m_create, vs.size());
    for (std::vector::iterator it = vs.begin(); it != vs.end(); ++it) {
        jstring element = env->NewStringUTF((*it).c_str());
        env->CallBooleanMethod(result, m_add, element);
        env->DeleteLocalRef(element);
    }
    return result;
}

extern "C"
JNIEXPORT jlong JNICALL
Java_com_heiko_opencvwxtest_WeChatQRCode_WeChatQRCode(JNIEnv *env, jobject thiz,
                                                      jstring detector_prototxt_path,
                                                      jstring detector_caffe_model_path,
                                                      jstring super_resolution_prototxt_path,
                                                      jstring super_resolution_caffe_model_path) {
    using namespace cv::wechat_qrcode;
    static const char method_name[] = "WeChatQRCode_WeChatQRCode";
    try {
        LOGD("%s", method_name);
        const char *utf_detector_prototxt_path = env->GetStringUTFChars(detector_prototxt_path, 0);
        std::string n_detector_prototxt_path(
                utf_detector_prototxt_path ? utf_detector_prototxt_path : "");
        env->ReleaseStringUTFChars(detector_prototxt_path, utf_detector_prototxt_path);
        const char *utf_detector_caffe_model_path = env->GetStringUTFChars(
                detector_caffe_model_path, 0);
        std::string n_detector_caffe_model_path(
                utf_detector_caffe_model_path ? utf_detector_caffe_model_path : "");
        env->ReleaseStringUTFChars(detector_caffe_model_path, utf_detector_caffe_model_path);
        const char *utf_super_resolution_prototxt_path = env->GetStringUTFChars(
                super_resolution_prototxt_path, 0);
        std::string n_super_resolution_prototxt_path(
                utf_super_resolution_prototxt_path ? utf_super_resolution_prototxt_path : "");
        env->ReleaseStringUTFChars(super_resolution_prototxt_path,
                                   utf_super_resolution_prototxt_path);
        const char *utf_super_resolution_caffe_model_path = env->GetStringUTFChars(
                super_resolution_caffe_model_path, 0);
        std::string n_super_resolution_caffe_model_path(
                utf_super_resolution_caffe_model_path ? utf_super_resolution_caffe_model_path : "");
        env->ReleaseStringUTFChars(super_resolution_caffe_model_path,
                                   utf_super_resolution_caffe_model_path);
        cv::wechat_qrcode::WeChatQRCode *_retval_ = new cv::wechat_qrcode::WeChatQRCode(
                n_detector_prototxt_path, n_detector_caffe_model_path,
                n_super_resolution_prototxt_path, n_super_resolution_caffe_model_path);
        return (jlong) _retval_;
    } catch (const std::exception &e) {
        throwJavaException(env, &e, method_name);
    } catch (...) {
        throwJavaException(env, 0, method_name);
    }
    return 0;
}

extern "C"
JNIEXPORT jobject JNICALL
Java_com_heiko_opencvwxtest_WeChatQRCode_detectAndDecode(JNIEnv *env,
                                                         jobject thiz,
                                                         jlong self,
                                                         jlong img_nativeObj,
                                                         jlong points_mat_nativeObj) {
    using namespace cv::wechat_qrcode;
    static const char method_name[] = "WeChatQRCode_detectAndDecode";
    try {
        LOGD("%s", method_name);
        std::vector points;
        Mat &points_mat = *((Mat *) points_mat_nativeObj);
        cv::wechat_qrcode::WeChatQRCode *me = (cv::wechat_qrcode::WeChatQRCode *) self;
        Mat &img = *((Mat *) img_nativeObj);
        std::vector _ret_val_vector_ = me->detectAndDecode(img, points);
        vector_Mat_to_Mat(points, points_mat);
        jobject _retval_ = vector_string_to_List(env, _ret_val_vector_);
        return _retval_;
    } catch (const std::exception &e) {
        throwJavaException(env, &e, method_name);
    } catch (...) {
        throwJavaException(env, 0, method_name);
    }
    return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_heiko_opencvwxtest_WeChatQRCode_delete(JNIEnv *env, jobject thiz, jlong self) {
    delete (cv::wechat_qrcode::WeChatQRCode *) self;
}

修改CMakeLists.txt文件

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.

project("learningandroidopencv")

# 注意这里的路径,需要和OpenCV sdk的路径对应
set(ocvlibs "${PROJECT_SOURCE_DIR}/../../../../sdk/native/libs")
include_directories(${PROJECT_SOURCE_DIR}/include)

add_library(libopencv_java4 SHARED IMPORTED)
set_target_properties(libopencv_java4 PROPERTIES
        IMPORTED_LOCATION "${ocvlibs}/${ANDROID_ABI}/libopencv_java4.so")

include_directories(
        wechat
        wechat/detector
        wechat/scale
        wechat/zxing
        wechat/zxing/common
        wechat/zxing/common/binarizer
        wechat/zxing/common/reedsolomon
        wechat/zxing/qrcode
        wechat/zxing/qrcode/detector
        wechat/zxing/qrcode/decoder
)

aux_source_directory(wechat W)
aux_source_directory(wechat/detector WD)
aux_source_directory(wechat/scale WS)
aux_source_directory(wechat/zxing WZ)
aux_source_directory(wechat/zxing/common WZC)
aux_source_directory(wechat/zxing/common/binarizer WZCB)
aux_source_directory(wechat/zxing/common/reedsolomon WZCR)
aux_source_directory(wechat/zxing/qrcode WZQ)
aux_source_directory(wechat/zxing/qrcode/decoder WZQD)
aux_source_directory(wechat/zxing/qrcode/detector WZQD2)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp
        ${W}
        ${WD}
        ${WS}
        ${WZ}
        ${WZC}
        ${WZCB}
        ${WZCR}
        ${WZQ}
        ${WZQD}
        ${WZQD2}
        )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} libopencv_java4)

然后,我们需要在Application中初始化native-lib

init {
    //System.loadLibrary("opencv_java4")
    System.loadLibrary("native-lib")
}

接着,我们需要初始化扫码模型

下载模型文件,并导入到res/raw目录下

然后在Application中进行初始化,将模型文件复制到内部存储中

MMKV.initialize(this)
GlobalScope.launch(Dispatchers.IO) {
    MMKV.defaultMMKV()?.encode(
        MMKVKey.WeChatQRCodeDetectProtoTxt,
        copyFromAssets(R.raw.detect_prototxt, "wechat_qrcode", "detect.prototxt")
    )
    MMKV.defaultMMKV()?.encode(
        MMKVKey.WeChatQRCodeDetectCaffeModel,
        copyFromAssets(R.raw.detect_caffemodel, "wechat_qrcode", "detect.caffemodel")
    )
    MMKV.defaultMMKV()?.encode(
        MMKVKey.WeChatQRCodeSrProtoTxt,
        copyFromAssets(R.raw.sr_prototxt, "wechat_qrcode", "sr.prototxt")
    )
    MMKV.defaultMMKV()?.encode(
        MMKVKey.WeChatQRCodeSrCaffeModel,
        copyFromAssets(R.raw.sr_caffemodel, "wechat_qrcode", "sr.caffemodel")
    )
}

fun Context.copyFromAssets(@RawRes resId: Int, targetDir:String, targetFileName:String): String {
    val targetDirFile = getDir(targetDir, Context.MODE_PRIVATE)
    val targetFile = File(targetDirFile, targetFileName)
    targetFile.outputStream().use {
        resources.openRawResource(resId).copyTo(it)
    }
    return targetFile.absolutePath
}

在初始化WeChatQrCode的时候,将模型路径传入

mWeChatQRCode = WeChatQRCode(
    MMKV.defaultMMKV()?.decodeString(MMKVKey.WeChatQRCodeDetectProtoTxt) ?: "",
    MMKV.defaultMMKV()?.decodeString(MMKVKey.WeChatQRCodeDetectCaffeModel) ?: "",
    MMKV.defaultMMKV()?.decodeString(MMKVKey.WeChatQRCodeSrProtoTxt) ?: "",
    MMKV.defaultMMKV()?.decodeString(MMKVKey.WeChatQRCodeSrCaffeModel) ?: ""
)

然后,我们只需要将摄像头中的图像,传递给微信扫码库进行解析即可

override fun analyze(image: ImageProxy) {
    val rectangles = ArrayList<Mat>()
    //进行解析并识别,并获得解析结果results
    val results : List<String> = weChatQRCode.detectAndDecode(gray(image), rectangles)
    Toast.makeText(this, results.toString(), Toast.LENGTH_SHORT).show()
    image.close()
}

fun gray(image: ImageProxy): Mat {
    val planeProxy = image.planes
    val width = image.width
    val height = image.height
    val yPlane = planeProxy[0].buffer
    val yPlaneStep = planeProxy[0].rowStride
    return Mat(height, width, CvType.CV_8UC1, yPlane, yPlaneStep.toLong())
}

最后,别忘了申请权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

ActivityCompat.requestPermissions(
        this,
        arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
        1
)

完工 !

附上源码地址 https://download.csdn.net/download/EthanCo/20618652

不过接入微信扫码库还是有一定门槛的,如果想要更快速的接入扫码库,可以使用华为的扫码库,某些扫码场景下,比微信扫码还要强 ! 详见我的另一篇博客 Android 接入华为扫码库,实现堪比微信扫码的效果

感谢
https://blog.csdn.net/qq_35054151/article/details/113488372
https://mp.weixin.qq.com/s/GBXkRuS_fPsxr0uT5tR0Sw

你可能感兴趣的:(Android)