NDK开发前奏 - 实现支付宝人脸识别功能

1. 基于 Android Studio 的 opencv 配置与使用

先推荐一本书《计算机视觉 - 算法与应用》,相信用过 OpenCV 的哥们都知道这是用来干啥的,这里我就不再啰嗦。只说一下他的应用领域:人机互动、物体识别、图像分割、人脸识别、动作识别、运动跟踪、机器人、运动分析、机器视觉、结构分析、汽车安全驾驶等等。这次我们主要用它来做人脸识别,注意人脸检测和人脸识别是两个概念。

首先先去官网 https://opencv.org/opencv-3-2.html 下载 Android SDK: sourceforge ,下载下来以后我们的开发方式目前有两种:一种是基于 OpenCV_3.2.0_Manager.apk 的纯 Java 代码;还有一种方式是配置好 opencv 后利用 Android NDK,使用C++开发

不管怎样都需要配置依赖 openCV 的开发环境,开发环境都起不来那就白扯了,目前我们采用的是:Android Studio 3.0.1(最高版本,建议 2.3 及以上) + OpenCV for Android SDK 3.2版本(点我上面的链接就可下载) 。支付宝就有人脸识别功能,相信我们都用过,在看我搭环境的同时大家不妨思考一下,人脸识别匹配到底匹配的是啥信息?

新建 Android Studio 项目工程,导入这个 module 但注意这是一个 Eclipse 工程,需要自己额外添加 build.gradle 文件。然后找到 native\libs 目录如图所示:

把 armeabi 拷贝到 jniLibs 下面,然后 app 添加依赖第一步就算大功告成。接下来就可以写一个简单的事例代码了。

2. 基于 opencv 的简单测试事例

刚开始我们就可以做一些简单的项目了,先熟悉 API 再去熟悉原理最后去熟悉算法。比如边缘检测,边缘检测又是啥?如果你要做图形图像识别就要用到他,再说通俗一些比如你要做车牌号识别就要用到他。这里我们写一下 opencv 处理图片灰度和边缘检测的代码:

#include <jni.h>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <android/bitmap.h>
#include <android/log.h>

#define TAG "JNI_TAG"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)


using namespace cv;

extern "C" {
JNIEXPORT void
JNICALL
Java_com_example_administrator_opencv_OpenCV_cannyCheck(JNIEnv *env, jclass type, jobject src,
                                                        jobject dst);
// bitmap -> mat
Mat bitmap2Mat(jobject pJobject);
// mat -> bitmap
void mat2bitmap(JNIEnv *env, Mat mat, jobject bitmap);
}

Mat bitmap2Mat(JNIEnv *env, jobject bitmap) {
    // 1. 获取图片的宽高,以及格式信息
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);
    void *pixels;
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    Mat mat;

    if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
        LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
        mat = Mat(info.height, info.width, CV_8UC4, pixels);
    } else if (info.format = ANDROID_BITMAP_FORMAT_RGB_565) {
        LOGD("nMatToBitmap: CV_8UC2 -> RGBA_565");
        mat = Mat(info.height, info.width, CV_8UC2, pixels);
    }

    AndroidBitmap_unlockPixels(env, bitmap);
    return mat;
}

void mat2bitmap(JNIEnv *env, Mat src, jobject bitmap) {
    // 1. 获取图片的宽高,以及格式信息
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);
    void *pixels;
    AndroidBitmap_lockPixels(env, bitmap, &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");
            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);
}


JNIEXPORT void JNICALL
Java_com_example_administrator_opencv_OpenCV_cannyCheck(JNIEnv *env, jclass type, jobject src,
                                                        jobject dst) {
    // 1. bitmap2Mat
    Mat src_mat = bitmap2Mat(env, src);
    Mat gray_mat, dst_mat;
    // 2. 讲图片处理成 Gray 可以提升处理速度
    cvtColor(src_mat, gray_mat, COLOR_BGRA2GRAY);
    // 2.2 3X3降噪处理
    blur(gray_mat, gray_mat, Size(3, 3));
    // 3. 处理边缘检测
    Canny(gray_mat, dst_mat, 50, 30);
    // 4. mat2bitmap
    mat2bitmap(env, dst_mat, dst);
}

3. 人脸检测和人脸识别

