Android高级 NDK开发图片识别

NDK开发
案例图片上的身份证号码

alipay 支付中的libidcardtextcut.so
wechat 微信中libIDCardRecog.so

身份证识别

身份证识别

OpenCV

OpenCV

处理流程

轮廓检测,图片膨胀,轮廓检测


身份证识别

图像处理流程

图像处理流程

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 光学字符识别

traineddata 训练库的制作

https://sourceforge.net/projects/vietocr/files/jTessBoxEditor/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 注:首发地址 0. 前言 如果只学理论,不做实践,不踩踩坑,一般很难发现真正实践项目中的问题的,也比较难以加深对技...
    cfanr阅读 9,490评论 4 50
  • 目录 需求背景 Tesseract简介及环境搭建 字库训练 Tesseract for iOS 总结 需求背景 由...
    Thomson__阅读 27,280评论 2 16
  • 技术要点分析:此次项目中主要的技术划分为身份证号码区域提取和光学字符识别。身份证号码区域的提取涉及有:图像灰度化阀...
    HoFie阅读 8,956评论 12 34
  • 不管是在工作、学习还是生活中,我们总是面临各种各样的选择,选择一所好的学校、选择一个合适的伴侣、选择工作任务的优先...
    浪矢小徒阅读 439评论 0 2
  • 文/孤鸟差鱼 在一个夕阳西沉的傍晚下 公园的座椅上的姑娘 被小孩问到 你喜欢青蛙还是蛤蟆 姑娘什么都没选 指着天 ...
    孤鸟差鱼阅读 308评论 4 6