移動端 人臉識別詳細流程(適用Android & iOS)

上篇文章已經(jīng)簡單了說了一下人臉識別的流程桥氏,那接下來的篇幅效诅,我將詳細的寫出整個流程中用到的Library 和 具體的代碼输吏,讓大家更直觀的感受到整個人臉識別的檢測過程

回顧一下人臉識別的過程

這里我將標注出用到的 Library

  • 1.發(fā)現(xiàn)人臉 (ios-AVFoundation+MTCNN Android-MTCNN)
  • 2.關鍵點檢測 (MTCNN)
  • 3.確定人臉姿態(tài) (關鍵點計算乡数,這里可以不用管织盼,因為我用了9個方向的特征,比如說左轉的姿態(tài)就用底庫中左臉的特征點來比較盗似,這里只做正臉檢測)
  • 4.特征抽取 (MobileFaceNet模型)
  • 5.根據(jù)姿態(tài)對比相應的特征 (特征余弦相似度)

單張圖片特征抽取的流程

image.png

1.發(fā)現(xiàn)人臉 && 關鍵點抽取

即 找到人臉的位置哩陕,這里iOS端多用了一個 用AVFoundation來檢測人臉,為什么這么做呢赫舒?
因為MTCNN里面是3個網(wǎng)絡悍及,跑起來CPU很高,手機發(fā)熱接癌,耗電量也高心赶,為了不一直去跑MTCNN,所以先用原生方法檢測人臉缺猛,因為原生檢測人臉效率更高缨叫,也不是特別發(fā)熱,當檢測到人臉后再丟給MTCNN檢測荔燎,拿出關鍵點耻姥;
Android 因為系統(tǒng)版本差異太大,沒有用原生湖雹,直接丟進MTCNN咏闪,檢測出人臉框和關鍵點

  • iOS代碼 檢測最大人臉,返回格式[關鍵點(array),人臉Rect(value)]
- (NSArray *)detectMaxFace:(UIImage *)image
{
    
    int w = image.size.width;
    int h = image.size.height;
    unsigned char* rgba = new unsigned char[w*h*4];
    {
        CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
        CGContextRef contextRef = CGBitmapContextCreate(rgba, w, h, 8, w*4,
                                                        colorSpace,
                                                        kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault);
        
        CGContextDrawImage(contextRef, CGRectMake(0, 0, w, h), image.CGImage);
        CGContextRelease(contextRef);
    }

    ncnn::Mat ncnn_img;
    ncnn_img = ncnn::Mat::from_pixels(rgba, ncnn::Mat::PIXEL_RGBA2RGB, w, h);
    
    std::vector<Bbox> finalBbox;
    //    new_mtcnn->detect(ncnn_img, finalBbox);
    cv::Mat temp;
    UIImageToMat(image, temp);
    //    float cost;
    new_mtcnn->detectMaxFace(ncnn_img, finalBbox);
    int32_t num_face = static_cast<int32_t>(finalBbox.size());
    
    int out_size = 1+num_face*14;
    
    NSMutableArray *faceInfoArr = [NSMutableArray arrayWithCapacity:0];
    //
    int *faceInfo = new int[out_size];
    faceInfo[0] = num_face;
    for(int i=0;i<num_face;i++){
        NSMutableArray *points = [NSMutableArray arrayWithCapacity:0];
        
        CGRect rect = CGRectMake(finalBbox[i].x1, finalBbox[i].y1, finalBbox[i].x2 - finalBbox[i].x1, finalBbox[i].y2 - finalBbox[i].y1);
        
        for (int j =0;j<5;j++){
            CGPoint point = CGPointMake(finalBbox[i].ppoint[j], finalBbox[i].ppoint[j + 5]);
            [points addObject:[NSValue valueWithCGPoint:point]];
        }

        [faceInfoArr addObject:points];
        [faceInfoArr addObject:[NSValue valueWithCGRect:rect]];
    }
    
    delete [] rgba;
    delete [] faceInfo;
    finalBbox.clear();
    return faceInfoArr;
}

2.判斷人臉姿態(tài)是否為正臉

因為我們要抽取特征的臉 一定要是正臉 所抽取的特征才有意義


image.png

上圖為MTCNN抽取出來的關鍵點摔吏,同時在數(shù)組里的位置我也有標注出來,主要比的就是眼睛到鼻子的距離

