为什么从相机缩小UIImage的速度如此缓慢?

4
使用UIImagePickerController返回的相机UIImage进行调整大小,如果按照此帖子中的常规方式处理,将会花费非常长的时间。
[更新:这里最后一次呼吁有创意的想法!我下一个选项是去问苹果公司,我想。]
是的,它有很多像素,但是iPhone上的图形硬件完全能够在1/60秒内将许多1024x1024纹理四边形绘制到屏幕上,因此应该有一种方法可以将2048x1536的图像缩小到640x480,而不需要花费1.5秒以上的时间。
那么为什么速度如此之慢?操作系统从选择器返回的底层图像数据是否无法准备好以便绘制,因此必须以某种GPU无法帮助的方式进行交换?
我最好的猜测是需要将其从RGBA转换为ABGR或类似的格式;有没有人能想出一种可能的方法,可以说服系统快速地给我数据,即使它是错误的格式,我稍后自己处理?
据我所知,iPhone没有任何专用的“图形”内存,因此不应该有将图像数据从一个位置移动到另一个位置的问题。
那么,问题是:是否有一些替代的绘图方法,而不仅仅是使用CGBitmapContextCreate和CGContextDrawImage来更充分地利用GPU?
需要调查的内容:如果我从不是来自图像选择器的相同大小的UIImage开始,速度是否也很慢?显然不是...
更新:Matt Long发现,如果您启用手动相机控件进行裁剪,那么从选择器返回的图像调整大小只需30毫秒。这对我关心的情况没有帮助,因为我正在使用takePicture程序化地拍照。我看到编辑后的图像是kCGImageAlphaPremultipliedFirst,但原始图像是kCGImageAlphaNoneSkipFirst。
进一步更新:Jason Crawford建议CGContextSetInterpolationQuality(context,kCGInterpolationLow),这确实将时间从大约1.5秒缩短到1.3秒,但代价是图像质量——但这仍然远远不能达到GPU应该具备的速度!
在本周结束之前的最后更新:用户refulgentis进行了一些分析,似乎表明1.5秒的时间花费在将捕获的相机图像写入磁盘作为JPEG,然后再读取它。如果是真的,那就非常奇怪。

如果你在2014年阅读这篇文章(5年后!),并且正在使用UIImagePickerController和takePicture(即具有自定义覆盖层,allowsEditing关闭以及自己的按钮的更常见过程)。任何合理的缩放/裁剪都将花费大约0.15秒在5S上。但是实际愚蠢的“takePicture”函数需要超过0.4秒!这是iOS中最傻的事情之一。他们必须添加一些东西,可以在不等待几个世纪的情况下提供较小的图片,但仍然使用高级库。简而言之,在“takePicture”本身很慢的情况下,忘记优化吧!-2014 - Fattie
5个回答

3
似乎你在这里做了几个可能是真实的也可能不是真实的假设。我的经验与你不同。这种方法在我的3Gs上似乎只需要20-30ms的时间,通过调用将从相机拍摄的照片按比例缩放到原始大小的0.31:
CGImageRef scaled = CreateScaledCGImageFromCGImage([image CGImage], 0.31);

顺便提一下,通过宽度比例640.0/2048.0,我得到了0.31的值。

我已经检查过确保图片与您的工作尺寸相同。这是我的NSLog输出:

2009-12-07 16:32:12.941 ImagePickerThing[8709:207] Info: {
    UIImagePickerControllerCropRect = NSRect: {{0, 0}, {2048, 1536}};
    UIImagePickerControllerEditedImage = <UIImage: 0x16c1e0>;
    UIImagePickerControllerMediaType = "public.image";
    UIImagePickerControllerOriginalImage = <UIImage: 0x184ca0>;
}

我不确定为什么会有这种差异,也无法回答你关于GPU的问题,但是我认为1.5秒和30毫秒的差别非常显著。也许可以将博客文章中的代码与你正在使用的代码进行比较?

最好的问候。


你的NSLog输出是直接来自相机拍摄的图像,还是从相机胶卷中加载的图像?我的图像没有EditedImage或CropRect键... - David Maymudes
我修改了我的CGBitmapContextCreate代码以更好地匹配你的代码,但我仍然看到CGContextDrawImage调用需要1.5秒才能完成。也许差异在于保存的图像与相机图像;你有什么想法吗? - David Maymudes
感谢您发布代码;我看到您有[picker setAllowsImageEditing:YES],这可能解释了额外的键。我正在使用takePicture以编程方式拍照,所以我不知道这两个东西如何相互作用,但我会让您知道的。 - David Maymudes
好的:你的代码正在调整剪裁后的图片大小;如果我改用 [info objectForKey:@"UIImagePickerControllerOriginalImage"],那就需要1.5秒。请参见上面问题的更新。 - David Maymudes
这种方法“更快”的原因是操作系统在相机拍摄照片和用户查看照片进行编辑之间自己执行CGContextDrawImage操作。速度是一样的,只是调用顺序不同,使得程序员的代码更快,因为它获得了已经执行的CGContextDrawImage操作的结果。 - refulgentis
显示剩余4条评论

