![](https://i-blog.csdnimg.cn/blog_migrate/ba93400ed9c5dff761a176dc8f0919ae.png)
案例图片上的身份证号码
alipay 支付中的libidcardtextcut.so
wechat 微信中libIDCardRecog.so
身份证识别
![](https://i-blog.csdnimg.cn/blog_migrate/acce1bed4ae26d73f4c2d071169a4076.png)
OpenCV
![](https://i-blog.csdnimg.cn/blog_migrate/ddcdb057035597ee4e651df036828f00.png)
处理流程
轮廓检测,图片膨胀,轮廓检测
![](https://i-blog.csdnimg.cn/blog_migrate/d72ff852d417534df66e90faae2b0f4b.png)
图像处理流程
![](https://i-blog.csdnimg.cn/blog_migrate/0a2d7d202e1b55bcd39c8d34aaa740a3.png)
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
#头文件配置
include_directories(include)
#编译头文件
file(GLOB my_source_path ${CMAKE_SOURCE_DIR}/*.cpp ${CMAKE_SOURCE_DIR}/*.c)
#添加动态连接库
add_library(
OpenCV
SHARED
${my_source_path})
add_library(
lib_opencv
SHARED
IMPORTED)
#设置本地so库
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libopencv_java4.so)
#find_library(
# log-lib
#
# log)
target_link_libraries(
OpenCV
log
jnigraphics#图像
${log-lib}
lib_opencv)
java代码
static {
System.loadLibrary("OpenCV");
}
private ImageView iv_pic_idcard;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_pic_idcard = (ImageView) findViewById(R.id.pic_idcard);
findViewById(R.id.pic_prev).setOnClickListener(this);
findViewById(R.id.pic_next).setOnClickListener(this);
findViewById(R.id.pic_parse).setOnClickListener(this);
}
public native String stringFromJNI();
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.pic_prev) {
iv_pic_idcard.setImageResource(R.drawable.idcard0);
} else if (id == R.id.pic_next) {
iv_pic_idcard.setImageResource(R.drawable.idcard0);
} else if (id == R.id.pic_parse) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.idcard0);
Bitmap bitmap1 = findInNumber(bitmap,Bitmap.Config.ARGB_8888);
bitmap.recycle();
if (bitmap1!=null){
iv_pic_idcard.setImageBitmap(bitmap1);
}else {
return;
}
}
}
private native Bitmap findInNumber(Bitmap bitmap, Bitmap.Config argb8888);
layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:id="@+id/pic_idcard"
android:layout_width="wrap_content"
android:layout_height="160dp"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/pic_prev"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="上一张" />
<Button
android:id="@+id/pic_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="下一张" />
</LinearLayout>
<Button
android:id="@+id/pic_parse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="识别" />
</LinearLayout>
native代码
缺少一段mat2Bitmap 和bitmap2Mat的代码
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ljp_idrecognize_MainActivity_findInNumber(JNIEnv *env, jobject instance,
jobject bitmap, jobject argb8888) {
//1,bitmap转为矩阵
Mat src_img;
Mat dst_img;
BitmapToMat(env, bitmap, src_img);
//2,归一化
Mat dst;
resize(src_img, dst, FIX_IDCARD_SIZE);
//3,灰度化
cvtColor(src_img, dst, COLOR_RGB2GRAY);
//4,二值化 阈值100高于100是255白色
threshold(dst, dst, 100, 255, THRESH_BINARY);
//5,膨胀处理
Mat erodeElement = getStructuringElement(MORPH_RECT, Size(40, 10));
erode(dst, dst, erodeElement);
//6,轮廓检测
vector<vector<Point>> contours;
vector<Rect> rects;
findContours(dst, contours, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//7,逻辑处理
for (int i = 0; i < contours.size(); ++i) {
//获取矩形
Rect rect = boundingRect(contours.at(i));
//绘制
// rectangle(dst, rect, Scalar(0, 0, 255));
if (rect.width > rect.height * 8 && rect.width < rect.height * 16) {
//需要的区域
rects.push_back(rect);
}
}
//8,获取最终区域
int lowPoint = 0;
Rect finalRect;
for (int i = 0; i < rects.size(); ++i) {
Rect rect = rects.at(i);
Point point = rect.tl();
if (point.y > lowPoint) {
lowPoint = point.y;
finalRect = rect;
}
}
//9,裁剪
dst_img = src_img(finalRect);
//10,回收
free(&dst);
free(&erodeElement);
free(&contours);
free(&rects);
free(&finalRect);
//11,矩阵转bitmap
return createBitmap(env,dst_img,argb8888);
}
Utils.h
//
// Created by LJP on 2020/4/6.
//
#ifndef IDRECOGNIZE_UTILS_H
#define IDRECOGNIZE_UTILS_H
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
#include <android/log.h>
#include <jni.h>
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "error", __VA_ARGS__))
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "debug", __VA_ARGS__))
using namespace std;
using namespace cv;
extern "C"{
void MatToBitmap (JNIEnv *env, Mat& mat, jobject& bitmap);
void BitmapToMat (JNIEnv *env, jobject& bitmap, Mat& mat);
jobject createBitmap (JNIEnv *env, Mat& mat,jobject config);
}
#endif //IDRECOGNIZE_UTILS_H
Utils.cpp
//
// Created by LJP on 2020/4/6.
//
#include "utils.h"
//#include "opencv2/core/base.hpp"
void BitmapToMat2(JNIEnv *env, jobject& bitmap, Mat& mat, jboolean needUnPremultiplyAlpha) {
AndroidBitmapInfo info;
void *pixels = 0;
Mat &dst = mat;
try {
LOGD("nBitmapToMat");
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) {
LOGD("nBitmapToMat: RGBA_8888 -> CV_8UC4");
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
LOGD("nBitmapToMat: RGB_565 -> CV_8UC4");
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);
LOGE("nBitmapToMat catched cv::Exception: %s", e.what());
jclass je = env->FindClass("org/opencv/core/CvException");
if (!je) je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, e.what());
return;
} catch (...) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nBitmapToMat catched unknown exception (...)");
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
return;
}
}
void BitmapToMat(JNIEnv *env, jobject& bitmap, Mat& mat) {
BitmapToMat2(env, bitmap, mat, false);
}
void MatToBitmap2
(JNIEnv *env, Mat& mat, jobject& bitmap, jboolean needPremultiplyAlpha) {
AndroidBitmapInfo info;
void *pixels = 0;
Mat &src = mat;
try {
LOGD("nMatToBitmap");
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(src.dims == 2 && info.height == (uint32_t) src.rows &&
info.width == (uint32_t) src.cols);
CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
CV_Assert(pixels);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
Mat tmp(info.height, info.width, CV_8UC4, pixels);
if (src.type() == CV_8UC1) {
LOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888");
cvtColor(src, tmp, COLOR_GRAY2RGBA);
} else if (src.type() == CV_8UC3) {
LOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888");
cvtColor(src, tmp, COLOR_RGB2RGBA);
} else if (src.type() == CV_8UC4) {
LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
if (needPremultiplyAlpha)
cvtColor(src, tmp, COLOR_RGBA2mRGBA);
else
src.copyTo(tmp);
}
} else {
// info.format == ANDROID_BITMAP_FORMAT_RGB_565
Mat tmp(info.height, info.width, CV_8UC2, pixels);
if (src.type() == CV_8UC1) {
LOGD("nMatToBitmap: CV_8UC1 -> RGB_565");
cvtColor(src, tmp, COLOR_GRAY2BGR565);
} else if (src.type() == CV_8UC3) {
LOGD("nMatToBitmap: CV_8UC3 -> RGB_565");
cvtColor(src, tmp, COLOR_RGB2BGR565);
} else if (src.type() == CV_8UC4) {
LOGD("nMatToBitmap: CV_8UC4 -> RGB_565");
cvtColor(src, tmp, COLOR_RGBA2BGR565);
}
}
AndroidBitmap_unlockPixels(env, bitmap);
return;
} catch (const cv::Exception &e) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nMatToBitmap catched cv::Exception: %s", e.what());
jclass je = env->FindClass("org/opencv/core/CvException");
if (!je) je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, e.what());
return;
} catch (...) {
AndroidBitmap_unlockPixels(env, bitmap);
LOGE("nMatToBitmap catched unknown exception (...)");
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
return;
}
}
void MatToBitmap(JNIEnv *env, Mat& mat, jobject& bitmap) {
MatToBitmap2(env, mat, bitmap, false);
}
jobject createBitmap(JNIEnv *env, Mat &mat, jobject config) {
jclass jclass1 = env->FindClass("android/graphics/Bitmap");
jmethodID jmethodID1 = env->GetStaticMethodID(jclass1, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jobject jobject1 = env->CallStaticObjectMethod(jclass1, jmethodID1, mat.cols, mat.rows, config);
MatToBitmap(env, mat, jobject1);
return jobject1;
}
问题1undefined reference to 'cv::error(int, std::string const&, char const, char const, int)'
如果遇到链接错误,一般是lib的路径不对,但是显然这次不是,错误如下:
error: undefined reference to 'cv::error(int, std::string const&, char const, char const, int)'
error: undefined reference to 'cv::error(int, std::string const&, char const, char const, int)'
显然是链上了,但是找不到特定函数的实现,比如error() ,imread(),imwrite()等等
幸而stackover上opencv4.0.1已经有过这个问题了,见https://stackoverflow.com/questions/54376290/opencv-4-0-1-link-failure-in-android
从NDK r16版本开始,Android NDK 切换到LLVM的libc ++。在新的主要发行版OpenCV4.0中,也从GNU的libstdc ++切换到了libc ++。
如果您设置“ -DANDRID_STL = gnustl_shared”,则它将无法工作,因为默认的OpenCV二进制文件是使用libc ++而非gnustl构建的。
您应该在build.gradle文件中设置cmake参数“ -DANDROID_STL = c ++ _ shared”,如下所示:
externalNativeBuild {
cmake {
//sample cpp flag parameters
cppFlags "-std=c++14 -Ofast -Rpass-analysis=loop-vectorize -fsave-optimization-record -fdiagnostics-show-hotness"
//sample abi filter parameters
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
//set -DANDROID_STL to c++_shared
arguments "-DANDROID_STL=c++_shared"
}
}
build.gradle
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.ljp.idcard"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
abiFilters 'armeabi-v7a' //添加平台架构类型
arguments "-DANDROID_STL=c++_shared"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
}
ORC 光学字符识别
- https://tesseract-ocr.github.io/tessdoc/Data-Files
- implementation 'com.rmtheis:tess-two:7.0.0'
traineddata 训练库的制作
https://sourceforge.net/projects/vietocr/files/jTessBoxEditor/