(十三)深入了解AVFoundation-采集:视频帧采集与实时滤镜处理

引言

在移动应用中,实时视频处理已成为视频拍摄、短视频、直播、美颜相机等功能的核心技术之一。从简单的滤镜叠加,到复杂的美颜、AR 特效,背后都离不开对每一帧图像的高效采集与处理。在前几篇文章中,我们已经实现了基本的视频采集、人脸识别等功能,而本篇将迈出更进一步的一步 —— 实时处理每一帧图像,打造动态视觉效果

本篇内容将围绕 AVCaptureVideoDataOutput 展开,讲解如何获取原始视频帧,并借助 CoreImage 或 Metal 实现滤镜、美颜等实时图像处理效果。最后,我们还将以一个“实时美颜相机”为示例,串联起采集、处理与渲染的完整流程,帮助你搭建具备实用价值的实时视频处理系统。

采集视频帧:AVCaptureVideoDataOutput

在使用 AVFoundation 进行图像采集时,无论是拍照、录像、还是视频帧处理,整体的配置流程几乎一致。我们依然需要:

  1. 创建 AVCaptureSession。
  2. 添加输入设备(通常是摄像头)。
  3. 添加输出对象。
  4. 启动会话。

唯一的区别在于 输出类型的不同。在拍照场景中,我们使用的是 AVCapturePhotoOutput;录制视频则使用 AVCaptureMovieFileOutput。而本篇重点关注的实时视频帧处理,需要使用的是:AVCaptureVideoDataOutput。

AVCaptureVideoDataOutput 负责将摄像头捕捉到的原始帧(CVPixelBuffer)逐帧输出给我们,这种输出是“实时的”,每一帧都会通过代理方法交付给我们进行处理,非常适合用于:

  • 添加滤镜
  • 美颜处理
  • 实时图像识别

类的基本结构

我们先来看一下 PHCaptureVideoController 的基本结构:

import UIKit
import AVFoundationclass PHCaptureVideoController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {/// 会话let session = AVCaptureSession()/// 输出private let videoDataOutput = AVCaptureVideoDataOutput()/// 输入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: "PHCaptureController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))return}// 3.设置会话输出if !setupSessionOutput() {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))return}session.commitConfiguration()}/// 设置会话话预设private func setupSessionPreset() {session.sessionPreset = .high}/// 设置会话输入private func setupSessionInput(device: AVCaptureDevice? = nil) -> Bool {return true}/// 设置会话输出private func setupSessionOutput() -> Bool {return true}/// 启动会话func startSession() {}/// 停止会话func stopSession() {}//MARK: private/// 获取默认摄像头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}//MARK: - AVCaptureVideoDataOutputSampleBufferDelegate/// 捕获输出func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {// 处理视频数据delegate?.captureVideo(sampleBuffer)}/// 捕获输出丢失func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {}}

整个类中划分为了几个方法:

  1. 配置会话。
  2. 设置会话输入。
  3. 设置会话输出。
  4. 启动会话、停止会话。
  5. 捕捉到视频帧的回调、丢失视频帧的回调。

配置会话

我们在 setupConfigureSession 方法中需要完成三个操作:

  1. 设置会话预设。
  2. 添加摄像头输入。
  3. 添加照片输出。

并且保证这些操作需要在会话 beginConfiguration 与 commitConfiguration 方法之间执行。

    /// 配置会话func setupConfigureSession() {session.beginConfiguration()// 1.设置会话预设setupSessionPreset()// 2.设置会话输入if !setupSessionInput(device: self.getDefaultCameraDevice()) {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))return}// 3.设置会话输出if !setupSessionOutput() {delegate?.captureError(NSError(domain: "PHCaptureController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))return}session.commitConfiguration()}
    /// 设置会话话预设private func setupSessionPreset() {session.sessionPreset = .high}

会话输入

添加摄像头设备,并包装一层 AVCaptureDeviceInput 添加到会话中。

    /// 设置会话输入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}

会话输出

会话输出使用AVCaptureVideoDataOutput输出,设置像素合适以及检查是否可以添加。

/// 设置会话输出
private func setupSessionOutput() -> Bool {videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]videoDataOutput.setSampleBufferDelegate(self, queue: sessionQueue)if session.canAddOutput(videoDataOutput) {session.addOutput(videoDataOutput)} else {return false}return true
}
  1. 设置像素格式:我们选择 kCVPixelFormatType_32BGRA,这是 CoreImage 和 Metal 最常用、兼容性最强的格式;
  2. 设置代理与处理队列:setSampleBufferDelegate(_:queue:) 会将每一帧回调给你指定的队列处理,避免阻塞主线程;
  3. 检查输出是否可添加:通过 canAddOutput 判断 session 是否支持添加该输出类型,确保稳定性。

启动、停止会话

在自定义的串行队列中执行启动和停止会话。

    /// 启动会话func startSession() {sessionQueue.async {if !self.session.isRunning {self.session.startRunning()}}}/// 停止会话func stopSession() {sessionQueue.async {if self.session.isRunning {self.session.stopRunning()}}}

