计算AVCaptureVideoDataOutput feed的平均RGB像素值的最快方法- CPU/GPU



我想要AVCaptureVideoDataOutput提要中整个图像的平均像素值,我目前正在捕获图像并通过像素循环求和。

我想知道是否有一种更有效的方法来使用GPU/openGL,因为这是一个并行的图像处理任务。(也许是一个严重的高斯模糊,并读取中心像素值?)

一个特定的要求是高精度的结果,利用高水平的平均。注意下面CGFloat的结果。

当前swift 2代码:

编辑:添加了CIAreaAverage的实现,正如Simon下面建议的那样。用useGPU bool分隔

func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) {
    var redmean:CGFloat = 0.0;
    var greenmean:CGFloat = 0.0;
    var bluemean:CGFloat = 0.0;
    if (useGPU) {
            let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
            let cameraImage = CIImage(CVPixelBuffer: pixelBuffer!)
            let filter = CIFilter(name: "CIAreaAverage")
            filter!.setValue(cameraImage, forKey: kCIInputImageKey)
            let outputImage = filter!.valueForKey(kCIOutputImageKey) as! CIImage!
            let ctx = CIContext(options:nil)
            let cgImage = ctx.createCGImage(outputImage, fromRect:outputImage.extent)
            let rawData:NSData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage))!
            let pixels = UnsafePointer<UInt8>(rawData.bytes)
            let bytes = UnsafeBufferPointer<UInt8>(start:pixels, count:rawData.length)
            var BGRA_index = 0
            for pixel in UnsafeBufferPointer(start: bytes.baseAddress, count: bytes.count) {
                switch BGRA_index {
                case 0:
                    bluemean = CGFloat (pixel)
                case 1:
                    greenmean = CGFloat (pixel)
                case 2:
                    redmean = CGFloat (pixel)
                case 3:
                    break
                default:
                    break
                }
                BGRA_index++
            }
     } else {
            let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
            CVPixelBufferLockBaseAddress(imageBuffer!, 0)
            let baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer!, 0)
            let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer!)
            let width = CVPixelBufferGetWidth(imageBuffer!)
            let height = CVPixelBufferGetHeight(imageBuffer!)
            let colorSpace = CGColorSpaceCreateDeviceRGB()
            let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue).rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
            let context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo)
            let imageRef = CGBitmapContextCreateImage(context)
            CVPixelBufferUnlockBaseAddress(imageBuffer!, 0)
            let data:NSData = CGDataProviderCopyData(CGImageGetDataProvider(imageRef))!
            let pixels = UnsafePointer<UInt8>(data.bytes)
            let bytes = UnsafeBufferPointer<UInt8>(start:pixels, count:data.length)
            var redsum:CGFloat = 0
            var greensum:CGFloat  = 0
            var bluesum:CGFloat  = 0
            var BGRA_index = 0
            for pixel in UnsafeBufferPointer(start: bytes.baseAddress, count: bytes.count) {
            switch BGRA_index {
            case 0:
                bluesum += CGFloat (pixel)
            case 1:
                greensum += CGFloat (pixel)
            case 2:
                redsum += CGFloat (pixel)
            case 3:
                //alphasum += UInt64(pixel)
                break
            default:
                break
            }
            BGRA_index += 1
            if BGRA_index == 4 { BGRA_index = 0 }
        }
        redmean = redsum / CGFloat(bytes.count)
        greenmean = greensum / CGFloat(bytes.count)
        bluemean = bluesum / CGFloat(bytes.count)            
        }
print("R:(redmean) G:(greenmean) B:(bluemean)")

CIAreaAverage过滤器性能差的问题和原因是缺少输入范围的定义。因此,过滤器的输出与输入图像具有相同的大小,因此您可以遍历完整的图像,而不是1 × 1像素的图像。因此,执行所需的时间与初始版本相同。

如CIAreaAverage文档中所述,您可以指定 inputext 参数。如何在swift中做到这一点可以在这个类似问题的答案中找到:

    let cameraImage = CIImage(CVPixelBuffer: pixelBuffer!)
    let extent = cameraImage.extent
    let inputExtent = CIVector(x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height)
    let filter = CIFilter(name: "CIAreaAverage", withInputParameters: [kCIInputImageKey: cameraImage, kCIInputExtentKey: inputExtent])!
    let outputImage = filter.outputImage!

如果您想挤出更多的性能,您可以确保重用您的CIContext,而不是为每个捕获的帧重新创建它。

有一个核心图像过滤器CIAreaAverage可以完成这项工作,它返回一个单像素图像,其中包含感兴趣区域的平均颜色(您感兴趣的区域将是整个图像)。

仅供参考,我有一篇博客文章讨论了将核心图像过滤器应用于实时摄像机馈送。简而言之,过滤器需要一个CIImage,您可以基于sampleBuffercaptureImage中创建它:

let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
let cameraImage = CIImage(CVPixelBuffer: pixelBuffer!)

…你需要传递给CIAreaAverage的就是cameraImage

欢呼,西蒙

如果数据为浮点值,则可以使用

func vDSP_meanv

如果这不是一个选项,请尝试以一种方式处理数据,以便优化器可以使用SIMD指令。我没有任何好的方法,这对我来说是一种尝试和错误的练习,但是某些代码的重新排列可能比其他的更好。例如,我会尝试从回路中移除开关。SIMD将向量化您的计算,此外,您可以通过GCD使用多线程,在单独的核心上处理每一行图像数据…

最新更新