5

我正在尝试从 iPhoneX TrueDepth 相机保存深度图像。使用AVCamPhotoFilter示例代码,我可以在手机屏幕上实时查看深度,转换为灰度格式。我无法弄清楚如何以原始(16 位或更多)格式保存深度图像序列。

我有depthData哪个是AVDepthData. 它的成员之一是图像格式类型depthDataMap的实例。有没有办法将其保存到手机中以进行离线操作?CVPixelBufferkCVPixelFormatType_DisparityFloat16

4

2 回答 2

6

“原始”深度/视差图没有标准的视频格式,这可能与 AVCapture 没有真正提供记录它的方法有关。

您有几个值得研究的选项:

  1. 将深度图转换为灰度纹理(您可以使用AVCamPhotoFilter示例代码中的代码执行此操作),然后将这些纹理传递给以AVAssetWriter生成灰度视频。根据您选择的视频格式和灰度转换方法,您为读取视频而编写的其他软件可能能够从灰度帧中以足够的精度恢复深度/视差信息。

  2. 任何时候CVPixelBuffer,您都可以自己获取数据并使用它做任何您想做的事情。使用CVPixelBufferLockBaseAddress(带有readOnly标志)确保内容在您阅读时不会更改,然后将数据从指针CVPixelBufferGetBaseAddress提供到您想要的任何位置。(使用其他像素缓冲区函数查看要复制多少字节,并在完成后解锁缓冲区。)

    但请注意:如果您花费太多时间从缓冲区复制,或者以其他方式保留它们,当新的缓冲区从捕获系统进入时,它们将不会被释放,并且您的捕获会话将挂起。(总而言之,如果不测试设备是否具有用于大量记录的内存和 I/O 带宽,则尚不清楚。)

于 2017-12-20T22:41:37.060 回答
1

您可以使用压缩库创建一个包含原始 CVPixelBuffer 数据的 zip 文件。这个解决方案的问题很少。

  1. 它有很多数据,而 zip 不是一个很好的压缩方式。(压缩文件比相同帧数的每帧 32 位视频大 20 倍)。
  2. Apple 的 Compression 库会创建一个标准 zip 程序无法打开的文件。我在 C 代码中使用 zlib 来读取它并使用inflateInit2(&strm, -15);它来使其工作。
  3. 您需要做一些工作才能将文件从应用程序中导出

这是我的代码(我限制为 250 帧,因为它保存在 RAM 中,但如果需要更多帧,您可以刷新到磁盘):

//  DepthCapture.swift
//  AVCamPhotoFilter
//
//  Created by Eyal Fink on 07/04/2018.
//  Copyright © 2018 Resonai. All rights reserved.
//
// Capture the depth pixelBuffer into a compress file.
// This is very hacky and there are lots of TODOs but instead we need to replace
// it with a much better compression (video compression)....

import AVFoundation
import Foundation
import Compression


class DepthCapture {
    let kErrorDomain = "DepthCapture"
    let maxNumberOfFrame = 250
    lazy var bufferSize = 640 * 480 * 2 * maxNumberOfFrame  // maxNumberOfFrame frames
    var dstBuffer: UnsafeMutablePointer<UInt8>?
    var frameCount: Int64 = 0
    var outputURL: URL?
    var compresserPtr: UnsafeMutablePointer<compression_stream>?
    var file: FileHandle?

    // All operations handling the compresser oobjects are done on the
    // porcessingQ so they will happen sequentially
    var processingQ = DispatchQueue(label: "compression",
                                    qos: .userInteractive)


    func reset() {
        frameCount = 0
        outputURL = nil
        if self.compresserPtr != nil {
            //free(compresserPtr!.pointee.dst_ptr)
            compression_stream_destroy(self.compresserPtr!)
            self.compresserPtr = nil
        }
        if self.file != nil {
            self.file!.closeFile()
            self.file = nil
        }
    }
    func prepareForRecording() {
        reset()
        // Create the output zip file, remove old one if exists
        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
        self.outputURL = URL(fileURLWithPath: documentsPath.appendingPathComponent("Depth"))
        FileManager.default.createFile(atPath: self.outputURL!.path, contents: nil, attributes: nil)
        self.file = FileHandle(forUpdatingAtPath: self.outputURL!.path)
        if self.file == nil {
            NSLog("Cannot create file at: \(self.outputURL!.path)")
            return
        }

        // Init the compression object
        compresserPtr = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
        compression_stream_init(compresserPtr!, COMPRESSION_STREAM_ENCODE, COMPRESSION_ZLIB)
        dstBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
        compresserPtr!.pointee.dst_ptr = dstBuffer!
        //defer { free(bufferPtr) }
        compresserPtr!.pointee.dst_size = bufferSize


    }
    func flush() {
        //let data = Data(bytesNoCopy: compresserPtr!.pointee.dst_ptr, count: bufferSize, deallocator: .none)
        let nBytes = bufferSize - compresserPtr!.pointee.dst_size
        print("Writing \(nBytes)")
        let data = Data(bytesNoCopy: dstBuffer!, count: nBytes, deallocator: .none)
        self.file?.write(data)
    }

    func startRecording() throws {
        processingQ.async {
            self.prepareForRecording()
        }
    }
    func addPixelBuffers(pixelBuffer: CVPixelBuffer) {
        processingQ.async {
            if self.frameCount >= self.maxNumberOfFrame {
                // TODO now!! flush when needed!!!
                print("MAXED OUT")
                return
            }

            CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
            let add : UnsafeMutableRawPointer = CVPixelBufferGetBaseAddress(pixelBuffer)!
            self.compresserPtr!.pointee.src_ptr = UnsafePointer<UInt8>(add.assumingMemoryBound(to: UInt8.self))
            let height = CVPixelBufferGetHeight(pixelBuffer)
            self.compresserPtr!.pointee.src_size = CVPixelBufferGetBytesPerRow(pixelBuffer) * height
            let flags = Int32(0)
            let compression_status = compression_stream_process(self.compresserPtr!, flags)
            if compression_status != COMPRESSION_STATUS_OK {
                NSLog("Buffer compression retured: \(compression_status)")
                return
            }
            if self.compresserPtr!.pointee.src_size != 0 {
                NSLog("Compression lib didn't eat all data: \(compression_status)")
                return
            }
            CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
            // TODO(eyal): flush when needed!!!
            self.frameCount += 1
            print("handled \(self.frameCount) buffers")
        }
    }
    func finishRecording(success: @escaping ((URL) -> Void)) throws {
        processingQ.async {
            let flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)
            self.compresserPtr!.pointee.src_size = 0
            //compresserPtr!.pointee.src_ptr = UnsafePointer<UInt8>(0)
            let compression_status = compression_stream_process(self.compresserPtr!, flags)
            if compression_status != COMPRESSION_STATUS_END {
                NSLog("ERROR: Finish failed. compression retured: \(compression_status)")
                return
            }
            self.flush()
            DispatchQueue.main.sync {
                success(self.outputURL!)
            }
            self.reset()
        }
    }
}
于 2018-05-10T03:43:10.410 回答