iOS 端基于OpenCV實現(xiàn)簡單人臉識別

iOS 端基于OpenCV實現(xiàn)人臉識別

一、OpenCV的編譯

OpenCV是一個基于BSD許可(開源)發(fā)行的跨平臺計算機視覺機器學習軟件庫,它可以運行在大多數(shù)的主流操作系統(tǒng)上,其中包括iOS。

在OpenCV官網(wǎng)有提供已經(jīng)編譯好的 iOS 端 framework 下載,但是當前版本(4.5.1)OpenCV提供的二進制包中是不包括人臉識別等擴展模塊的,這需要我們自己手動編譯。

1、環(huán)境準備

編譯OpenCV iOS版 需要確保電腦中已經(jīng)安裝以下軟件:

  • Xcode
  • Xcode Command Line Tools
  • cmake
  • Python

Xcode 可以在App Store下載,安裝完成后在終端執(zhí)行 xcode-select --install 來安裝 Xcode Command Line Tools。

然后可以通過 Home Brew 安裝 cmake :brew install cmake , 至于 如何安裝Home Brew 可以參考Home Brew 官網(wǎng)的教程。

Mac OS 已經(jīng)自帶了Python2.7 所有我們不需要安裝Python。

2、下載代碼

首先到 https://github.com/opencv/opencv 下載最新的OpenCV代碼,建議不要克隆整個倉庫,因為這樣會花費較多的時間,下載最新的代碼zip包即可。

然后到 https://github.com/opencv/opencv_contrib下載最新的擴展包代碼,注意應(yīng)該和前面下載的代碼保持版本號一致。

將下載的代碼放到同一個文件夾方便操作,例如:

./OpenCV
  ├── opencv-4.5.1
  └── opencv_contrib-4.5.1

3、編譯

OpenCV已經(jīng)為我們寫好了編譯用的工具腳本,放在opencv-4.5.1/platforms/ios/ 目錄下,其中有一個 build_framework.py,它就是編譯iOS 端 framework 的工具腳本。

在終端執(zhí)行這個工具腳本(注意將python文件的路徑換為你電腦上的真實路徑

# 切換到你的工作目錄,編譯后的文件將輸出到這個目錄
cd <yourWorkSpace>
# 執(zhí)行編譯腳本, --contrib 參數(shù)指定為我們下周的擴展包的目錄
python path/to/build_framework.py --contrib path/to/opencv_contrib iOS

我們通過 --contrib 參數(shù)指定了OpenCV的擴展包目錄,這樣就能將擴展包中的功能一起編譯到最終的 framework 中。

最后的ios 參數(shù)指定了輸出路徑為當前目錄的ios目錄。

如果你不想包含擴展包中的全部擴展,可以用 --without 參數(shù)來排除特定的擴展,更多的編譯參數(shù)可以查看 build_framework.pyif __name__ == "__main__": 這一行代碼后面的參數(shù)解析代碼,可以知道還有哪些參數(shù)可以使用。

如果一切順利,你將在工作目錄的 ios文件夾中找到編譯好的 opencv2.framework 文件,這就是我們需要的庫文件。

二、基于OpenCV實現(xiàn)人臉識別

實現(xiàn)最基礎(chǔ)的人臉識別主要分為以下幾步 :

  • 錄入
    • 通過攝像頭采集樣本照片
    • 通過人臉檢測技術(shù),從照片中提取人臉
    • 將人臉數(shù)據(jù)做灰度處理
    • 將處理后的人臉數(shù)據(jù)交給OpenCV自帶的人臉識別器進行訓練
  • 識別
    • 通過攝像頭采集樣本照片
    • 通過人臉檢測技術(shù),從照片中提取人臉
    • 將人臉數(shù)據(jù)做灰度處理
    • 將處理后的數(shù)據(jù)交給訓練好的識別器進行對比得到相似度

首先將編譯得到的framework導入到Xcode的iOS 項目中。然后在需要的地方 #import <opencv2/opencv2.h> 就可以開始使用了

1、圖像采集

OpenCV 為我們提供了方便的封裝:CvVideoCamera2,可以很容易的獲取到攝像頭數(shù)據(jù)。

// 初始化一個 CvVideoCamera2 對象,ParentView參數(shù)是一個UIView 對象,用來展示攝像頭預(yù)覽。
CvVideoCamera2 *videoCamera = [[CvVideoCamera2 alloc] initWithParentView:self];
// 設(shè)置使用的攝像頭,這里設(shè)置為前置攝像頭
videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
// 設(shè)置圖像分辨率
videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480;
// 設(shè)置視頻的方向。這里為豎屏方向展示
videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
// 設(shè)置采集幀率
videoCamera.defaultFPS = 30;
// 是否灰度
videoCamera.grayscaleMode = NO;
// 是否使用預(yù)覽
videoCamera.useAVCaptureVideoPreviewLayer = YES;
// 設(shè)置代理,回傳采集到的數(shù)據(jù)
videoCamera.delegate = self;

然后實現(xiàn) CvVideoCameraDelegate2 代理協(xié)議

- (void*)processImage:(Mat *)image; 回調(diào)方法中處理捕獲到的圖像。系統(tǒng)將以你設(shè)置的幀率回傳攝像頭捕捉到的圖像(例如這里的30幀每秒)。

其中參數(shù) Mat * imageOpenCV的圖片對象。它可以和UIImage CGImage相互轉(zhuǎn)換。

轉(zhuǎn)換方式請查看 <opencv2/Mat+Converters.h>

2、人臉檢測

OpenCV

OpenCV提供了基礎(chǔ)的人臉檢測工具 CascadeClassifier。

// 初始化一個檢測工具
CascadeClassifier * classifier = [[CascadeClassifier alloc] initWithFilename:filePath];

這里需要一個filePath參數(shù),它指定了圖像分類器的預(yù)設(shè),這里我們需要傳入一個人臉檢測是預(yù)設(shè)。文件在OpenCV的源代碼里:

opencv-4.5.1/data/haarcascades_cuda/haarcascade_frontalface_alt2.xml

這里有好幾種人臉檢測是預(yù)設(shè),你可以自行選擇。

將這個文件導入Xcode工程,然后獲取到它的路徑傳入到 CascadeClassifier的初始化參數(shù)里即可。

接下來進行人臉檢測

[classifier detectMultiScale:image objects:resultData];

這里有兩個參數(shù),第一個參數(shù)是需要進行檢測的圖片,也就是剛才攝像頭捕捉到的圖片,第二個參數(shù)是一個 NSMutableArray<Rect2i *> *類型的數(shù)組,用來保存檢測結(jié)果,當檢測到圖片中的人臉之后,人臉在圖片中的坐標,將會保存在這個數(shù)組中。

CoreImage

除了OpenCV之外,蘋果也提供了方便的人臉檢測工具。它就是包含在CoreImage模塊中的 CIDetector 類。

//  CIDetector 的配置
 NSDictionary *detectorOptions = @{
    CIDetectorAccuracy:CIDetectorAccuracyHigh, // 設(shè)置檢測精確度
    CIDetectorEyeBlink:@(YES), // 是否檢測眨眼
    CIDetectorTracking:@(YES) // 是否追蹤人臉
};
// 初始化一個 CIDetector
CIDetector* faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context: nil options:detectorOptions];

