Camera2 - 获取静止捕获位图的最有效方法

Posted

技术标签:

【中文标题】Camera2 - 获取静止捕获位图的最有效方法【英文标题】:Camera2 - most efficient way to obtain a still capture Bitmap 【发布时间】:2018-12-22 10:57:23 【问题描述】:

从问题开始:什么是初始化和使用 ImageReader 和 camera2 api 的最有效方法,知道我总是要将捕获转换为 Bitmap

我正在使用 android camera2 示例,一切运行良好。但是,出于我的目的,我总是需要对捕获的静止图像执行一些后期处理,为此我需要一个 Bitmap 对象。目前我正在使用BitmapFactory.decodeByteArray(...) 使用来自ImageReader.acquireNextImage().getPlanes()[0].getBuffer() 的字节(我在解释)。虽然这可以接受,但我仍然觉得应该有一种方法来提高性能。捕获以ImageFormat.Jpeg 编码,需要再次解码以获得Bitmap,这似乎是多余的。理想情况下,我会在PixelFormat.RGB_888 中获取它们,然后使用Bitmap.copyPixelsFromBuffer(...) 将其复制到位图,但使用该格式初始化ImageReader 似乎并不具有可靠的设备支持。 YUV_420_888 可能是另一种选择,但环顾四周,似乎需要跳过一些障碍才能解码为 Bitmap。有推荐的方法吗?

【问题讨论】:

【参考方案1】:

问题是你在优化什么。

Jpeg 无疑是所有设备都支持的最简单的格式。将其解码为位图并不像看起来那样多余,因为将图片编码为 jpeg 他通常由某种硬件执行。这意味着使用最小的带宽将图像从传感器传输到您的应用程序。在某些设备上,这是获得最大分辨率的唯一方法。 BitmapFactory.decodeByteArray(...) 也经常由特殊的硬件解码器执行。此调用的主要问题是可能导致内存不足异常,因为输出位图太大。因此,您会发现许多进行二次采样解码的示例,这些示例针对必须在手机屏幕上显示位图的用例进行了调整。

如果您的设备通过 RGB_8888 支持所需的分辨率,那就试试吧:这需要最少的后期处理。但是缩小这样的图像可能比处理 Jpeg 更占用 CPU,并且内存消耗可能很大。无论如何,只有少数设备支持这种格式的相机捕捉。

对于YUV_420_888YUV格式, Jpeg 的优势甚至比 RGB 还要小。

如果您需要最优质的图像并且没有内存限制,您应该选择目前大多数高端设备都支持的 RAW 图像。您将需要自己的转换算法,并且可能会针对不同的设备做出不同的适配,但至少您将完全掌握图片采集。

【讨论】:

很高兴知道 JPEG 编码是由硬件完成的 - 我很怀疑,但我不确定。我实际上需要从最终图像中提取一块,但我希望它具有尽可能高的质量;所以我真的无法避免解码整个位图。不过,它也需要在普通设备上工作。我想我需要尝试一些设备来找到最佳解决方案,看看我是否没有出现 OutOfMemory 异常。 内置的 BitmapFactory 不支持此功能,但您可以运行自定义 Jpeg 解码器以仅处理裁剪区域。这涉及围绕 libjpeg 库编写一些 C 代码。使用 RAW,您将获得更快的代码,但出现 OOM 的可能性更高。 这里是链接:github.com/libjpeg-turbo/libjpeg-turbo/issues/34。另请参阅***.com/questions/14068124/crop-image-from-byte-array。 哈哈,是的,您应该将其理解为“如果我实现了某些东西,请在我可以使用的所有设备上对其进行测试,看看它是否真的有效”。我对 Android 的经验是,对问题的推理只会带你到第一个拒绝运行它的设备;)。不过建议很好。 这就是群体智慧很重要的原因。这是我们(开发人员)对抗设备(和网络)多样性的唯一机会。我在这里给出的建议不仅基于学习官方文档,不仅基于我在不同情况下使用数百种不同模型的个人经验,还基于密切关注世界各地同事的报告。【参考方案2】:

过了一会儿,我现在有点对我自己的问题有了一个答案,尽管不是一个非常令人满意的答案。经过深思熟虑,我尝试了以下方法:

设置所需输出大小的ScriptIntrinsicYuvToRGB RenderScript 获取已使用输入分配的Surface,并将其设置为静止捕获的目标表面 当有新分配可用时运行此 RenderScript 并将生成的字节转换为 Bitmap

这实际上就像一个魅力,而且超级快。然后我开始注意到相机的奇怪行为,这也发生在其他设备上。事实证明,相机 HAL 并没有真正将其识别为静态捕捉。这意味着(a)在这种情况下,闪光灯/曝光例程在需要时不会触发,并且(b)如果您在捕捉之前启动了预捕捉序列,自动曝光将保持锁定状态,除非您设法使用 @ 解锁它987654325@ (API >= 23) 或其他一些我无法在任一设备上使用的锁定/解锁魔法。除非您只在不需要调整曝光的最佳照明条件下工作,否则这种方法完全没用。

我还有一个想法,即设置一个带有YUV_420_888 输出的ImageReader 并结合conversion routine from this answer 以从中获取RGB 像素。但是,我实际上正在使用Xamarin.Android,并且那里不支持 RenderScript 用户脚本。我也许可以解决这个问题,但这绝非易事。

对于我的特定用例,我已经设法将 JPEG 解码速度提高到可接受的水平,方法是在处理的多个阶段仔细安排后台任务以及我需要的版本的子采样解码,因此实现这一点可能不值得我花时间很快。如果有人正在寻找有关如何处理类似问题的想法;这是你可以做的。

【讨论】:

【参考方案3】:

使用不同的 ImageFormat 更改 Imagereader 实例,如下所示:

ImageReader.newInstance(width, height, ImageFormat.JPEG, 1)

【讨论】:

如前所述,我考虑过这一点。 PixelFormat.RGB_888 可能无法在可能的设备上工作,所以这不是一个真正的选择。稍微不同的谷歌搜索术语产生了一个与我类似的问题:***.com/questions/25776671/…。 那么我认为您必须在创建 ImageReader 实例之前执行一个小基准测试以检测最佳可用的 PixelFormat。

以上是关于Camera2 - 获取静止捕获位图的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章

在YUV_420_888中将图像从Android发送到OpenCV Mat中的JNI的最有效方式

在 Free Flight 示例应用程序 (ARDrone) 中使用 ARDrone 捕获静止图像

在Android camera2下将YUV_420_888转换为位图的图像不正确

从 Django Queryset 获取值列表的最有效方法

从 HDF5 获取表索引的最有效方法

前向填充位数组的最有效方法