第一章 Android:基于OpenCV实现身份证识别(C++)——图像处理
第二章 Android:基于OpenCV实现身份证识别(C++)——移植图像算法
文章目录
- 系列文章目录
- 前言
- 一、Android Studio中配置OpenCV
- 1.创建项目
- 2.下载并解压opencv-android-sdk
- 3.配置OpenCV SDK路径
- 4.配置CMakeLists.txt文件
- 二、添加选择图片功能
- 1.自定义协定
- 2.调用registerForActivityResult
- 三、移植C++图像处理程序
- 1.定义原生方法
- 2.Mat与Bitmap数据格式转换
- 3.移植图像处理逻辑
- 四、效果展示
- 总结
前言
我们要做一个Android上的身份证号码识别功能,在上一篇用OpenCV做了图像处理,本文目标是将我们的C++程序移植到Android程序中。
软件环境:
- Android Studio Chipmunk | 2021.2.1
- opencv-4.5.5-android-sdk
一、Android Studio中配置OpenCV 1.创建项目
新建项目——选择Native C++——点击Next;
修改名称和包名——点击Next;
这里可以选择C++的标准库版本,我这里保持默认,点击Finish。
在官网下载opencv-4.5.5-android-sdk.zip 并解压至磁盘,后面我们要把此SDK目录配置到项目中。
解压后效果如下:
修改gradle.properties文件,加入opencvsdk路径;
opencvsdk=D:\Soft\Dev\OpenCV-android-sdk
修改build.gradle(:app)文件 ,加入cmake配置;
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
arguments "-DOpenCV_DIR=" + opencvsdk + "/sdk/native"
arguments "-DANDROID_STL=c++_shared"
}
}
}
}
如果没有配置 arguments “-DANDROID_STL=c++_shared”,可能会在运行时闪退,并报以下错误。
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.idrec, PID: 28414
java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found
at java.lang.Runtime.loadLibrary0(Runtime.java:1071)
at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
at java.lang.System.loadLibrary(System.java:1667)
at com.example.idrec.MainActivity.(MainActivity.kt:69)
at java.lang.Class.newInstance(Native Method)
4.配置CMakeLists.txt文件
修改CMakeLists.txt文件,加入以下配置;
include_directories(${OpenCV_DIR}/jni/include)
add_library( lib_opencv SHARED IMPORTED )
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${OpenCV_DIR}/libs/${ANDROID_ABI}/libopencv_java4.so)
并在target_link_libraries内加入lib_opencv,效果如下;
target_link_libraries( # Specifies the target library.
idrec
lib_opencv
# Links the target library to the log library
# included in the NDK.
${log-lib}
)
二、添加选择图片功能
要实现从手机选择图片,传统的startActivityForResult 官方已经废弃了,如果想启动一个 Activity 并获取返回的结果,推荐使用 registerForActivityResult 来代替。
1.自定义协定关于如何自定义协定?可参考官方文档:自定义协定
选择手机图片的协定如下:
class SelectPhotoContract : ActivityResultContract() { private lateinit var context: Context override fun createIntent(context: Context, input: Unit?): Intent { this.context = context return Intent(Intent.ACTION_PICK).setType("image private var mSrcBitmap: Bitmap? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.btChooseImage.setOnClickListener { mSelectPhoto.launch(Unit) } binding.btReadId.setOnClickListener { readID() } } private fun selectPhoto() = registerForActivityResult(SelectPhotoContract()) { it?.let { mSrcBitmap = it binding.ivImage.setImageBitmap(it) return@registerForActivityResult } Toast.makeText(this, "没有选择图片", Toast.LENGTH_SHORT).show() } }
三、移植C++图像处理程序 1.定义原生方法注意:private val mSelectPhoto = selectPhoto() 不要写成private val mSelectPhoto1 by lazy { selectPhoto() }懒加载的形式,registerForActivityResult是不支持这样初始化的。
定义原生方法getIdNumber(),点击“识别ID”按钮时,通过readID()调用getIdNumber()来获取识别后的Bitmap图像,并显示出来;
class MainActivity : AppCompatActivity() {
...
private fun readID(){
if(mSrcBitmap == null){
Toast.makeText(this,"请先选择图片", Toast.LENGTH_SHORT).show()
}else{
mSrcBitmap?.let {
val idBitmap = getIdNumber(it, Bitmap.Config.ARGB_8888)
binding.ivImage.setImageBitmap(idBitmap)
}
}
}
external fun getIdNumber(src: Bitmap, config: Bitmap.Config): Bitmap
companion object {
// Used to load the 'idrec' library on application startup.
init {
System.loadLibrary("idrec")
}
}
}
2.Mat与Bitmap数据格式转换
在C++中我们使用的Mat格式,其实它就相当于Android中的Bitmap格式;
如果我们要在C++中做图像处理,就面临两个问题:
(1)Bitmap如何转为Mat格式?
(2)Mat又如何转为Bitmap格式?
这个在OpenCV-android-sdksdkjavasrcorgopencvandroidUtils.java中已经帮我们提供了格式转换方法,现在需要在cpp文件中声明一下,就可以使用了。
修改main/cpp/native-lib.cpp文件,添加以下代码:
#include3.移植图像处理逻辑#include #include #define CARD_SIZE Size(640, 400) using namespace cv; using namespace std; extern "C" JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat2(JNIEnv *env, jclass clazz, jobject bitmap, jlong m_addr, jboolean un_premultiply_alpha); extern "C" JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nMatToBitmap(JNIEnv *env, jclass, jlong m_addr, jobject bitmap); jobject createBitmap(JNIEnv *env, Mat srcData, jobject config){ int imgWidth = srcData.cols; int imgHeight = srcData.rows; // 利用反射创建Bitmap对象 jclass bmpCls = env->FindClass("android/graphics/Bitmap"); jmethodID createBitmapMid = env->GetStaticMethodID(bmpCls, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); jobject jBmpObj = env->CallStaticObjectMethod(bmpCls, createBitmapMid, imgWidth, imgHeight, config); Java_org_opencv_android_Utils_nMatToBitmap(env, nullptr, (jlong) &srcData, jBmpObj); return jBmpObj; } extern "C" JNIEXPORT jobject JNICALL Java_com_example_idrec_MainActivity_getIdNumber(JNIEnv *env, jobject thiz, jobject src, jobject config) { Mat src_img; Mat dst_img; // bitmap转——》Mat Java_org_opencv_android_Utils_nBitmapToMat2(env, (jclass)thiz, src, (jlong)&src_img, 0); Mat dst; ... 图像处理逻辑 ... // 创建Bitmap对象 jobject bitmap = createBitmap(env, dst_img, config); // 释放资源 src_img.release(); dst_img.release(); dst.release(); return bitmap; }
现在将上一篇的图像处理逻辑复制到getIdNumber方法中,代码如下:
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_idrec_MainActivity_getIdNumber(JNIEnv *env, jobject thiz, jobject src,
jobject config) {
Mat src_img;
Mat dst_img;
// bitmap转——》Mat
Java_org_opencv_android_Utils_nBitmapToMat2(env, (jclass)thiz, src, (jlong)&src_img, 0);
Mat dst;
// 无损压缩 640*400
resize(src_img, src_img, CARD_SIZE);
// 灰度化
cvtColor(src_img, dst, COLOR_BGR2GRAY);
// 二值化
threshold(dst, dst, 100, 255, THRESH_BINARY);
// 膨胀
Mat erodeElement = getStructuringElement(MORPH_RECT, Size(20, 10));
erode(dst, dst, erodeElement);
// 轮廓检测
vector> contours;
vector rects;
findContours(dst, contours, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
for (auto & contour : contours) {
Rect rect = boundingRect(contour);
rectangle(dst, rect, Scalar(0, 255));
// 筛选轮廓图片
if (rect.width > rect.height * 9) {
rects.push_back(rect);
rectangle(dst, rect, Scalar(0, 0, 255));
}
}
if (rects.size() == 1) {
dst_img = src_img(rects.at(0));
}
else {
Rect rectTmp = rects.at(0);
// 遍历查找Y最大的轮廓
for (auto rect : rects) {
if (rect.tl().y > rectTmp.tl().y) {
rectTmp = rect;
}
}
rectangle(dst, rectTmp, Scalar(255, 255, 0));
dst_img = src_img(rectTmp);
}
// 创建Bitmap对象
jobject bitmap = createBitmap(env, dst_img, config);
// 释放资源
src_img.release();
dst_img.release();
dst.release();
return bitmap;
}
四、效果展示
先从相册选择图片后,点击“识别ID”按钮,开始识别身份证号码。
总结
以上就是本文要讲的内容,从中我们学到了如何在Android 引入OpenCV?如何Mat与Bitmap格式转换?如何在Android中调用C++图像计算?