- (BOOL)faceFront:(NSArray *)shape
{
    int mStartNumRightTemp = 0;
    int mStartNumLeftTemp = 0;
    
    CGPoint right_pupil;
    CGPoint left_pupil;
    left_pupil.x = [shape[0] CGPointValue].x ;  //左眼瞳孔坐標x
    left_pupil.y = [shape[0] CGPointValue].y ;  //左眼瞳孔坐標y
    
    right_pupil.x = [shape[1] CGPointValue].x  ;  //右眼瞳孔坐標x
    right_pupil.y = [shape[1] CGPointValue].y ;  //右眼瞳孔坐標y
    
    mStartNumRightTemp = abs(right_pupil.x - [shape[2] CGPointValue].x);  //到鼻子中間的距離
    mStartNumLeftTemp = abs([shape[2] CGPointValue].x  - left_pupil.x);
    
    float tempp1 = mStartNumRightTemp / (float)mStartNumLeftTemp;
    float tempp2 = mStartNumLeftTemp / (float)mStartNumRightTemp;

    if (tempp1 > 0.7 && tempp2>0.7)
        return YES;
    else
        return NO; 
}

3.人臉對齊 (openCV)

所謂人臉對齊鸽嫂,看下面的圖就知道什么是人臉對齊了

image.png

左邊如果是原圖的話,對齊后的效果就是右邊這樣征讲,旋轉了一下圖像
下面的方法包括了 旋轉與摳出人臉圖像据某,
輸入整張圖片,人臉的5個關鍵點诗箍,返回對齊后 并截取了臉部的圖

- (Mat)getAlignMat:(Mat)bgrmat landmarks:(NSArray *)landmarks
{
    //left eye
    float left_eye_x = [landmarks[0] CGPointValue].x;
    float left_eye_y = [landmarks[0] CGPointValue].y;
    //right eye
    float right_eye_x = [landmarks[1] CGPointValue].x;
    float right_eye_y = [landmarks[1] CGPointValue].y;
    //nose
    float nose_x = [landmarks[2] CGPointValue].x;
    float nose_y = [landmarks[2] CGPointValue].y;
    //mouth left
    float mouth_left_x = [landmarks[3] CGPointValue].x;
    float mouth_left_y = [landmarks[3] CGPointValue].y;
    //mouth right
    float mouth_right_x = [landmarks[4] CGPointValue].x;
    float mouth_right_y = [landmarks[4] CGPointValue].y;
    //mouth center
    float mouth_center_x = (mouth_left_x + mouth_right_x) / 2;
    float mouth_center_y = (mouth_left_y + mouth_right_y) / 2;
    
    cv::Mat affineMat;
    std::vector<cv::Point2f> src_pts ;
    src_pts.push_back(cv::Point2f(left_eye_x, left_eye_y));
    src_pts.push_back(cv::Point2f(right_eye_x, right_eye_y));
    src_pts.push_back(cv::Point2f(mouth_center_x, mouth_center_y));
    
    
    cv::Point2f left_eye(38, 52);
    cv::Point2f right_eye(74, 52);
    cv::Point2f mouth_center(56, 92);
    cv::Size dsize(112, 112);
    
    std::vector<cv::Point2f> dst_pts;
    dst_pts.push_back(left_eye);
    dst_pts.push_back(right_eye);
    dst_pts.push_back(mouth_center);
    
    affineMat = cv::getAffineTransform(src_pts, dst_pts);
    
    cv::Mat alignedImg;
    cv::warpAffine(bgrmat, alignedImg, affineMat, dsize, cv::INTER_CUBIC, cv::BORDER_REPLICATE);
    
    return alignedImg;
    
}

4.抽取特征 (MobileFaceNet)

ncnn::Mat NCNNNet::getFaceFeatures(cv::Mat face)
    {
        ncnn::Mat in = ncnn::Mat::from_pixels(face.data, ncnn::Mat::PIXEL_BGR, face.cols, face.rows);
        const float mean_vals[3] = {127.5f, 127.5f, 127.5f};
        const float std_vals[3] = {0.0078125f, 0.0078125f, 0.0078125f};
        in.substract_mean_normalize(mean_vals, std_vals);
        
        ncnn::Extractor ex = features.create_extractor();
        ex.set_light_mode(true);
        ex.set_num_threads(4);

        ex.input(mobilefacenet_proto_id::BLOB_data, in);
        ncnn::Mat out;
        ex.extract(mobilefacenet_proto_id::BLOB_fc1_fc1_scale, out);

        
        return out;
    }

