Сохраняйте изображения глубины с камеры TrueDepth
Я пытаюсь сохранить изображения глубины с камеры iPhoneX TrueDepth. Используя пример кода AVCamPhotoFilter, я могу просматривать глубину, преобразованную в формат оттенков серого, на экране телефона в режиме реального времени. Я не могу понять, как сохранить последовательность изображений глубины в необработанном (16 бит или более) формате.
я имею depthData
который является примером AVDepthData
, Один из его членов depthDataMap
который является примером CVPixelBuffer
и тип формата изображения kCVPixelFormatType_DisparityFloat16
, Есть ли способ сохранить его на телефон для переноса для автономной манипуляции?
2 ответа
Для "сырых" карт глубины / диспаратности нет стандартного видеоформата, который может иметь какое-то отношение к AVCapture, не предлагая способ его записи.
У вас есть несколько вариантов, которые стоит изучить здесь:
Преобразуйте карты глубины в текстуры в оттенках серого (что можно сделать с помощью кода в примере кода AVCamPhotoFilter), а затем передайте эти текстуры
AVAssetWriter
для создания видео в оттенках серого. В зависимости от формата видео и метода преобразования оттенков серого, другое программное обеспечение, которое вы пишете для чтения видео, может восстановить информацию о глубине / несоответствии с достаточной точностью для ваших целей из кадров в оттенках серого.В любое время у вас есть
CVPixelBuffer
Вы можете получить данные самостоятельно и делать с ними все, что захотите. использованиеCVPixelBufferLockBaseAddress
(сreadOnly
флаг), чтобы убедиться, что содержимое не изменится во время чтения, затем скопируйте данные из указателяCVPixelBufferGetBaseAddress
обеспечивает, где вы хотите. (Используйте другие функции буфера пикселей, чтобы увидеть, сколько байтов нужно скопировать, и разблокируйте буфер, когда закончите.)Однако будьте осторожны: если вы тратите слишком много времени на копирование из буферов или сохраняете их иным образом, они не будут освобождены, поскольку новые буферы поступают из системы захвата, и ваш сеанс захвата будет зависать. (В общем, неясно, не протестировав, имеет ли устройство память и пропускную способность ввода / вывода для такой записи.)
Вы можете использовать библиотеку сжатия для создания zip-файла с необработанными данными CVPixelBuffer. Мало проблем с этим решением.
- Это много данных и zip не очень хорошее сжатие. (сжатый файл в 20 раз превышает 32 бита на кадр видео с таким же количеством кадров).
- Библиотека Apple Compression создает файл, который стандартная программа zip не открывает. Я использую zlib в коде C, чтобы прочитать его и использовать
inflateInit2(&strm, -15);
чтобы это работало. - Вам нужно будет поработать, чтобы экспортировать файл из вашего приложения.
Вот мой код (который я ограничил 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()
}
}
}