2
我曾经遇到过同样的问题,花了很长时间才解决。据我所知,第一次访问由图像选择器返回的UIImage时速度较慢。作为一个实验,请计时任何两个操作与UIImage - 例如,缩小比例,然后是UIImageJPEGRepresentation或其他操作。然后交换顺序。在我以前做这个实验时,第一个操作会有时间惩罚。我最好的猜测是内存仍然在CCD上,将其转移到主存储器中以进行任何操作都很慢。
当您设置allowsImageEditing=YES时,您得到的图像被调整大小并裁剪到约320x320。这使它更快,但可能不是您想要的。
我发现的最佳加速方法是:
CGContextSetInterpolationQuality(context, kCGInterpolationLow)

在执行CGContextDrawImage之前,您需要从CGBitmapContextCreate返回的上下文进行处理。

问题在于,缩小后的图像可能不太好看。但是,如果您按整数因子缩小 - 例如,从1600x1200缩小到800x600,则看起来还可以。


将图像质量降低后,处理时间从约1.5秒减少到1.3秒,但仍明显低于应有的速度水平。因此,我仍然希望能够得到更好的解决方案。 - David Maymudes
祝你好运,如果你找到了解决方法,请告诉我。我已经为此苦恼了很久,我不确定是否有解决方案。尝试使用 Facebook 发布照片,甚至从本地相册发送电子邮件。它们都有几秒钟的延迟。 - jasoncrawford
唯一让我有希望的是,在这些情况下,照片应用程序正在处理全分辨率图像,因此如果您只想要低分辨率版本并且不必交换那么多像素,可能有更快的方法。 - David Maymudes

2
使用Shark进行分析,找出哪些地方耗时较长。
我经常使用MediaPlayer.framework,在从iPod上获取歌曲属性时,第一个属性请求与后续请求相比非常慢,因为在第一个属性请求中,MobileMediaPlayer会将所有属性打包成字典并传递给我的应用程序。
我敢打赌这里也存在类似的情况。
编辑:我能够在Shark中对Matt Long的UIImagePickerControllerEditedImage情况和通用的UIImagePickerControllerOriginalImage情况进行时间分析。
在两种情况下,大部分时间都被CGContextDrawImage占用。在Matt Long的情况下,UIImagePickerController在用户捕捉图像和进入“编辑”模式之间处理了这个过程。
将CGContextDrawImage所花费的时间百分比缩放为100%,然后CGContextDelegateDrawImage占用100%,接着是ripc_DrawImage(来自libRIP.A.dylib),占用100%,然后是ripc_AcquireImage(看起来是解压JPEG,并且大部分时间都花费在_cg_jpeg_idct_islow、vec_ycc_bgrx_convert、decompress_onepass、sep_upsample中),占用93%的时间。只有7%的时间实际上用于ripc_RenderImage,我认为这是实际绘制的时间。

慢的部分是对CGContextDrawImage的单个调用,因此我认为Shark在这里帮不上太大忙——它会分解一个单独的操作系统调用中发生的情况吗?无论如何,我还是会尝试一下... - David Maymudes
是的,它会 - 我想自己尝试一下,但我无法弄清楚这里到底使用了什么代码 :/ - refulgentis
听起来很奇怪,但显然是这样的。在配置文件中调用了[PLUICameraViewController cameraView:photoSaved:],然后调用[PLNotifyImagePickerOImageAvailability],最后调用[ImagePickerThingViewController imagePickerController:didFinishPickingMediaWithInfo:],因此必须将图像以JPEG格式保存到磁盘,然后告诉UIImagePickerController。 - refulgentis
这太奇怪了。显然目标是找到一些我可以做而不必在私有框架中四处搜寻的东西... - David Maymudes
好的,这并不是我所期望的答案,但也许是那些调查过的人中最新的信息。 - David Maymudes
显示剩余3条评论

1

-1

在这种情况下不要使用CGBitmapImageContextCreate!我曾经花了将近一周的时间处于你现在所处的同样困境。性能会非常糟糕,而且会像疯狂一样消耗内存。相反,请使用UIGraphicsBeginImageContext:

// create a new CGImage of the desired size
UIGraphicsBeginImageContext(desiredImageSize);
CGContextRef c = UIGraphicsGetCurrentContext();

// clear the new image
CGContextClearRect(c, CGRectMake(0,0,desiredImageSize.width, desiredImageSize.height));

// draw in the image
CGContextDrawImage(c, rect, [image CGImage]);

// return the result to our parent controller
UIImage * result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

在上面的例子中(来自我的图像调整大小代码),“rect”比图像明显小得多。上面的代码运行非常快,应该正好满足您的需求。
我不完全确定为什么UIGraphicsBeginImageContext如此快,但我相信这与内存分配有关。我注意到这种方法需要的内存明显较少,这意味着操作系统已经在某个地方分配了图像上下文的空间。

只是顺便提一下,发现这个方法后,我检查了我的代码,并开始在许多其他地方使用UIGraphics...调用来创建图像上下文。你可能也能够在其他地方使用这种方法获得一些速度提升! - Ben Gotow
2
我很难相信这是正确的 - 我很确定UIGraphicsBeginImageContext只是CGBitmapImageContextCreate的一个包装器。你使用的代码上面的图像来源是什么?为什么可以看到快速绘制? - David Maymudes
我尝试使用它,但没有提升速度。缓慢是由于从相机解码图像引起的,与CGContext的创建方式无关。 - refulgentis
-1 对于听起来合理、格式良好但实际上是错误的内容。 - David Maymudes
1
请注意,您只能在主线程上创建类似这样的CGContext。如果您想在后台压缩图像(例如,在此期间显示活动指示器),则需要使用CGBitmapContextCreate。 - jasoncrawford

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