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 保存人脸特征信息(可保存多份)

  2. 人脸特征匹配识别
     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

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