基于Objective-C的触摸式图像处理

3
我正在建造一个iPhone/iPad应用程序,可以进行一些用户交互式的图像处理。这基本上是一个依赖于触摸的功能,可以根据触摸重新塑造任何图像。在Facetune应用程序中也可以找到非常相似的功能。
我的算法需要根据触摸移动计算控制点。然后根据这些控制点,我生成了一个结果网格,用于插值图像。我的整个方法都运行得很好。唯一的问题是实时速度较慢。我正在使用...
dispatch_async(dispatch_get_main_queue(), ^{

在触摸移动函数中,但仍然很慢。将图像重新缩放到大约360×某些大小可以加速处理过程,但会牺牲图像质量。
我的触摸移动代码是:
- (void)updateImage
{
    int bytesPerRow = CGImageGetBytesPerRow([staticBG.image CGImage]);
    int height = CGImageGetHeight([staticBG.image CGImage]);
    int width = CGImageGetWidth([staticBG.image CGImage]);
    CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider([staticBG.image CGImage]));
    unsigned char *baseImage = (unsigned char *)CFDataGetBytePtr(pixelData);

    unsigned char *output_image;
    output_image = [self wrappingInterpolation :baseImage :orig :changed :width :height :bytesPerRow];

    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast;
    CGContextRef context = CGBitmapContextCreate(output_image, width, height, 8, bytesPerRow, colorSpaceRef, bitmapInfo);
    CGImageRef imageRef = CGBitmapContextCreateImage (context);
    UIImage *newimage = [UIImage imageWithCGImage:imageRef];
    CGColorSpaceRelease(colorSpaceRef);
    CGContextRelease(context);
    CFRelease(imageRef);

    free(output_image);
    resultView.image = newimage;
    //staticBG.hidden = YES;
}

方法“wrappingInterpolation”对图像像素进行实际的图像处理,该方法经过高度优化,可能以实时方式运行。orig和changed参数只是我需要的两个浮点*浮点矩阵控制点。我在这里看到了一个恒定的开销,每次都从UIImage获取像素数据,然后创建CGImageRef、CGCOntextRef、ColorSpaceRef等,然后再释放它。 有什么方法可以进行优化吗? 如果可能的话,请提出任何其他可能的加速建议。但是我对OpenGL或着色器没有什么概念,所以可能无法通过着色器进行优化。

(void)updateImage在touch-move内被调用。 - Soumyajit
是的,请将图像包裹在280*280像素或更小的尺寸内。在保存之前,将最终控制点导出到较大的图像中。 :P - metsburg
1个回答

1
你说得对,瓶颈在于内存管理。当你将原始位图数据提取到CFDataRef pixelData上时,你正在解压完整的图像,并完全复制它。然后插值例程再次创建一个被处理并存储到output_image中的数据副本。然后两次释放内存,一次是为了释放通过属性分配释放的旧图像,一次是为了free(...)命令。就数字而言:iPhone 5以3264x2448的分辨率拍摄照片,这意味着在RGB格式下22MB(每通道8b),由包装例程加倍。这种双重分配和双重复制(以及双重释放)发生的速度与事件触发的速度相同。您还必须添加例程用于处理输入数据所需的时间:在8MP图像中循环每个像素意味着800万次迭代。这绝对需要很多计算能力。请注意,您想要实现的内容与实时视频/图像处理有些类似;至于视频处理,您应该采用特定技术。OpenGL确实是您最好的选择,但需要更多的努力。一些基本建议可能是:
  • 避免重复分配内存:如果您的算法允许原地转换,请直接在baseImage上工作,因为您已经复制了数据。

  • 缓存图像数据:不要每次都提取位图数据。如果您的算法可以“累加”,请保持相同的CFDataRef并每次操作它。

    在您的例程中,您始终使用staticBG作为输入数据:这表明算法始终更改基础图像。您可以一次缓存原始图像数据。

  • 避免使用UIImageCALayer有一个有用的属性contents,可以设置为CGImageRef以显示内容。此外,您正在做的事情与动画有关,因此您确实可以放弃UI部分,在CA级别上工作,这更加灵活(和快速)。请参阅Apple文档

  • 如果您真的必须使用中间内存存储,则重用相同的内存并将其复制到其中:图像的大小可能不会改变,并且您至少节省了分配时间。

  • 预渲染最终结果:您实际上可以只在屏幕大小上工作,并存储您需要处理完整大小图像的信息。然后您可以在随后的阶段执行此操作。

这些建议可以避免所有不必要的分配,剩下的只是一次复制操作,如果您的算法不是可加的或者不能原地运行。以下是一些对于您的算法可能已经实现了的建议:

  • 如果可能的话,就在原地处理数据。
  • 仅循环需要修改的像素。
  • 仅复制需要处理的数据。
  • 将像素视为向量,并使用优化的函数,如果可以的话,请查看 Accelerate 框架。有许多直接在图像上工作的例程。
  • 在适当情况下使用 Quartz 2D

仍然,最好的建议是:切换到OpenGL。即使在最好的情况下,你也会遇到一个问题:调用CGBitmapContextCreateImage,这是无法避免的。文档说你的数据被CoreGraphics复制:这意味着每帧至少需要一次完整的数据复制。据我所知,你不能实时修改正在显示的缓冲区。最好的近似值由OpenGL给出,它几乎在硬件级别运行,并写入一个缓冲区,该缓冲区直接刷新到由CoreAnimation显示的EAGLContext中。
这是一种非常不同的方法,但非常快速。一些视觉效果(如高斯模糊)需要相当多的功率,但在OpenGL下实时运行。你可以开始看看这个GLImageProcessing,它是一个非常有趣的例子,用于栅格图像操作。此外,OpenGL也非常适合图像变形:你可以将图像作为纹理显示在平面网格上,然后对其进行变形。

非常感谢您详细的回答。我很快就会查看GLImageProcessing教程,与此同时,我正在实现您提到的改进。顺便说一句,我查看了Facetune是如何做到这一点的,他们确实使用了自己定制的OpenGLES引擎。 - Soumyajit
不客气。是的,GL是一个很好的策略。不要被示例中使用的管理代码数量所迷惑:这是OpenGLES 1.1,现在有GLKit和ES2.0,使用着色器可以为您完成大部分工作。所有有趣的东西都在“Imaging.c”中,基本上将vfx分解成非常简单的像素加法操作,通过渲染带有适当混合函数的纹理平面来实现。 - Pietro Saccardi

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