抽取出來的為128個float的數(shù)據(jù)癣籽,我們需要將ncnn:Mat 轉換成你要的Array,然后再進行相似度計算,這里以iOS端為??

- (NSArray <NSNumber *>*)turnNCNNMat:(ncnn::Mat)feature
{
    NSMutableArray *features = [NSMutableArray arrayWithCapacity:0];
    for (int i = 0; i < feature.w; i++) {
        [features addObject:@((float)feature[i])];
    }
    return features;
}

同時為了提高運算精度,我們需要將抽取的特征數(shù)據(jù)歸一化筷狼,這里得到的就是我們需要的人臉特征了

- (NSArray *)normalizeFeature:(NSArray <NSNumber *>*)feature
{
    NSMutableArray *fixFArr = [NSMutableArray arrayWithCapacity:0];
    if (!feature.count) return nil;
    float norm = 0;
    for (int i = 0; i < feature.count; i++) {
        norm += [feature[i] floatValue] * [feature[i] floatValue];
    }
    norm = sqrt(norm);
    if (norm == 0) return feature;
    for (int i = 0; i < feature.count; i++) {
        float newFeature = [feature[i] floatValue] / norm;
        [fixFArr addObject:@(newFeature)];
    }
    return fixFArr;
}

5.人臉相似度比對(余弦相似度)

通過以上步驟瓶籽,我們已經(jīng)可以抽取一張人臉的特征了,如果我們抽取兩張埂材,就可以得到兩張人臉的信息塑顺,這時候,我們就可以進行對比的操作了

- (CGFloat)getFeatureDistanceWithFirstFeatures:(NSArray *)firstFeature second:(NSArray *)secondFeature
{
    if (!firstFeature.count || !secondFeature.count) return 0;
    float distance = 0;
    for (int i = 0; i < firstFeature.count && i < firstFeature.count; ++i) {
        distance += ([firstFeature[i] floatValue] - [secondFeature[i] floatValue]) * ([firstFeature[i] floatValue]- [secondFeature[i] floatValue]);
    }
    distance = sqrt(distance);
    CGFloat similarity = (1 - pow(distance / 2, 2)) * 100;
    return similarity;
}

PS:以上部分代碼或者模型為網(wǎng)絡上下載俏险,如有侵權請及時通知我刪除

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末严拒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子竖独,更是在濱河造成了極大的恐慌裤唠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莹痢,死亡現(xiàn)場離奇詭異种蘸,居然都是意外死亡,警方通過查閱死者的電腦和手機竞膳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門劈彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人顶猜,你說我怎么就攤上這事《焕ǎ” “怎么了长窄?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纲菌。 經(jīng)常有香客問我挠日,道長,這世上最難降的妖魔是什么翰舌? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任嚣潜,我火速辦了婚禮,結果婚禮上椅贱,老公的妹妹穿的比我還像新娘懂算。我一直安慰自己,他們只是感情好庇麦,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布计技。 她就那樣靜靜地躺著,像睡著了一般山橄。 火紅的嫁衣襯著肌膚如雪垮媒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音睡雇,去河邊找鬼萌衬。 笑死,一個胖子當著我的面吹牛它抱,可吹牛的內(nèi)容都是我干的秕豫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼抗愁,長吁一口氣:“原來是場噩夢啊……” “哼馁蒂!你這毒婦竟也來了?” 一聲冷哼從身側響起蜘腌,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤沫屡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撮珠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沮脖,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年芯急,在試婚紗的時候發(fā)現(xiàn)自己被綠了勺届。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡娶耍,死狀恐怖免姿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榕酒,我是刑警寧澤胚膊,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站想鹰,受9級特大地震影響紊婉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜辑舷,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一喻犁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧何缓,春花似錦肢础、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至氓皱,卻和暖如春路召,著一層夾襖步出監(jiān)牢的瞬間勃刨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工股淡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留身隐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓唯灵,卻偏偏與公主長得像贾铝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子埠帕,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容