iPhone 图像处理与 Accelerate 框架和 vDSP

Posted

技术标签:

【中文标题】iPhone 图像处理与 Accelerate 框架和 vDSP【英文标题】:iPhone Image Processing with Accelerate Framework and vDSP 【发布时间】:2011-05-09 13:06:09 【问题描述】:

更新:请参阅下面的附加问题以及更多代码;

我正在尝试编写用于模糊图像的类别。我的出发点是Jeff LaMarche's sample here。虽然这(在其他人建议的修复之后)工作正常,但对于我的要求来说它太慢了一个数量级 - 在 3GS 上,可能需要 3 秒才能进行体面的模糊,我希望将其降低到 0.5 以下秒全屏(越快越好)。

他提到 Accelerate 框架是一种性能增强,所以我花了最后一天研究这个,特别是根据 Apple 文档的 vDSP_f3x3

通过执行过滤图像 具有 3x3 的二维卷积 核心;单精度。

完美 - 我有一个合适的过滤器矩阵,我有一个图像......但这就是我难过的地方。

vDSP_f3x3 假设图像数据是 (float *) 但我的图像来自;

srcData = (unsigned char *)CGBitmapContextGetData (context);

上下文来自 CGBitmapContextCreate 和 kCGImageAlphaPremultipliedFirst,所以我的 srcData 实际上是 ARGB,每个组件有 8 位。

我怀疑我真正需要的是带有浮动组件的上下文,但according to the Quartz documentation here,kCGBitMapFloatComponents 仅适用于 Mac OS 而不是 ios :-(

有没有一种非常快速的方法使用加速框架将我拥有的整数分量转换为 vDSP_f3x3 需要的浮点分量?我的意思是我可以自己做,但是当我这样做的时候,然后是卷积,然后再转换回来,我怀疑我会让它变得比现在更慢,因为我可以随心所欲地进行卷积。

也许我的方法有误?

有人对我使用 vDSP 在 iphone 上进行了一些图像处理有什么建议吗?我能找到的文档是非常面向参考的,对于这类事情不是很友好。

如果有人有关于真正快速模糊的参考(和高质量,而不是降低分辨率然后重新缩放我见过的看起来很裤子的东西),那就太棒了!

编辑:

感谢@Jason。我已经这样做了,它几乎可以工作了,但现在我的问题是,虽然图像确实模糊,但在每次调用时它都会向左移动 1 个像素。它似乎也使图像变成黑白,但这可能是别的东西。

这段代码中有什么明显不正确的地方吗?我还没有优化,有点粗糙,但希望卷积代码足够清晰。

CGImageRef CreateCGImageByBlurringImage(CGImageRef inImage, NSUInteger pixelRadius, NSUInteger gaussFactor)

unsigned char *srcData, *finalData;

CGContextRef context = CreateARGBBitmapContext(inImage);
if (context == NULL) 
    return NULL;

size_t width = CGBitmapContextGetWidth(context);
size_t height = CGBitmapContextGetHeight(context);
size_t bpr = CGBitmapContextGetBytesPerRow(context);

int componentsPerPixel = 4; // ARGB

CGRect rect = 0,0,width,height; 
CGContextDrawImage(context, rect, inImage); 

// Now we can get a pointer to the image data associated with the bitmap
// context.

srcData = (unsigned char *)CGBitmapContextGetData (context);

if (srcData != NULL)


    size_t dataSize = bpr * height;
    finalData = malloc(dataSize);
    memcpy(finalData, srcData, dataSize);

    //Generate Gaussian kernel

    float *kernel;  

    // Limit the pixelRadius

    pixelRadius = MIN(MAX(1,pixelRadius), 248);
    int kernelSize = pixelRadius * 2 + 1;

    kernel = malloc(kernelSize * sizeof *kernel);

    int gauss_sum =0;

    for (int i = 0; i < pixelRadius; i++)
    
        kernel[i] = 1 + (gaussFactor*i);
        kernel[kernelSize - (i + 1)] = 1 + (gaussFactor * i);
        gauss_sum += (kernel[i] + kernel[kernelSize - (i + 1)]);
    

    kernel[(kernelSize - 1)/2] = 1 + (gaussFactor*pixelRadius);

    gauss_sum += kernel[(kernelSize-1)/2];

    // Scale the kernel

    for (int i=0; i<kernelSize; ++i) 
        kernel[i] = kernel[i]/gauss_sum;
    

    float * srcAsFloat,* resultAsFloat;

    srcAsFloat = malloc(width*height*sizeof(float)*componentsPerPixel);
    resultAsFloat = malloc(width*height*sizeof(float)*componentsPerPixel);

   // Convert uint source ARGB to floats

    vDSP_vfltu8(srcData,1,srcAsFloat,1,width*height*componentsPerPixel);

    // Convolve (hence the -1) with the kernel

    vDSP_conv(srcAsFloat, 1, &kernel[kernelSize-1],-1, resultAsFloat, 1, width*height*componentsPerPixel, kernelSize);

    // Copy the floats back to ints

    vDSP_vfixu8(resultAsFloat, 1, finalData, 1, width*height*componentsPerPixel);

    free(resultAsFloat);
    free(srcAsFloat);



size_t bitmapByteCount = bpr * height;

CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, finalData, bitmapByteCount, &providerRelease);

