
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| (一)深入了解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,因此整体结构与二维码扫描的配置几乎一致。我们无需重复搭建会话、添加输入等流程,只需在输出中指定不同的元数据类型即可完成切换。
下面我们快速回顾相关代码配置,并重点指出区别所在。
输入
输入部分与二维码识别完全相同。我们使用设备的摄像头作为输入源,添加到会话中用于实时视频采集:
Swift
import UIKit
import AVFoundation
class 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: - AVCaptureMetadataOutputObjectsDelegate
func 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).devices
return devices.first
}
}
输出配置
输出依然使用 AVCaptureMetadataOutput,但这里我们将 metadataObjectTypes 设置为人脸识别类型 .face。
Swift
/// 设置会话输出
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中定义的代理方法,将识别到的元数据传递到视图控制来处理。
Swift
//MARK: - AVCaptureMetadataOutputObjectsDelegate
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
delegate?.captureFace(metadataObjects)
}
在 captureFace() 方法中,我们可以读取到人脸信息包括人脸所在的区域以及人脸id:
坐标转换与画面映射
在二维码识别中,我们已经进行过了一次坐标转换,同样在获取到人脸元数据对象后,我们也需要进行坐标转换,将人脸在摄像头图像中的位置坐标,准确映射到实际的 UI 预览图层中,用于绘制人脸框或进行其他界面响应。
AVFoundation 中使用的是摄像头原始坐标系(以图像像素为基础,左上角为 (0,0),右下为 (1,1) 的比例坐标),而我们在界面上使用的却是 UIKit 的坐标系,因此需要进行一次坐标系转换。
AVCaptureVideoPreviewLayer 提供了现成的方法来进行这一转换:
Swift
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 或 CAShapeLayer
drawFaceBoundingBox(in: faceFrame)
}
}
绘制人脸框与多人识别支持
将人脸元数据转换为 UI 坐标后,我们可以将其用于实际的界面反馈,例如在人脸区域绘制可视化边框,提升识别体验。由于摄像头中可能出现多个面孔,绘制逻辑需要支持动态增减人脸框,并根据系统识别结果不断更新。
创建与更新人脸框
每一个被识别到的人脸对象都包含一个唯一的 faceID,我们可以用它作为字典的 Key,将其与对应的 CALayer一一对应。
以下是一个标准的绘制流程:
- 遍历所有识别到的人脸;
- 对每个 faceID 判断是否已有对应图层,没有则创建;
- 更新图层的 frame 与角度;
- 对丢失的人脸进行清除。
Swift
func didDetectFaces(_ faces: [AVMetadataFaceObject]) {
let transformedFaces = faces.compactMap { face in
return previewLayer.transformedMetadataObject(for: face) as? AVMetadataFaceObject
}
var lostFaceIDs = Set(faceLayers.keys)
for face in transformedFaces {
let faceID = face.faceID
lostFaceIDs.remove(faceID)
let layer: CALayer
if let existingLayer = faceLayers[faceID] {
layer = existingLayer
} else {
layer = makeFaceLayer()
overlayLayer.addSublayer(layer)
faceLayers[faceID] = layer
}
layer.transform = CATransform3DIdentity
layer.frame = face.bounds
if 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 来绘制矩形边框,并通过角度信息使其旋转对齐面部朝向。
Swift
func makeFaceLayer() -> CALayer {
let layer = CALayer()
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 2.0
layer.cornerRadius = 4.0
return layer
}
人脸元数据中的 rollAngle 表示绕 Z 轴(平面内)旋转,而 yawAngle 表示绕 Y 轴(前后方向)旋转:
Swift
func transformForRollAngle(_ degrees: CGFloat) -> CATransform3D {
let radians = degrees * .pi / 180.0
return CATransform3DMakeRotation(radians, 0.0, 0.0, 1.0)
}
func transformForYawAngle(_ degrees: CGFloat) -> CATransform3D {
let radians = degrees * .pi / 180.0
let yawTransform = CATransform3DMakeRotation(radians, 0.0, -1.0, 0.0)
return CATransform3DConcat(yawTransform, orientationTransform())
}
func orientationTransform() -> CATransform3D {
let orientation = UIDevice.current.orientation
let angle: CGFloat
switch orientation {
case .landscapeLeft:
angle = .pi / 2
case .landscapeRight:
angle = -.pi / 2
case .portraitUpsideDown:
angle = .pi
default:
angle = 0
}
return CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
}
支持多人识别与人脸移除
在每一帧中,我们根据当前返回的面孔数组来判断有哪些 faceID 不再出现,从而动态移除对应的图层。这样既保持了界面的同步性,也避免了残留 UI 的问题。
结语
本篇我们聚焦于 AVFoundation 中人脸识别的实现方式,从元数据输出类型的设置出发,详细讲解了识别流程、坐标转换,以及如何支持多人识别与人脸框绘制。借助系统提供的 AVMetadataFaceObject,我们可以较为轻松地将摄像头中的面孔在界面上实时呈现,为人脸相关的 UI 效果打下基础。
尽管 AVFoundation 的人脸识别功能较为基础,但对于实时展示、面部 UI 跟随等需求已经足够。更复杂的面部关键点检测、情绪识别等功能,则可以结合 Vision 框架进一步拓展,我们将在后续章节中继续探索。