opencv 人臉識別

 

背景知識

OpenCV 是一個開源的計算機視覺和機器學習庫。它包含成千上萬優化過的算法,爲各類計算機視覺應用提供了一個通用工具包。根據這個項目的關於頁面,OpenCV 已被普遍運用在各類項目上,從谷歌街景的圖片拼接,到交互藝術展覽的技術實現中,都有 OpenCV 的身影。html

OpenCV 起始於 1999 年 Intel 的一個內部研究項目。從那時起,它的開發就一直很活躍。進化到如今,它已支持如 OpenCL 和 OpenGL 等現代技術,也支持如 iOS 和 Android 等平臺。java

1999 年,半條命發佈後大紅大熱。Intel 奔騰 3 處理器是當時最高級的 CPU,400-500 MHZ 的時鐘頻率已被認爲是至關快。2006 年 OpenCV 1.0 版本發佈的時候,當時主流 CPU 的性能也只和 iPhone 5 的 A6 處理器至關。儘管計算機視覺從傳統上被認爲是計算密集型應用,但咱們的移動設備性能已明顯地超出可以執行有用的計算機視覺任務的閾值,帶着攝像頭的移動設備能夠在計算機視覺平臺上大有所爲。ios

在本文中,我會從一個 iOS 開發者的視角概述一下 OpenCV,並介紹一點基礎的類和概念。隨後,會講到如何集成 OpenCV 到你的 iOS 項目中以及一些 Objective-C++ 基礎知識。最後,咱們會看一個 demo 項目,看看如何在 iOS 設備上使用 OpenCV 實現人臉檢測與人臉識別。git

OpenCV 概述

概念

OpenCV 的 API 是 Chttps://objccn.io/issue-21-9/++ 的。它由不一樣的模塊組成,這些模塊中包含範圍極爲普遍的各類方法,從底層的圖像顏色空間轉換到高層的機器學習工具。github

使用 C++ API 並非絕大多數 iOS 開發者天天都作的事,你須要使用 Objective-C++ 文件來調用 OpenCV 的函數。 也就是說,你不能在 Swift 或者 Objective-C 語言內調用 OpenCV 的函數。 這篇 OpenCV 的 iOS 教程告訴你只要把全部用到 OpenCV 的類的文件後綴名改成 .mm 就好了,包括視圖控制器類也是如此。這麼幹或許能行得通,卻不是什麼好主意。正確的方式是給全部你要在 app 中使用到的 OpenCV 功能寫一層 Objective-C++ 封裝。這些 Objective-C++ 封裝把 OpenCV 的 C++ API 轉化爲安全的 Objective-C API,以方便地在全部 Objective-C 類中使用。走封裝的路子,你的工程中就能夠只在這些封裝中調用 C++ 代碼,從而避免掉不少讓人頭痛的問題,好比直接改文件後綴名會由於在錯誤的文件中引用了一個 C++ 頭文件而產生難以追蹤的編譯錯誤。objective-c

OpenCV 聲明瞭命名空間 cv,所以 OpenCV 的類的前面會有個 cv:: 前綴,就像 cv::Mat、 cv::Algorithm 等等。你也能夠在 .mm 文件中使用 using namespace cv 來避免在一堆類名前使用 cv::前綴。可是,在某些類名前你必須使用命名空間前綴,好比 cv::Rect 和 cv::Point,由於它們會跟定義在 MacTypes.h 中的 Rect 和 Point 相沖突。儘管這只是我的偏好問題,我仍是偏向在任何地方都使用 cv::以保持一致性。算法

模塊

下面是在官方文檔中列出的最重要的模塊。數組

  • core:簡潔的核心模塊,定義了基本的數據結構,包括稠密多維數組 Mat 和其餘模塊須要的基本函數。
  • imgproc:圖像處理模塊,包括線性和非線性圖像濾波、幾何圖像轉換 (縮放、仿射與透視變換、通常性基於表的重映射)、顏色空間轉換、直方圖等等。
  • video:視頻分析模塊,包括運動估計、背景消除、物體跟蹤算法。
  • calib3d:包括基本的多視角幾何算法、單體和立體相機的標定、對象姿態估計、雙目立體匹配算法和元素的三維重建。
  • features2d:包含了顯著特徵檢測算法、描述算子和算子匹配算法。
  • objdetect:物體檢測和一些預約義的物體的檢測 (如人臉、眼睛、杯子、人、汽車等)。
  • ml:多種機器學習算法,如 K 均值、支持向量機和神經網絡。
  • highgui:一個簡單易用的接口,提供視頻捕捉、圖像和視頻編碼等功能,還有簡單的 UI 接口 (iOS 上可用的僅是其一個子集)。
  • gpu:OpenCV 中不一樣模塊的 GPU 加速算法 (iOS 上不可用)。
  • ocl:使用 OpenCL 實現的通用算法 (iOS 上不可用)。
  • 一些其它輔助模塊,如 Python 綁定和用戶貢獻的算法。

