YUV 相关内容梳理

最近做了差不多半年的图像识别相关项目,涉及到 YUV 图像的处理,这里对相关内容作下简单梳理。

YUV 介绍

视觉的产生

我们人类的视觉主要跟两种细胞有关:视杆细胞和视锥细胞。

其中对区分颜色至关重要的是视锥细胞(大多数哺乳动物只有蓝绿两种视锥细胞,而鸟类的视锥细胞种类比人类还多,所以不同物种看到的色彩世界也是不同的)。

由上图可以看到:三种视锥细胞的感光频谱都覆盖了中间绿光部分的波长,所以人眼对绿光更敏感。

什么是 YUV

Y 是对 RGB 三通道按权重取平均值得到的灰度图;因为人眼对绿色更敏感,所以 G 通道权重更高;

而 U(Cb) 和 V(Cr) 则是对 B 和 R 两个通道的采样。

下面通过矩阵乘法表示 RGB 和 YUV 之间的转换:

YUV 编码方式

根据 U 和 V 采样程度的不同,YUV 有 YUV444、YUV422、YUV420 三种编码方式:

前两种适用于对画质要求高的场景,常用的标准视频编码则是 YUV420。

根据 UV 存储方式的不同,又分为 YUV420p 和 YUV420sp:

  • 420p:Y、U、V 分别存储在 3 个平面上;按 UV 顺序又分为 YU12(I420)和 YV12;

  • 420sp:Y 一个平面,U 和 V 交叉存储于同一平面;按 UV 顺序又分为 NV12 和 NV21。

为什么需要 YUV

  • 能兼容早期的黑白电视机(相当于只有 Y 通道灰度图);

  • 常用的 YUV420 编码方式比 RGB 更节省数据量。

YUV 图像变换

旋转

一般 YUV 图像需要旋转的角度都是 90° 的整数倍(比如 Android 手机摄像头采集到的原始数据通常就是旋转了 90°),所以这里以旋转 90° 为例。

首先是 Y 通道直接行列反转:

然后 UV 作为一个整体反转:

放大

放大就是将原数据向右、向下拷贝:

缩小

缩小就是采样:

第三方库

目前支持 YUV 处理的库主要有 libyuvOpenCVFFmpeg,而我个人接触比较多的主要是前两个(Android 自带的 YuvImage 因为有内存泄漏问题,并且不跨平台,不在讨论范围内)。

libyuv

这是个 C/C++ 实现的专门处理 YUV 的库,并且底层做了汇编级优化,所以性能首先毋庸置疑,也是一开始就采用的方案。

后来弃用主要是因为:

  • 不支持 RGB 编码;

  • 某些 Android 设备上,旋转操作会概率性导致图片出现花花绿绿的部分。

由于是 C 的 API,我这边在移植到 Android 的时候做了层 JNI 和工具类的封装: AND-YUV

OpenCV

OpenCV 是个专业的图像处理库,不仅支持 YUV 预处理,还支持诸如高斯模糊、边缘检测、目标检测等。

它也是针对 ARM Neon 做了指令优化;并且它的 T-API 还支持硬件加速(充分利用 GPU 甚至 FPGA、DSP 的算力)。

它的内部集成了 libjpeg,所以支持 JPG 编码。

OpenCV 中有个核心对象:Mat,借助这个矩阵,旋转/缩放/裁剪等操作的耗时都远低于 libyuv。