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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值