写点什么

Android 中的图像格式

用户头像
如浴春风
关注
发布于: 2021 年 04 月 12 日
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 = CbV = 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 分量数量,达到减少图像体积的目的。让我们用一张图来阐述它们:

YUV采样格式图解


Q:听说过 planar、packed,semi-planar,这些又是什么?有什么不同。

A:这些是存储方式。以 YUV 格式为例,一个像素点由 Y、U、V 三个分量构成,

  • planar:就是把整张图像单个分量各自存储在一个数组中,每组分量紧挨在一起。

  • packed:就是把一个像素的 Y、U、V 分量“打包”放在一个数组,然后一个像素一个像素挨着存储。

  • semi-planar:是前两者的混合,一部分分量用 planar 方式存储,另一部分分量用 packed 分量存储。

YUV存储方式图示


2.2 图像传感器(Image Sensor)

图像传感器上分布了大量的感光单元,将接收到的光信号转换为电信号。众多感光单元一起,组成了色彩滤波阵列(Color Filter Array,简称CFA)。


常见的感光单元可分为两大类:能够同时输出 RGB 三个分量的 RGB 感光单元、只能输出单个分量的感光单元。我们当然希望用前者作为 Image Sensor 的主材料,但遗憾的是,前者成本高,工艺复杂,所以在实际的市场上,后者流通更为主流。


如果一个像素点只能输出一种颜色的信息,那我们怎么做才能还原出完成的图像呢?比较容易想到的是邻近差值策略,即一个像素点输出一种颜色分量信息,它附近的其他像素点输出其他分量的信息,再将这些信息以某种策略汇合,从而得到某一点“完整”的所有分量信息。


这里比较出名的是拜耳滤波阵列(Bayer Filter),它的 CFA 排列如下:

拜耳滤波阵列(图片引自Wikipedia)


这种 CFA 以 nxn 个像素矩阵为一个单元,每个单元由 50%的绿25%的红25%的蓝组成。


为什么绿色占的比重高?这源自一个理论:人眼对绿色更敏感。具体原因还请自行查找相关资料。


比较常见的是以 2x2 像素矩阵为一个单元,即 RGGB,也叫 BGGR、RGBG、GRBG,它们指的是同一个东西的不同表现。


关于 CFA,还有两个概念感兴趣的读者可以搜索下:QuadCFA 和九合一 CFA。(见参考资料 2、3)


2.3 Android 图像格式的两个类

Android 上,表示图像格式的,主要有两个类:ImageFormatPixelFormat它们都位于包android.graphics内,有一些重叠之处,前者用的更多一些,后者中的许多值已经被为废弃(Deprecated)。本文主要介绍ImageFormat中的图像格式。


下面将会大致按类别和使用频率介绍主要图像编码格式,每个格式尽可能的会用图来展示它的存储方式。


注:如果没有特别说明,单个分量默认为占用 8bit(即一字节)的宽度。

三、Android 中的图像格式之ImageFormat

个人习惯将格式划分为:Raw、Yuv 和其他格式(Jpeg、PNG 等),这三大类。前两者主要用于图像传输与算法处理,后者更多用作呈现。

3.1 YUV

3.1.1 ImageFormat.NV21

NV21420 采样,semi-planar 方式存储的一种格式。Y 分量单独用一个 planar 存储,UV 分量以 V 分量在前,U 分量在后的方式交错存储

NV21存储方式,注意UV分量的先后顺序


NV21Android 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 分量存储的先后顺序

NV12存储方式,与NV21的UV分量存储先后顺序相反

3.1.3 ImageFormat.YUV_420_888

YUV_420_888420 采样,可以以 planar semi-planar 方式存储的一种格式。一般而言,应用无需考虑它具体的存储方式,因为通过Image.getPlanes()接口,总能保证返回的第一个 plane 是 Y 分量,第二个 plane 是 U 分量,第三个 plane 是 V 分量


值得注意的是,不同的安卓机器上,大概率会存在字节对齐现象,这点在处理时尤其要注意。


stride 信息可以通过Image.Plane.getRowStride()Image.Plane.getPixelStride()获取到。


ImageFormat.YUV_422_888ImageFormat.YUV_444_888和它类似,却别在于取样方式。关于取样方式,前文 2.1 节中已经介绍过。

3.1.4 ImageFormat.YCBCR_P010

YCBCR_P010420 采样,semi-planar 方式存储的一种格式,单个分量用 16bit小端的数值表示(低 6 位始终为 0)。YCBCR_P010是 Android S(Android 12)上新增的一个图像格式。

YCBCR_P010存储方式


3.2 Raw

3.2.1 ImageReader.RAW10

RAW10像素紧密排列,每个占据 10bit 的宽度,通常表示为未处理的 Bayer 格式数据。


这里的紧密排列注意一下。所谓紧密,是指像素与像素之间没有多余的元素。我们知道,计算机是以字节为基本存储单位,而每个像素又是 10bit 的,故而它是5 字节存储了 4 个像素的信息,且遵循如下规则:

  • 前 4 字节存储了每个像素的高 8 位信息。

  • 第 5 个字节存储了这四个像素的低 2 位信息。

所以实际上是下面这样排列:

Raw10的存储排列


3.2.2 ImageReader.RAW12

RAW12RAW10类似,区别在于它的每个像素以 12bit 表示。其存储排列遵循如下规则:

  • 3 个字节存储 2 个像素的信息。

  • 前两个字节存储了每个像素的高 8 位

  • 第 3 个字节存储了每个像素的低 4 位

Raw12存储排列


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 拿到相关信息,进而做解析的一种图像格式。

四、结束语

各式各样的图像格式是进行图像处理所避不开的一道关卡,有时碰到的一些玄学问题可能正是因为其中的一两个大意导致的,例如未考虑某些格式的内存对齐导致图像显示异常。


当我们熟稔常见格式的特点后,后期进行编码设计时,遇到的问题、代码中留的隐患会更少。能够熟练、详尽地描述出各个格式的采样方式、存储方式、应用场景等特点,应是身为音视频开发者的一个基本功。


参考资料:

  1. Wikipedia, RGB color model

  2. Wikipedia,Bayer filter

  3. Wikipedia,Samsung CMOS

  4. YUV pixel formats

  5. Android developers, ImageFormat

  6. Android developers, PixelFormat

  7. Android source, Android Compatibility Definition Document (Android 11)


发布于: 2021 年 04 月 12 日阅读数: 99
用户头像

如浴春风

关注

还未添加个人签名 2020.02.29 加入

某Top Android手机厂商,相机开发工程师一枚

评论

发布
暂无评论
Android中的图像格式