【问题标题】:The memory-efficient way of using Core Image on iOS?在 iOS 上使用 Core Image 的内存高效方式?
【发布时间】:2014-01-12 18:11:42
【问题描述】:

我在我的应用程序中使用 Core Image 过滤器,在运行 iOS 7 的 iPhone 5 设备上一切正常,但是当我在总内存仅为 512MB 的 iPhone 4s 上测试它时,应用程序崩溃了。

情况是这样,我从相机拍摄了 2 张图像,每张的分辨率为 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];

有没有更节省内存的方法来处理这种情况,或者我在这里做错了什么?

非常感谢!

【问题讨论】:

  • 将一些相关代码放入@autoreleasepool {相关代码}可能会有所帮助?
  • 我使用的是 ARC,所以我猜一切都在自动释放池中?
  • 自己试试看。
  • => 查找 NSAutoReleasePool 的 Xcode 文档
  • @DigiMonk,我在自动释放池中扭曲了上面的全部代码,但应用程序仍然崩溃,你建议的 relevant code 是什么?

标签: ios iphone objective-c memory core-image


【解决方案1】:

短版:

虽然从概念上看似乎微不足道,但对于相关设备而言,这实际上是一项相当消耗内存的任务。

加长版:

考虑一下:2 张图像 * RGBA 各 8 位 * 2448 * 3264 ~= 64MB。然后 CoreImage 将需要另外约 32MB 用于过滤操作的输出。然后从CIContextCGImage 可能会再消耗32MB。我希望UIImage 副本至少通过使用带有写时复制的 VM 映射映像来共享CGImage 的内存表示,尽管您可能会因为没有消耗“真实”而受到双重使用的困扰内存,它仍然计入映射的页面。

因此,至少,您正在使用 128MB(加上您的应用碰巧使用的任何其他内存)。对于像 4S 这样只有 512MB 开始的设备来说,这是一个相当大的 RAM。 IME,我会说这将是可能的。我希望它至少在某些时候能工作,但听到它收到内存警告和内存压力终止,我并不感到惊讶。您需要确保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;
}

如 cmets 中所示,高水位线将是采用两张输入图像并创建一张输出图像的操作。无论如何,这总是需要 3 张图像在内存中。要从那里进一步降低高水位线,您必须在部分/平铺中制作图像或将它们缩小到更小的尺寸。

【讨论】:

  • 感谢 ipmcc 和 digimonk,您的评论帮助我解决了这个问题。现在我知道如何使用 local autoreleasepool 来敦促释放内存。非常感谢!
  • 我们的应用也遇到了同样的问题,同样使用了 GPUImage 库,并且在 iPhone 4/4S 上必须将图像大小调整为 800 像素宽、640 像素。还将大部分处理都包含在@autorelease 中。
  • 前两个自动释放池无效。 CIImage 不会自己分配内存(将它们视为食谱)。在您的示例中,内存分配峰值出现在第三个自动释放池中,如果该峰值超出可用内存,它将崩溃。最后一个 autolrelease 池可能使 CIContext 内部资源提前释放了一点,与没有它相比,这仍然是一个改进。
  • 我有许多上下文一个接一个地工作,但它一直在设备上崩溃。这解决了它。谢谢ipmcc。
  • 你能发布这个答案的快速版本吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-20
  • 2017-03-05
  • 1970-01-01
  • 2014-01-29
  • 2017-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多