视频帧数据处理

接下来我需要实现AVCaptureVideoDataOutputSampleBufferDelegate的代理方法,并处理回调中的视频数据。

回调方法

AVCaptureVideoDataOutputSampleBufferDelegate提供了两个代理方法,一个用于捕获实时输出的视频帧数据,一个用来捕获丢失的帧数据。

我们在捕获视频帧数据的方法中将 CMSampleBuffer 数据回调到视图控制器。

    //MARK: - AVCaptureVideoDataOutputSampleBufferDelegate/// 捕获输出func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {// 处理视频数据delegate?.captureVideo(sampleBuffer)}

添加滤镜

在这里我们就采用一个最简单的方式为实时预览添加一个滤镜,通过CIFilter来创建。它支持很多类型的滤镜,比如颜色翻转、漫画风格、色彩分层、像素化等等。

    // 视频帧func captureVideo(_ sampleBuffer: CMSampleBuffer) {// 处理视频帧guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }// 处理像素缓冲区let ciImage = CIImage(cvPixelBuffer: pixelBuffer)// 添加滤镜let filter = CIFilter(name: "CIComicEffect")filter?.setValue(ciImage, forKey: kCIInputImageKey)guard let outputImage = filter?.outputImage,let cgImage = ciContext.createCGImage(outputImage, from: ciImage.extent) else {return}let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right)// 显示到预览图层DispatchQueue.main.async {self.previewImageView.image = uiImage}}

最终效果如下:

结语

通过本文,我们实现了使用 AVCaptureVideoDataOutput 获取原始视频帧,并结合 Core Image 对其进行实时处理的完整流程。无论是美颜、滤镜,还是图像分析,这种方式都为实时图像处理提供了极大的灵活性和可扩展性。

不过,需要注意的是,CoreImage 虽然上手简单、易于调试,但在处理高分辨率视频帧或多个滤镜叠加时,性能可能会成为瓶颈。如果你希望在性能上进一步优化,或者实现更加复杂、专业的图像处理效果,推荐使用更底层的图形处理框架,例如 Metal 或 OpenGLES,它们可以更精细地控制渲染流程、内存使用和 GPU 资源调度,是构建高性能视频应用的不二之选。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/84076.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字政务安全实战:等保2.0框架下OA系统防护全解析

近期在Python基础教学领域深入钻研函数机制、数据结构优化等内容时,深刻意识到信息安全作为技术基石的战略价值。在政务数字化转型浪潮中,Python凭借其高扩展性与丰富的安全生态库,成为构建政务OA系统安全防护体系的核心工具。本文将以等保2.…

Pytorch项目实战-2:花卉分类

一、前言 在深度学习项目中,数据集的处理和模型的训练、测试、预测是关键环节。本文将为小白详细介绍从数据集搜集、清洗、划分到模型训练、测试、预测以及模型结构查看的全流程,附带代码和操作说明,让你轻松上手! 二、数据集 …

React Flow 边事件处理实战:鼠标事件、键盘操作及连接规则设置(附完整代码)

本文为《React Agent:从零开始构建 AI 智能体》专栏系列文章。 专栏地址:https://blog.csdn.net/suiyingy/category_12933485.html。项目地址:https://gitee.com/fgai/react-agent(含完整代码示​例与实战源)。完整介绍…

java小结(一)

