本文的主要内容是:利用NDK开发技术调用OpenCV库,所以身份证号码识别并不是重点,开发环境的搭建才是重点!!!! 事实上,本人C++入门级别,要是让我用python调OpenCV写一个比较完善的身份证号码识别算法或许还有戏,用C++,写不动。。。本文的身份证识别只能识别我准备好的身份证(尴尬),如果想真的实现算法,建议系统学学C++和OpenCV。
项目地址:https://github.com/CAM1113/IDReg
OpenCV环境:链接:https://pan.baidu.com/s/1TSOFxte1DvK7UXlAHDyZQQ 提取码:j3y0
在IDRecon
模块的src/main
目录下新建文件夹cpp
,用于存放NDK开发相关的代码。在cpp
文件夹下新建文件夹libs
,用于存放OpenCV
的动态链接库。将 解压路径/OpenCV-android-sdk/sdk/native/libs 文件夹下的四个文件夹拷贝到libs
中。因为打包成时需要把OpenCV
中的.so
库一起打包。在CPP
文件夹下新建CMakeLists.txt
文件,用来编写编译的配置信息;新建OpenCVHelper.cpp
文件,用来编写C++
的代码。
最重要的第一步: 在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"]
}
}
}
//****//
最重要的二步: 编写 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})
令app
模块依赖IDRecon
模块,在app
模块的build.gradle
文件中的dependencies
闭包下添加依赖
//****//
dependencies{
implementation project(":IDRecong")
}
环境配置完毕,剩下的按NDK
开发的流程编写代码即可
在IDRecon
模块下的/src/main
文件夹下新建asserts
文件夹,文件夹下存放身份证数字匹配的模板(0-9十个数字的图片,资源见源码)
在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);
}
}
}
编写OpenCVHelper.cpp
代码,代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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");
}
}
编写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.运行!