作者: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。你可以選擇CIDetectorAccuracyHigh或CIDetectorAccuracyLow。本文希望得到高精度的結(jié)果,因此選擇了CIDetectorAccuracyHigh。 - 第 8 行:創(chuàng)建一個(gè)
faceDetector變量并設(shè)置為CIDetector的實(shí)例。實(shí)例化CIDetector時(shí)將前文創(chuàng)建的accuracy作為參數(shù)傳入。 - 第 9 行:通過調(diào)用
faceDetector的features(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。