Android开发之OpenCV实战:开发环境的搭建(身份证号码识别为例)

Android开发之OpenCV实战:开发环境的搭建(身份证号码识别为例)

声明

本文的主要内容是:利用NDK开发技术调用OpenCV库,所以身份证号码识别并不是重点,开发环境的搭建才是重点!!!! 事实上,本人C++入门级别,要是让我用python调OpenCV写一个比较完善的身份证号码识别算法或许还有戏,用C++,写不动。。。本文的身份证识别只能识别我准备好的身份证(尴尬),如果想真的实现算法,建议系统学学C++和OpenCV。
项目地址:https://github.com/CAM1113/IDReg
OpenCV环境:链接:https://pan.baidu.com/s/1TSOFxte1DvK7UXlAHDyZQQ 提取码:j3y0

开发环境

  1. Android Studio Arctic Fox | 2020.3.1(官网下载安装)
  2. OpenCV-android-sdk 版本4.5.5(下载后解压到一个文件夹里,不用进行配置)
  3. NDK(Android Studio自己安装)

环境配置步骤

  1. 新建项目IDReg新建项目

  2. 新建IDRecon模块,我们将在这个模块中进行OpenCV相关的开发。
    在这里插入图片描述

  3. IDRecon模块的src/main目录下新建文件夹cpp,用于存放NDK开发相关的代码。在cpp文件夹下新建文件夹libs,用于存放OpenCV的动态链接库。将 解压路径/OpenCV-android-sdk/sdk/native/libs 文件夹下的四个文件夹拷贝到libs中。因为打包成时需要把OpenCV中的.so库一起打包。在CPP文件夹下新建CMakeLists.txt文件,用来编写编译的配置信息;新建OpenCVHelper.cpp文件,用来编写C++的代码。
    在这里插入图片描述

  4. 最重要的第一步:IDRecon模块的build.gradle文件中添加NDK相关的配置。
    首先在defaultConfig闭包中添加externalNativeBuild闭包,增加编译d的参数配置。
    然后在android闭包中添加externalNativeBuild 闭包,指出CMakeLists.txt文件的路径和CMake的版本
    最后在android闭包中添加sourceSets 闭包,指明OpenCV的动态链接库的存放位置。

    //****//
    android{
    	//****//
    	defaultConfig{
    		//****//
    		externalNativeBuild {
    	        cmake {
    	            cppFlags ''
    	            arguments "-DANDROID_STL=c++_shared"
    	        }
    	    }
    	}
    	//****//
    	externalNativeBuild {
            cmake {
                path file('src/main/cpp/CMakeLists.txt')
                version '3.10.2'
            }
    	 }
        sourceSets{
            main{
                jniLibs.srcDirs = ["src/main/cpp/opencv/libs"]
            }
        }
    }
    //****//
    
  5. 最重要的二步: 编写 src/main/cpp/CMakeLists.txt,代码如下(说明见注释):

    # cmake版本
    cmake_minimum_required(VERSION 3.10.2)
    #替换,相当于宏定义
    set(PROJECT_NAME opencv)
    #项目名称,非必选
    project(${PROJECT_NAME})
    # 很重要,设置OpenCV_DIR,让项目能找到OpenCV的库
    set(OpenCV_DIR opencv的解压地址\\OpenCV-android-sdk\\sdk\\native\\jni)
    find_package(OpenCV REQUIRED)
    
    if(OpenCV_FOUND)
    	   # 把OpenCV的库引入项目
           include_directories(${OpenCV_INCLUDE_DIRS})
    
           message(STATUS "OpenCV library status:")
           message(STATUS "    version: ${OpenCV_VERSION}")
           message(STATUS "    libraries: ${OpenCV_LIBS}")
           message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
    else(OpenCV_FOUND)
           message(FATAL_ERROR "OpenCV library not found")
    endif(OpenCV_FOUND)
    
    
    add_library(
    	   #库的名称
           ${PROJECT_NAME}
           # 将库设置为.so动态连接库
           SHARED
           # 添加动态库里包含的所有cpp文件,包括引用的lib里的cpp
           OpenCVHelper.cpp
    )
    
    find_library(
           #库位置对应的名称,给其他地方使用
           log-lib
    	   #库生成的中间文件的位置
           log)
    #链接对应的文件,很重要,要连接jni图像库,OpenCV的库和自己编写的文件(${log-lib})
    target_link_libraries(
           ${PROJECT_NAME}
           jnigraphics
           ${OpenCV_LIBRARIES}
           ${log-lib})
    
  6. app模块依赖IDRecon模块,在app模块的build.gradle文件中的dependencies闭包下添加依赖

    	//****//
    	dependencies{
    		implementation project(":IDRecong")
    	}
    
  7. 环境配置完毕,剩下的按NDK开发的流程编写代码即可