基礎類和操做

OpenCV 包含幾百個類。爲簡便起見,咱們只看幾個基礎的類和操做,進一步閱讀請參考所有文檔。過一遍這幾個核心類應該足以對這個庫的機理產生一些感受認識。安全

cv::Mat

cv::Mat 是 OpenCV 的核心數據結構,用來表示任意 N 維矩陣。由於圖像只是 2 維矩陣的一個特殊場景,因此也是使用 cv::Mat 來表示的。也就是說,cv::Mat 將是你在 OpenCV 中用到最多的類。網絡

一個 cv::Mat 實例的做用就像是圖像數據的頭,其中包含着描述圖像格式的信息。圖像數據只是被引用,並能爲多個 cv::Mat 實例共享。OpenCV 使用相似於 ARC 的引用計數方法,以保證當最後一個來自 cv::Mat 的引用也消失的時候,圖像數據會被釋放。圖像數據自己是圖像連續的行的數組 (對 N 維矩陣來講,這個數據是由連續的 N-1 維數據組成的數組)。使用 step[] 數組中包含的值,圖像的任一像素地址均可經過下面的指針運算獲得:

uchar *pixelPtr = cvMat.data + rowIndex * cvMat.step[0] + colIndex * cvMat.step[1]

每一個像素的數據格式能夠經過 type() 方法得到。除了經常使用的每通道 8 位無符號整數的灰度圖 (1 通道,CV_8UC1) 和彩色圖 (3 通道,CV_8UC3),OpenCV 還支持不少不經常使用的格式,例如 CV_16SC3 (每像素 3 通道,每通道使用 16 位有符號整數),甚至 CV_64FC4 (每像素 4 通道,每通道使用 64 位浮點數)。

cv::Algorithm

Algorithm 是 OpenCV 中實現的不少算法的抽象基類,包括將在咱們的 demo 工程中用到的 FaceRecognizer。它提供的 API 與蘋果的 Core Image 框架中的 CIFilter 有些類似之處。建立一個 Algorithm 的時候使用算法的名字來調用 Algorithm::create(),而且能夠經過 get() 和 set()方法來獲取和設置各個參數,這有點像是鍵值編碼。另外,Algorithm 從底層就支持從/向 XML 或 YAML 文件加載/保存參數的功能。

在 iOS 上使用 OpenCV

添加 OpenCV 到你的工程中

集成 OpenCV 到你的工程中有三種方法:

  • 使用 CocoaPods 就好: pod "OpenCV"
  • 下載官方 iOS 框架發行包,並把它添加到工程裏。
  • 從 GitHub 拉下代碼,並根據教程本身編譯 OpenCV 庫。

Objective-C++

如前面所說,OpenCV 是一個 C++ 的 API,所以不能直接在 Swift 和 Objective-C 代碼中使用,但能在 Objective-C++ 文件中使用。

Objective-C++ 是 Objective-C 和 C++ 的混合物,讓你能夠在 Objective-C 類中使用 C++ 對象。clang 編譯器會把全部後綴名爲 .mm 的文件都當作是 Objective-C++。通常來講,它會如你所指望的那樣運行,但仍是有一些使用 Objective-C++ 的注意事項。內存管理是你最應該格外注意的點,由於 ARC 只對 Objective-C 對象有效。當你使用一個 C++ 對象做爲類屬性的時候,其惟一有效的屬性就是 assign。所以,你的 dealloc 函數應確保 C++ 對象被正確地釋放了。

第二重要的點就是,若是你在 Objective-C++ 頭文件中引入了 C++ 頭文件,當你在工程中使用該 Objective-C++ 文件的時候就泄露了 C++ 的依賴。任何引入你的 Objective-C++ 類的 Objective-C 類也會引入該 C++ 類,所以該 Objective-C 文件也要被聲明爲 Objective-C++ 的文件。這會像森林大火同樣在工程中迅速蔓延。因此,應該把你引入 C++ 文件的地方都用 #ifdef __cplusplus 包起來,而且只要可能,就儘可能只在 .mm實現文件中引入 C++ 頭文件。

要得到更多如何混用 C++ 和 Objective-C 的細節,請查看 Matt Galloway 寫的這篇教程

Demo:人臉檢測與識別

