【问题标题】:Save depth images from TrueDepth camera从 TrueDepth 相机保存深度图像
【发布时间】:2018-05-19 17:52:48
【问题描述】:

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

我有depthData,它是AVDepthData 的一个实例。它的成员之一是depthDataMap,它是CVPixelBuffer 和图像格式类型kCVPixelFormatType_DisparityFloat16 的一个实例。有没有办法将其保存到手机中以供离线操作?

【问题讨论】:

  • 您要保存一系列“静止”深度图像还是编写视频?请注意,您引用的示例代码项目已经包含将深度缓冲区转换为灰度纹理的工具,因此您可以查看有关 getting CVPixelBuffers out of Metal texturesrecording video from a Metal renderer with AVAssetWriter 的现有问题。
  • @rickster,我想保存一系列静止深度图像。我不想将深度图像转换为灰度纹理,而是保存原始深度值(以米为单位)。
  • @rickster,对我来说灰度是很好的解决方案(解析器可以解码深度信息),但我找不到保存 16 位灰度视频的方法。我的示例在所有通道中使用具有相同值的常规 rgba 视频,但深度为 8 位,而不是 16。

标签: ios swift iphone-x truedepth-camera


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

    1. 数据量很大,而 zip 不是一种好的压缩方式。 (压缩文件比相同帧数的每帧 32 位视频大 20 倍)。
    2. Apple 的压缩库会创建一个标准 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()
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-23
      • 2018-08-20
      • 2016-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多