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 处理的库主要有 libyuv、OpenCV、FFmpeg,而我个人接触比较多的主要是前两个(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。