如今,咱們對 OpenCV 及如何把它集成到咱們的應用中有了大概認識,那讓咱們來作一個小 demo 應用:從 iPhone 的攝像頭獲取視頻流,對它持續進行人臉檢測,並在屏幕上標出來。當用戶點擊一個臉孔時,應用會嘗試識別這我的。若是識別結果正確,用戶必須點擊 「Correct」。若是識別錯誤,用戶必須選擇正確的人名來糾正錯誤。咱們的人臉識別器就會從錯誤中學習,變得愈來愈好。

demo 應用中人臉檢測與識別系統線框圖

本 demo 應用的源碼可從 GitHub 得到。

視頻拍攝

OpenCV 的 highgui 模塊中有個類,CvVideoCamera,它把 iPhone 的攝像機抽象出來,讓咱們的 app 經過一個代理函數 - (void)processImage:(cv::Mat&)image 來得到視頻流。CvVideoCamera 實例可像下面這樣進行設置:

CvVideoCamera *videoCamera = [[CvVideoCamera alloc] initWithParentView:view]; videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; videoCamera.defaultFPS = 30; videoCamera.grayscaleMode = NO; videoCamera.delegate = self; 

攝像頭的幀率被設置爲 30 幀每秒, 咱們實現的 processImage 函數將每秒被調用 30 次。由於咱們的 app 要持續不斷地檢測人臉,因此咱們應該在這個函數裏實現人臉的檢測。要注意的是,若是對某一幀進行人臉檢測的時間超過 1/30 秒,就會產生掉幀現象。

人臉檢測

其實你並不須要使用 OpenCV 來作人臉檢測,由於 Core Image 已經提供了 CIDetector 類。用它來作人臉檢測已經至關好了,而且它已經被優化過,使用起來也很容易:

CIDetector *faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}]; NSArray *faces = [faceDetector featuresInImage:image]; 

從該圖片中檢測到的每一張面孔都在數組 faces 中保存着一個 CIFaceFeature 實例。這個實例中保存着這張面孔的所處的位置和寬高,除此以外,眼睛和嘴的位置也是可選的。

另外一方面,OpenCV 也提供了一套物體檢測功能,通過訓練後可以檢測出任何你須要的物體。該庫爲多個場景自帶了能夠直接拿來用的檢測參數,如人臉、眼睛、嘴、身體、上半身、下半身和笑臉。檢測引擎由一些很是簡單的檢測器的級聯組成。這些檢測器被稱爲 Haar 特徵檢測器,它們各自具備不一樣的尺度和權重。在訓練階段,決策樹會經過已知的正確和錯誤的圖片進行優化。關於訓練與檢測過程的詳情可參考此原始論文。當正確的特徵級聯及其尺度與權重經過訓練確立之後,這些參數就可被加載並初始化級聯分類器了:

// 正面人臉檢測器訓練參數的文件路徑 NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_alt2" ofType:@"xml"];  const CFIndex CASCADE_NAME_LEN = 2048; char *CASCADE_NAME = (char *) malloc(CASCADE_NAME_LEN); CFStringGetFileSystemRepresentation( (CFStringRef)faceCascadePath, CASCADE_NAME, CASCADE_NAME_LEN); CascadeClassifier faceDetector; faceDetector.load(CASCADE_NAME); 

這些參數文件可在 OpenCV 發行包裏的 data/haarcascades 文件夾中找到。

在使用所須要的參數對人臉檢測器進行初始化後,就能夠用它進行人臉檢測了:

cv::Mat img; vector<cv::Rect> faceRects; double scalingFactor = 1.1; int minNeighbors = 2; int flags = 0; cv::Size minimumSize(30,30); faceDetector.detectMultiScale(img, faceRects, scalingFactor, minNeighbors, flags cv::Size(30, 30) ); 

檢測過程當中,已訓練好的分類器會用不一樣的尺度遍歷輸入圖像的每個像素,以檢測不一樣大小的人臉。參數 scalingFactor 決定每次遍歷分類器後尺度會變大多少倍。參數 minNeighbors 指定一個符合條件的人臉區域應該有多少個符合條件的鄰居像素才被認爲是一個可能的人臉區域;若是一個符合條件的人臉區域只移動了一個像素就再也不觸發分類器,那麼這個區域很是可能並非咱們想要的結果。擁有少於 minNeighbors 個符合條件的鄰居像素的人臉區域會被拒絕掉。若是 minNeighbors 被設置爲 0,全部可能的人臉區域都會被返回回來。參數 flags 是 OpenCV 1.x 版本 API 的遺留物,應該始終把它設置爲 0。最後,參數 minimumSize 指定咱們所尋找的人臉區域大小的最小值。faceRects 向量中將會包含對 img 進行人臉識別得到的全部人臉區域。識別的人臉圖像能夠經過 cv::Mat 的 () 運算符提取出來,調用方式很簡單:cv::Mat faceImg = img(aFaceRect)

