0

I'm trying to use the vImageConvert_ARGB8888toPlanar8 API to extract RGB channels. Suppose the following inputImage is a solid white image from: https://www.solidbackgrounds.com/images/1080x1920/1080x1920-white-solid-color-background.jpg

Here is my code:

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)")
    }
}

and here is what I got:

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

Question: How come I'm getting pixel value 0 if the image is supposed to be solid white (i.e. all pixel value = 255?)

Also note that if I'm running the code above using the Xcode's simulator, I'm getting a different number of Pixel Val 0, but the total capacity is still 2073600.

alwc
  • 182
  • 4
  • 14

1 Answers1

1

This may be because the actual row bytes of your buffer is wider than your image (see Create Floating Point Pixels to Use in vDSP in Finding the Sharpest Image in a Sequence of Captured Images for an explanation of how row bytes extend beyond an image's bounding box).

If you use the code from Specifying Histograms with vImage, you should get the value that you're expecting:

     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)")
            }
        }
alwc
  • 182
  • 4
  • 14
Flex Monkey
  • 3,583
  • 17
  • 19
  • Thanks @Simon your provided URLs are really helpful! The reason why I'm using `vImageConvert_ARGB8888toPlanar8` is that I also need to find the max intensity across the RGB channel for each pixel. Right now based on the first URL, for each RGB Planar8 I create an intermediate vImage buffer with explicit row bytes, convert it to PlanarF, then run `vDSP_vmax` twice to get the element-wise maximum for the planar elements. Does it sound like a reasonable solution to you? – alwc Nov 06 '20 at 03:06
  • 1
    I think `vDSP_vmax` is probably your best bet. However, there may be an overhead in the 8-bit to 32-bit conversion. It might be worth trying the regular `max` function, but I suspect vDSP will be faster. – Flex Monkey Nov 06 '20 at 11:51
  • The best solution is just to write some vector code to calculate this in a single pass. – Ian Ollmann Dec 08 '20 at 16:44
  • Hi @IanOllmann , can you give me some pointers on vectorizing the code with the Accelerate APIs so that it can be done in a single pass? Thanks – alwc Dec 14 '20 at 07:22