基於iOS用CoreImage實現人臉識別

2018-09-04更新: 好久沒有更新文章了,工做之餘花時間看了以前寫的這篇文章並運行了以前寫的配套Demo,經過打印人臉特徵CIFaceFeature的屬性,發現識別的效果並非很好,具體說明見文章最底部的更新標題,後續我將分別用OpenCV(跨平臺計算機視覺庫) 和 Vision (iOS 11新API)兩種庫實現人臉面部識別,敬請期待~~
OC版下載地址, swift版下載地址swift

```
CoreImage是Cocoa Touch中一個強大的API,也是iOS SDK中的關鍵部分,不過它常常被忽視。在本篇教程中,我會帶你們一塊兒驗證CoreImage的人臉識別特性。在開始以前,咱們先要簡單瞭解下CoreImage framework 組成
CoreImage framework組成
Apple 已經幫咱們把image的處理分類好,來看看它的結構:數組

 

主要分爲三個部分:app


1.定義部分:CoreImage 和CoreImageDefines。見名思義,表明了CoreImage 這個框架和它的定義。
2.操做部分:框架

濾鏡(CIFliter):CIFilter 產生一個CIImage。典型的,接受一到多的圖片做爲輸入,通過一些過濾操做,產生指定輸出的圖片。
檢測(CIDetector):CIDetector 檢測處理圖片的特性,如使用來檢測圖片中人臉的眼睛、嘴巴、等等。
特徵(CIFeature):CIFeature 表明由 detector處理後產生的特徵。ide


3.圖像部分:函數

畫布(CIContext):畫布類可被用與處理Quartz 2D 或者 OpenGL。能夠用它來關聯CoreImage類。如濾鏡、顏色等渲染處理。
顏色(CIColor): 圖片的關聯與畫布、圖片像素顏色的處理。
向量(CIVector): 圖片的座標向量等幾何方法處理。
圖片(CIImage): 表明一個圖像,可表明關聯後輸出的圖像。測試

 

在瞭解上述基本知識後,咱們開始經過建立一個工程來帶你們一步步驗證Core Image的人臉識別特性。
將要構建的應用阿里雲

iOS的人臉識別從iOS 5(2011)就有了,不過一直沒怎麼被關注過。人臉識別API容許開發者不只能夠檢測人臉,也能夠檢測到面部的一些特殊屬性,好比說微笑或眨眼。
首先,爲了瞭解Core Image的人臉識別技術咱們會建立一個app來識別照片中的人臉並用一個方框來標記它。在第二個demo中,讓用戶拍攝一張照片,檢測其中的人臉並檢索人臉位置。這樣一來,就充分掌握了iOS中的人臉識別,而且學會如何利用這個強大卻總被忽略的API。
話很少說,開搞!
創建工程(我用的是Xcode8.0)3d

這裏提供了初始工程,固然你也能夠本身建立(主要是爲了方便你們)點我下載 用Xcode打開下載後的工程,能夠看到裏面只有一個關聯了IBOutlet和imageView的StoryBoard。code

使用CoreImage識別人臉

在開始工程中,故事板中的imageView組件與代碼中的IBOutlet已關聯,接下來要編寫實現人臉識別的代碼部分。在ViewController.swift文件中寫下以下代碼:

import UIKit
import CoreImage // 引入CoreImage
class ViewController: UIViewController {
    @IBOutlet weak var personPic: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        personPic.image = UIImage(named: "face-1")
        // 調用detect
        detect()

    }
    //MARK: - 識別面部
    func detect() {
        // 建立personciImage變量保存從故事板中的UIImageView提取圖像並將其轉換爲CIImage,使用Core Image時須要用CIImage
        guard let personciImage = CIImage(image: personPic.image!) else {
            return
        }
        // 建立accuracy變量並設爲CIDetectorAccuracyHigh,能夠在CIDetectorAccuracyHigh(較強的處理能力)與CIDetectorAccuracyLow(較弱的處理能力)中選擇,由於想讓準確度高一些在這裏選擇CIDetectorAccuracyHigh
        let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
        // 這裏定義了一個屬於CIDetector類的faceDetector變量,並輸入以前建立的accuracy變量
        let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
        // 調用faceDetector的featuresInImage方法,識別器會找到所給圖像中的人臉,最後返回一我的臉數組
        let faces = faceDetector?.features(in: personciImage)
        // 循環faces數組裏的全部face,並將識別到的人臉強轉爲CIFaceFeature類型
        for face in faces as! [CIFaceFeature] {
            
            print("Found bounds are \(face.bounds)")
            // 建立名爲faceBox的UIView,frame設爲返回的faces.first的frame,繪製一個矩形框來標識識別到的人臉
            let faceBox = UIView(frame: face.bounds)
            // 設置faceBox的邊框寬度爲3
            faceBox.layer.borderWidth = 3
            // 設置邊框顏色爲紅色
            faceBox.layer.borderColor = UIColor.red.cgColor
            // 將背景色設爲clear,意味着這個視圖沒有可見的背景
            faceBox.backgroundColor = UIColor.clear
            // 最後,把這個視圖添加到personPic imageView上
            personPic.addSubview(faceBox)
            // API不只能夠幫助你識別人臉,也可識別臉上的左右眼,咱們不在圖像中標識出眼睛,只是給你展現一下CIFaceFeature的相關屬性
            if face.hasLeftEyePosition {
                print("Left eye bounds are \(face.leftEyePosition)")
            }
            
            if face.hasRightEyePosition {
                print("Right eye bounds are \(face.rightEyePosition)")
            }
        }
    }
}

  編譯並運行app,結果應以下圖所示:

2.png
根據控制檯的輸出來看,貌似識別器識別到了人臉:
Found bounds are (314.0, 243.0, 196.0, 196.0)
當前的實現中沒有解決的問題:

人臉識別是在原始圖像上進行的,因爲原始圖像的分辨率比image view要高,所以須要設置image view的content mode爲aspect fit(保持縱橫比的狀況下縮放圖片)。爲了合適的繪製矩形框,須要計算image view中人臉的實際位置與尺寸
還要注意的是,CoreImage與UIView使用兩種不一樣的座標系統(看下圖),所以要實現一個CoreImage座標到UIView座標的轉換。

UIView座標系:

 

CoreImage座標系:

 

如今使用下面的代碼替換detect()方法:

func detect1() {

    guard let personciImage = CIImage(image: personPic.image!) else { return }
    let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
    let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
    let faces = faceDetector?.features(in: personciImage)
        
    // 轉換座標系
    let ciImageSize = personciImage.extent.size
    var transform = CGAffineTransform(scaleX: 1, y: -1)
    transform = transform.translatedBy(x: 0, y: -ciImageSize.height)
        
    for face in faces as! [CIFaceFeature] {
        print("Found bounds are \(face.bounds)")     
        // 應用變換轉換座標
        var faceViewBounds = face.bounds.applying(transform)
        // 在圖像視圖中計算矩形的實際位置和大小
        let viewSize = personPic.bounds.size
        let scale = min(viewSize.width / ciImageSize.width, viewSize.height / ciImageSize.height)
        let offsetX = (viewSize.width - ciImageSize.width * scale) / 2
        let offsetY = (viewSize.height - ciImageSize.height * scale) / 2
            
        faceViewBounds = faceViewBounds.applying(CGAffineTransform(scaleX: scale, y: scale))
        faceViewBounds.origin.x += offsetX
        faceViewBounds.origin.y += offsetY
            
        let faceBox = UIView(frame: faceViewBounds)
        faceBox.layer.borderWidth = 3
        faceBox.layer.borderColor = UIColor.red.cgColor
        faceBox.backgroundColor = UIColor.clear
        personPic.addSubview(faceBox)
            
        if face.hasLeftEyePosition {
            print("Left eye bounds are \(face.leftEyePosition)")
        }
            
        if face.hasRightEyePosition {
            print("Right eye bounds are \(face.rightEyePosition)")
        }
    }
}

  

 

上述代碼中,首先使用仿射變換(AffineTransform)將Core Image座標轉換爲UIKit座標,而後編寫了計算實際位置與矩形視圖尺寸的代碼。

再次運行app,應該會看到人的面部周圍會有一個框。OK,你已經成功使用Core Image識別出了人臉。

可是有的童鞋在使用了上面的代碼運行後可能會出現方框不存在(即沒有識別人臉)這種狀況,這是因爲忘記關閉Auto Layout以及Size Classes了。 選中storyBoard中的ViewController,選中view下的imageView。而後在右邊的面板中的第一個選項卡中找到use Auto Layout ,將前面的✔️去掉就能夠了

通過上面的設置後咱們再次運行App,就會看到圖三出現的效果了。

構建一我的臉識別的相機應用

想象一下你有一個用來照相的相機app,照完相後你想運行一下人臉識別來檢測一下是否存在人臉。若存在一些人臉,你也許想用一些標籤來對這些照片進行分類。咱們不會構建一個保存照片後再處理的app,而是一個實時的相機app,所以須要整合一下UIImagePicker類,在照完相時馬上進行人臉識別。
在開始工程中已經建立好了CameraViewController類,使用以下代碼實現相機的功能:

class CameraViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    @IBOutlet var imageView: UIImageView!
    let imagePicker = UIImagePickerController()

    override func viewDidLoad() {
        super.viewDidLoad()
        imagePicker.delegate = self
    }
    
    @IBAction func takePhoto(_ sender: AnyObject) {
        
        if !UIImagePickerController.isSourceTypeAvailable(.camera) {
            return
        }
        
        imagePicker.allowsEditing = false
        imagePicker.sourceType = .camera
        
        present(imagePicker, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
            imageView.contentMode = .scaleAspectFit
            imageView.image = pickedImage
        }
        
        dismiss(animated: true, completion: nil)
        self.detect()
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }
}

  

前面幾行設置UIImagePicker委託爲當前視圖類,在didFinishPickingMediaWithInfo方法(UIImagePicker的委託方法)中設置imageView爲在方法中所選擇的圖像,接着返回上一視圖調用detect函數。
尚未實現detect函數,插入下面代碼並分析一下

func detect() {
    let imageOptions =  NSDictionary(object: NSNumber(value: 5) as NSNumber, forKey: CIDetectorImageOrientation as NSString)
    let personciImage = CIImage(cgImage: imageView.image!.cgImage!)
    let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
    let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
    let faces = faceDetector?.features(in: personciImage, options: imageOptions as? [String : AnyObject])
        
    if let face = faces?.first as? CIFaceFeature {
        print("found bounds are \(face.bounds)")
            
        let alert = UIAlertController(title: "提示", message: "檢測到了人臉", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "肯定", style: UIAlertActionStyle.default, handler: nil))
        self.present(alert, animated: true, completion: nil)
            
        if face.hasSmile {
            print("face is smiling");
        }
            
        if face.hasLeftEyePosition {
            print("左眼的位置: \(face.leftEyePosition)")
        }
            
        if face.hasRightEyePosition {
            print("右眼的位置: \(face.rightEyePosition)")
        }
    } else {
        let alert = UIAlertController(title: "提示", message: "未檢測到人臉", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "肯定", style: UIAlertActionStyle.default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
}

  這個detect()函數與以前實現的detect函數很是像,不過此次只用它來獲取圖像不作變換。當識別到人臉後顯示一個警告信息「檢測到了人臉!」,不然顯示「未檢測到人臉」。運行app測試一下:

 

 

 

 

咱們已經使用到了一些CIFaceFeature的屬性與方法,好比,若想檢測人物是否微笑,能夠調用.hasSmile,它會返回一個布爾值。能夠分別使用.hasLeftEyePosition與.hasRightEyePosition檢測是否存在左右眼。

一樣,能夠調用hasMouthPosition來檢測是否存在嘴,若存在則可使用mouthPosition屬性,以下所示:

if (face.hasMouthPosition) {
    print("mouth detected")
}

  

如你所見,使用Core Image來檢測面部特徵是很是簡單的。除了檢測嘴、笑容、眼睛外,也能夠調用leftEyeClosed與rightEyeClosed檢測左右眼是否睜開,這裏就不在貼出代碼了。
總結

在這篇教程中嘗試了CoreImage的人臉識別API與如何在一個相機app中應用它,構建了一個簡單的UIImagePicker來選取照片並檢測圖像中是否存在人物。
如你所見,Core Image的人臉識別是個強大的API!但願這篇教程能給你提供一些關於這個不爲人知的iOS API有用的信息。
點擊swift版地址,OC版地址下載最終工程, 若是以爲對您有幫助的話,請幫我點個星星哦,您的星星是對我最大的支持。(__) 嘻嘻……**
更新:
好久沒有更新文章了,工做之餘花時間回顧了以前寫的這篇文章並運行了以前寫的配套Demo,經過打印人臉特徵CIFaceFeature的屬性(以下),發現識別的效果並非很好,以下圖:

人臉特徵CIFaceFeature的屬性

/** CIDetector發現的臉部特徵。
  全部的位置都是相對於原始圖像. */
NS_CLASS_AVAILABLE(10_7, 5_0)
@interface CIFaceFeature : CIFeature
{
    CGRect bounds;
    BOOL hasLeftEyePosition;
    CGPoint leftEyePosition;
    BOOL hasRightEyePosition;
    CGPoint rightEyePosition;
    BOOL hasMouthPosition;
    CGPoint mouthPosition;
    
    BOOL hasTrackingID;
    int trackingID;
    BOOL hasTrackingFrameCount;
    int trackingFrameCount;
    
    BOOL hasFaceAngle;
    float faceAngle;
    
    BOOL hasSmile;
    BOOL leftEyeClosed;
    BOOL rightEyeClosed;
}

/** coordinates of various cardinal points within a face.
 臉部各個基點的座標。

 Note that the left eye is the eye on the left side of the face
 from the observer's perspective. It is not the left eye from
 the subject's perspective.
請注意,左眼是臉左側的眼睛從觀察者的角度來看。 這不是左眼主體的視角.
 */
@property (readonly, assign) CGRect bounds;              // 指示圖像座標中的人臉位置和尺寸的矩形。
@property (readonly, assign) BOOL hasLeftEyePosition;    // 指示檢測器是否找到了人臉的左眼。
@property (readonly, assign) CGPoint leftEyePosition;    // 左眼的座標
@property (readonly, assign) BOOL hasRightEyePosition;   // 指示檢測器是否找到了人臉的右眼。
@property (readonly, assign) CGPoint rightEyePosition;   // 右眼的座標
@property (readonly, assign) BOOL hasMouthPosition;      // 指示檢測器是否找到了人臉的嘴部
@property (readonly, assign) CGPoint mouthPosition;      // 嘴部的座標

@property (readonly, assign) BOOL hasTrackingID;         // 指示面部對象是否具備跟蹤ID。
/**
 * 關於trackingID:
 * coreImage提供了在視頻流中檢測到的臉部的跟蹤標識符,您可使用該標識符來識別在一個視頻幀中檢測到的CIFaceFeature對象是在先前視頻幀中檢測到的同一個臉部。
 * 只有在框架中存在人臉而且不與特定人臉相關聯時,該標識符纔會一直存在。若是臉部移出視頻幀並在稍後返回到幀中,則分配另外一個ID。 (核心圖像檢測面部,但不識別特定的面部。)
 * 這個有點抽象
 */
@property (readonly, assign) int trackingID;
@property (readonly, assign) BOOL hasTrackingFrameCount; // 指示面部對象的布爾值具備跟蹤幀計數。
@property (readonly, assign) int trackingFrameCount;     // 跟蹤幀計數

@property (readonly, assign) BOOL hasFaceAngle;          // 指示是否有關於臉部旋轉的信息可用。
@property (readonly, assign) float faceAngle;            // 旋轉是以度數逆時針測量的,其中零指示在眼睛之間畫出的線相對於圖像方向是水平的。

@property (readonly, assign) BOOL hasSmile;              // 是否有笑臉
@property (readonly, assign) BOOL leftEyeClosed;         // 左眼是否閉上
@property (readonly, assign) BOOL rightEyeClosed;        // 右眼是否閉上

 

問題:那麼如何讓人臉識別的效果更好呢? 如何讓面部識別點更加精確呢?有沒有別的方法呢? 答案是確定的。
如今市面上有不少成熟的面部識別產品:

Face++, 收費

Video++,收費

ArcFace 虹軟人臉認知引擎, 收費

百度雲人臉識別, 收費

阿里雲識別, 收費

等等, 咱們看到都是收費的。 固然這些sdk是能夠試用的。若是你有折騰精神,想本身嘗試人臉識別的實現,咱們能夠一塊兒交流。 畢竟市面上的這些sdk也不是一開始就有的, 也是經過人們不斷研究開發出來的。 並且本身折騰過程當中,經過不斷地遇坑爬坑,對知識的理解更加深透,本身的技術也會有增進,不是嗎? 很差意思,有點扯遠了。Core Image只是簡單的圖像識別, 並不能對流中的人臉進行識別。 它只適合對圖片的處理。比較有名的OpenCV(跨平臺計算機視覺庫)就能夠用來進行面部識別,識別精度天然很高。還有就是iOS 11.0+ 推出的Vision框架(讓咱們輕鬆訪問蘋果的模型,用於面部檢測、面部特徵點、文字、矩形、條形碼和物體)也能夠進行面部識別。後面我將會用這兩個框架講解如何進行面部識別。敬請期待!!!

相關文章
相關標籤/搜索