iOS应用开发:实时摄像头流处理

在iOS应用开发中,处理实时摄像头流是一个常见的需求,尤其是在进行物体检测、人脸识别等场景时。本文将介绍如何在iOS应用中捕获并预览实时摄像头流,并为后续使用YOLO模型进行物体检测做准备。

环境准备

为了运行本文中的示例代码,需要具备以下条件:

  • macOS 10.15 或更高版本
  • Xcode11.7 或更高版本
  • iOS 13 或更高版本的iPhone设备

应用布局

从Storyboard的角度来看,应用程序非常简单。它只包含一个视图控制器(ViewController),并且在该控制器上有一个预览视图(Preview View)控件。将使用这个视图来显示实时摄像头流。

捕获摄像头

所有负责处理摄像头输入和视频预览的代码都在Controllers/VideoCapture类中,该类实现了AVCaptureVideoDataOutputSampleBufferDelegate。以下是存储其设置的成员变量:

private let captureSession = AVCaptureSession() private var videoPreviewLayer: AVCaptureVideoPreviewLayer! = nil private let videoDataOutput = AVCaptureVideoDataOutput() private let videoDataOutputQueue = DispatchQueue(label: "VideoDataOutput", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem) private var videoFrameSize: CGSize = .zero

在类构造函数中调用的setupPreview方法将所有元素绑定在一起。首先,它获取第一个可用的后置摄像头作为输入设备:

var deviceInput: AVCaptureDeviceInput! let videoDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first do { // ... }

然后,它开始配置过程,强制从摄像头获取640x480的帧。这样的分辨率对于YOLO v2模型来说是足够的,因为它使用的是缩放到416x416像素的图像。注意,由于固定了竖屏方向,在videoFrameSize变量中存储了48x640作为输入尺寸,以备将来使用:

captureSession.beginConfiguration() captureSession.sessionPreset = .vga640x480 self.videoFrameSize = CGSize(width: 480, height: 640)

配置继续建立一个单元素队列来处理帧(alwaysDiscardLateVideoFrames标志)。这意味着在当前帧的处理完成之前,后续的帧将被丢弃。

captureSession.addInput(deviceInput) if captureSession.canAddOutput(videoDataOutput) { captureSession.addOutput(videoDataOutput) videoDataOutput.alwaysDiscardsLateVideoFrames = true videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)] videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue) } else { print("Could not add video data output to the session") captureSession.commitConfiguration() return }

固定竖屏方向将使处理和绘制物体检测预测变得更容易。

摄像头流预览

在接下来的步骤中,setup方法创建了一个videoPreviewLayer实例,并将其作为子层添加到应用程序视图(viewLayer)中:

self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) self.videoPreviewLayer.videoGravity = .resizeAspectFill videoPreviewLayer.frame = viewLayer.bounds viewLayer.addSublayer(videoPreviewLayer)

使用.resizeAspectFill值确保视频填充整个可用屏幕。由于没有iPhone的屏幕比例等于1.33:1(由640x480分辨率推断),每个帧在竖屏视图中将在两侧被裁剪。如果使用.resizeAspect,整个帧将可见,但上面和下面会有空白条。

完成摄像头预览配置

VideoCapture类中,还需要三个方法:

public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 将在这里处理帧 } public func captureOutput(_ captureOutput: AVCaptureOutput, didDrop didDropSampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 可以在这里处理丢弃的帧 } public func startCapture() { if !captureSession.isRunning { captureSession.startRunning() } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485