iOS黑科技之(AVFoundation)動(dòng)態(tài)人臉識(shí)別(二)
上一篇介紹了
Core Image
實(shí)現(xiàn)的靜態(tài)人臉識(shí)別, 這里介紹AVFoundation
的強(qiáng)大功能之一的動(dòng)態(tài)人臉識(shí)別
一. 首先介紹一些人臉識(shí)別的方式
1. CoreImage
靜態(tài)人臉識(shí)別, 可識(shí)別照片, 圖像等
- 詳情可查看上一篇博客介紹
3. OpenCV
- 由一系列 C 函數(shù)和少量 C++ 類構(gòu)成, 實(shí)現(xiàn)了圖像處理和計(jì)算機(jī)視覺方面的很多通用算法, 其他的具體的不是很了解
4. Vision
- Vision 是 Apple 在 WWDC 2017 伴隨iOS 11推出的基于CoreML的圖像識(shí)別框架
- 根據(jù)Vision官方文檔看,
Vision
本身就有Face Detection and Recognition
(人臉檢測識(shí)別)、Machine Learning Image Analysis
(機(jī)器學(xué)習(xí)圖片分析)、Barcode Detection
(條形碼檢測)、Text Detection
(文本檢測)薄风。。丐一。卓练。。等等這些功能 - 感興趣的同學(xué)可以查看相關(guān)文檔學(xué)習(xí)一下, 這里小編就不過多作介紹了
5. AVFoundation
- 可以用來使用和創(chuàng)建基于時(shí)間的視聽媒體的框架
- 這里我們使用的人臉識(shí)別方式也是使用
AVFoundation
框架
二. 對關(guān)鍵類的簡單介紹
1. AVCaptureDevice
:代表硬件設(shè)備
- 我們可以從這個(gè)類中獲取手機(jī)硬件的照相機(jī)晒哄、聲音傳感器等睁宰。
- 當(dāng)我們在應(yīng)用程序中需要改變一些硬件設(shè)備的屬性(例如:切換攝像頭肪获、閃光模式改變、相機(jī)聚焦改變)的時(shí)候必須要先為設(shè)備加鎖柒傻,修改完成后解鎖孝赫。
- 示例: 切換攝像頭
//4. 移除舊輸入,添加新輸入
//4.1 設(shè)備加鎖
session.beginConfiguration()
//4.2. 移除舊設(shè)備
session.removeInput(deviceIn)
//4.3 添加新設(shè)備
session.addInput(newVideoInput)
//4.4 設(shè)備解鎖
session.commitConfiguration()
2. AVCaptureDeviceInput
:設(shè)備輸入數(shù)據(jù)管理對象
- 可以根據(jù)
AVCaptureDevice
創(chuàng)建對應(yīng)的AVCaptureDeviceInput對象红符, - 該對象將會(huì)被添加到AVCaptureSession中管理,代表輸入設(shè)備青柄,它配置抽象硬件設(shè)備的ports。通常的輸入設(shè)備有(麥克風(fēng)预侯,相機(jī)等)
3. AVCaptureOutput
: 代表輸出數(shù)據(jù)
- 輸出的可以是圖片(
AVCaptureStillImageOutput
)或者視頻(AVCaptureMovieFileOutput
)
4. AVCaptureSession
: 媒體(音致开、視頻)捕捉會(huì)話
- 負(fù)責(zé)把捕捉的音頻視頻數(shù)據(jù)輸出到輸出設(shè)備中。
- 一個(gè)
AVCaptureSession
可以有多個(gè)輸入或輸出萎馅。 - 是連接
AVCaptureInput
和AVCaptureOutput
的橋梁喇喉,它協(xié)調(diào)input到output之間傳輸數(shù)據(jù)。 - 它有startRunning和stopRunning兩種方法來開啟會(huì)話和結(jié)束會(huì)話校坑。
- 每個(gè)session稱之為一個(gè)會(huì)話,也就是在應(yīng)用運(yùn)行過程中如果你需要改變會(huì)話的一些配置(例如:切換攝像頭),此時(shí)需要先開啟配置千诬,配置完成之后再提交配置耍目。
5. AVCaptureVideoPreviewLayer
: 圖片預(yù)覽層
- 我們的照片以及視頻是如何顯示在手機(jī)上的呢?那就是通過把這個(gè)對象添加到
UIView
的layer
上的
好了, 上面吧啦吧啦的說了那么多廢話, 那么我們的人臉識(shí)別究竟是怎樣實(shí)現(xiàn)的呢? 下面干貨來了
三. 添加掃描設(shè)備
- 獲取設(shè)備(攝像頭)
- 創(chuàng)建輸入設(shè)備
- 創(chuàng)建掃描輸出
- 創(chuàng)建捕捉回話
1. 輸出設(shè)備
- 這里使用
AVCaptureMetadataOutput
, 可以掃描人臉, 二維碼, 條形碼等信息 - 必須設(shè)置代理, 否則獲取不到掃描結(jié)果
- 需要設(shè)置要輸出什么樣的數(shù)據(jù): face(人臉), qr(二維碼)等等
//3.創(chuàng)建原數(shù)據(jù)的輸出對象
let metadataOutput = AVCaptureMetadataOutput()
//4.設(shè)置代理監(jiān)聽輸出對象輸出的數(shù)據(jù)徐绑,在主線程中刷新
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//7.告訴輸出對象要輸出什么樣的數(shù)據(jù),識(shí)別人臉, 最多可識(shí)別10張人臉
metadataOutput.metadataObjectTypes = [.face]
主要代碼如下:
fileprivate func addScaningVideo(){
//1.獲取輸入設(shè)備(攝像頭)
guard let device = AVCaptureDevice.default(for: .video) else { return }
//2.根據(jù)輸入設(shè)備創(chuàng)建輸入對象
guard let deviceIn = try? AVCaptureDeviceInput(device: device) else { return }
deviceInput = deviceIn
//3.創(chuàng)建原數(shù)據(jù)的輸出對象
let metadataOutput = AVCaptureMetadataOutput()
//4.設(shè)置代理監(jiān)聽輸出對象輸出的數(shù)據(jù)邪驮,在主線程中刷新
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//4.2 設(shè)置輸出代理
faceDelegate = previewView
//5.設(shè)置輸出質(zhì)量(高像素輸出)
session.sessionPreset = .high
//6.添加輸入和輸出到會(huì)話
if session.canAddInput(deviceInput!) {
session.addInput(deviceInput!)
}
if session.canAddOutput(metadataOutput) {
session.addOutput(metadataOutput)
}
//7.告訴輸出對象要輸出什么樣的數(shù)據(jù),識(shí)別人臉, 最多可識(shí)別10張人臉
metadataOutput.metadataObjectTypes = [.face]
//8.創(chuàng)建預(yù)覽圖層
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
previewView.layer.insertSublayer(previewLayer, at: 0)
//9.設(shè)置有效掃描區(qū)域(默認(rèn)整個(gè)屏幕區(qū)域)(每個(gè)取值0~1, 以屏幕右上角為坐標(biāo)原點(diǎn))
metadataOutput.rectOfInterest = previewView.bounds
//10. 開始掃描
if !session.isRunning {
DispatchQueue.global().async {
self.session.startRunning()
}
}
}
2. 切換攝像頭
- 獲取當(dāng)前攝像頭方向
- 創(chuàng)建新的輸入input
- 移除舊輸入
capture
, 添加新的輸入capture
- 具體代碼如下:
@IBAction func switchCameraAction(_ sender: Any) {
//1. 執(zhí)行轉(zhuǎn)場動(dòng)畫
let anima = CATransition()
anima.type = "oglFlip"
anima.subtype = "fromLeft"
anima.duration = 0.5
view.layer.add(anima, forKey: nil)
//2. 獲取當(dāng)前攝像頭
guard let deviceIn = deviceInput else { return }
let position: AVCaptureDevice.Position = deviceIn.device.position == .back ? .front : .back
//3. 創(chuàng)建新的input
let deviceSession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position)
guard let newDevice = deviceSession.devices.filter({ $0.position == position }).first else { return }
guard let newVideoInput = try? AVCaptureDeviceInput(device: newDevice) else { return }
//4. 移除舊輸入,添加新輸入
//4.1 設(shè)備加鎖
session.beginConfiguration()
//4.2. 移除舊設(shè)備
session.removeInput(deviceIn)
//4.3 添加新設(shè)備
session.addInput(newVideoInput)
//4.4 設(shè)備解鎖
session.commitConfiguration()
//5. 保存最新輸入
deviceInput = newVideoInput
}
3. 處理掃描結(jié)果
實(shí)現(xiàn)
AVCaptureMetadataOutputObjectsDelegate
該協(xié)議的協(xié)議方法(只有一個(gè)方法)
//`metadataObjects`就是返回的掃描結(jié)果
optional public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)
4. AVMetadataFaceObject
介紹
-
faceID
: 人臉的唯一標(biāo)識(shí)- 掃描出來的每一個(gè)人, 有不同的
faceID
- 同一個(gè)人, 不同的狀態(tài)下(搖頭, 歪頭, 抬頭等), 都會(huì)有不同
faceID
- 掃描出來的每一個(gè)人, 有不同的
-
hasRollAngle
: 是否有傾斜角,側(cè)傾角(左右歪頭)(BOOL類型) -
rollAngle
: 傾斜角,側(cè)傾角的角度(CGFloat
類型) -
hasYawAngle
: 是否有偏轉(zhuǎn)角(左右搖頭) -
yawAngle
: 偏轉(zhuǎn)角角度
5. 處理掃描結(jié)果
5.1 獲取預(yù)覽圖層的人臉數(shù)組
- 遍歷掃描的人臉數(shù)組, 轉(zhuǎn)換成在預(yù)覽圖層的人臉數(shù)組
- 主要是人臉在圖層的左邊的轉(zhuǎn)換
- 返回轉(zhuǎn)換后的新的數(shù)組
fileprivate func transformedFaces(faceObjs: [AVMetadataObject]) -> [AVMetadataObject] {
var faceArr = [AVMetadataObject]()
for face in faceObjs {
//將掃描的人臉對象轉(zhuǎn)成在預(yù)覽圖層的人臉對象(主要是坐標(biāo)的轉(zhuǎn)換)
if let transFace = previewLayer.transformedMetadataObject(for: face){
faceArr.append(transFace)
}
}
return faceArr
}
5.2 根據(jù)人臉位置添加紅框
- 設(shè)置紅框的frame
faceLayer?.frame = face.bounds
- 根據(jù)偏轉(zhuǎn)角和傾斜角的角度獲取
CATransform3D
fileprivate func transformDegress(yawAngle: CGFloat) -> CATransform3D {
let yaw = degreesToRadians(degress: yawAngle)
//圍繞Y軸旋轉(zhuǎn)
let yawTran = CATransform3DMakeRotation(yaw, 0, -1, 0)
//紅框旋轉(zhuǎn)問題
return CATransform3DConcat(yawTran, CATransform3DIdentity)
}
//處理偏轉(zhuǎn)角問題
fileprivate func transformDegress(rollAngle: CGFloat) -> CATransform3D {
let roll = degreesToRadians(degress: rollAngle)
//圍繞Z軸旋轉(zhuǎn)
return CATransform3DMakeRotation(roll, 0, 0, 1)
}
//角度轉(zhuǎn)換
fileprivate func degreesToRadians(degress: CGFloat) -> CGFloat{
return degress * CGFloat(Double.pi) / 180
}
- 根據(jù)有無偏轉(zhuǎn)角和傾斜角旋轉(zhuǎn)紅框
//3.4 設(shè)置偏轉(zhuǎn)角(左右搖頭)
if face.hasYawAngle{
let tranform3D = transformDegress(yawAngle: face.yawAngle)
//矩陣處理
faceLayer?.transform = CATransform3DConcat(faceLayer!.transform, tranform3D)
}
//3.5 設(shè)置傾斜角,側(cè)傾角(左右歪頭)
if face.hasRollAngle{
let tranform3D = transformDegress(rollAngle: face.rollAngle)
//矩陣處理
faceLayer?.transform = CATransform3DConcat(faceLayer!.transform, tranform3D)
}
- 至此, 動(dòng)態(tài)的人臉識(shí)別就完成了, 會(huì)在人臉位置增加紅框顯示, 并且紅框會(huì)根據(jù)人臉的位置動(dòng)態(tài)的, 實(shí)時(shí)的調(diào)整
- 下面就快拿起你的相機(jī)測試吧
GitHub--Demo地址
- 注意:
- 這里只是列出了主要的核心代碼,具體的代碼邏輯請參考demo
- 文中相關(guān)介紹有的地方如果有不是很詳細(xì)或者有更好建議的,歡迎聯(lián)系小編