不論是使用 CIDetector 仍是 OpenCV 的 CascadeClassifier,只要咱們得到了至少一我的臉區域,咱們就能夠對圖像中的人進行識別了。

人臉識別

OpenCV 自帶了三我的臉識別算法:Eigenfaces,Fisherfaces 和局部二值模式直方圖 (LBPH)。若是你想知道它們的工做原理及相互之間的區別,請閱讀 OpenCV 的詳細文檔

針對於咱們的 demo app,咱們將採用 LBPH 算法。由於它會根據用戶的輸入自動更新,而不須要在每添加一我的或糾正一次出錯的判斷的時候都要從新進行一次完全的訓練。

要使用 LBPH 識別器,咱們也用 Objective-C++ 把它封裝起來。這個封裝中暴露如下函數:

+ (FJFaceRecognizer *)faceRecognizerWithFile:(NSString *)path; - (NSString *)predict:(UIImage*)img confidence:(double *)confidence; - (void)updateWithFace:(UIImage *)img name:(NSString *)name; 

像下面這樣用工廠方法來建立一個 LBPH 實例:

+ (FJFaceRecognizer *)faceRecognizerWithFile:(NSString *)path { FJFaceRecognizer *fr = [FJFaceRecognizer new]; fr->_faceClassifier = createLBPHFaceRecognizer(); fr->_faceClassifier->load(path.UTF8String); return fr; } 

預測函數能夠像下面這樣實現:

- (NSString *)predict:(UIImage*)img confidence:(double *)confidence { cv::Mat src = [img cvMatRepresentationGray]; int label; self->_faceClassifier->predict(src, label, *confidence); return _labelsArray[label]; } 

請注意,咱們要使用一個類別方法把 UIImage 轉化爲 cv::Mat。此轉換自己卻是至關簡單直接:使用 CGBitmapContextCreate 建立一個指向 cv::Image 中的 data 指針所指向的數據的 CGContextRef。當咱們在此圖形上下文中繪製此 UIImage 的時候,cv::Image 的 data 指針所指就是所須要的數據。更有趣的是,咱們能對一個 Objective-C 類建立一個 Objective-C++ 的類別,而且確實管用。

另外,OpenCV 的人臉識別器僅支持整數標籤,可是咱們想使用人的名字做標籤,因此咱們得經過一個 NSArray 屬性來對兩者實現簡單的轉換。

一旦識別器給了咱們一個識別出來的標籤,咱們把此標籤給用戶看,這時候就須要用戶給識別器一個反饋。用戶能夠選擇,「是的,識別正確」,也能夠選擇,「不,這是 Y,不是 X」。在這兩種狀況下,咱們均可以經過人臉圖像和正確的標籤來更新 LBPH 模型,以提升將來識別的性能。使用用戶的反饋來更新人臉識別器的方式以下:

- (void)updateWithFace:(UIImage *)img name:(NSString *)name { cv::Mat src = [img cvMatRepresentationGray]; NSInteger label = [_labelsArray indexOfObject:name]; if (label == NSNotFound) { [_labelsArray addObject:name]; label = [_labelsArray indexOfObject:name]; } vector<cv::Mat> images = vector<cv::Mat>(); images.push_back(src); vector<int> labels = vector<int>(); labels.push_back((int)label); self->_faceClassifier->update(images, labels); } 

這裏,咱們又作了一次了從 UIImage 到 cv::Matint 到 NSString 標籤的轉換。咱們還得如 OpenCV 的 FaceRecognizer::update API所指望的那樣,把咱們的參數放到 std::vector 實例中去。

如此「預測,得到反饋,更新循環」,就是文獻上所說的監督式學習

結論

OpenCV 是一個強大而用途普遍的庫,覆蓋了不少現現在仍在活躍的研究領域。想在一篇文章中給出詳細的使用說明只會是讓人徒勞的事情。所以,本文僅意在從較高層次對 OpenCV 庫作一個概述。同時,還試圖就如何集成 OpenCV 庫到你的 iOS 工程中給出一些實用建議,並經過一我的臉識別的例子來向你展現如何在一個真正的項目中使用 OpenCV。若是你以爲 OpenCV 對你的項目有用, OpenCV 的官方文檔寫得很是好很是詳細,請繼續前行,創造出下一個偉大的 app!


原文 Face Recognition with OpenCV

來自:https://objccn.io/issue-21-9/

相關文章
相關標籤/搜索