如何处理iOS 8照片扩展中的内存限制?

7

我为我的现有照片编辑应用添加了新的iOS 8照片扩展。我的应用程序有相当复杂的过滤流程,并需要同时保留多个纹理在内存中。然而,在只有1 GB RAM的设备上,我可以轻松地处理8 MP的图像。

然而,在扩展中,有更高的内存限制。我不得不将图像缩小到低于2 MP,以便在不崩溃扩展的情况下进行处理。我还发现,只有在没有调试器附加到扩展时才会出现内存问题。使用调试器时,一切都正常。

我进行了一些实验。我修改了一个内存预算测试应用程序,使其在扩展中工作,并得出了以下结果(显示在崩溃之前可以分配的RAM量,以MB为单位):

╔═══════════════════════╦═════╦═══════════╦══════════════════╗
║        Device         ║ App ║ Extension ║ Ext. (+Debugger) ║
╠═══════════════════════╬═════╬═══════════╬══════════════════╣
║ iPhone 6 Plus (8.0.2) ║ 646 ║       115 ║              645 ║
║ iPhone 5 (8.1 beta 2) ║ 647 ║        97 ║              646 ║
║ iPhone 4s (8.0.2)     ║ 305 ║        97 ║              246 ║
╚═══════════════════════╩═════╩═══════════╩══════════════════╝

一些观察结果:
- 附加调试器后,扩展程序的行为类似于“正常”应用程序。 - 尽管 iPhone 4s 只有总内存量(512 MB)的一半,但它从系统中获得了与其他设备相同的 ~100MB 的内存供扩展程序使用。
现在我的问题是:在照片编辑扩展程序中,我应该如何处理这么少的内存?一个包含8MP(相机分辨率)RGBA图像的纹理就已经占用了大约31MB的内存。如果我必须告诉用户只有在使用主应用程序时才能进行全尺寸编辑,那这个扩展机制还有什么意义呢?
你们中有人也遇到了这个限制吗?你找到了规避这个限制的解决方案吗?

我应该指出这里的罪魁祸首是UIImage;苹果文档说明,任何由UIImage对象引用的1,920×1,080照片或视频都将不可避免地为照片编辑扩展创建一个内存问题。您在Photos应用程序中使用的不是手机限制,而是应用程序特定的限制(我认为这与防止应用程序崩溃的恶意软件有关);指向摄像头内存或其他内容是无济于事的。不幸的是,他们说你必须将媒体的大小减小到1,280×720或更低。平铺不是选项。 - James Bush
好奇在 iOS 10 中会有什么变化,尤其是在使用像 GPUImage 这样的其他图像技术时,根据 @rickster 在下面提到的 CIImageProcessorKernel - brandonscript
3个回答

3
我正在为公司开发一款照片编辑扩展程序,我们遇到了同样的问题。我们内部的图像处理引擎需要超过150MB的内存才能对图像应用某些效果。这还不包括全景图像,每个副本需要大约100MB的内存。

我们只找到了两个解决方法,但并非实际解决方案。

  1. 缩小图像大小,然后应用滤镜。这将需要更少的内存,但图像结果很差。至少扩展程序不会崩溃。

或者

  1. 使用CoreImage或Metal进行图像处理。正如我们分析的苹果示例照片编辑扩展所使用的CoreImage,可以处理非常大的图像,甚至是全景图像,而不会损失质量或分辨率。事实上,我们无法通过加载非常大的图像来使扩展程序崩溃。示例代码可以处理具有40MB内存峰值的全景图像,这相当令人印象深刻。

根据苹果的应用扩展编程指南第55页“处理内存限制”章节,解决扩展程序中的内存压力问题是审查您的图像处理代码。到目前为止,我们正在将我们的图像处理引擎移植到CoreImage,结果比以前的引擎要好得多。

我希望能对你有所帮助。 Marco Paiva


2
谢谢,Marco。我猜CoreImage在底层做了平铺。我会尝试使用Instruments来弄清楚它到底在做什么。我的问题是,我需要支持存储有符号值的纹理,据我所知,目前无法使用CoreImage实现这一点。另一个问题是,自定义的CoreImage滤镜仅在iOS 8中受支持,而我仍然希望在我的主应用程序中支持iOS 7。我想最终我会自己实现一个平铺机制。但很高兴知道CoreImage可以处理大图像。再次感谢。 - Frank Rupprecht
1
请注意,Core Image 提供了多种机会来插入自己的图像处理(同时受益于 CI 内存/GPU/颜色等管理)。除了一直存在的编写自定义内核的能力外,iOS 10 还引入了 CIImageProcessorKernel,它可以让您将其他技术插入到 CI 图像处理链中。 - rickster

0

以下是如何在核心图像中应用两个连续的卷积核,并在它们之间使用“中间结果”:

- (CIImage *)outputImage { 

const double g = self.inputIntensity.doubleValue;
const CGFloat weights_v[] = { -1*g, 0*g, 1*g,
                              -1*g, 0*g, 1*g,
                              -1*g, 0*g, 1*g};

CIImage *result = [CIFilter filterWithName:@"CIConvolution3X3" keysAndValues:
          @"inputImage", self.inputImage,
          @"inputWeights", [CIVector vectorWithValues:weights_v count:9],
          @"inputBias", [NSNumber numberWithFloat:1.0],
          nil].outputImage;

CGRect rect = [self.inputImage extent];
rect.origin = CGPointZero;

CGRect cropRectLeft = CGRectMake(0, 0, rect.size.width, rect.size.height);
CIVector *cropRect = [CIVector vectorWithX:rect.origin.x Y:rect.origin.y Z:rect.size.width W:rect.size.height];
result = [result imageByCroppingToRect:cropRectLeft];

result = [CIFilter filterWithName:@"CICrop" keysAndValues:@"inputImage", result, @"inputRectangle", cropRect, nil].outputImage;


const CGFloat weights_h[] = {-1*g, -1*g, -1*g,
    0*g,   0*g,   0*g,
    1*g,   1*g,     1*g};


result = [CIFilter filterWithName:@"CIConvolution3X3" keysAndValues:
          @"inputImage", result,
          @"inputWeights", [CIVector vectorWithValues:weights_h count:9],
          @"inputBias", [NSNumber numberWithFloat:1.0],
          nil].outputImage;

result = [result imageByCroppingToRect:cropRectLeft];

result = [CIFilter filterWithName:@"CICrop" keysAndValues:@"inputImage", result, @"inputRectangle", cropRect, nil].outputImage;

result = [CIFilter filterWithName:@"CIColorInvert" keysAndValues:kCIInputImageKey, result, nil].outputImage;

return result;

}


就像我之前所说的,我知道Core Image是如何工作的,以及如何使用它来应用(非常简单的)卷积滤镜。我在你的另一个回答中试图告诉你的是:a)即使是Core Image在某些情况下也需要分配多个缓冲区(纹理),b)我不能用Core Image做我需要的一切。似乎Core Image在内存占用方面表现出色——我猜测这是因为它在底层支持平铺。然而,正如Brad Larson在他的GPUImage框架中指出的那样,这也使得它相对较慢。 - Frank Rupprecht

0

如果您正在使用Core Image“配方”,就不必担心内存问题,正如Marco所说的那样。只有在将应用了Core Image滤镜的图像对象返回到视图后,才会呈现该图像。

这意味着您可以对一个高速公路广告牌大小的照片应用一百万个滤镜,内存也不会成为问题。滤镜规格将被编译成卷积或核函数,它们都归结为相同的大小,无论如何。

关于内存管理和溢出等误解可以通过了解所选编程语言、开发环境和硬件平台的核心概念来轻松解决。

Apple介绍Core Image滤镜编程的文档已经足够了;如果您想要特定部分的参考文献,我可以为您提供。


抱歉,这并没有帮助到我。我知道 Core Image 是如何工作的以及它的能力。它可以通过将一些后续的滤镜步骤编译成一个来进行优化,但并不总是能够做到这一点。例如,两个连续的卷积核无法编译为一个——你需要一个中间结果。正如我对 Marco 所说的那样,Core Image 不支持我需要的所有管道功能。我已经将我的管道优化为“仅”同时使用 4 个纹理,但这仍然对扩展的内存要求过高... - Frank Rupprecht
一次使用四个纹理?在Core Image中如何使用一个“纹理”?这没有意义... - James Bush
正如我所说,我不能使用Core Image,因为它没有提供我所需要的一切。我使用OpenGL编写了自己的图像处理管道。 - Frank Rupprecht
我应该拥有OpenGL的子集glslang。你可能在谈论OpenGL的整体,但如果你正在编写照片编辑扩展程序,这不是你可能使用的。运行用glslang编写的OpenGL内核代码比不用更容易;你只需将其作为一个bundle资源加载到自定义的核心图像过滤器中。核心图像不仅限于内置过滤器,你是正确的,它们并不能适用于所有情况;我并不是建议你优先选择核心图像而不是OpenGL。OpenGL(glslang)内核可以加载到自定义的核心图像过滤器中;然后,如果需要,你可以将核心图像过滤器与你的内核组合使用。 - James Bush
抱歉,Frank;我没有将纹理转换为采样器。在你发表评论的时候,你是正确的;iOS不允许您向CIKernel类传递多个采样器对象(也无法创建CISampler对象)。随着iOS 9的推出,这一点已经改变了;就像MacOS X一样,可以创建CISampler对象,并将其多次传递给CIKernel类。再次抱歉;我应该花更多时间理解你的意思(而且,我非常想看到您成功地使用自定义Core Image过滤器在iOS应用程序中传递多个“纹理”)。 - James Bush

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