CGImageRef cgImage = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context),
                                   CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), 
                                   dataProvider, NULL, true, kCGRenderingIntentDefault);

CGDataProviderRelease(dataProvider);
CGContextRelease(context); 


return cgImage;

如果我注释掉 vDSP_conv 行,我应该补充一点,并将下面的行更改为;

       vDSP_vfixu8(srcAsFloat, 1, finalData, 1, width*height*componentsPerPixel);

然后正如预期的那样,我的结果是原始来源的克隆。在颜色上而不是向左移动。这对我来说意味着这是错误的卷积,但我看不出在哪里:-(

想法:实际上考虑到这一点,在我看来,卷积需要知道输入像素是 ARGB 格式,否则卷积会将值相乘而不知道它们的含义(即它将多个 R *乙等)。这可以解释为什么我得到了我认为的黑白结果,而不是转变。同样,我认为可能需要比我这里的幼稚版本更多...

最后的想法:我认为向左移动是过滤器的自然结果,我需要查看图像尺寸并可能将其填充...所以我认为代码实际上可以正常工作,因为我已经喂过它。

【问题讨论】:

【参考方案1】:

虽然 Accelerate 框架会比简单的串行代码更快,但您可能永远不会看到使用它模糊图像的最佳性能。

我的建议是使用 OpenGL ES 2.0 着色器(适用于支持此 API 的设备)进行两次框模糊。根据我的基准测试,GPU 处理此类图像处理操作的速度是 iPhone 4 上 CPU 的 14-28 倍,而 Apple 报告的 Accelerate 框架在最佳情况下可能为 4.5 倍。

在this question 以及GPU Pro 2 book 的“移动设备上的后处理效果”一章中描述了一些代码(可以在here 中找到示例代码)。通过将图像放置在纹理中,然后读取像素之间的值,GPU 上的双线性过滤可以免费为您提供一些模糊效果,然后可以结合一些快速查找和平均操作。

如果您需要一个启动项目来将图像输入 GPU 进行处理,您可以使用我在文章 here 中的示例应用程序。该示例应用程序将 AVFoundation 视频帧作为纹理传递到处理着色器中,但您可以对其进行修改以发送特定图像数据并运行模糊操作。您应该可以使用我的glReadPixels() 代码检索模糊图像以供以后使用。

自从我最初写这个答案以来,我已经创建了an open source image and video processing framework 用于在 GPU 上执行这些类型的操作。该框架中有几种不同的模糊类型,所有这些都可以非常快速地应用于图像或实时视频。 GPUImageGaussianBlurFilter 应用标准 9-hit 高斯模糊,在 iPhone 4 上对 640x480 帧视频运行时间为 16 毫秒。GPUImageFastBlurFilter 是使用硬件过滤的修改后的 9-hit 高斯模糊,运行时间为 2.0 ms相同的视频帧。同样,有一个 GPUImageBoxBlurFilter 使用 5 像素框,在相同硬件上运行相同图像的时间为 1.9 毫秒。我也有中值和双边模糊过滤器,尽管它们需要一些性能调整。

在我的基准测试中,Accelerate 并没有接近这种速度,尤其是在过滤实时视频时。

【讨论】:

这是一个非常有趣的想法......感谢横向思考。我没有意识到我可以在非 openGL 项目中使用 openGLES 进行图像处理,从而开辟了很多想法,谢谢!【参考方案2】:

您肯定希望转换为float 来执行过滤,因为这是加速功能所采用的,而且如果您想做任何额外的处理,它会更加灵活。二维卷积(过滤器)的计算时间很可能会使转换所花费的任何时间相形见绌。看一下函数vDSP_vfltu8(),它将快速将 uint8 数据转换为浮点数。 vDSP_vfixu8() 会将其转换回 uint8。

要进行模糊处理,您可能需要比 3x3 更大的卷积核,因此我建议使用函数 vDSP_imgfir(),它可以采用任何内核大小。

对编辑的回应:

一些事情:

    您需要单独对每个颜色通道执行过滤。也就是说,您需要将 R、G 和 B 分量拆分为各自的图像(float 类型),对其进行过滤,然后将它们重新复用为 ARGB 图像。

    vDSP_conv 计算一维卷积,但要模糊图像,您确实需要二维卷积。 vDSP_imgfir 本质上是计算二维卷积。为此,您还需要一个二维内核。您可以查找二维高斯函数的公式来生成内核。注意:如果您的内核是可分离的(高斯是)。我不会深入探讨这意味着什么,但您基本上必须在列上执行一维卷积,然后在结果行上执行一维卷积。除非你知道自己在做什么,否则我不会走这条路。

【讨论】:

谢谢@Jason,多亏了这些提示,我现在几乎可以正常工作了,尽管我使用的是 vDSP_conv 而不是 vDSP_imgfir。你能看出我的代码有什么明显的问题吗? @Roger,看我上面的回复 再次感谢。这一切都说得通……我明天会解决 demux 的问题,看看我的进展如何。 谢谢。这有帮助。我不知道 vDSP_conv 只做了一维卷积。但本可以使用vImageConvolve_ARGB8888,如@IanOllman anwer 是的,vImageConvolve_ARGB8888 是一个更好的选择,但在 2011 年 5 月被问到这个问题时还没有。它是在 iOS 5(2011 年 10 月)中添加的。【参考方案3】:

因此,在 Jason 的出色帮助下回答了我自己的问题,此处提供了最终的工作代码片段以供参考,以防它对其他人有所帮助。如您所见,策略是将源 ARGB(我忽略 A 以提高性能并假设数据为 XRGB)为 3 个浮点数组,应用过滤器,然后重新复用结果。

这是一种享受 - 但它非常缓慢。我正在使用 16x16 的大内核来获得严重的模糊效果,而在我的 3GS 上,全屏图像大约需要 5 秒,所以这不是一个可行的解决方案。

下一步是寻找替代方案......但感谢您让我开始运行。

    vDSP_vfltu8(srcData+1,4,srcAsFloatR,1,pixels);
    vDSP_vfltu8(srcData+2,4,srcAsFloatG,1,pixels);
    vDSP_vfltu8(srcData+3,4,srcAsFloatB,1,pixels);

    // Now apply the filter to each of the components. For a gaussian blur with a 16x16 kernel
    // this turns out to be really slow!

    vDSP_imgfir (srcAsFloatR, height, width, kernel,resultAsFloatR, frows, fcols);
    vDSP_imgfir (srcAsFloatG, height, width, kernel,resultAsFloatG, frows, fcols);
    vDSP_imgfir (srcAsFloatB, height, width, kernel,resultAsFloatB, frows, fcols);

    // Now re-multiplex the final image from the processed float data

    vDSP_vfixu8(resultAsFloatR, 1, finalData+1, 4, pixels);
    vDSP_vfixu8(resultAsFloatG, 1, finalData+2, 4, pixels);
    vDSP_vfixu8(resultAsFloatB, 1, finalData+3, 4, pixels);

【讨论】:

【参考方案4】:

如果您正在考虑实施此操作,以供将来参考不要:我已经为您完成了!

见: https://github.com/gdawg/uiimage-dsp

对于使用 vDSP 和 Accelerate 框架添加高斯/框模糊/锐化的 UIImage 类别。

【讨论】:

非常好 - 我无意重新发明任何***,并且很高兴使用你的图书馆,乍一看似乎绝对完美 - 非常感谢你引起我的注意。【参考方案5】:

为什么要使用 vDSP 进行图像过滤?试试 vImageConvolve_ARGB8888()。 vImage 是 Accelerate.framework 的图像处理组件。

【讨论】:

以上是关于iPhone 图像处理与 Accelerate 框架和 vDSP的主要内容,如果未能解决你的问题,请参考以下文章

Accelerate 的 vImage 与 vDSP

iPhone Accelerate Framework FFT 转换二维数组

是否有计算 Accelerate Framework for iPhone 中点数组的线性回归的函数? [关闭]

<Accelerate/Accelerate.h> 在设备上构建时找不到

使用 Accelerate 进行并行编程 (Data.Array.Accelerate)

如何从链接到 Apple Accelerate 框架的源代码构建 NumPy?