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閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375