8

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

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

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

通过使用 3x3 内核执行二维卷积来过滤图像;单精度。

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

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

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

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

我怀疑我真正需要的是带有浮动组件的上下文,但根据这里的 Quartz 文档,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 * B 等) . 这可以解释为什么我得到了我认为的黑白结果,而不是转变。同样,我认为可能需要比我这里的幼稚版本更多...

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

4

5 回答 5

12

While the Accelerate framework will be faster than simple serial code, you'll probably never see the greatest performance by blurring an image using it.

My suggestion would be to use an OpenGL ES 2.0 shader (for devices that support this API) to do a two-pass box blur. Based on my benchmarks, the GPU can handle these kinds of image manipulation operations at 14-28X the speed of the CPU on an iPhone 4, versus the maybe 4.5X that Apple reports for the Accelerate framework in the best cases.

Some code for this is described in this question, as well as in the "Post-Processing Effects on Mobile Devices" chapter in the GPU Pro 2 book (for which the sample code can be found here). By placing your image in a texture, then reading values in between pixels, bilinear filtering on the GPU gives you some blurring for free, which can then be combined with a few fast lookups and averaging operations.

If you need a starting project to feed images into the GPU for processing, you might be able to use my sample application from the article here. That sample application passes AVFoundation video frames as textures into a processing shader, but you can modify it to send in your particular image data and run your blur operation. You should be able to use my glReadPixels() code to then retrieve the blurred image for later use.

Since I originally wrote this answer, I've created an open source image and video processing framework for doing these kinds of operations on the GPU. The framework has several different blur types within it, all of which can be applied very quickly to images or live video. The GPUImageGaussianBlurFilter, which applies a standard 9-hit Gaussian blur, runs in 16 ms for a 640x480 frame of video on the iPhone 4. The GPUImageFastBlurFilter is a modified 9-hit Gaussian blur that uses hardware filtering, and it runs in 2.0 ms for that same video frame. Likewise, there's a GPUImageBoxBlurFilter that uses a 5-pixel box and runs in 1.9 ms for the same image on the same hardware. I also have median and bilateral blur filters, although they need a little performance tuning.

In my benchmarks, Accelerate doesn't come close to these kinds of speeds, especially when it comes to filtering live video.

于 2011-05-10T19:01:03.010 回答
9

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

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

回复编辑:

一些东西:

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

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

于 2011-05-09T16:07:22.570 回答
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);
于 2011-05-10T10:06:06.913 回答
3

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

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

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

于 2011-05-18T14:51:35.483 回答
2

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

于 2014-01-21T02:22:37.440 回答