Сохраняйте изображения глубины с камеры TrueDepth

Я пытаюсь сохранить изображения глубины с камеры iPhoneX TrueDepth. Используя пример кода AVCamPhotoFilter, я могу просматривать глубину, преобразованную в формат оттенков серого, на экране телефона в режиме реального времени. Я не могу понять, как сохранить последовательность изображений глубины в необработанном (16 бит или более) формате.

я имею depthData который является примером AVDepthData, Один из его членов depthDataMap который является примером CVPixelBuffer и тип формата изображения kCVPixelFormatType_DisparityFloat16, Есть ли способ сохранить его на телефон для переноса для автономной манипуляции?

2 ответа

Решение

Для "сырых" карт глубины / диспаратности нет стандартного видеоформата, который может иметь какое-то отношение к AVCapture, не предлагая способ его записи.

У вас есть несколько вариантов, которые стоит изучить здесь:

  1. Преобразуйте карты глубины в текстуры в оттенках серого (что можно сделать с помощью кода в примере кода AVCamPhotoFilter), а затем передайте эти текстуры AVAssetWriter для создания видео в оттенках серого. В зависимости от формата видео и метода преобразования оттенков серого, другое программное обеспечение, которое вы пишете для чтения видео, может восстановить информацию о глубине / несоответствии с достаточной точностью для ваших целей из кадров в оттенках серого.

  2. В любое время у вас есть CVPixelBufferВы можете получить данные самостоятельно и делать с ними все, что захотите. использование CVPixelBufferLockBaseAddressreadOnly флаг), чтобы убедиться, что содержимое не изменится во время чтения, затем скопируйте данные из указателя CVPixelBufferGetBaseAddress обеспечивает, где вы хотите. (Используйте другие функции буфера пикселей, чтобы увидеть, сколько байтов нужно скопировать, и разблокируйте буфер, когда закончите.)

    Однако будьте осторожны: если вы тратите слишком много времени на копирование из буферов или сохраняете их иным образом, они не будут освобождены, поскольку новые буферы поступают из системы захвата, и ваш сеанс захвата будет зависать. (В общем, неясно, не протестировав, имеет ли устройство память и пропускную способность ввода / вывода для такой записи.)

Вы можете использовать библиотеку сжатия для создания zip-файла с необработанными данными CVPixelBuffer. Мало проблем с этим решением.

  1. Это много данных и zip не очень хорошее сжатие. (сжатый файл в 20 раз превышает 32 бита на кадр видео с таким же количеством кадров).
  2. Библиотека Apple Compression создает файл, который стандартная программа zip не открывает. Я использую zlib в коде C, чтобы прочитать его и использовать inflateInit2(&strm, -15); чтобы это работало.
  3. Вам нужно будет поработать, чтобы экспортировать файл из вашего приложения.

Вот мой код (который я ограничил 250 кадрами, так как он хранит его в оперативной памяти, но вы можете записать на диск, если нужно больше кадров):

//  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()
        }
    }
}
Другие вопросы по тегам