在iOS上使用Core Image的内存高效方法是什么?

9
我在我的应用程序中使用Core Image滤镜,在iPhone 5设备上运行iOS 7时一切正常,但当我在只有512MB总内存的iPhone 4s上测试时,应用程序会崩溃。
情况如下:我有两张从相机拍摄的图片,每张分辨率为2448x3264。在我的iPhone 5上,整个过程的峰值占用了150MB,根据工具显示。
然而,当我尝试在iPhone 4s上运行相同的代码时,即使整个内存使用很低(约为8 MB),仪器也一直给出内存不足警告。以下是屏幕截图。
以下是代码,基本上,我从我的应用程序文档文件夹中加载了两个图像,并连续应用了2个滤镜:
    CIImage *foreground = [[CIImage alloc] initWithContentsOfURL:foregroundURL];
    CIImage *background = [[CIImage alloc] initWithContentsOfURL:backgroundURL];
    CIFilter *softLightBlendFilter = [CIFilter filterWithName:@"CISoftLightBlendMode"];
    [softLightBlendFilter setDefaults];
    [softLightBlendFilter setValue:foreground forKey:kCIInputImageKey];
    [softLightBlendFilter setValue:background forKey:kCIInputBackgroundImageKey];

    foreground = [softLightBlendFilter outputImage];
    background = nil;
    softLightBlendFilter = nil;

    CIFilter *gammaAdjustFilter = [CIFilter filterWithName:@"CIGammaAdjust"];
    [gammaAdjustFilter setDefaults];
    [gammaAdjustFilter setValue:foreground forKey:kCIInputImageKey];
    [gammaAdjustFilter setValue:[NSNumber numberWithFloat:value] forKey:@"inputPower"];
    foreground = [gammaAdjustFilter valueForKey:kCIOutputImageKey];

    gammaAdjustFilter = nil;

    CIContext *context = [CIContext contextWithOptions:nil];
    CGRect extent = [foreground extent];
    CGImageRef cgImage = [context createCGImage:foreground fromRect:extent];

    UIImage *image = [UIImage imageWithCGImage:cgImage scale:1.0 orientation:imgOrientation];
    CFRelease(cgImage);
    foreground = nil;

    return image;

应用程序在这一行崩溃:CGImageRef cgImage = [context createCGImage:foreground fromRect:extent]; 有没有更节省内存的处理方式,或者我在这里做错了什么?
非常感谢!

1
如果您将一些相关的代码放入 @autoreleasepool { relevant code } 中,可能会有所帮助。 - user1804762
我正在使用ARC,所以我猜所有东西都在自动释放池中? - Void Main
自己试一试,看看效果如何。 - user1804762
请查阅Xcode文档以获取有关NSAutoReleasePool的信息。 - user1804762
@DigiMonk,我已经将上面的一大堆代码包裹在自动释放池中,但应用程序仍然崩溃了,您建议哪些是“相关代码”? - Void Main
显示剩余2条评论
1个回答

15

简短版:

尽管这个概念看起来微不足道,但对于涉及的设备来说,这实际上是一项相当占用内存的任务。

详细版:

考虑这个问题:2个图像*每个图像8位RGBA * 2448 * 3264 ≈ 64MB。然后CoreImage还需要另外约32MB用于滤镜操作的输出。然后将其从CIContext获取到CGImage中可能需要消耗另外32MB。我期望UIImage复制至少通过使用VM映射具有写时复制功能的图像来共享CGImage的内存表示,尽管由于尽管它不会占用“真实”内存,但您仍可能因为页面映射而计入双重使用。

所以在最低限度情况下,您使用了128MB(加上应用程序可能使用的任何其他内存)。对于像4S这样的设备来说,这是相当大的内存量,其初始内存只有512MB。根据我的经验,我会说这可能会达到可能性的边缘。我期望它至少在某些时候可以工作,但听到它正在收到内存警告和内存压力杀死时我并不感到惊讶。您需要确保CIContext和所有输入图像在生成CGImage后尽快被释放/处理,并且在从CGImage制作UIImage之前。

