为啥从相机缩小 UIImage 这么慢?

Posted

技术标签:

【中文标题】为啥从相机缩小 UIImage 这么慢?【英文标题】:Why is scaling down a UIImage from the camera so slow?为什么从相机缩小 UIImage 这么慢? 【发布时间】:2009-12-05 06:34:03 【问题描述】:

如果您按照this post 中的常规方式来调整由 UIImagePickerController 返回的相机 UIImage 的大小,会花费非常长的时间。

[更新:这里最后一次征集创意!我猜我的下一个选择是去问苹果。]

是的,它有很多像素,但 iPhone 上的图形硬件完全能够在 1/60 秒内将大量 1024x1024 纹理四边形绘制到屏幕上,因此确实应该有一种方法来调整 2048x1536 的大小在不到 1.5 秒的时间内将图像缩小到 640x480。

那么为什么这么慢呢?操作系统从选取器返回的底层图像数据是否还没有准备好被绘制,所以它必须以某种 GPU 无法帮助的方式混合?

我最好的猜测是它需要从 RGBA 转换为 ABGR 或类似的东西;任何人都可以想出一种方法,可以说服系统快速向我提供数据,即使它的格式错误,我稍后会自己处理?

据我所知,iPhone 没有任何专用的“图形”内存,因此不存在将图像数据从一个地方移动到另一个地方的问题。

那么,问题来了:除了使用 CGBitmapContextCreate 和 CGContextDrawImage 之外,还有其他更能利用 GPU 的绘图方法吗?

需要调查的事情:如果我从不是来自图像选择器的相同大小的 UIImage 开始,它是否同样慢?显然不是……

更新:Matt Long 发现,如果您启用了手动相机控件的裁剪,则只需 30 毫秒即可调整您从 [info objectForKey:@"UIImagePickerControllerEditedImage"] 中的选取器返回的图像大小。这对于我关心我在哪里使用takePicture 以编程方式拍照的情况没有帮助。我看到编辑后的图像是kCGImageAlphaPremultipliedFirst,但原始图像是kCGImageAlphaNoneSkipFirst

进一步更新:Jason Crawford 建议使用CGContextSetInterpolationQuality(context, kCGInterpolationLow),实际上这确实将时间从大约 1.5 秒缩短到了 1.3 秒,但以牺牲图像质量为代价——但这与 GPU 应该能够达到的速度还相差甚远!

一周结束前的最后一次更新:用户 refulgentis 做了一些分析,这似乎表明 1.5 秒用于将捕获的相机图像以 JPEG 格式写入磁盘,然后将其读回. 如果是真的,那就太离奇了。

【问题讨论】:

对于任何在 2014 年(5 年后!)阅读本文的人,如果您使用的是 UIImagePickerController,然后是 takePicture。 (即,具有自定义叠加层、允许编辑关闭和您自己的按钮的更常见的过程)。您编写的任何合理的缩放/裁剪在 5S 上大约需要 0.15。但实际愚蠢的“takePicture”功能需要 0.4 秒(!)这是 ios 中最愚蠢的事情之一。他们必须添加一些可以提供较小图片的内容,而无需等待几个世纪,但仍然使用高级库。简而言之,忘记优化,而“takePicture”本身就是 slooo! -2014 【参考方案1】:

您似乎在这里做了几个可能正确也可能不正确的假设。我的经历与你不同。 This method 在我的 3G 上似乎只需要 20-30 毫秒,将相机拍摄的照片缩放到原始尺寸的 0.31 并调用:

CGImageRef scaled = CreateScaledCGImageFromCGImage([image CGImage], 0.31);

(顺便说一下,我通过宽度比例得到 0.31,640.0/2048.0)

