对于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包,附上下载地址
这里选择的是OpenCV 4.5.2版本,选择android进行下载
下载完成解压后,我们得到了如下所示的文件
这里的sdk文件夹,就是我们需要的OpenCV的Java SDK包了。
接着,我们新建一个项目,选择Native C++
接着,我们导入这个Java SDK
接着,在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 <jni.h>
#include <string>
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/wechat_qrcode.hpp>
#include <opencv2/imgcodecs.hpp>
#include <android/log.h>
#define CONSTRUCTOR(ENV, CLS) ENV->GetMethodID(CLS, "<init>", "(I)V")
#define ARRAYLIST(ENV) static_cast<jclass>(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<const cv::Exception *>(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<cv::Mat> &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<Vec<int, 2> >(i, 0) = Vec<int, 2>(addr >> 32, addr & 0xffffffff);
}
}
jobject vector_string_to_List(JNIEnv *env, std::vector<std::string> &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<std::string>::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<Mat> 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<std::string> _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
)
完工 !
附上源码地址
不过接入微信扫码库还是有一定门槛的,如果想要更快速的接入扫码库,可以使用华为的扫码库,某些扫码场景下,比微信扫码还要强 !