代码编写

  1. IDRecon模块下的/src/main文件夹下新建asserts文件夹,文件夹下存放身份证数字匹配的模板(0-9十个数字的图片,资源见源码)

  2. IDRecon模块下的/src/main/java/包名文件夹下新建IDRecong.kt文件,代码如下:

    package com.cam.idrecong
    import android.content.Context
    import android.graphics.Bitmap
    import android.graphics.BitmapFactory
    class OpenCVUtils {
        companion object {
            init {
                System.loadLibrary("opencv")
            }
            private external fun idRecognise(bitmap: Bitmap, list:List<Bitmap>): String
            fun idRecognise(bitmap: Bitmap,context: Context): String{
                val list = ArrayList<Bitmap>()
                for(i in 0..9){
                    list.add(BitmapFactory.decodeStream(context.assets.open("$i.jpg")))
                }
                return idRecognise(bitmap,list);
            }
        }
    }
    
  3. 编写OpenCVHelper.cpp代码,代码如下:

    	#include <jni.h>
    	#include <iostream>
    	#include <opencv2/core/core.hpp>
    	#include <opencv2/imgcodecs/imgcodecs.hpp>
    	#include <opencv2/highgui/highgui.hpp>
    	#include <opencv2/core.hpp>
    	#include <opencv2/imgproc.hpp>
    	#include <string>
    	#include <opencv2/opencv.hpp>
    	#include <android/bitmap.h>
    	
    	using namespace std;
    	using namespace cv;
    	Size numSize = Size(16, 24);
    	
    	void BitmapToMat2(JNIEnv *env, jobject &bitmap, Mat &mat, jboolean needUnPremultiplyAlpha) {
    	    AndroidBitmapInfo info;
    	    void *pixels = 0;
    	    Mat &dst = mat;
    	    try {
    	        CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
    	        CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
    	                  info.format == ANDROID_BITMAP_FORMAT_RGB_565);
    	        CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
    	        CV_Assert(pixels);
    	        dst.create(info.height, info.width, CV_8UC4);
    	        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
    	            Mat tmp(info.height, info.width, CV_8UC4, pixels);
    	            if (needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA);
    	            else tmp.copyTo(dst);
    	        } else {
    	            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
    	            Mat tmp(info.height, info.width, CV_8UC2, pixels);
    	            cvtColor(tmp, dst, COLOR_BGR5652RGBA);
    	        }
    	        AndroidBitmap_unlockPixels(env, bitmap);
    	        return;
    	    } catch (const cv::Exception &e) {
    	        AndroidBitmap_unlockPixels(env, bitmap);
    	        jclass je = env->FindClass("java/lang/Exception");
    	        env->ThrowNew(je, e.what());
    	        return;
    	    } catch (...) {
    	        AndroidBitmap_unlockPixels(env, bitmap);
    	        jclass je = env->FindClass("java/lang/Exception");
    	        env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
    	        return;
    	    }
    	}
    	bool compareFacesByHist(const Mat &srcImage, const Mat &tempalteImage) {
    	    int sums = 0;
    	    for (int i = 0; i < srcImage.rows; i++) {
    	        for (int j = 0; j < srcImage.cols; j++) {
    	            sums += abs(srcImage.at<uchar>(i, j) - tempalteImage.at<uchar>(i, j));
    	        }
    	    }
    	    float s = sums / (srcImage.rows * srcImage.cols + 0.0);
    	    return s < 20;
    	}
    	
    	string getNums(Mat src, vector<Mat> oriMats) {
    	    if (src.empty()) {
    	        return "";
    	    }
    	
    	    resize(src, src, Size(640, 400));
    	
    	//    灰度图
    	    cvtColor(src, src, COLOR_BGR2GRAY);
    	    Mat dst;
    	//    二值化
    	    threshold(src, dst, 150, 255, THRESH_BINARY);
    	
    	    Mat erodeElement = getStructuringElement(MORPH_RECT, Size(20, 10));
    	    erode(dst, dst, erodeElement);
    	
    	    vector<vector<Point>> contours;
    	    findContours(dst, contours, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
    	    for (auto &contour: contours) {
    	        Rect rect = boundingRect(contour);
    	        if (rect.width > rect.height * 9) {
    	            dst = src(rect);
    	        }
    	    }
    	
    	    threshold(dst, dst, 150, 255, THRESH_BINARY);
    	    Mat erods;
    	    erodeElement = getStructuringElement(MORPH_RECT, Size(2, 2));
    	    
    	    dilate(dst, erods, erodeElement);
    	    erode(erods, erods, erodeElement);
    	    erode(erods, erods, erodeElement);
    	    erode(erods, erods, erodeElement);
    	    erode(erods, erods, erodeElement);
    	    
    	    contours.clear();
    	    findContours(erods, contours, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
    	    vector<Mat> mats;
    	    for (auto &contour: contours) {
    	        Rect rect = boundingRect(contour);
    	        if (rect.width > 5 && rect.height > 5 && rect.height >= rect.width &&
    	            rect.width * rect.height >= 200) {
    	            mats.push_back(dst(rect));
    	        }
    	    }
    	    vector<int> result;
    	    for (int i = 0; i < mats.size(); i++) {
    	        Mat m = mats.at(i);
    	        resize(m, m, numSize);
    	        for (int j = 0; j < oriMats.size(); j++) {
    	            if (compareFacesByHist(m, oriMats.at(j))) {
    	                result.push_back(j);
    	            }
    	        }
    	    }
    	    string s;
    	    for (int i = result.size() - 1; i >= 0; i--) {
    	        s.append(format("%d", result[i]));
    	    }
    	    return s;
    	}
    	
    	
    	extern "C"
    	JNIEXPORT jstring JNICALL
    	Java_com_cam_idrecong_OpenCVUtils_00024Companion_idRecognise(JNIEnv *env, jobject thiz,
    	                                                             jobject bitmap, jobject list) {
    	    Mat m;
    	    BitmapToMat2(env, bitmap, m, false);
    	    vector<Mat> mats;
    	    jclass clazz = env->GetObjectClass(list);
    	//    get(int index)
    	    jmethodID methodId = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;");
    	    for (int i = 0; i < 10; i++) {
    	        Mat x;
    	        jobject oriBitmap = (env->CallObjectMethod(list, methodId, i));
    	        BitmapToMat2(env, oriBitmap, x, false);
    	        cvtColor(x, x, COLOR_BGR2GRAY);
    	        resize(x, x, numSize);
    	        mats.push_back(x);
    	    }
    	    try {
    	        string s = getNums(m, mats);
    	        return env->NewStringUTF(s.c_str());
    	    } catch (Exception e) {
    	        return env->NewStringUTF("error");
    	    }
    	}
    
  4. 身份证照片id_test.jpg放在app模块下的下的drawable下。
    在这里插入图片描述

  5. 编写app模块下的MainActivity代码,代码如下:

    	class MainActivity : AppCompatActivity() {
    	    override fun onCreate(savedInstanceState: Bundle?) {
    	        super.onCreate(savedInstanceState)
    	        setContentView(R.layout.activity_main)
    	        val bitmap = BitmapFactory.decodeResource(resources,R.drawable.id_test);
    	        val s = OpenCVUtils.idRecognise(bitmap,this)
    	        Log.e("CAM",s)
    	    }
    	}
    

7.运行! 在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值