在 iOS 上用 Core Image 實(shí)現(xiàn)人臉檢測(cè)

作者:Gregg Mojica,原文鏈接,原文日期:2016-09-06
譯者:智多芯;校對(duì):Crystal Sun;定稿:CMB

Core Image 是 Cocoa Touch 框架提供的功能強(qiáng)大的 API,是 iOS SDK 中常常被忽視的關(guān)鍵部件。本教程將嘗試探索 Core Image 提供的人臉識(shí)別功能,并將其應(yīng)用到 iOS App 中。

注:這是中高級(jí) iOS 教程,本教程假設(shè)你已經(jīng)使用過類似 UIImagePicker,Core Image 等技術(shù)。如果你對(duì)這些還不熟悉,先看看我們的 iOS 教程系列,等你準(zhǔn)備好了再看這篇文章。

接下來要做的事

自從 iOS 5(大概在2011年左右)之后,iOS 開始支持人臉識(shí)別,只是用的人不多。人臉識(shí)別 API 讓開發(fā)者不僅可以進(jìn)行人臉檢測(cè),還能識(shí)別微笑、眨眼等表情。

首先創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用,探索一下 Core Image 提供的人臉識(shí)別技術(shù),該應(yīng)用可以識(shí)別出照片中的人臉并用方框?qū)⑷四樋蚱饋?。在第二個(gè)例子中,用戶可以拍照并檢測(cè)照片上是否有人臉出現(xiàn),如果有則提取人臉坐標(biāo)。通過這兩個(gè)例子,你將學(xué)會(huì) iOS 上所有關(guān)于人臉識(shí)別的技術(shù),并充分利用它強(qiáng)大卻經(jīng)常被忽視的功能。

下面開始吧!

設(shè)置工程

下載并在 Xcode 中打開起始工程。該工程中的 Storyboard 僅包含一個(gè)已連接到代碼的 IBOutlet 和 imageView。

:項(xiàng)目中的圖片由 unsplash.com 提供。

在開始使用 Core Image 進(jìn)行人臉識(shí)別之前,需要將 Core Image 庫導(dǎo)入項(xiàng)目中。打開 ViewController.swift 文件,在文件最上方插入如下代碼:

import CoreImage

用 Core Image 實(shí)現(xiàn)人臉檢測(cè)

在起始工程的 storyboard 里包含一個(gè)通過 IBOutlet 連接到代碼中的 imageView。下一步將實(shí)現(xiàn)人臉檢測(cè)的代碼。先把以下代碼加入 swift 文件中,后面再解釋:

func detect() {
    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)
    
    for face in faces as! [CIFaceFeature] {
        print("Found bounds are \(face.bounds)")
        
        let faceBox = UIView(frame: face.bounds)
        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)")
        }
    }
}

這里解釋一下上面的代碼:

  • 第 3 行:從 storyboard 中的 UIImageView 提取出 UIImage 并轉(zhuǎn)換成 CIImage,將其保存在新創(chuàng)建的 personciImage 變量中。Core Image 需要用到 CIImage。
  • 第 7 行:創(chuàng)建一個(gè) accuracy 變量并設(shè)置為 CIDetectorAccuracyHigh。你可以選擇 CIDetectorAccuracyHighCIDetectorAccuracyLow。本文希望得到高精度的結(jié)果,因此選擇了 CIDetectorAccuracyHigh。
  • 第 8 行:創(chuàng)建一個(gè) faceDetector 變量并設(shè)置為 CIDetector 的實(shí)例。實(shí)例化 CIDetector 時(shí)將前文創(chuàng)建的 accuracy 作為參數(shù)傳入。
  • 第 9 行:通過調(diào)用 faceDetectorfeatures(in:) 方法可檢測(cè)出給定圖像的所有人臉,最終以數(shù)組的形式返回所有人臉。
  • 第 11 行:遍歷數(shù)組中所有的人臉,并將其轉(zhuǎn)換為 CIFaceFeature 類型。
  • 第 15 行:創(chuàng)建一個(gè) UIView 實(shí)例并命名為 faceBox,然后根據(jù) faces.first 設(shè)置其大小。這將畫一個(gè)方框用于高亮檢測(cè)到的人臉。
  • 第 17 行:將 faceBox 的邊框?qū)挾仍O(shè)為 3。
  • 第 18 行:將邊框顏色設(shè)置為紅色。
  • 第 19 行:將背景色設(shè)為透明,表示該視圖沒有可見的背景。
  • 第 20 行:最后,將該視圖添加到 personPic 視圖中。
  • 第 22-28 行:這些 API 不僅可以檢測(cè)出人臉,還能檢測(cè)出人臉的左右眼,但本文就不在圖像中高亮人眼了。本文只想展示一些 CIFaceFeature 的相關(guān)屬性。

接著調(diào)用在 viewDidLoad中調(diào)用 detect 方法,在方法中增加下列一行代碼:

detect()

編譯并運(yùn)行程序,可以看到如下效果:

根據(jù)控制臺(tái)的輸出結(jié)果,似乎可以檢測(cè)出人臉:

Found bounds are (177.0, 415.0, 380.0, 380.0)

