【问题标题】:Extracting image channels via vImage comes with side effect通过 vImage 提取图像通道有副作用
【发布时间】:2021-02-16 10:08:47
【问题描述】:

我正在尝试使用vImageConvert_ARGB8888toPlanar8 API 来提取 RGB 通道。假设以下inputImage 是来自https://www.solidbackgrounds.com/images/1080x1920/1080x1920-white-solid-color-background.jpg 的纯白色图像

这是我的代码:

let inputImage = #imageLiteral(resourceName: "1080x1920-white-solid-color-background.jpg")

guard let sourceCGImage = inputImage.cgImage,
      let sourceImageFormat = vImage_CGImageFormat(cgImage: sourceCGImage),
      var sourceImageBuffer = try? vImage_Buffer(cgImage: sourceCGImage,
                                                 format: sourceImageFormat) else {
    fatalError()
}

let componentCount = sourceImageFormat.componentCount
var argbSourcePlanarBuffers: [vImage_Buffer] = (0..<componentCount).map { _ in
    let bitsPerPixel = UInt32(8)

    guard let buffer = try? vImage_Buffer(width: Int(sourceImageBuffer.width),
                                          height: Int(sourceImageBuffer.height),
                                          bitsPerPixel: bitsPerPixel) else {
        fatalError("Error creating planar buffers.")
    }

    return buffer
}

vImageConvert_ARGB8888toPlanar8(&sourceImageBuffer,
                                &argbSourcePlanarBuffers[0],  // R
                                &argbSourcePlanarBuffers[1],  // G
                                &argbSourcePlanarBuffers[2],  // B
                                &argbSourcePlanarBuffers[3],  // A
                                vImage_Flags(kvImageNoFlags))

print("Image H: \(sourceCGImage.height), W: \(sourceCGImage.width)")
let capacity = sourceCGImage.width * sourceCGImage.height
print("capacity: \(capacity)")

// Repeat the following code for other 3 channels
let channelDataPtrR = argbSourcePlanarBuffers[0].data.bindMemory(to: Pixel_8.self,
                                                                 capacity: capacity)
let channelRawDataR = UnsafeBufferPointer(start: channelDataPtrR, count: capacity)
let channelDataR = Array(channelRawDataR)
var histogramR = [Double](repeating: 0.0, count: 255 + 1)
channelDataR.map { histogramR[Int($0)] += 1.0 }
print("histogramR:")
for (i, px) in histogramR.enumerated() {
    if px > 0.0 {
        print("Pixel Val \(i): \(px)")
    }
}

这是我得到的:

Image H: 1920, W: 1080
capacity: 2073600
histogramR:
Pixel Val 0: 129600.0
Pixel Val 255: 1944000.0
histogramG:
Pixel Val 0: 129600.0
Pixel Val 255: 1944000.0
histogramB:
Pixel Val 0: 129600.0
Pixel Val 255: 1944000.0
histogramA:
Pixel Val 0: 129600.0
Pixel Val 255: 1944000.0

问题:如果图像应该是纯白色(即所有像素值 = 255?),我怎么会得到像素值 0?

还要注意,如果我使用 Xcode 的模拟器运行上面的代码,我会得到不同数量的 Pixel Val 0,但 capacity 的总数仍然是 2073600。

【问题讨论】:

    标签: ios image-processing core-graphics accelerate-framework vimage


    【解决方案1】:

    这可能是因为缓冲区的实际行字节比图像宽(请参阅Finding the Sharpest Image in a Sequence of Captured Images 中的 Create Floating Point Pixels to Use in vDSP 以了解行字节如何超出图像的边界框)。

    如果您使用来自Specifying Histograms with vImage 的代码,您应该会得到您期望的值:

         var histogramBinZero = [vImagePixelCount](repeating: 0, count: 256)
            var histogramBinOne = [vImagePixelCount](repeating: 0, count: 256)
            var histogramBinTwo = [vImagePixelCount](repeating: 0, count: 256)
            var histogramBinThree = [vImagePixelCount](repeating: 0, count: 256)
            
            histogramBinZero.withUnsafeMutableBufferPointer { zeroPtr in
                histogramBinOne.withUnsafeMutableBufferPointer { onePtr in
                    histogramBinTwo.withUnsafeMutableBufferPointer { twoPtr in
                        histogramBinThree.withUnsafeMutableBufferPointer { threePtr in
                            
                            var histogramBins = [zeroPtr.baseAddress, onePtr.baseAddress,
                                                 twoPtr.baseAddress, threePtr.baseAddress]
                            
                            histogramBins.withUnsafeMutableBufferPointer { histogramBinsPtr in
                                let error = vImageHistogramCalculation_ARGB8888(&sourceImageBuffer,
                                                                                histogramBinsPtr.baseAddress!,
                                                                                vImage_Flags(kvImageNoFlags))
                                
                                guard error == kvImageNoError else {
                                    fatalError("Error calculating histogram: \(error)")
                                }
                            }
                        }
                    }
                }
            }
            
            for (i, px) in histogramBinTwo.enumerated() {
                if px > 0 {
                    print("Pixel Val \(i): \(px)")
                }
            }
    

    【讨论】:

    • 感谢@Simon,您提供的网址真的很有帮助!我使用vImageConvert_ARGB8888toPlanar8 的原因是我还需要找到每个像素在 RGB 通道上的最大强度。现在基于第一个 URL,对于每个 RGB Planar8,我创建一个带有显式行字节的中间 vImage 缓冲区,将其转换为 PlanarF,然后运行 ​​vDSP_vmax 两次以获得平面元素的元素最大值。对您来说,这听起来像是一个合理的解决方案吗?
    • 我认为vDSP_vmax 可能是您最好的选择。但是,8 位到 32 位的转换可能会有开销。可能值得尝试常规的 max 函数,但我怀疑 vDSP 会更快。
    • 最好的解决方案就是写一些向量代码来一次计算。
    • 嗨@IanOllmann,您能给我一些关于使用Accelerate API 对代码进行矢量化的建议,以便可以一次性完成吗?谢谢
    猜你喜欢
    • 2021-12-20
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-21
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    相关资源
    最近更新 更多