0

我从 TrueDepth 相机获取深度数据,并将其转换为灰度图像。(我意识到我可以将 传递AVDepthDataCIImage构造函数,但是,出于测试目的,我想确保我的数组被正确填充,因此手动构造图像将确保是这种情况。)

我注意到当我尝试转换灰度图像时,我得到了奇怪的结果。即图像出现在上半部分,而下半部分失真(有时显示图像两次,有时显示胡说八道)。

例如:

预期输出(即CIImage(depthData: depthData)):

在此处输入图像描述

实际输出(20% 的时间):

在此处输入图像描述

实际输出(80% 的时间):

在此处输入图像描述

我从Apple 的示例代码开始,尝试提取 CVPixelBuffer 中的像素

let depthDataMap: CVPixelBuffer = ...
let width = CVPixelBufferGetWidth(depthDataMap) // 640
let height = CVPixelBufferGetHeight(depthDataMap) // 480
let bytesPerRow = CVPixelBufferGetBytesPerRow(depthDataMap) // 1280
let baseAddress = CVPixelBufferGetBaseAddress(depthDataMap)
assert(kCVPixelFormatType_DepthFloat16 == CVPixelBufferGetPixelFormatType(depthDataMap))
let byteBuffer = unsafeBitCast(baseAddress, to: UnsafeMutablePointer<Float16>.self)

var pixels = [Float]()
for row in 0..<height {
  for col in 0..<width {
    let byteBufferIndex = col + row * bytesPerRow
    let distance = byteBuffer[byteBufferIndex]
    pixels += [distance]
  }
}

// TODO: render pixels as a grayscale image

知道这里有什么问题吗?

4

1 回答 1

2

TL;博士

您应该始终打开调用,CVPixelBufferGetBaseAddress以免错过重要警告。


原来问题是如何访问 byteBuffer 中的值。如果不使用unsafeBitCast()Apple 在他们的示例中使用的方法 ( assumingMemoryBound),您将得到正确的结果。

虽然它看起来像:

// BAD CODE

let byteBuffer = unsafeBitCast(baseAddress, to: UnsafeMutablePointer<Float16>.self)
// ...
let byteBufferIndex = col + row * bytesPerRow
let distance = byteBuffer[byteBufferIndex]

... 的行为应与以下内容相同:

// GOOD CODE

let rowData = baseAddress! + row * bytesPerRow
let distance = rowData.assumingMemoryBound(to: Float16.self)[col]

……这两者其实有很大的不同,前者产生上述的坏结果,而后者产生好的结果。

最终(固定)代码应如下所示:

let depthDataMap: CVPixelBuffer = ...
let width = CVPixelBufferGetWidth(depthDataMap) // 640
let height = CVPixelBufferGetHeight(depthDataMap) // 480
let bytesPerRow = CVPixelBufferGetBytesPerRow(depthDataMap) // 1280
let baseAddress = CVPixelBufferGetBaseAddress(depthDataMap)!
assert(kCVPixelFormatType_DepthFloat16 == CVPixelBufferGetPixelFormatType(depthDataMap))

var pixels = [Float]()
for row in 0..<height {
  for col in 0..<width {
    let rowData = baseAddress + row * bytesPerRow
    let distance = rowData.assumingMemoryBound(to: Float16.self)[col]
    pixels += [distance]
  }
}

// TODO: render pixels as a grayscale image

我实际上不确定为什么会这样,因为我们知道:

assert(MemoryLayout<Float16>.size == 2)
assert(width == 640)
assert(bytesPerRow == 1280)
assert(width * 2 == bytesPerRow)

这似乎意味着在一行的末尾没有多余的字节,我们应该能够将它作为一个巨大的数组来读取。

如果有人知道前者为什么失败,请分享!


更新:

如果您强制解开对以下的调用CVPixelBufferGetBaseAddress

let baseAddress = CVPixelBufferGetBaseAddress(depthDataMap)!

......事情开始变得更有意义了。

即,您将在此行看到警告:

let byteBuffer = unsafeBitCast(baseAddress, to: UnsafeMutablePointer<Float16>.self)

⚠️ 从 'UnsafeMutableRawPointer' 到 'UnsafeMutablePointer' 的 'unsafeBitCast' 为原始指针提供了一个类型,并可能导致未定义的行为

⚠️ 如果已知指针指向内存中的现有值或类型为“Float16”的数组,请使用“假设内存绑定”方法

我猜我看到的结果与“未定义行为”警告有关。

因此,教训是,CVPixelBufferGetBaseAddress在尝试使用它之前,您应该始终解开结果(例如 in unsafeBitCast)。

于 2020-12-09T03:14:34.743 回答