使用CPU/GPU计算AVCaptureVideoDataOutput源的平均RGB像素值的最快方法

4
我希望从AVCaptureVideoDataOutput的整个图像中获取平均像素值,目前我正在捕获图像并循环遍历像素进行求和。
我在想是否有更有效的方法使用GPU / openGL来完成此任务,因为这是可并行化的图像处理任务。(也许是重度高斯模糊,并读取中心像素值?)
一个具体的要求是需要高精度的结果,利用高水平的平均值。请注意下面的CGFloat结果。
当前Swift 2代码:
编辑:根据Simon的建议,添加了使用CIAreaAverage的实现。它由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)")

如果您从该API中获取了一个表面(我不熟悉它),则应该能够通过OpenGL的显式mipmap生成将其输入。它将依次平均1/4分辨率mipmaps以达到最终的LOD:1x1。最后一个LOD是您的平均值。但我不知道在iOS或OS X上它是如何实现的,因此性能可能相同或更差。 - Andon M. Coleman
3个回答

6
你的CIAreaAverage滤镜表现差的问题和原因在于缺少输入范围的定义。这导致滤镜的输出大小与输入图像相同,因此您循环遍历整个图像而不是一个1x1像素的图像。因此,执行时间和你最初的版本一样长。
CIAreaAverage的文档中所述,可以指定一个inputExtent参数。如何在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,而不是为每个捕获的帧重新创建它。


这个很好,但是我发现如果不在autoreleasepool中封装它,它会泄漏内存。 - YourMJK

2
有一个核心图像滤镜可以完成这个工作,CIAreaAverage,它返回一个单像素图像,其中包含感兴趣区域的平均颜色(您的感兴趣区域将是整个图像)。
顺便说一下,我在博客文章中讨论了将Core Image滤镜应用于实时相机视频源。简而言之,该滤镜需要一个CIImage,您可以在captureImage中基于sampleBuffer创建。
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
let cameraImage = CIImage(CVPixelBuffer: pixelBuffer!)

"...你需要将那个cameraImage传递给CIAreaAverage。"
"谢谢,"
"Simon"

好建议!我刚刚实现了它,现在正准备将代码添加到问题中。我想得到浮点精度,但目前这种方法只给我整数。有什么想法吗? - Ian
有趣的是,这似乎比我的手动循环运行得更慢。我正在iPhone 5上运行它,所以可能与5上相对GPU性能有关。 - Ian
抱歉这个问题。我知道一些核心图像滤镜是由Metal支持的,而在5s上使用Metal会有一些不稳定的情况。如果您之前的问题仍然存在,或者您已放弃这种方法,请告诉我。 - Flex Monkey
1
我想继续尝试这种方法。我很快就会得到一部6S,如果需要更高版本的设备来运行这个应用程序也没关系。如果您对浮点数有任何想法,我会很感激您的思路。谢谢! - Ian
一般来说,当您将 kCGBitmapFloatComponents 作为选项传递给 CGBitmapContextCreate 时,您会得到浮点数据。您还需要协调其他参数,如位深度等。显然。 - MirekE

1
如果您的数据为浮点值,您可以使用
func vDSP_meanv

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

vDSP_meanv看起来很不错...我开始实现它,但被@simon-gladman的建议分散了注意力。如果你认为vDSP会是一条更快的路线,我很乐意坚持下去。 - Ian
我会从Simon的建议开始。看起来非常酷。顺便说一下,vDSP仅支持CPU加速。 - MirekE
如何使用 vDSP_meanv? - Rubaiyat Jahan Mumu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接