java(上) 模块一 1.JDK,JRE,JVM 知识点 核心内容 易混淆点 JDK定义 Java Development Kit(Java开发工具包),包含开发所需全部工具 JDK包含JRE的关系容易混淆 JRE定义 Java Runtime Environment(Jav…

ddns-go安装介绍-强大的ipv6动态域名解析神器-家庭云计算专家

ddns-go 是一款轻量级开源动态域名解析工具,专注于解决动态IP环境下的域名绑定问题,尤其适配IPv6网络环境。其核心功能包括: 1.IPv6动态解析:自动检测本地IPv6地址变化(支持网卡、接口或命令获取)&#xf…

Docker-mongodb

拉取 MongoDB 镜像: docker pull mongo 创建容器并设置用户: 要挂载本地数据目录,请替换此路径: /Users/Allen/Env/AllenDocker/mongodb/data/db docker run -d --name local-mongodb \-e MONGO_INITDB_ROOT_USERNAMEadmin \-e MONGO_INITDB_ROOT_PA…

WooCommerce缓存教程 – 如何防止缓存破坏你的WooCommerce网站?

我们在以前的文章中探讨过如何加快你的WordPress网站的速度,并研究过各种形式的缓存。 然而,像那些使用WooCommerce的动态电子商务网站,在让缓存正常工作方面往往会面临重大挑战。 在本指南中,我们将告诉你如何为WooCommerce设置…

贪心算法 Part04

总结下重叠区间问题 LC 452. 用最少数量的箭引爆气球 和 LC 435. 无重叠区间 本质上是一样的。 LC 452. 用最少数量的箭引爆气球 是求n个区间当中 , 区间的种类数量 k。此处可以理解为,重叠在一起的区间属于同一品种,没有重叠的区间当然…

云原生CD工具-Argocd+ArgoRollout入门到精通

第一章 Argo CD简介 课时1.1 Argo产品介绍 ARGO官网地址:https://argoproj.github.io/ 旗下产品有: Argo Workflows、ArgoCD 、Argo Rollouts 、Argo Events 课时1.2 什么是Argo CD Argo CD 是一个开源的持续交付工具, 是 Kubernetes 的声明式 GitOps 持续交付工具。专…

数据分析与应用---数据可视化基础

目录 Matplotlib基础绘图 (一)、pyplot绘图基础语法与常用参数 1、pyplot基础语法 (1) 创建画布与创建子图 (2) 添加画布内容 (3) 保存与显示图形 案例代码 2. 设置pyplot的动态rc参数 (二)、使用Matplotlib绘制进阶图形 1. 绘制散点图----scatter 2. 绘制折线…

PP-YOLOE-SOD学习笔记1

项目:基于PP-YOLOE-SOD的无人机航拍图像检测案例全流程实操 - 飞桨AI Studio星河社区 一、安装环境 先准备新环境py>3.9 1.先cd到源代码的根目录下 2.pip install -r requirements.txt 3.python setup.py install 这一步需要看自己的GPU情况,去飞浆…

力扣HOT100之二叉树:114. 二叉树展开为链表

这道题自己尝试着做了一下,感觉还是得用递归来做比较简单,但是一直想的是用前序遍历来构造链表,导致怎么做都不对,去看了下灵神的题解,然后问了下GPT,现在终于弄明白了。虽然构造出来的链表的排列顺序是按照…

Spring Boot 注解 @ConditionalOnMissingBean是什么

一句话总结: ConditionalOnMissingBean 是 Spring Boot 提供的一个 条件注解(Conditional Annotation),意思是: 只有当 Spring 容器中 不存在 某个 Bean 时,当前的 Bean 或配置才会被加载。 这是一种典型的…

PyInstaller 如何在mac电脑上生成在window上可执行的exe文件

PyInstaller跨平台打包限制 PyInstaller 无法直接从macOS生成Windows可执行文件,因为它需要访问目标平台的系统库和Python环境来构建可执行文件。要在macOS上为Windows打包Python应用,需要通过以下方法之一: 方法一:使用虚拟机或…

零基础设计模式——创建型模式 - 抽象工厂模式

第二部分:创建型模式 - 抽象工厂模式 (Abstract Factory Pattern) 我们已经学习了单例模式(保证唯一实例)和工厂方法模式(延迟创建到子类)。现在,我们来探讨创建型模式中更为复杂和强大的一个——抽象工厂…

【通用智能体】Serper API 详解:搜索引擎数据获取的核心工具

Serper API 详解:搜索引擎数据获取的核心工具 一、Serper API 的定义与核心功能二、技术架构与核心优势2.1 技术实现原理2.2 对比传统方案的突破性优势 三、典型应用场景与代码示例3.1 SEO 监控系统3.2 竞品广告分析 四、使用成本与配额策略五、开发者注意事项六、替…

Flask-SQLAlchemy核心概念:模型类与数据库表、类属性与表字段、外键与关系映射

前置阅读,关于Flask-SQLAlchemy支持哪些数据库及基本配置,链接:Flask-SQLAlchemy_数据库配置 摘要 本文以一段典型的 SQLAlchemy 代码示例为引入,阐述以下核心概念: 模型类(Model Class) ↔ 数…

野火鲁班猫(arrch64架构debian)从零实现用MobileFaceNet算法进行实时人脸识别(四)安装RKNN Toolkit2

RKNN Toolkit2是用来将onnx模型转成rknn专用模型,并可通过RKNN Toolkit Lite2或者RKNPU调用NPU进行加速计算的工具。 一开始我安装很多次都无法成功安装。后来跟售后技术对接,必须是PC平台的Linux环境才可以。我的电脑是windows,所以我需要用…

基于深度学习的工件检测系统设计与实现

在工业自动化领域,工件检测一直是提高生产效率和产品质量的关键环节。传统的人工检测方法不仅效率低下,而且容易受到主观因素的影响,导致误判率较高。随着深度学习技术的飞速发展,基于图像识别的自动检测系统逐渐成为研究热点。今…

CyberSecAsia专访CertiK首席安全官:区块链行业亟需“安全优先”开发范式

近日,权威网络安全媒体CyberSecAsia发布了对CertiK首席安全官Wang Tielei博士的专访,双方围绕企业在进军区块链领域时所面临的关键安全风险与防御策略展开深入探讨。 Wang博士在采访中指出,跨链桥攻击、智能合约漏洞以及私钥管理不当&#x…