(一)深入了解AVFoundation:框架概述與核心模塊解析-CSDN博客 |
(二) 深入了解AVFoundation - 播放:AVFoundation 播放基礎入門-CSDN博客 |
(三)深入了解AVFoundation-播放:AVPlayer 進階 播放狀態 & 進度監聽全解析_avplayer 播放狀態-CSDN博客 |
(四)深入理解AVFoundation-播放:高度自定義視頻播放器 UI-CSDN博客 |
(五)深入了解AVFoundation-播放:多音軌、字幕、倍速播放與橫豎屏切換-CSDN博客 |
(六)深入了解AVFoundation-播放:AirPlay、畫中畫后臺播放_air.av-CSDN博客 |
(七)深入了解AVFoundation-采集:采集系統架構與 AVCaptureSession 全面梳理_avcapturesession startrunning子線程調用-CSDN博客 |
(八)深入了解AVFoundation-采集:拍照功能的實現_ios avcapturephotooutput-CSDN博客 |
(九)深入了解AVFoundation-采集:拍照 攝像頭切換、拍照參數和照片數據EXIF 信息-CSDN博客 |
(十)深入了解AVFoundation-采集:錄制視頻功能的實現-CSDN博客 |
(十一)深入了解AVFoundation-采集:二維碼識別-CSDN博客 |
(十二)深入了解AVFoundation-采集:人臉識別與元數據處理-CSDN博客 |
引言
在 AVFoundation 的采集體系中,元數據輸出(AVCaptureMetadataOutput)不僅可用于識別二維碼等圖像信息,也支持對特定對象的實時檢測與追蹤,其中就包括人臉識別功能。與二維碼掃描相比,人臉識別在邏輯處理和 UI 顯示方面更具互動性和復雜性,也更貼近實際產品需求,如人臉識別登錄、鏡頭跟蹤拍攝、美顏濾鏡等功能。
本篇將基于前文二維碼采集的基礎,深入講解如何利用 AVFoundation 實現人臉的實時識別、坐標轉換、識別框繪制與多人追蹤。我們將粗略概括重復的輸入與權限設置,聚焦在?元數據處理的核心邏輯?和?UI 動態響應機制,幫助你構建一個功能清晰、體驗流暢的人臉識別系統。
配置回顧:輸入與輸出設置
在本節中,我們簡要回顧用于人臉識別的輸入與輸出配置流程。由于人臉識別同樣基于?AVCaptureMetadataOutput,因此整體結構與二維碼掃描的配置幾乎一致。我們無需重復搭建會話、添加輸入等流程,只需在輸出中指定不同的元數據類型即可完成切換。
下面我們快速回顧相關代碼配置,并重點指出區別所在。
輸入
輸入部分與二維碼識別完全相同。我們使用設備的攝像頭作為輸入源,添加到會話中用于實時視頻采集:
import UIKit
import AVFoundationclass PHCaptureFaceController: NSObject,AVCaptureMetadataOutputObjectsDelegate {/// 會話let session = AVCaptureSession()/// 輸出private let metadataOutput = AVCaptureMetadataOutput()/// 輸入private var captureDeviceInput: AVCaptureDeviceInput?/// 隊列private let sessionQueue = DispatchQueue(label: "com.example.captureSession")/// 代理weak var delegate: PHCaptureProtocol?/// 配置會話func setupConfigureSession() {session.beginConfiguration()// 1.設置會話預設setupSessionPreset()// 2.設置會話輸入if !setupSessionInput(device: self.getDefaultCameraDevice()) {delegate?.captureError(NSError(domain: "PHCaptureQRController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))return}// 3.設置會話輸出if !setupSessionOutput() {delegate?.captureError(NSError(domain: "PHCaptureQRController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))return}session.commitConfiguration()}/// 設置會話話預設private func setupSessionPreset() {session.sessionPreset = .photo}/// 設置會話輸入private func setupSessionInput(device: AVCaptureDevice? = nil) -> Bool {// 1.獲取攝像頭guard let device = device else { return false }do {captureDeviceInput = try AVCaptureDeviceInput(device: device)if session.canAddInput(captureDeviceInput!) {session.addInput(captureDeviceInput!)} else {return false}} catch {delegate?.captureError(error)return false}return true}/// 設置會話輸出private func setupSessionOutput() -> Bool {}/// 啟動會話func startSession() {sessionQueue.async {if !self.session.isRunning {self.session.startRunning()}}}/// 停止會話func stopSession() {sessionQueue.async {if self.session.isRunning {self.session.stopRunning()}}}//MARK: - AVCaptureMetadataOutputObjectsDelegatefunc metadataOutput(_ output: AVCaptureMetadataOutput,didOutput metadataObjects: [AVMetadataObject],from connection: AVCaptureConnection) {delegate?.captureFace(metadataObjects)}/// 獲取默認攝像頭private func getDefaultCameraDevice() -> AVCaptureDevice? {return getCameraDevice(position: .back)}/// 獲取指定攝像頭private func getCameraDevice(position: AVCaptureDevice.Position) -> AVCaptureDevice? {let devices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devicesreturn devices.first}}
輸出配置
輸出依然使用?AVCaptureMetadataOutput,但這里我們將?metadataObjectTypes?設置為人臉識別類型?.face。
/// 設置會話輸出private func setupSessionOutput() -> Bool {if session.canAddOutput(metadataOutput) {session.addOutput(metadataOutput)metadataOutput.metadataObjectTypes = [.face]// 設置 代理及輸出隊列metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)} else {return false}return true}
在設置完成后,只要攝像頭畫面中檢測到人臉,系統就會將人臉對象作為?AVMetadataFaceObject?回調到代理方法中,供后續處理。
元數據處理核心邏輯
關于這部分的內容我們分成三個小結來介紹一下:
- 實現代理方法與人臉識別回調。
- 坐標準話與畫面映射。
- 繪制人臉框與多人識別支持。
實現代理方法與人臉識別回調
同樣我們實現?AVCaptureMetadataOutputObjectsDelegate 中定義的代理方法,將識別到的元數據傳遞到視圖控制來處理。
//MARK: - AVCaptureMetadataOutputObjectsDelegatefunc metadataOutput(_ output: AVCaptureMetadataOutput,didOutput metadataObjects: [AVMetadataObject],from connection: AVCaptureConnection) {delegate?.captureFace(metadataObjects)}
在 captureFace() 方法中,我們可以讀取到人臉信息包括人臉所在的區域以及人臉id:
坐標轉換與畫面映射
在二維碼識別中,我們已經進行過了一次坐標轉換,同樣在獲取到人臉元數據對象后,我們也需要進行坐標轉換,將人臉在攝像頭圖像中的位置坐標,準確映射到實際的 UI 預覽圖層中,用于繪制人臉框或進行其他界面響應。
AVFoundation 中使用的是攝像頭原始坐標系(以圖像像素為基礎,左上角為 (0,0),右下為 (1,1) 的比例坐標),而我們在界面上使用的卻是 UIKit 的坐標系,因此需要進行一次坐標系轉換。
AVCaptureVideoPreviewLayer?提供了現成的方法來進行這一轉換:
if let faceObject = face as? AVMetadataFaceObject {// 獲取人臉的矩形區域let transformedMetadataObject = previewLayer.transformedMetadataObject(for: faceObject)if let transformedFace = previewLayer.transformedMetadataObject(for: faceObject) {// transformedFace.bounds 就是可以直接用于繪圖的 CGRect(相對于界面的坐標系)let faceFrame = transformedFace.bounds// 后續用于添加 UIView 或 CAShapeLayerdrawFaceBoundingBox(in: faceFrame)}}
繪制人臉框與多人識別支持
將人臉元數據轉換為 UI 坐標后,我們可以將其用于實際的界面反饋,例如在人臉區域繪制可視化邊框,提升識別體驗。由于攝像頭中可能出現多個面孔,繪制邏輯需要支持動態增減人臉框,并根據系統識別結果不斷更新。
創建與更新人臉框
每一個被識別到的人臉對象都包含一個唯一的?faceID,我們可以用它作為字典的 Key,將其與對應的?CALayer一一對應。
以下是一個標準的繪制流程:
- 遍歷所有識別到的人臉;
- 對每個?faceID?判斷是否已有對應圖層,沒有則創建;
- 更新圖層的?frame?與角度;
- 對丟失的人臉進行清除。
func didDetectFaces(_ faces: [AVMetadataFaceObject]) {let transformedFaces = faces.compactMap { face inreturn previewLayer.transformedMetadataObject(for: face) as? AVMetadataFaceObject}var lostFaceIDs = Set(faceLayers.keys)for face in transformedFaces {let faceID = face.faceIDlostFaceIDs.remove(faceID)let layer: CALayerif let existingLayer = faceLayers[faceID] {layer = existingLayer} else {layer = makeFaceLayer()overlayLayer.addSublayer(layer)faceLayers[faceID] = layer}layer.transform = CATransform3DIdentitylayer.frame = face.boundsif face.hasRollAngle {let rollTransform = transformForRollAngle(face.rollAngle)layer.transform = CATransform3DConcat(layer.transform, rollTransform)}if face.hasYawAngle {let yawTransform = transformForYawAngle(face.yawAngle)layer.transform = CATransform3DConcat(layer.transform, yawTransform)}}for faceID in lostFaceIDs {faceLayers[faceID]?.removeFromSuperlayer()faceLayers.removeValue(forKey: faceID)}
}
圖層樣式與角度旋轉
我們可以使用?CALayer?來繪制矩形邊框,并通過角度信息使其旋轉對齊面部朝向。
func makeFaceLayer() -> CALayer {let layer = CALayer()layer.borderColor = UIColor.red.cgColorlayer.borderWidth = 2.0layer.cornerRadius = 4.0return layer
}
人臉元數據中的?rollAngle?表示繞 Z 軸(平面內)旋轉,而?yawAngle?表示繞 Y 軸(前后方向)旋轉:
func transformForRollAngle(_ degrees: CGFloat) -> CATransform3D {let radians = degrees * .pi / 180.0return CATransform3DMakeRotation(radians, 0.0, 0.0, 1.0)
}func transformForYawAngle(_ degrees: CGFloat) -> CATransform3D {let radians = degrees * .pi / 180.0let yawTransform = CATransform3DMakeRotation(radians, 0.0, -1.0, 0.0)return CATransform3DConcat(yawTransform, orientationTransform())
}func orientationTransform() -> CATransform3D {let orientation = UIDevice.current.orientationlet angle: CGFloatswitch orientation {case .landscapeLeft:angle = .pi / 2case .landscapeRight:angle = -.pi / 2case .portraitUpsideDown:angle = .pidefault:angle = 0}return CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
}
支持多人識別與人臉移除
在每一幀中,我們根據當前返回的面孔數組來判斷有哪些?faceID?不再出現,從而動態移除對應的圖層。這樣既保持了界面的同步性,也避免了殘留 UI 的問題。
結語
本篇我們聚焦于 AVFoundation 中人臉識別的實現方式,從元數據輸出類型的設置出發,詳細講解了識別流程、坐標轉換,以及如何支持多人識別與人臉框繪制。借助系統提供的?AVMetadataFaceObject,我們可以較為輕松地將攝像頭中的面孔在界面上實時呈現,為人臉相關的 UI 效果打下基礎。
盡管 AVFoundation 的人臉識別功能較為基礎,但對于實時展示、面部 UI 跟隨等需求已經足夠。更復雜的面部關鍵點檢測、情緒識別等功能,則可以結合 Vision 框架進一步拓展,我們將在后續章節中繼續探索。