总的来说,通过缩小图像大小,可以使这个过程更容易。

没有进行测试,假设使用ARC,我提出以下作为潜在改进:

- (UIImage*)imageWithForeground: (NSURL*)foregroundURL background: (NSURL*)backgroundURL orientation:(UIImageOrientation)orientation value: (float)value
{
    CIImage* holder = nil;
    @autoreleasepool
    {
        CIImage *foreground = [[CIImage alloc] initWithContentsOfURL:foregroundURL];
        CIImage *background = [[CIImage alloc] initWithContentsOfURL:backgroundURL];
        CIFilter *softLightBlendFilter = [CIFilter filterWithName:@"CISoftLightBlendMode"];
        [softLightBlendFilter setDefaults];
        [softLightBlendFilter setValue:foreground forKey:kCIInputImageKey];
        [softLightBlendFilter setValue:background forKey:kCIInputBackgroundImageKey];

        holder = [softLightBlendFilter outputImage];
        // This probably the peak usage moment -- I expect both source images as well as the output to be in memory.
    }
    //  At this point, I expect the two source images to be flushed, leaving the one output image
    @autoreleasepool
    {
        CIFilter *gammaAdjustFilter = [CIFilter filterWithName:@"CIGammaAdjust"];
        [gammaAdjustFilter setDefaults];
        [gammaAdjustFilter setValue:holder forKey:kCIInputImageKey];
        [gammaAdjustFilter setValue:[NSNumber numberWithFloat:value] forKey:@"inputPower"];
        holder = [gammaAdjustFilter outputImage];
        // At this point, I expect us to have two images in memory, input and output
    }
    // Here we should be back down to just one image in memory
    CGImageRef cgImage = NULL;

    @autoreleasepool
    {
        CIContext *context = [CIContext contextWithOptions:nil];
        CGRect extent = [holder extent];
        cgImage = [context createCGImage: holder fromRect:extent];
        // One would hope that CG and CI would be sharing memory via VM, but they probably aren't. So we probably have two images in memory at this point too
    }
    // Now I expect all the CIImages to have gone away, and for us to have one image in memory (just the CGImage)
    UIImage *image = [UIImage imageWithCGImage:cgImage scale:1.0 orientation:orientation];
    // I expect UIImage to almost certainly be sharing the image data with the CGImageRef via VM, but even if it's not, we only have two images in memory
    CFRelease(cgImage);
    // Now we should have only one image in memory, the one we're returning.
    return image;
}

如评论所示,高水位标记将是将两个输入图像合并成一个输出图像的操作。无论如何,这始终需要在内存中保存3张图片。要进一步降低高水位标记,您必须将图像分成部分/瓷砖或将它们缩小到较小的尺寸。


1
感谢ipmcc和digimonk的评论,你们的建议帮助我解决了这个问题。现在我知道如何使用localautoreleasepool来释放内存了。非常感谢! - Void Main
3
前两个自动释放池没有任何作用。CIImage本身不会分配内存(将其视为配方)。在您的示例中,内存分配峰值发生在第三个自动释放池内,如果该峰值超出可用内存,程序将崩溃。最后一个自动释放池可能会使CIContext内部资源更早地被释放,这仍然比没有它要好。 - rsebbe
我有许多上下文一个接一个地工作,但它在设备上一直崩溃。这个修复了它。谢谢ipmcc。 - Custom Bonbons
1
请问您能否发布这个答案的 Swift 版本? - Syed Qamar Abbas
请纠正我,但我相信在注释“现在我期望所有CIImages都已经消失”的时候,“holder”中的“CIImage”不会消失。(并不是像上面的注释所提到的那样分配内存,但仍然如此。) - Jordan H
显示剩余3条评论

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