Android 中的图像格式
一、前言
在音视频开发过程中,熟悉图像格式(图像编码)是十分有帮助的。这属于音视频开发者的基本功,就像计算机组成原理、数据结构之于普通程序员。
本文主要面向在 Android 平台进行音视频开发的人员。当然,如果您是 iOS、嵌入式设备或者其他平台的音视频开发者,本文也能起到一定的参考借鉴作用。
二、背景知识
2.1 图像编码和存储
我们所看到的图像,实际是光经过反射而来。根据光学理论,各种颜色的光是由红(R)、绿(G)、蓝(B)以不同的比例组成。我们知道,计算机世界是数字的、抽象的,那它是如何来描述一张图像,或者再具体点,如何描述一个像素的呢?
以 RGB 颜色模型为例,我们可以用分别占 1 字节大小的 R、G、B 组合起来描述一种颜色,这样一个像素需要花 3 字节的空间来存储;我们也可以用 2 字节大小的 R、G、B 来描述一种颜色,这样一个像素需要 6 字节存储。显然,不难推断:描述 R、G、B 用的比特位越多,所能描述的颜色也就越多,存储空间占用的也就越多。
考虑到实际的需求与资源限制,通常需要做 trade-off,即找到一个平衡点。这就诞生了形形色色的编码、采样、存储方式。
这里本文以问答形式,对本文即将用的一些知识点作简单介绍,其余部分不作深入。感兴趣的读者可以与笔者交流或自行搜索资料。
Q:YUV 和 YCbCr 是什么关系?
A:U = Cb,V = Cr。
Q:注意到部分 YUV 格式有后缀,比如 YUV_420_888、YUV_422_888、YUV_444_888,它们有什么不同呢?
A:采样方式的不同。最末尾的 888 表示三个分量各自占据的宽度,即 Y、U、V 分别用一个长度为 8 的二进制序列表示;两个"_"中间的三个数字表示由 Raw 转换到 Yuv 时的一个采样方式,比如:
420
:每四个像素点,共用 4 个 Y 分量、1 个 U 分量、1 个 V 分量表示。422
:每四个像素点,共用 4 个 Y 分量、2 个 U 分量、2 个 V 分量表示。444
:每四个像素点,共用 4 个 Y 分量、4 个 U 分量、4 个 V 分量表示。
注意到,其实只有 UV 分量的数量不同,这样做是基于人眼对色度信息不敏感的特性,通过降低 UV 分量数量,达到减少图像体积的目的。让我们用一张图来阐述它们:
Q:听说过 planar、packed,semi-planar,这些又是什么?有什么不同。
A:这些是存储方式。以 YUV 格式为例,一个像素点由 Y、U、V 三个分量构成,
planar:就是把整张图像的单个分量各自存储在一个数组中,每组分量紧挨在一起。
packed:就是把一个像素的 Y、U、V 分量“打包”放在一个数组,然后一个像素一个像素挨着存储。
semi-planar:是前两者的混合,一部分分量用 planar 方式存储,另一部分分量用 packed 分量存储。
2.2 图像传感器(Image Sensor)
图像传感器上分布了大量的感光单元,将接收到的光信号转换为电信号。众多感光单元一起,组成了色彩滤波阵列(Color Filter Array,简称CFA
)。
常见的感光单元可分为两大类:能够同时输出 RGB 三个分量的 RGB 感光单元、只能输出单个分量的感光单元。我们当然希望用前者作为 Image Sensor 的主材料,但遗憾的是,前者成本高,工艺复杂,所以在实际的市场上,后者流通更为主流。
如果一个像素点只能输出一种颜色的信息,那我们怎么做才能还原出完成的图像呢?比较容易想到的是邻近差值策略,即一个像素点输出一种颜色分量信息,它附近的其他像素点输出其他分量的信息,再将这些信息以某种策略汇合,从而得到某一点“完整”的所有分量信息。
这里比较出名的是拜耳滤波阵列(Bayer Filter),它的 CFA 排列如下:
这种 CFA 以 nxn 个像素矩阵为一个单元,每个单元由 50%的绿,25%的红,25%的蓝组成。
为什么绿色占的比重高?这源自一个理论:人眼对绿色更敏感。具体原因还请自行查找相关资料。
比较常见的是以 2x2 像素矩阵为一个单元,即 RGGB,也叫 BGGR、RGBG、GRBG,它们指的是同一个东西的不同表现。
关于 CFA,还有两个概念感兴趣的读者可以搜索下:QuadCFA 和九合一 CFA。(见参考资料 2、3)
2.3 Android 图像格式的两个类
Android 上,表示图像格式的,主要有两个类:ImageFormat
和PixelFormat
。它们都位于包android.graphics
内,有一些重叠之处,前者用的更多一些,后者中的许多值已经被为废弃(Deprecated)。本文主要介绍ImageFormat
中的图像格式。
下面将会大致按类别和使用频率介绍主要图像编码格式,每个格式尽可能的会用图来展示它的存储方式。
注:如果没有特别说明,单个分量默认为占用 8bit(即一字节)的宽度。
三、Android 中的图像格式之ImageFormat
个人习惯将格式划分为:Raw、Yuv 和其他格式(Jpeg、PNG 等),这三大类。前两者主要用于图像传输与算法处理,后者更多用作呈现。
3.1 YUV
3.1.1 ImageFormat.NV21
NV21
是 420 采样,semi-planar 方式存储的一种格式。Y 分量单独用一个 planar 存储,UV 分量以 V 分量在前,U 分量在后的方式交错存储。
NV21
是 Android Camera API1 相机预览的默认格式(引证:Android CDD,7.5.2 节,C-0-1 和 C-0-2 条目)。
注:如果使用 Camera API2,需要自己实例化一个 ImageReader 类来获取 YUV Callback,设置其宽高和格式。切不可认为 API2 的预览就一定是 NV21!!!(尽管大部分情况下是)
3.1.2 ImageFormat.NV12
类似于NV21
,却别在于 UV 分量存储的先后顺序。
3.1.3 ImageFormat.YUV_420_888
YUV_420_888
是 420 采样,可以以 planar 或 semi-planar 方式存储的一种格式。一般而言,应用无需考虑它具体的存储方式,因为通过Image.getPlanes()
接口,总能保证返回的第一个 plane 是 Y 分量,第二个 plane 是 U 分量,第三个 plane 是 V 分量。
值得注意的是,不同的安卓机器上,大概率会存在字节对齐现象,这点在处理时尤其要注意。
stride 信息可以通过Image.Plane.getRowStride()
和Image.Plane.getPixelStride()
获取到。
ImageFormat.YUV_422_888
和ImageFormat.YUV_444_888
和它类似,却别在于取样方式。关于取样方式,前文 2.1 节中已经介绍过。
3.1.4 ImageFormat.YCBCR_P010
YCBCR_P010
是 420 采样,semi-planar 方式存储的一种格式,单个分量用 16bit、小端的数值表示(低 6 位始终为 0)。YCBCR_P010
是 Android S(Android 12)上新增的一个图像格式。
3.2 Raw
3.2.1 ImageReader.RAW10
RAW10
的像素紧密排列,每个占据 10bit 的宽度,通常表示为未处理的 Bayer 格式数据。
这里的紧密排列注意一下。所谓紧密,是指像素与像素之间没有多余的元素。我们知道,计算机是以字节为基本存储单位,而每个像素又是 10bit 的,故而它是每 5 字节存储了 4 个像素的信息,且遵循如下规则:
前 4 字节存储了每个像素的高 8 位信息。
第 5 个字节存储了这四个像素的低 2 位信息。
所以实际上是下面这样排列:
3.2.2 ImageReader.RAW12
RAW12
与RAW10
类似,区别在于它的每个像素以 12bit 表示。其存储排列遵循如下规则:
3 个字节存储 2 个像素的信息。
前两个字节存储了每个像素的高 8 位。
第 3 个字节存储了每个像素的低 4 位。
3.2.3 ImageReader.RAW_SENSOR
RAW_SENSOR
跟 Sensor 密切相关,一般不会拿来使用(除了手机厂商或和手机厂商有相关合作),如果确实需要使用,则需要通过CameraManager.getCameraCharacteristics()
拿到相关属性后才能正确解析数据。操作十分复杂,且不具有通用性,不推荐使用。
RAW_SENSOR
格式的每个像素需要用 2 字节存储。
3.3 Others
3.3.1 ImageReader.PRIVATE
PRIVATE
是私有格式,和平台实现有关,主要在 App 层以下使用。安卓定义此格式,其目的是给厂商开放拓展能力,借助平台能力,在设备的 frameworks/HAL 之间能够高效、低功耗的共享和处理数据。
Camera API2 的预览格式一般默认就是这种。
像高通会将这个实现为他们的私有格式,有的手机厂商出于某些目的和需求,又会将它的实现改为NV21
或者NV12
。
总而言之,对音视频开发人员来说,需要记住:Camera API2 的预览格式不一定默认是 NV21。
3.3.2 ImageReader.RAW_PRIVATE
类似与ImageReader.PRIVATE
,也是一种私有格式,厂商可能会用到,一般应用开发者用不到这个。
注意区别于ImageReader.RAW_SENSOR
,前者是无法通过现有公共 API 接口获取到的数据,去解析数据的一种格式;而后者,则是可以通过 framework API 拿到相关信息,进而做解析的一种图像格式。
四、结束语
各式各样的图像格式是进行图像处理所避不开的一道关卡,有时碰到的一些玄学问题可能正是因为其中的一两个大意导致的,例如未考虑某些格式的内存对齐导致图像显示异常。
当我们熟稔常见格式的特点后,后期进行编码设计时,遇到的问题、代码中留的隐患会更少。能够熟练、详尽地描述出各个格式的采样方式、存储方式、应用场景等特点,应是身为音视频开发者的一个基本功。
参考资料:
版权声明: 本文为 InfoQ 作者【如浴春风】的原创文章。
原文链接:【http://xie.infoq.cn/article/61df4549361397fa3957a02ae】。未经作者许可,禁止转载。
评论