从CIImage获取CGImage

35

我有一个UIImage,它是从CIImage加载的:

tempImage = [UIImage imageWithCIImage:ciImage];

问题是,我需要将tempImage剪裁到特定的CGRect中,我唯一知道如何做到这一点的方法是使用CGImage。 但是在iOS 6.0文档中,我找到了以下内容:

CGImage
如果UIImage对象是使用CIImage对象初始化的,则该属性的值为NULL。

A. 如何从CIImage转换为CGImage? 我正在使用此代码,但是我有一个内存泄漏(并且不知道在哪里):

+(UIImage*)UIImageFromCIImage:(CIImage*)ciImage {  
    CGSize size = ciImage.extent.size;  
    UIGraphicsBeginImageContext(size);  
    CGRect rect;  
    rect.origin = CGPointZero;  
    rect.size   = size;  
    UIImage *remImage = [UIImage imageWithCIImage:ciImage];  
    [remImage drawInRect:rect];  
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();  
    UIGraphicsEndImageContext();  
    remImage = nil;  
    ciImage = nil;  
    //
    return result;  
}

你提到需要使用CGImage进行裁剪。正如Joris Kluivers所说,您可以通过在CIImage上使用CICrop滤镜来完成裁剪,而无需使用CGImage。如果还有其他需要使用CGImage的地方,请问是什么? - Peter Hosey
另外,关于内存泄漏,你尝试使用Instruments的Leaks模板了吗?通过Leaks工具和Allocations工具的Heapshot工具,你应该能够找出你的应用程序在哪里泄漏或积累内存。 - Peter Hosey
@PeterHosey,我做了,发现由于某种原因,我有200多个CIImage实例和100多个CGImage实例,这些实例都来自这个方法。我只是看不到它们的来源。 - tagyro
3个回答

47

Swift 3Swift 4Swift 5

以下是一个将CIImage转换为CGImage的小函数(使用 Swift)。

func convertCIImageToCGImage(inputImage: CIImage) -> CGImage? {
    let context = CIContext(options: nil)
    if let cgImage = context.createCGImage(inputImage, from: inputImage.extent) {
        return cgImage
    }
    return nil
}

在桌面电脑或电视上,通常会使用:

let ctx = CIContext(options: [.useSoftwareRenderer: false])
let cgImage = ctx.createCGImage(output, from: output.extent)

核心图像处理器中有许多其他替代提示选项,例如.allowLowPower.cacheIntermediates.highQualityDownsample、优先级等等。

注:

  • CIContext(options: nil)将使用软件渲染器,可能会很慢。为了提高性能,请使用CIContext(options: [CIContextOption.useSoftwareRenderer: false]) - 这将强制操作在GPU上运行,速度会更快。
  • 如果您使用CIContext超过一次,请按照Apple的建议对其进行缓存。

26

请参阅 CIContext 文档,了解createCGImage:fromRect:的用法。

CGImageRef img = [myContext createCGImage:ciImage fromRect:[ciImage extent]];

从类似问题的回答中得知:https://stackoverflow.com/a/10472842/474896

另外,由于您已经拥有一个CIImage,因此您可以使用CIFilter来实际裁剪您的图像。


2
Could和Should:Core Image非常懒惰,因为createCGImage:fromRect:(或任何其他需要完成像素的方法)是实际完成所有工作的地方;在那之前没有进行任何实际的过滤。使用Core Image过滤器进行裁剪实际上会节省相当多的工作量(与您裁剪的数量成比例),因为这样您就不会请求裁剪掉的像素,因此它们根本不会被渲染。(当然,另一种方法是传递fromRect:的裁剪矩形,其效果相同。) - Peter Hosey
@Joris Kluivers 谢谢您的回答,但我得到了相同的结果:CIContext *context = [CIContext new]; CGImageRef ref = [context createCGImage:ciImage fromRect:[ciImage extent]]; tempImage = [UIImage imageWithCGImage:ref]; CGImageRelease(ref); NSLog(@"tempImage: %f %f",tempImage.size.width,tempImage.size.height); 输出:tempImage: 0.000000 0.000000 - tagyro
@PeterHosey 您说得对,但不幸的是,在进行转换时我没有裁剪信息,而且我需要先进行转换,因为我使用CGImage。谢谢。 - tagyro
@AndreiStoleru:你可以尝试使用contextWithOptions:来创建上下文,而不是使用new。另外,如果你记录[ciImage extent]的值,你会得到什么日志? - Peter Hosey
@PeterHosey NSLog(@"extent: %f",ciImage.extent.size.width); DEBUG -[CamViewController captureOutput:didOutputSampleBuffer:fromConnection:]:180 - extent: 1280.000000 同样尝试使用contextWithOptions:,但结果相同。 - tagyro
显示剩余2条评论

-2

经过一些谷歌搜索,我找到了这个方法,可以将CMSampleBufferRef转换为CGImage:

+ (CGImageRef)imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer // Create a CGImageRef from sample buffer data
{
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer,0);        // Lock the image buffer

    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);   // Get information of the image
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    CGImageRef newImage = CGBitmapContextCreateImage(newContext);
    CGContextRelease(newContext);

    CGColorSpaceRelease(colorSpace);
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);
    /* CVBufferRelease(imageBuffer); */  // do not call this!

    return newImage;
}

但我关闭了标签,所以不知道它来自哪里。


1
这根本不涉及CIImage。所以,你一直打算从CMSampleBuffer创建CGImage,而CIImage只是你考虑到的实现方式? - Peter Hosey
@PeterHosey 你可能已经推断出来了,我正在从AVCaptureOutput获取sampleBuffer,并使用CIImage进行人脸检测。最终目标是从捕获的图像中裁剪出人脸,因为我太蠢了,不理解CIImage和CGImage,所以我寻找了另一个解决方案:CMSampleBuffer。附:我接受了Joris的答案,因为那是我问题的正确答案。 - tagyro
啊,人脸识别。很正规啊。你看过AVCaptureMetadataOutput和AVMetadataFaceObject吗? - Peter Hosey
@PeterHosey 感谢您的建议,看起来比 CIDetector 更快 ;) - tagyro

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