使用 CIDetector 檢測人臉我們需要將Mat 轉(zhuǎn)換成 CIImage 類型。

// 先轉(zhuǎn)換成CGImage 
CGImageRef cgImg = [image toCGImage];
// 再轉(zhuǎn)換成 CIImage 
CIImage *ciImage = [CIImage imageWithCGImage:cgImg];

然后檢測人臉:

NSArray<CIFeature *> *features = [faceDetector featuresInImage:ciImage];

返回的數(shù)組就是檢測到的人臉,里面的 CIFeature ,是檢測到的物體的一些屬性,它是一個抽象類。

因為 CIDetector 不止可以檢測人臉。如果數(shù)組里的對象是CIFaceFeature 則檢測到了人臉。

我們可以對人臉進行很多的判斷。

for (CIFeature *feature in features) {
        if ([feature isKindOfClass:CIFaceFeature.class]) {
            CIFaceFeature *faceFeature = (CIFaceFeature *)feature;
            // 判斷是否檢測到了嘴巴,是否檢測到了左右眼,是否有追蹤ID,如果在創(chuàng)建CIDetector 時允許了人臉追蹤,則相同的人臉會有相同的trackingID。
            if (faceFeature.hasMouthPosition && faceFeature.hasLeftEyePosition && faceFeature.hasRightEyePosition && faceFeature.hasTrackingID) {
              // faceFeature.bounds 屬性保存了人臉的位置信息。
             // 處理圖像,處理圖像的部分,比如裁剪和縮放灰度等,ios原生也能處理,你也可以選擇用Opencv處理。
            }
        }
}

3、圖像處理

現(xiàn)在我們拿到了人臉在圖片中的位置,下一步就是將人臉裁剪下來,并做統(tǒng)一的大小處理。

使用 Mat- (instancetype)initWithMat:(Mat*)mat rect:(Rect2i*)roi;初始化方法可以將人臉從原始圖片中裁剪下來。

// faceRect 參數(shù)為上一步檢測人臉時得到的數(shù)組中的數(shù)據(jù)。
Mat * face = [[Mat alloc] initWithMat:image rect:faceRect];

裁剪出來的人臉圖片大小不一,不適合進行識別,接下來對圖片進行統(tǒng)一大小的縮放,這里需要一點c++代碼了,請確保你寫代碼的文件名是.mm 結(jié)尾的,這樣這個文件才支持 c++ 和 Objective-C 的混編。

// 獲取原生C++的mat
cv::Mat nativeFace = face.nativeRef;
// 創(chuàng)建要縮放的目標尺寸
cv::Size destSize = cv::Size(128,128);
// 用于保存縮放后的圖片的原生 c++ mat
cv::Mat newNativeFace;
// 縮放
cv::resize(nativeFace, newNativeFace, destSize);
// 轉(zhuǎn)換成OC對象
Mat * newFace = [Mat fromNative:newNativeFace];

