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

在iOS应用开发中,实时处理摄像头流是一个常见需求。本文将介绍如何在Xcode环境下,使用Swift语言开发一个简单的iOS应用,该应用能够捕获并预览实时摄像头流。将使用macOS 10.15+,Xcode 11.7+,以及iOS 13+环境进行开发。

应用布局

从故事板(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 { // 捕获设备初始化代码 }

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

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(由640 x 480分辨率推断),每个帧在肖像视图中将在两侧被裁剪。如果使用.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