NDK開發(fā)前奏 - 實現(xiàn)支付寶人臉識別功能

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

先推薦一本書《計算機視覺 - 算法與應(yīng)用》,相信用過 OpenCV 的哥們都知道這是用來干啥的,這里我就不再啰嗦。只說一下他的應(yīng)用領(lǐng)域:人機互動、物體識別、圖像分割、人臉識別、動作識別、運動跟蹤、機器人、運動分析、機器視覺、結(jié)構(gòu)分析、汽車安全駕駛等等。這次我們主要用它來做人臉識別,注意人臉檢測和人臉識別是兩個概念。

首先先去官網(wǎng) https://opencv.org/opencv-3-2.html 下載 Android SDK: sourceforge ,下載下來以后我們的開發(fā)方式目前有兩種:一種是基于 OpenCV_3.2.0_Manager.apk 的純 Java 代碼;還有一種方式是配置好 opencv 后利用 Android NDK,使用C++開發(fā)。

不管怎樣都需要配置依賴 openCV 的開發(fā)環(huán)境,開發(fā)環(huán)境都起不來那就白扯了,目前我們采用的是:Android Studio 3.0.1(最高版本,建議 2.3 及以上) + OpenCV for Android SDK 3.2版本(點我上面的鏈接就可下載) 。支付寶就有人臉識別功能,相信我們都用過,在看我搭環(huán)境的同時大家不妨思考一下,人臉識別匹配到底匹配的是啥信息?



新建 Android Studio 項目工程,導(dǎo)入這個 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. 人臉檢測和人臉識別

首先人臉檢測和人臉識別是兩個概念,人臉檢測是檢測是否有人臉,而人臉識別是比對人臉特征值的。支付寶支付有人臉識別功能,百度也有個人臉識別的開源工具,其實是基于服務(wù)器的,比較的是兩張圖片。那么拿 opencv 來說其實比較的也是圖片,在 opencv 中有個非常重要的數(shù)據(jù)那就是 Mat 。人臉識別比較的真的是圖片嗎?目前我所知道的是這樣,但像 tango 這些本身具有深度學(xué)習(xí)和機器學(xué)習(xí)的設(shè)備,或許以后就會變得不一樣,但是這些或多或少都跟硬件有關(guān)系。既然知道比較的是什么,那么來我們就可以來走下邏輯了。

  1. 人臉特征的錄入
    ?1.1 打開相機檢測是否有人臉
    ?1.2 保存人臉特征信息(可保存多份)

  2. 人臉特征匹配識別
    ?2.2 打開相機檢測是否有人臉
    ?2.2 根據(jù)人臉信息匹配人臉特征值

知道大致的原理和大致的步驟接下來就好搞了,中途就算有遇到不知道的寫代碼可以查查官方文檔,并不影響開發(fā),如果有想要了解算法也可深入研究一下。

// 加載人臉識別的級聯(lián)分類器
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("人臉識別級聯(lián)分類器加載成功");
}

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. 轉(zhuǎn)成灰度圖,提升運算速度,灰度圖所對應(yīng)的 CV_8UC1 單顏色通道,信息量少 0-255 1u
    cvtColor(mat, grayMat, CV_RGBA2GRAY);
    
    // 3. 轉(zhuǎn)換直方圖均衡化補償
    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("檢測到人臉的個數(shù):%d", faces.size());
    if (faces.size() == 1) {
        Rect faceRect = faces[0];
        // 畫一個框框,標(biāo)記出人臉
        rectangle(mat, faceRect, Scalar(255, 155, 155), 3);
        mat2Bitmap(env, mat, bitmap);

        // 只裁剪人臉部分的直方均衡補償
        Mat saveMat = Mat(equalizeMat, faceRect);
        // mat 保存成文件  png ,上傳到服務(wù)器吧,接著下一張(眨眼,張嘴巴)
        imwrite("xxxx/xxx.png", equalizeMat);
        return 1;
    }
    return 0;
}

4. 額外體會

記得曾經(jīng)的預(yù)判是 17 年人工智能會徹底火起來,所以16年自己選擇了一家主要做 AR 和 VR 的企業(yè),但并不知外面的世界,18 年這一年變化應(yīng)該會蠻大的,就是不知道會不會徹底起火。未來的就業(yè)機會肯定會越來越多,只不過可能都是一些高端就業(yè),要求高了一些而已,很多人說工作難找了,其實是你的工作難找了。

17年共享單車和共享汽車火了,我們只是一個開發(fā)者,有時很難去判斷其他東西,前幾天看了一篇文章《人民想念周鴻祎》有人說那是 360 自己炒作的,且不論是不是炒作,這其實說的就是一個趨勢。17年互聯(lián)網(wǎng)無論新萌芽的企業(yè)還是正在崛起的企業(yè)大部分都選擇了戰(zhàn)隊,要么是 T 隊要么是 A 隊,B 隊倒是比較少。所以我們想要過上好日子,選擇大企業(yè)未嘗不可,這就是一個大趨勢而已。

越往后走我們需要思考的問題肯定就會越多,面對的壓力就會越來越大,當(dāng)我們站的位置不一樣,所看到的便會不一樣這也是一種體驗,所以我們需要閱讀大量的書籍,做好規(guī)劃鍛煉好身體以便迎接未來,積極樂觀,各自珍重。

視頻講解:https://pan.baidu.com/s/1htG9vDU

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容