首先人脸检测和人脸识别是两个概念,人脸检测是检测是否有人脸,而人脸识别是比对人脸特征值的。支付宝支付有人脸识别功能,百度也有个人脸识别的开源工具,其实是基于服务器的,比较的是两张图片。那么拿 opencv 来说其实比较的也是图片,在 opencv 中有个非常重要的数据那就是 Mat 。人脸识别比较的真的是图片吗?目前我所知道的是这样,但像 tango 这些本身具有深度学习和机器学习的设备,或许以后就会变得不一样,但是这些或多或少都跟硬件有关系。既然知道比较的是什么,那么来我们就可以来走下逻辑了。
1. 人脸特征的录入
 1.1 打开相机检测是否有人脸
 1.2 保存人脸特征信息(可保存多份)

  1. 人脸特征匹配识别
     2.2 打开相机检测是否有人脸
     2.2 根据人脸信息匹配人脸特征值

知道大致的原理和大致的步骤接下来就好搞了,中途就算有遇到不知道的写代码可以查查官方文档,并不影响开发,如果有想要了解算法也可深入研究一下。

// 加载人脸识别的级联分类器
CascadeClassifier cascadeClassifier;

JNIEXPORT void JNICALL
Java_com_darren_ndk_day05_FaceDetection_loadCascade(JNIEnv *env, jobject instance,
                                                    jstring filePath_) {
    const char *filePath = env->GetStringUTFChars(filePath_, 0);
    cascadeClassifier.load(filePath);
    env->ReleaseStringUTFChars(filePath_, filePath);

    LOGE("人脸识别级联分类器加载成功");
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_darren_ndk_day05_FaceDetection_faceDetectionSave(JNIEnv *env, jobject instance,
                                                          jobject bitmap) {

    // opencv 操作图片操作的都是 矩阵 Mat
    // 1. bitmap2Mat
    Mat mat = bitmap2Mat(env, bitmap);

    Mat grayMat;
    // 2. 转成灰度图,提升运算速度,灰度图所对应的 CV_8UC1 单颜色通道,信息量少 0-255 1u
    cvtColor(mat, grayMat, CV_RGBA2GRAY);

    // 3. 转换直方图均衡化补偿
    Mat equalizeMat;
    equalizeHist(grayMat, equalizeMat);

    // 4. 检测人脸,这是个大问题
    std::vector<Rect> faces;
    cascadeClassifier.detectMultiScale(equalizeMat, faces, 1.1, 5, 0 | CV_HAAR_SCALE_IMAGE,
                                       Size(160, 160));

    LOGE("检测到人脸的个数:%d", faces.size());
    if (faces.size() == 1) {
        Rect faceRect = faces[0];
        // 画一个框框,标记出人脸
        rectangle(mat, faceRect, Scalar(255, 155, 155), 3);
        mat2Bitmap(env, mat, bitmap);

        // 只裁剪人脸部分的直方均衡补偿
        Mat saveMat = Mat(equalizeMat, faceRect);
        // mat 保存成文件  png ,上传到服务器吧,接着下一张(眨眼,张嘴巴)
        imwrite("xxxx/xxx.png", equalizeMat);
        return 1;
    }
    return 0;
}

4. 额外体会

记得曾经的预判是 17 年人工智能会彻底火起来,所以16年自己选择了一家主要做 AR 和 VR 的企业,但并不知外面的世界,18 年这一年变化应该会蛮大的,就是不知道会不会彻底起火。未来的就业机会肯定会越来越多,只不过可能都是一些高端就业,要求高了一些而已,很多人说工作难找了,其实是你的工作难找了。

17年共享单车和共享汽车火了,我们只是一个开发者,有时很难去判断其他东西,前几天看了一篇文章《人民想念周鸿祎》有人说那是 360 自己炒作的,且不论是不是炒作,这其实说的就是一个趋势。17年互联网无论新萌芽的企业还是正在崛起的企业大部分都选择了战队,要么是 T 队要么是 A 队,B 队倒是比较少。所以我们想要过上好日子,选择大企业未尝不可,这就是一个大趋势而已。

越往后走我们需要思考的问题肯定就会越多,面对的压力就会越来越大,当我们站的位置不一样,所看到的便会不一样这也是一种体验,所以我们需要阅读大量的书籍,做好规划锻炼好身体以便迎接未来,积极乐观,各自珍重。

视频讲解:https://pan.baidu.com/s/1htG9vDU

猜你喜欢

转载自blog.csdn.net/z240336124/article/details/79406222
今日推荐