iOS利用CoreImage實(shí)現(xiàn)人臉識(shí)別詳解
前言
CoreImage是Cocoa Touch中一個(gè)強(qiáng)大的API,也是iOS SDK中的關(guān)鍵部分,不過(guò)它經(jīng)常被忽視。在本篇教程中,我會(huì)帶大家一起驗(yàn)證CoreImage的人臉識(shí)別特性。在開(kāi)始之前,我們先要簡(jiǎn)單了解下CoreImage framework 組成
CoreImage framework組成
Apple 已經(jīng)幫我們把image的處理分類好,來(lái)看看它的結(jié)構(gòu):
主要分為三個(gè)部分:
1、定義部分:CoreImage 和CoreImageDefines。見(jiàn)名思義,代表了CoreImage 這個(gè)框架和它的定義。
2、操作部分:
- 濾鏡(CIFliter):CIFilter 產(chǎn)生一個(gè)CIImage。典型的,接受一到多的圖片作為輸入,經(jīng)過(guò)一些過(guò)濾操作,產(chǎn)生指定輸出的圖片。
- 檢測(cè)(CIDetector):CIDetector 檢測(cè)處理圖片的特性,如使用來(lái)檢測(cè)圖片中人臉的眼睛、嘴巴、等等。
- 特征(CIFeature):CIFeature 代表由 detector處理后產(chǎn)生的特征。
3、圖像部分:
- 畫布(CIContext):畫布類可被用與處理Quartz 2D 或者 OpenGL??梢杂盟鼇?lái)關(guān)聯(lián)CoreImage類。如濾鏡、顏色等渲染處理。
- 顏色(CIColor): 圖片的關(guān)聯(lián)與畫布、圖片像素顏色的處理。
- 向量(CIVector): 圖片的坐標(biāo)向量等幾何方法處理。
- 圖片(CIImage): 代表一個(gè)圖像,可代表關(guān)聯(lián)后輸出的圖像。
在了解上述基本知識(shí)后,我們開(kāi)始通過(guò)創(chuàng)建一個(gè)工程來(lái)帶大家一步步驗(yàn)證Core Image的人臉識(shí)別特性。
將要構(gòu)建的應(yīng)用
iOS的人臉識(shí)別從iOS 5(2011)就有了,不過(guò)一直沒(méi)怎么被關(guān)注過(guò)。人臉識(shí)別API允許開(kāi)發(fā)者不僅可以檢測(cè)人臉,也可以檢測(cè)到面部的一些特殊屬性,比如說(shuō)微笑或眨眼。
首先,為了了解Core Image的人臉識(shí)別技術(shù)我們會(huì)創(chuàng)建一個(gè)app來(lái)識(shí)別照片中的人臉并用一個(gè)方框來(lái)標(biāo)記它。在第二個(gè)demo中,讓用戶拍攝一張照片,檢測(cè)其中的人臉并檢索人臉位置。這樣一來(lái),就充分掌握了iOS中的人臉識(shí)別,并且學(xué)會(huì)如何利用這個(gè)強(qiáng)大卻總被忽略的API。
話不多說(shuō),開(kāi)搞!
建立工程(我用的是Xcode8.0)
這里提供了初始工程,當(dāng)然你也可以自己創(chuàng)建(主要是為了方便大家)點(diǎn)我下載 用Xcode打開(kāi)下載后的工程,可以看到里面只有一個(gè)關(guān)聯(lián)了IBOutlet和imageView的StoryBoard。
使用CoreImage識(shí)別人臉
在開(kāi)始工程中,故事板中的imageView組件與代碼中的IBOutlet已關(guān)聯(lián),接下來(lái)要編寫實(shí)現(xiàn)人臉識(shí)別的代碼部分。在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") // 調(diào)用detect detect() } //MARK: - 識(shí)別面部 func detect() { // 創(chuàng)建personciImage變量保存從故事板中的UIImageView提取圖像并將其轉(zhuǎn)換為CIImage,使用Core Image時(shí)需要用CIImage guard let personciImage = CIImage(image: personPic.image!) else { return } // 創(chuàng)建accuracy變量并設(shè)為CIDetectorAccuracyHigh,可以在CIDetectorAccuracyHigh(較強(qiáng)的處理能力)與CIDetectorAccuracyLow(較弱的處理能力)中選擇,因?yàn)橄胱寽?zhǔn)確度高一些在這里選擇CIDetectorAccuracyHigh let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh] // 這里定義了一個(gè)屬于CIDetector類的faceDetector變量,并輸入之前創(chuàng)建的accuracy變量 let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy) // 調(diào)用faceDetector的featuresInImage方法,識(shí)別器會(huì)找到所給圖像中的人臉,最后返回一個(gè)人臉數(shù)組 let faces = faceDetector?.features(in: personciImage) // 循環(huán)faces數(shù)組里的所有face,并將識(shí)別到的人臉強(qiáng)轉(zhuǎn)為CIFaceFeature類型 for face in faces as! [CIFaceFeature] { print("Found bounds are \(face.bounds)") // 創(chuàng)建名為faceBox的UIView,frame設(shè)為返回的faces.first的frame,繪制一個(gè)矩形框來(lái)標(biāo)識(shí)識(shí)別到的人臉 let faceBox = UIView(frame: face.bounds) // 設(shè)置faceBox的邊框?qū)挾葹? faceBox.layer.borderWidth = 3 // 設(shè)置邊框顏色為紅色 faceBox.layer.borderColor = UIColor.red.cgColor // 將背景色設(shè)為clear,意味著這個(gè)視圖沒(méi)有可見(jiàn)的背景 faceBox.backgroundColor = UIColor.clear // 最后,把這個(gè)視圖添加到personPic imageView上 personPic.addSubview(faceBox) // API不僅可以幫助你識(shí)別人臉,也可識(shí)別臉上的左右眼,我們不在圖像中標(biāo)識(shí)出眼睛,只是給你展示一下CIFaceFeature的相關(guān)屬性 if face.hasLeftEyePosition { print("Left eye bounds are \(face.leftEyePosition)") } if face.hasRightEyePosition { print("Right eye bounds are \(face.rightEyePosition)") } } } }
編譯并運(yùn)行app,結(jié)果應(yīng)如下圖所示:
根據(jù)控制臺(tái)的輸出來(lái)看,貌似識(shí)別器識(shí)別到了人臉:
Found bounds are (314.0, 243.0, 196.0, 196.0)
當(dāng)前的實(shí)現(xiàn)中沒(méi)有解決的問(wèn)題:
- 人臉識(shí)別是在原始圖像上進(jìn)行的,由于原始圖像的分辨率比image view要高,因此需要設(shè)置image view的content mode為aspect fit(保持縱橫比的情況下縮放圖片)。為了合適的繪制矩形框,需要計(jì)算image view中人臉的實(shí)際位置與尺寸
- 還要注意的是,CoreImage與UIView使用兩種不同的坐標(biāo)系統(tǒng)(看下圖),因此要實(shí)現(xiàn)一個(gè)CoreImage坐標(biāo)到UIView坐標(biāo)的轉(zhuǎn)換。
UIView坐標(biāo)系:
CoreImage坐標(biāo)系:
現(xiàn)在使用下面的代碼替換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) // 轉(zhuǎn)換坐標(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)") // 應(yīng)用變換轉(zhuǎn)換坐標(biāo) 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)") } } }
上述代碼中,首先使用仿射變換(AffineTransform)將Core Image坐標(biāo)轉(zhuǎn)換為UIKit坐標(biāo),然后編寫了計(jì)算實(shí)際位置與矩形視圖尺寸的代碼。
再次運(yùn)行app,應(yīng)該會(huì)看到人的面部周圍會(huì)有一個(gè)框。OK,你已經(jīng)成功使用Core Image識(shí)別出了人臉。
但是有的童鞋在使用了上面的代碼運(yùn)行后可能會(huì)出現(xiàn)方框不存在(即沒(méi)有識(shí)別人臉)這種情況,這是由于忘記關(guān)閉Auto Layout以及Size Classes了。 選中storyBoard中的ViewController,選中view下的imageView。然后在右邊的面板中的第一個(gè)選項(xiàng)卡中找到use Auto Layout ,將前面的✔️去掉就可以了
經(jīng)過(guò)上面的設(shè)置后我們?cè)俅芜\(yùn)行App,就會(huì)看到圖三出現(xiàn)的效果了。
構(gòu)建一個(gè)人臉識(shí)別的相機(jī)應(yīng)用
想象一下你有一個(gè)用來(lái)照相的相機(jī)app,照完相后你想運(yùn)行一下人臉識(shí)別來(lái)檢測(cè)一下是否存在人臉。若存在一些人臉,你也許想用一些標(biāo)簽來(lái)對(duì)這些照片進(jìn)行分類。我們不會(huì)構(gòu)建一個(gè)保存照片后再處理的app,而是一個(gè)實(shí)時(shí)的相機(jī)app,因此需要整合一下UIImagePicker類,在照完相時(shí)立刻進(jìn)行人臉識(shí)別。
在開(kāi)始工程中已經(jīng)創(chuàng)建好了CameraViewController類,使用如下代碼實(shí)現(xiàn)相機(jī)的功能:
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委托為當(dāng)前視圖類,在didFinishPickingMediaWithInfo方法(UIImagePicker的委托方法)中設(shè)置imageView為在方法中所選擇的圖像,接著返回上一視圖調(diào)用detect函數(shù)。
還沒(méi)有實(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: "提示", message: "檢測(cè)到了人臉", 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: "未檢測(cè)到人臉", preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "確定", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } }
這個(gè)detect()
函數(shù)與之前實(shí)現(xiàn)的detect函數(shù)非常像,不過(guò)這次只用它來(lái)獲取圖像不做變換。當(dāng)識(shí)別到人臉后顯示一個(gè)警告信息“檢測(cè)到了人臉!”,否則顯示“未檢測(cè)到人臉”。運(yùn)行app測(cè)試一下:
我們已經(jīng)使用到了一些CIFaceFeature的屬性與方法,比如,若想檢測(cè)人物是否微笑,可以調(diào)用.hasSmile,它會(huì)返回一個(gè)布爾值??梢苑謩e使用.hasLeftEyePosition與.hasRightEyePosition檢測(cè)是否存在左右眼。
同樣,可以調(diào)用hasMouthPosition來(lái)檢測(cè)是否存在嘴,若存在則可以使用mouthPosition屬性,如下所示:
if (face.hasMouthPosition) { print("mouth detected") }
如你所見(jiàn),使用Core Image來(lái)檢測(cè)面部特征是非常簡(jiǎn)單的。除了檢測(cè)嘴、笑容、眼睛外,也可以調(diào)用leftEyeClosed與rightEyeClosed檢測(cè)左右眼是否睜開(kāi),這里就不在貼出代碼了。
總結(jié)
在這篇教程中嘗試了CoreImage的人臉識(shí)別API與如何在一個(gè)相機(jī)app中應(yīng)用它,構(gòu)建了一個(gè)簡(jiǎn)單的UIImagePicker來(lái)選取照片并檢測(cè)圖像中是否存在人物。
如你所見(jiàn),Core Image的人臉識(shí)別是個(gè)強(qiáng)大的API!希望這篇教程能給你提供一些關(guān)于這個(gè)鮮為人知的iOS API有用的信息。
Github地址:點(diǎn)擊swift版地址,OC版地址下載
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
iOS開(kāi)發(fā)之路--仿網(wǎng)易抽屜效果
本文是IOS開(kāi)發(fā)之路系列的第一篇,主要講訴了如何仿網(wǎng)易新聞客戶端實(shí)現(xiàn)抽屜效果,全部源代碼都分享給大家,希望對(duì)大家有所幫助2014-08-08iOS 使用Moya網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法
這篇文章主要介紹了iOS 使用Moya網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07IOS開(kāi)發(fā)自定義view方法規(guī)范示例
這篇文章主要為大家介紹了IOS開(kāi)發(fā)自定義view方法規(guī)范示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07iOS自定義轉(zhuǎn)場(chǎng)動(dòng)畫的幾種情況
這篇文章主要給大家介紹了關(guān)于iOS自定義轉(zhuǎn)場(chǎng)動(dòng)畫的幾種情況,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位iOS開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06iOS App中UIPickerView選擇欄控件的使用實(shí)例解析
這篇文章主要介紹了iOS App中的UIPickerView選擇欄控件的使用,文中演示了兩個(gè)超詳細(xì)的例子,示例代碼為Objective-C,需要的朋友可以參考下2016-04-04