OpenCV的MatToUIImage导致内存泄漏

3
我的项目涉及iOS的openCV(2.4.9版本)。我发现函数MatToUIImage会导致内存泄漏,而且只在iOS 10.X上出现。
当我将这个函数(2.4.9)更新到最新版本(3.2.0)后,一切都正常了。唯一的区别是CGBitmapInfo。
那么,有人能告诉我为什么吗?
2.4.9
UIImage* MatToUIImage(const cv::Mat& image) {

    NSData *data = [NSData dataWithBytes:image.data
                                  length:image.elemSize()*image.total()];

    CGColorSpaceRef colorSpace;

    if (image.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }

    CGDataProviderRef provider =
            CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(image.cols,
                                        image.rows,
                                        8,
                                        8 * image.elemSize(),
                                        image.step.p[0],
                                        colorSpace,
                                        kCGImageAlphaNone|
                                        kCGBitmapByteOrderDefault,
                                        provider,
                                        NULL,
                                        false,
                                        kCGRenderingIntentDefault
                                        );


    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage;
}

3.2.0

UIImage* MatToUIImage(const cv::Mat& image) {

    NSData *data = [NSData dataWithBytes:image.data
                                  length:image.elemSize()*image.total()];

    CGColorSpaceRef colorSpace;

    if (image.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }

    CGDataProviderRef provider =
            CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // Preserve alpha transparency, if exists
    bool alpha = image.channels() == 4;
    CGBitmapInfo bitmapInfo = (alpha ? kCGImageAlphaLast : kCGImageAlphaNone) | kCGBitmapByteOrderDefault;

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(image.cols,
                                        image.rows,
                                        8,
                                        8 * image.elemSize(),
                                        image.step.p[0],
                                        colorSpace,
                                        bitmapInfo,
                                        provider,
                                        NULL,
                                        false,
                                        kCGRenderingIntentDefault
                                        );


    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage;
}

请查看最近的答案更新! - rudyryk
1个回答

1

重要更新(2017年5月6日) 最终,手动执行CFRelease被证明是一个坏主意,因为它可能会引起更多的麻烦而不是解决问题!虽然这给了我一个线索,泄漏与NSData(未)释放有某种联系。

我注意到,当从后台线程中的块调用时,它会像预期的那样自动释放:

- (void)runInBackgroundWithImageBuffer:(CVImageBufferRef)imageBuffer
                              callback:(void (^)())callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [self processImageBuffer:imageBuffer];
        if (callback != nil) {
            callback();
        }
    });
}

- (void)previewOpenCVImage:(cv::Mat *)image {
    UIImage *preview = MatToUIImage(*image);

    dispatch_async(dispatch_get_main_queue(), ^{
        // _imagePreview has (UIImageView *) type
        [_imagePreview setImage:preview];
    });
}

我可以确认这个问题只出现在iPhone模拟器中。看起来当前的MatToUIImage实现会导致内存泄漏,但是我无法在设备上重现这个问题。
由于某种原因,这些内存泄漏不会被分析器检测到,但是在多次调用后,内存使用量会急剧增加。
我已经添加了一些调整使其正常工作:
  1. 在返回最终图像之前添加代码行 CFRelease((CFTypeRef)data)
  2. 当图像不再需要时,我们需要执行 CFRelease(image.CGImage) 和可能的 CFRelease((CFTypeRef)image)
希望这能有所帮助。实际上,我并不完全理解为什么会发生这种情况,谁持有引用以及为什么需要手动释放。

1
非常感谢!你提供了一个真正的解决方案,而我只是替换了文件并将其放在一边... 我会测试它的。但我仍然不知道为什么改变CGBitmapInfo就能修复这个错误。这真的很奇怪。 - Tepmnthar
实际上事情更加奇怪。行为不仅在设备和模拟器之间有所不同,而且在模拟器的 iPhone 版本之间也有所不同。例如,在 iPhone 6(模拟器)上需要使用 CFRelease(oldImage.CGImage),但是 CFRelease((CFTypeRef)data) 将会导致崩溃。 - rudyryk

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