在iOS中如何释放CGImageRef?

11

我正在编写这个方法来计算图像的平均R、G、B值。以下方法以UIImage作为输入,并返回包含输入图像的R、G、B值的数组。但我有一个问题:如何/在哪里正确释放CGImageRef?

-(NSArray *)getAverageRGBValuesFromImage:(UIImage *)image
{
    CGImageRef rawImageRef = [image CGImage];

    //This function returns the raw pixel values
    const UInt8 *rawPixelData = CFDataGetBytePtr(CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef)));

    NSUInteger imageHeight = CGImageGetHeight(rawImageRef);
    NSUInteger imageWidth = CGImageGetWidth(rawImageRef);

    //Here I sort the R,G,B, values and get the average over the whole image
    int i = 0;
    unsigned int red = 0;
    unsigned int green = 0;
    unsigned int blue = 0;

    for (int column = 0; column< imageWidth; column++)
    {
        int r_temp = 0;
        int g_temp = 0;
        int b_temp = 0;

        for (int row = 0; row < imageHeight; row++) {
            i = (row * imageWidth + column)*4;
            r_temp += (unsigned int)rawPixelData[i];
            g_temp += (unsigned int)rawPixelData[i+1];
            b_temp += (unsigned int)rawPixelData[i+2];

        }

        red += r_temp;
        green += g_temp;
        blue += b_temp;

    }

    NSNumber *averageRed = [NSNumber numberWithFloat:(1.0*red)/(imageHeight*imageWidth)];
    NSNumber *averageGreen = [NSNumber numberWithFloat:(1.0*green)/(imageHeight*imageWidth)];
    NSNumber *averageBlue = [NSNumber numberWithFloat:(1.0*blue)/(imageHeight*imageWidth)];


    //Then I store the result in an array
    NSArray *result = [NSArray arrayWithObjects:averageRed,averageGreen,averageBlue, nil];


    return result;
}

我尝试了两种方法: 选项1: 我不释放CGImageRef,但是在几个循环(5次以上)后程序会崩溃,并出现“低内存警告错误”。

选项2: 我添加了一行 CGImageRelease(rawImageRef) 然后再方法返回之前。现在,在第二个循环后程序就崩溃了,我得到了传递给该方法的UIImage的EXC_BAD_ACCESS错误。当我在Xcode中尝试分析(而不是运行)时,会在这一行收到以下警告。 “错误地减少了调用者此时未拥有的对象的引用计数”

我应该在哪里以及如何释放CGImageRef?

谢谢!


一种找到这些问题的方法是前往产品→分析,分析器会为您找到这个内存处理错误。 - Jonny
5个回答

69
你的内存问题是由于复制的数据引起的,正如其他人所说。但这里有另一个想法:使用核心图形优化的像素插值来计算平均值。
  1. 创建一个1x1位图上下文。
  2. 将插值质量设置为中等(稍后再看)。
  3. 将您的图像缩小到恰好这一个像素。
  4. 从上下文缓冲区读取RGB值。
  5. (当然要释放上下文。)
这可能会导致更好的性能,因为核心图形高度优化,甚至可能使用GPU进行缩小。
测试表明,中等质量似乎通过取颜色值的平均值来插值像素。 这就是我们想要的。
至少值得一试。 编辑: 好吧,这个想法似乎太有趣了,不能不尝试。 所以这里有一个示例项目展示了差异。 下面的测量是使用包含的512x512测试图像进行的,但如果您愿意,可以更改图像。
计算图像数据中所有像素的平均值需要约12.2毫秒。使用绘制单个像素的方法只需要3毫秒,因此速度大约快了4倍。当使用kCGInterpolationQualityMedium时,它似乎会产生相同的结果。
我认为这种巨大的性能提升是Quartz注意到它不必完全解压缩JPEG,而是只能使用DCT的低频部分所致。当组合比0.5小的缩放的JPEG压缩像素时,这是一种有趣的优化策略。但这只是我的猜测。
有趣的是,使用您的方法,70%的时间花费在CGDataProviderCopyData上,只有30%的时间花费在像素数据遍历上。这暗示着大量时间花费在JPEG解压缩上。

Pixel Iterating Screenshot Draw-To-One-Pixel Screenshot

注意:以上示例图的迟到的跟进在此提供。

@Jonny 对于非矩形区域的平均值计算,可以通过在绘制之前设置剪辑(路径或掩码)来轻松完成。剪辑将使一些像素变为透明,这意味着它们不会影响最终结果。大功告成! - Nikolai Ruhe
好的,我可能会考虑在这个类中添加这样一个功能。哦,我只是试图编写一个“魔法棒”,用于选择具有相似肤色/色调的面部区域。 - Jonny
测试似乎表明,由核心图形执行的alpha采样并未产生预期的平均值。如果不进一步深入探讨这个话题,我不建议在非矩形区域使用这种方法。 - Nikolai Ruhe
平均颜色和合并颜色有什么区别?感谢提供代码! - Daniel
1
@Daniel,“Average”只是直接遍历所有像素并计算平均值的简单代码。“Merged”是提出的实现方案,它实际上绘制图像并使用结果作为平均颜色。 - Nikolai Ruhe

8
你不需要释放[image CGImage]所获得的CGImageRef rawImageRef,因为它并非你所拥有。但是,rawPixelData是你使用CGDataProviderCopyData获取的,你需要释放它。

CGDataProviderCopyData

返回值: 包含提供程序数据副本的新数据对象。您需要释放此对象。


2
你需要保留CFDataRef并在适当的时候调用CFRelease来释放它。请注意,这里的规范与Objective-C的规范完全相反;如果你传递CFRelease(NULL),Core Foundation会故意引发异常,请小心处理。 - Tommy
@Tommy 当然,因为在Obj-C中的nil和普通C语言中的NULL之间有很大的区别。 - Sulthan

4

您的mergedColor在从文件加载的图像上运作良好,但对于通过相机捕获的图像则不然。因为从捕获的示例缓冲区创建的上下文中的CGBitmapContextGetData()函数无法返回其位图。我已将您的代码更改如下。它适用于任何图像,并且与您的代码一样快。

- (UIColor *)mergedColor
{
     CGImageRef rawImageRef = [self CGImage];

    // scale image to an one pixel image

    uint8_t  bitmapData[4];
    int bitmapByteCount;
    int bitmapBytesPerRow;
    int width = 1;
    int height = 1;

    bitmapBytesPerRow = (width * 4);
    bitmapByteCount = (bitmapBytesPerRow * height);
    memset(bitmapData, 0, bitmapByteCount);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate (bitmapData,width,height,8,bitmapBytesPerRow,
             colorspace,kCGBitmapByteOrder32Little|kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorspace);
    CGContextSetBlendMode(context, kCGBlendModeCopy);
    CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), rawImageRef);
    CGContextRelease(context);
    return [UIColor colorWithRed:bitmapData[2] / 255.0f
                    green:bitmapData[1] / 255.0f
                             blue:bitmapData[0] / 255.0f
                            alpha:1];
}

4

我认为你的问题在于这个语句:

const UInt8 *rawPixelData = CFDataGetBytePtr(CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef)));

您应该释放CGDataProviderCopyData的返回值。

3
CFDataRef abgrData = CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef));

const UInt8 *rawPixelData = CFDataGetBytePtr(abgrData);

...

CFRelease(abgrData);

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