我已检查以确保图像与您正在使用的大小相同。这是我的 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 键... 好吧,我修改了我的 CGBitmapContextCreate 代码以更匹配您的代码,但我仍然看到 CGContextDrawImage 调用需要 1.5 秒才能完成。也许区别在于保存的图像与相机图像;有什么想法吗? 即使直接从相机拍摄,我的时间仍然在 20 到 30 毫秒之间。是的,我的也有该模式下的编辑图像和裁剪矩形键。我不确定是什么导致了这种差异。当您使用相机时,您的视图控制器是否调用 viewDidUnload 或 didReceiveMemoryWarning? 看看你在这个项目中获得了什么样的时间:cimgf.com/files/ImagePickerThing.zip 感谢您发布代码;我看到你有 [picker setAllowsImageEditing:YES] 这可能解释了额外的键。我正在使用 takePicture 以编程方式拍照,所以我不知道这两件事是如何相互作用的,但我会告诉你的。【参考方案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 在这里不会很有帮助——它会分解单个操作系统调用中发生的事情吗?反正我会试一试... 是的,它会的——我会尝试自己做,但我无法弄清楚这里到底使用了什么代码:/ 嗯,我使用了修改版的 Matt Long 示例代码来复制问题,但每次我尝试使用 Shark 对其进行分析时,Shark 都会崩溃,非常困难。此外,我认为它的行为类似于 MediaPlayer.framework 的理论是不正确的,因为您可以以正常速度对图像执行各种操作,但 CGContextDrawImage(); 我能够做一个时间配置文件,基本上它很慢,因为 JPEG 解压缩。如果您想查看我提供的时间配置文件,请阅读我对上述答案的编辑。 仍然不确定你在说什么——你是说当原始图像来自相机时,它是 JPEG 压缩的?这似乎很难相信......我在你的列表中看到了 bgrx_convert,这使得它看起来像是操作系统正在重新排序像素数据,并且再次让我想知道是否有某种方法可以让系统稍微给我未重新排序的数据更快,所以我可以自己处理......【参考方案3】:

我也遇到过同样的问题,而且很长一段时间都在用头撞它。据我所知,第一次访问图像选择器返回的 UIImage 时,它​​很慢。作为一个实验,尝试使用 UIImage 计时任何两个操作 - 例如,您的按比例缩小,然后是 UIImageJPEGRepresentation 或其他东西。然后切换顺序。当我过去这样做时,第一次操作会受到时间惩罚。我最好的假设是内存仍然在 CCD 上,并且将其转移到主内存以对其进行任何处理都很慢。

当您设置allowImageEditing=YES 时,您返回的图像会调整大小并裁剪到大约320x320。这使它更快,但它可能不是你想要的。

我发现的最佳加速是:

CGContextSetInterpolationQuality(context, kCGInterpolationLow)

关于从 CGBitmapContextCreate 返回的上下文,在执行 CGContextDrawImage 之前。

问题是您缩小的图像可能看起来不太好。但是,如果您按整数倍缩小(例如,从 1600x1200 缩小到 800x600),那么它看起来没问题。

【讨论】:

降低质量似乎将时间从大约 1.5 秒缩短到 1.3 秒,但仍然比看起来应该可能的要少得多,所以我仍然希望得到更好的答案。 祝你好运,如果你找到了,请告诉我。我一直在为此苦苦挣扎,但不确定是否有解决方案。尝试使用 Facebook 发布照片,甚至通过电子邮件发送本地相册中的照片。它们都有几秒的延迟。 给我希望的一件事是,在这些情况下,照片应用程序正在处理全分辨率图像,所以如果你只想要一个较低分辨率的版本并且不要',也许有一个更快的方法不必调整这么多像素。【参考方案4】:

这是我使用过的一个 git 项目,它似乎运行良好。用法也很简洁——一行代码。

https://github.com/AliSoftware/UIImage-Resize

【讨论】:

【参考方案5】:

在这种情况下不要使用 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... 调用在许多其他地方创建图像上下文。您也许可以在其他地方使用这种方法获得其他一些速度提升! 我很难相信这是对的——我很确定 UIGraphicsBeginImageContext 只是 CGBitmapImageContextCreate 的包装器。您在上面的代码中使用并看到快速绘图的图像的来源是什么? 我试过用这个,它没有任何加速。缓慢是由于从相机解码图像,它与 CGContext 的创建方式无关。 -1 听起来合理且格式正确,但实际上是错误的。 另请注意,您只能在主线程上创建这样的CGContexts。如果要在后台压缩图像(例如,同时显示活动指示器),则需要使用 CGBitmapContextCreate。

以上是关于为啥从相机缩小 UIImage 这么慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥同时使用 numba.cuda 和 CuPy 从 GPU 传输数据这么慢?

为啥这个查询这么慢? - PostgreSQL - 从 SERIAL、TIMESTAMP 和 NUMERIC(6,2) 中选择

为啥我的 UIImage 占用这么多内存?

逐帧调整动画 UIImage 的大小非常慢

为啥使用 CGContextDrawImage 旋转图像比绘制旋转的 UIImage 慢得多?

为啥 django 模板渲染字典这么慢?