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

红橙Darren
• 阅读 1842

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版本(点我上面的链接就可下载) 。支付宝就有人脸识别功能,相信我们都用过,在看我搭环境的同时大家不妨思考一下,人脸识别匹配到底匹配的是啥信息?

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

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

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

把 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

本文转自 https://juejin.cn/post/6844903577744310286,如有侵权,请联系删除。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这