這樣,我們就得到了大小統(tǒng)一都是 128x128 的圖像。

接下來對圖像進行灰度處理,將圖片都轉(zhuǎn)成灰色。

為了方便,我們給Mat 類加一個擴展,依然是c++ 與 Objective-C 混編:

// Mat+gray_img.h
@interface Mat(gray_img)
-(Mat *)grayMat;
@end
// Mat+gray_img.mm
@implementation Mat (gray_img)
-(Mat *)grayMat {
    cv::Mat cvMat = self.nativeRef;

    if (cvMat.channels() == 1) { // 如果已經(jīng)只有一個灰度通道,直接返回
        return [Mat fromNative:cvMat];
    } else { // 處理圖片,只保留灰度通道
        cv::Mat grayMat = cv::Mat(cvMat.rows, cvMat.cols, CV_8UC1);
        cv::cvtColor(cvMat, grayMat, COLOR_BGR2GRAY);
        return [Mat fromNative:grayMat];
    }
}
@end

我們需要多收集一些人臉數(shù)據(jù),大概幾十張就可以了,越多的話訓練速度越慢,供后面使用。

4、訓練人臉識別器

通過上面的步驟,我們已經(jīng)拿到了我們需要的人臉數(shù)據(jù)。

接下來就是人臉識別。OpenCV提供了三種基礎(chǔ)的人臉識別算法。分別是LBPHFaceRecognizer、EigenFaceRecognizerFisherFaceRecognizer。

他們的用法基本相同,這里以 LBPHFaceRecognizer舉例。

創(chuàng)建一個識別器,依然是c++與Objective-C混編:

LBPHFaceRecognizer* recognizer = [[LBPHFaceRecognizer alloc] initWithNativePtr: cv::face::LBPHFaceRecognizer::create()];

這樣初始化出來的識別器是空白的,我們需要使用圖片來訓練它。

封裝一個這樣的方法:


/// 訓練識別器
/// @param image 訓練用的圖片
/// @param label 圖片的標簽,和圖片一一對應(yīng)
-(void)train:(NSArray<Mat *> *)image label:(NSArray<NSNumber *> * )label {
    std::vector<cv::Mat> datas; // 將OC的數(shù)組轉(zhuǎn)成c++的數(shù)組
    for (Mat *item in image) {
        datas.push_back(item.nativeRef);
    }
    std::vector<int> labels;// 將OC的數(shù)組轉(zhuǎn)成c++的數(shù)組
    for (NSNumber *n in label) {
        labels.push_back(int(n.integerValue));
    }
    // 訓練
    self.recognizer.nativePtrFaceRecognizer->update(datas, labels);
    // 訓練完成后可以將訓練的結(jié)果保存到文件中。這樣下次使用就不需要再訓練了,直接從文件讀取。
    // [self.recognizer write:filePath];
    // 從文件讀取之前的訓練結(jié)果
    // [self.recognizer read:filePath];
}

確保label和image的數(shù)量一致且順序一一對應(yīng)。

假設(shè)我們采集了30張用戶的人臉照片,我們把它標記為100

則圖片數(shù)組里應(yīng)該有30 張圖片 label數(shù)組里有30個100,且順序要對,為了提高識別的精準度,我們還可以將一些提前準備好的其他人的臉加入到訓練集合里,方便識別器更好的區(qū)分不同的人臉。比如加入30張其他的人臉,并將它們標記為除了100以外的其他標簽。

這樣,最終傳入進行訓練的圖片有60張,前30張為用戶的人臉圖片,后30張為其他人的人臉圖片,label也有60個,前30個為100,后30個為其他人對應(yīng)的標簽。

5、識別人臉

經(jīng)過訓練,我們得到了一個可用的識別器。

接下來進行人臉識別,前幾步和之前的1~3部是一樣的。從攝像頭采集圖像,裁剪,然后做灰度處理,將處理后的圖片交給識別器進行識別。

使用函數(shù)- (void*)predict:(Mat*)src label:(int*)label confidence:(double*)confidence;進行識別

這里有三個參數(shù):

Mat* src 是需要識別的圖片

int* label 是一個int指針,用來接收識別到的標簽

double* confidence 是一個double指針,用來接收識別出來的相似度,越小相似度越高

[self.recognizer predict:image label:label confidence:confidence];  

至此,我們就完成了簡單的基于OpenCV的人臉識別。

三、總結(jié)

OpenCV是一個強大的計算機視覺庫,里面提供了很多強大的工具。我們用過OpenCV提供的方便的工具實現(xiàn)了一個基本的人臉識別程序,這只是最基礎(chǔ)的人臉識別,如果要應(yīng)用到生產(chǎn)上,還有很多細節(jié)需要優(yōu)化。

比如:

  • 活體檢測

    如何確保是真人而不是照片

  • 識別率較低

    OpenCV提供的幾種人臉識別算法都比較老了,在被識別的照片和訓練時的照片角度光線差距較大時識別率普遍不高

  • 用戶交互

    優(yōu)化用戶在錄入人臉和識別人臉時的交互體驗

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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