目录
介绍
应用布局
捕获相机反馈
相机反馈预览
完成相机预览配置
结论
总目录
将ONNX对象检测模型转换为iOS Core ML(一)
在这里,我们将开始在我们的iOS应用程序上工作。在此应用中实施对象检测之前,我们需要处理视频捕获。
本系列假定您熟悉Python、Conda和ONNX,并且具有使用Xcode开发iOS应用程序的经验。我们将使用macOS 10.15 +、Xcode 11.7+和iOS 13+运行代码。
在iOS应用程序中处理实时摄像头可能有点让人不知所措。我们将尝试使事情变得尽可能简单,更多地关注代码的可读性而不是性能。另外,为了减少要考虑的缩放选项的数量,我们将使用固定的纵向方向。
本文的代码最初是受此应用程序启发的。您也可以查看上一篇文章。
该演示应用程序是使用Xcode 11.7编写的,应该可以在iOS 7或更高版本,iOS 13或更高版本的任何iPhone上正常运行。
从故事板的角度来看,我们的应用程序非常简单。它包含一个视图控制器,上面只有一个预览视图控件。我们将使用此视图作为实时摄像机馈送。
负责处理摄像机输入和视频预览的所有代码都在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
}
let captureConnection = videoDataOutput.connection(with: .video)
captureConnection?.isEnabled = true
captureConnection?.videoOrientation = .portrait
captureSession.commitConfiguration()
固定的肖像方向将使处理和绘制对象检测的预测更加容易。
在接下来的步骤中,setup方法将创建一个videoPreviewLayer实例并将其作为子层添加到我们的应用程序视图中(如下viewLayer所示):
self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.videoPreviewLayer.videoGravity = .resizeAspectFill
videoPreviewLayer.frame = viewLayer.bounds
viewLayer.addSublayer(videoPreviewLayer)
为videoGravity使用.resizeAspectFill值可确保视频填满整个可用屏幕。由于没有iPhone的屏幕比例等于1,33:1(由640 x 480分辨率推断),因此在纵向视图中,每一帧都将在两侧被裁剪。如果我们改用.resizeAspect,整个框架将是可见的,但在其上方和下方都带有空条。
在VideoCapture类中,我们还需要三种方法:
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// We will handle frame(s) here
}
public func captureOutput(_ captureOutput: AVCaptureOutput, didDrop didDropSampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// Dropped frame(s) can be handled here
}
public func startCapture() {
if !captureSession.isRunning {
captureSession.startRunning()
}
}
前两种方法是必须的,因为它们在AVCaptureVideoDataOutputSampleBufferDelegate中定义,而我们的VideoCapture 类实现了它。现在,空的实现是可以的。我们需要最后一种方法,startCapture,开始处理视频反馈。
使用完整的VideoCapture实现从我们的Main.storyboard链接到cameraView,并用实例变量来存储所创建的VideoCapture实例,我们在主ViewController的viewDidLoad方法中创建一个新的VideoCapture实例:
self.videoCapture = VideoCapture(self.cameraView.layer)
self.videoCapture.startCapture()
现在,我们有一个简单的iOS应用程序,该应用程序配置为捕获和预览实时摄像机流。在本系列的下一篇(也是最后一篇)中,我们将扩展应用程序以使用我们的YOLO v2模型进行对象检测。
https://www.codeproject.com/Articles/5286804/iOS-Object-Detection-with-Live-Camera-Preview