還有幾個(gè)問題沒有處理:

  • 人臉識(shí)別程序應(yīng)用于原始圖像上,而原始圖像有著比 imageView 更高的分辨率。另外,工程中 imageView 的 content mode 被設(shè)置為 aspect fit。為了正確地畫出檢測(cè)框,還需要計(jì)算出 imageView 中識(shí)別到的人臉的實(shí)際位置和尺寸。
  • 再者,Core Image 和 UIView(或者UIKit)使用了不同的坐標(biāo)系(如下圖所示),因此還需要實(shí)現(xiàn) Core Image 坐標(biāo)到 UIView 坐標(biāo)的轉(zhuǎn)換。

現(xiàn)在使用下面的代碼替換 detect() 方法中的代碼:

func detect() {
    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)
    
    // 將 Core Image 坐標(biāo)轉(zhuǎn)換成 UIView 坐標(biāo)
    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)")
        
        // 實(shí)現(xiàn)坐標(biāo)轉(zhuǎn)換
        var faceViewBounds = face.bounds.applying(transform)
        
        // 計(jì)算實(shí)際的位置和大小
        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)")
        }
    }
}

首先,上面的代碼使用放射變換將 Core Image 坐標(biāo)轉(zhuǎn)換成了 UIKit 坐標(biāo)。然后,添加了一些額外的代碼用于計(jì)算框視圖的實(shí)際位置和尺寸。

現(xiàn)在再一次運(yùn)行程序,應(yīng)該可以看到檢測(cè)框?qū)⒆R(shí)別出的人臉框起來了,這樣就成功地用 Core Image 檢測(cè)到人臉了。

開發(fā)一個(gè)支持人臉識(shí)別的攝像應(yīng)用

假設(shè)有一個(gè)用于攝像或拍照的應(yīng)用程序,我們希望在拍照后檢測(cè)是否有人臉出現(xiàn)。如果出現(xiàn)了人臉,可能想將這張照片打上一些標(biāo)簽并對(duì)其分類。下面結(jié)合 UIImagePicker 類,拍照完成時(shí)立刻運(yùn)行上面的人臉檢測(cè)代碼。

上面的起始工程中已經(jīng)創(chuàng)建了一個(gè) CameraViewController 類,將其代碼更新成下面這樣,用以實(shí)現(xiàn)攝像功能:

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)
    }
}

開始的幾行代碼設(shè)置了 UIImagePicker 代理。在 didFinishPickingMediaWithInfo 方法(這是一個(gè) UIImagePicker 代理方法)中,將傳入的圖像設(shè)置到 imageView 上,最后關(guān)閉拾取器并調(diào)用 detect 函數(shù)。

上面的代碼還未實(shí)現(xiàn) detect 函數(shù),將下面的代碼加上:

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: "Say Cheese!", message: "We detected a face!", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
        self.present(alert, animated: true, completion: nil)
        
        if face.hasSmile {
            print("face is smiling");
        }
        
        if face.hasLeftEyePosition {
            print("Left eye bounds are \(face.leftEyePosition)")
        }
        
        if face.hasRightEyePosition {
            print("Right eye bounds are \(face.rightEyePosition)")
        }
    } else {
        let alert = UIAlertController(title: "No Face!", message: "No face was detected", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
}

這里的 detect() 函數(shù)和之前的實(shí)現(xiàn)非常相似,不過這一次我們使用的是臨時(shí)拍到的圖像。根據(jù)檢測(cè)結(jié)果會(huì)顯示一個(gè)提示框,提示是否檢測(cè)到人臉。運(yùn)行程序來快速測(cè)試一下。

CIFaceFeature 中的一些屬性和方法前面已經(jīng)嘗試過了。例如,若要判斷照片中的人是否正在微笑,可以通過 hasSmile 屬性判斷。還可以通過 hasLeftEyePosition (或hasRightEyePosition)屬性檢查是否有左眼(或右眼)出現(xiàn)(希望有)。

還可以通過 hasMouthPosition 來判斷是否出現(xiàn)了嘴巴。如果出現(xiàn)了,可以通過 mouthPosition 屬性得到其坐標(biāo),代碼如下:

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

如你所見,通過 Core Image 進(jìn)行人臉識(shí)別極其簡(jiǎn)單。除了檢測(cè)嘴、微笑、眼睛位置等,還可以通過 leftEyeClosed (或rightEyeClosed)判斷左眼(或右眼)是否睜開。

結(jié)語

本教程探索了 Core Image 提供的人臉識(shí)別 API,并展示了如何在攝像機(jī)應(yīng)用中使用該功能。本文通過 UIImagePicker 拍攝圖像,并檢測(cè)該圖像中是否有人的出現(xiàn)。

如你所見,Core Image 的人臉識(shí)別 API 有著非常多的用處!希望你能覺得本教程有所幫助,讓你了解到了這一鮮為人知的 iOS API!

注:歡迎繼續(xù)關(guān)注讓人臉識(shí)別更加強(qiáng)大的神經(jīng)網(wǎng)絡(luò)系列教程。

你可以從這里下載到最終的工程代碼。

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請(qǐng)?jiān)L問 http://swift.gg。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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