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