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

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

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

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

接下來要做的事

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

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

下面開始吧!

設(shè)置工程

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

:項目中的圖片由 unsplash.com 提供。

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

import CoreImage

用 Core Image 實現(xiàn)人臉檢測

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

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

detect()

編譯并運行程序,可以看到如下效果:

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

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

還有幾個問題沒有處理:

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

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

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

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

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

上面的起始工程中已經(jīng)創(chuàng)建了一個 CameraViewController 類,將其代碼更新成下面這樣,用以實現(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 方法(這是一個 UIImagePicker 代理方法)中,將傳入的圖像設(shè)置到 imageView 上,最后關(guān)閉拾取器并調(diào)用 detect 函數(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ù)和之前的實現(xiàn)非常相似,不過這一次我們使用的是臨時拍到的圖像。根據(jù)檢測結(jié)果會顯示一個提示框,提示是否檢測到人臉。運行程序來快速測試一下。

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)行人臉識別極其簡單。除了檢測嘴、微笑、眼睛位置等,還可以通過 leftEyeClosed (或rightEyeClosed)判斷左眼(或右眼)是否睜開。

結(jié)語

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

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,769評論 2 372

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