图像基础和前处理
一、基本概念:从参数的角度看视频图像
- 分辨率:分辨率就是指宽高像素的个数,像素越多视频越清晰
- 位深:例如RGB8就是RGB各占8位,一共代表256的三次方个颜色值,这种图像就是8bit图像,8bit就是位深
- stride:指的是图像存储中内存每行像素所占用的空间(涉及内存对齐,是内存对齐后所占用的空间)
- 帧率:1秒钟内图像的数量
- 码率:一秒钟的数据量
问题一:同一个视频被压缩后,是码率越高清晰度越高吗?
不是,压缩后的清晰度跟压缩时选用的压缩算法,以及压缩速度有关。
问题二:码率是固定的还是变化的?如果是固定的,怎么做的?
如果要求固定码率,编码后的码率小于固定码率,填充数据。编码后的码率大于固定码率,丢弃细节数据
二、YUV
yuv的类型
- yuv444:每一个y对应一个U和一个V
- yuv422:没俩个y共用一个U和一个V
- yuv420:每四个y共用一个U和一个V(四个y是上下俩行的)
yuv的存储方式
- planar:先连续存储所有像素点的Y,然后连续的U或者V
- packed:先存储完所有像素的y,然后U、V交错存储
color range
对于一个8bit的RGB每个分量的取值范围
- full range:0-255
- limited range:16-235
yuv与rgb怎么转换?
BT709与BT601定义了一个RGB与YUV互转的标准规范。BT601是标清的标准,BT709是高清的标准。都按照标准,不同厂家生产出的产品才能对接好
为什么用yuv替换rgb?
rgb三个颜色具有相关性,不方便做编码(R、G、B三张图像几乎完全一样,只是颜色不同,如果用来编码,三张图像同等重要,而且轮廓还差不多,但颜色又不同,因此不好编码),而YUV不同,Y是图像的大体轮廓,没有颜色信息,U、V是颜色信息,三张图像相互独立。并且人眼对于色彩信息相比于轮廓信息不敏感,可以缩小U、V大小。编码时Y、U、V相关性很小,可以独立编码。
三、缩放算法
图像的缩放就是将原图像的已有像素经过加权运算得到目标图像的目标像素。
例如720P生成1080P目标图像,目标图像的(1,1)位置就映射到原图像的(0.67,0.67)。最后通过原图像已有像素插值得到(0.67,0.67)位置的像素值,并将该像素值作为目标图像(1,1)位置的像素值
插值算法
最邻近插值算法
- 首先,将目标图像中目标像素位置,映射到原图像的映射位置
- 然后,找到原图像周围的四个像素
- 最后,取离映射位置最近的像素点的像素值作为目标像素
优点:不需要太多的计算,速度非常快
缺点:相邻位置的像素值很大概率是相同的,放大图像大概率会出现块状效应,缩小图像容易出现锯齿
双线性插值算法
也是取待插值附近的4个像素,不同的是,它需要将这四个像素值经过一定的运算得到最后的插值像素。
基础是线性插值:是一种以距离作为权重的插值方式,距离越近权重越大,距离越远权重越小。
双线性插值实际就是3次线性插值的过程
双三次插值算法
- 双三次插值选取的是周围的16个像素
- 周围的权重计算是使用一个特殊的BiCubic基函数来计算的
视频编码
一、编码原理
视频编码是按照一帧帧图像进行编码的。而对于每一帧图像,又是划分成一个个块来进行编码的,这一个个块在h264叫宏块,在vp9、av1中称为超级块。宏块大小一般是16x16(h264、vp8),32x32(h265、vp9),64x64(h265、vp9、av1),128x128(av1)。
图像的冗余
- 空间冗余:比如说将一帧图像划分成一个个宏块后,相邻的块都有比较明显的相似性,这种就叫空间冗余
- 时间冗余:俩帧图像之间变化比较小,相似度很高,这种叫时间冗余
- 视觉冗余:眼睛有视觉灵敏度的。人眼对于图像中的高频信息的敏感度是小于低频信息的。有时候去除图像中的一些高频信息,人眼看起来跟不去除差别不大,这种叫视频冗余。(高频信息:亮度或灰度变化剧烈的地方,如边缘。变化不大的地方是低频信息,如大片色块)
- 信息熵冗余:一般使用压缩工具去压缩文件,将文件大小减小,对于图像也是可以做的,这叫做信息熵冗余
为了能够在最后熵编码时压缩率更高,先通过帧内预测和帧间预测去除空间冗余和时间冗余,从而得到一个像素值相比编码块小很多的残叉块。之后再通过DCT变换将低频和高频信息分离开得到变换块,然后再对变换块的系数做量化。由于高频系数比较小,很容易量化为0,同时人眼对于高频信息不太敏感,这样就得到一串含有连续0的字符串,并且观感还不会很明显。这样最后的熵编码就能把图像压缩成比较小的数据。
编码步骤
预测、DCT变换、量化、熵编码
- 熵编码:视频编码真正实现“压缩”的步骤,主要去除信息熵冗余。在出现连续多个0像素的时候压缩率更高
- 帧内预测:为了提高熵编码的压缩率,先将当前编码块的的相邻像素经过帧内预测算法得到帧内预测块,再用当前编码块减去帧内预测块得到残差块,从而去掉冗余。
- 帧间预测:类似于帧内预测,在已经编码完成的帧中,先通过运动搜索得到帧间预测块,再与编码块相减得到残差块,从而去除时间冗余。
- DCT变换和量化:将残差块变换到频域,分离高频和低频信息。由于高频信息数量多但大小相对较小,人眼对高频信息相对不敏感,我们利用这个特点,使用QStep对DCT系数进行量化,将大部分高频信息量化为0,达到去除冗余的作用。
二、h264码流结构
h264的编码结构
首先,帧类型是编码的基础。其次,GOP是以其中的IDR帧作为分割点。最后的Slice是深入帧内部的一个重要概念
备注:
- open gop:会参考当前gop及之前gop内的I帧
- close gop:只参考当前gop内的I帧
gop的目的是减少I帧错误对后面的P、B帧的影响
三种图像帧类型
slice
Slice是为了并行编码设计的。将一帧图像划分为几个slice,并且slice之间相互独立,互不依赖、独立编码。
(如果是I帧,因为slice之间相互独立,帧内预测不能跨slice进行,因此编码性能会差一些)。
总结:图像内的层次结构一帧可以划分为一个或多个slice,而一个slice包含多个宏块,且一个宏块可以划分为一个或多个不同尺寸的子块。
h264的码流结构
码流头
h264码流结构有俩种
- 一种是Annexb格式:使用起始码表示一个编码数据的开始。起始码本身不是图像编码的内容,只是用来分割。起始码有俩种,一种是4字节的00 00 00 01,一种是三子节00 00 01。
- 一种MP4格式:没有起始码,而是在图像编码数据的开始使用了4个字节作为长度标识,用来表示编码数据的长度。
NALU
视频编码的时候还有一些编码参数数据,为了能够将一些通用的编码参数提取出来,不再图像编码数据中重复,H264设计了俩个重要的参数集:SPS(序列参数集),PPS(图像参数集)
- SPS:主要是包含图像的宽、高、YUV格式和位深等基本信息
- PPS:主要包含熵编码类型、基础QP和最大参考帧等基本编码信息
H264的码流主要由SPS、PPS、I Slice、P Slice和B Slice组成的
为了在码流中区分这几种数据,设计了NALU(网络抽象层单元)。SPS是一个NALU、PPS是一个NALU、每一个Slice也是一个NALU。每一个NALU又都是由1字节的NALU Header和若干字节的NALU Data组成的。而对于每一个Slice NALU,其NALU Data又是由Slice Header和Slice Data组成,并且Slice Data又是由一个个MB Data组成。结构如下:
NALU Header
总共占用一个字节
- F:forbidden_zero_bit,占1bit,禁止位。H264码流必须为0
- NRI:nal_ref_idc,占2bits,可以取00~11,表示当前NALU的重要性。参考帧、SPS和PPS对应的NALU必须要大于0
- Type:nal_unit_type,占5bits,表示NALU类型。
多个Slice时如何判断那几个Slice是同一帧?
Slice数据是由Slice Header与Slice Data组成。在Slice Header开始的位置有一个first_mb_in_slice字段,表示当前Slice的第一个宏块MB在当前编码图像中的序号。
- 如果first_mb_in_slice的值不等于0,就代表当前Slice
- 如果first_mb_in_slice的值不等于0,就代表当前Slice不是一帧的第一个Slice。并且使用同样的方式一直往下找,直到找到下一个first_mb_in_slice为0的Slice,就代表新一帧的开始。
如何从SPS中获取图像的宽高?
在SPS中有几个字段用来表示分辨率大小。我们可以解码出这几个字段并通过一定的规则计算得到分辨率大小。这几个字段是:
注意:pic_height_in_map_units_minus1需要考虑场编码和帧编码的区别(以下是帧编码的计算公式)
如何得到QP值?
量化是引入失真最主要的环节。而量化最主要的参数就是QP值,并且QP值的大小严重影响编码质量的清晰度。
在PPS中有一个全局基础的QP,且每一个Slice在这个基础QP的基础上做调整。在Slice Header中有一个slice_qp_delta字段来描述这个调整偏移值。更进一步,H264允许在宏块级别对QP做更进一步的精细化调整。这个字段在宏块数据里面,叫做mb_qp_delta.
如果需要得到Slice级别的QP则只需要考虑前俩个QP相关字段。如果需要计算宏块QP,则需要三个都考虑。但是宏块QP需要解析整个Slice,计算量大。
三、帧内编码
一般来说,一幅图像的相邻像素的亮度和色度信息是比较接近的,而且亮度和色度信息也是逐渐变化的,不太会出现突变。也就是说,空间具有相关性。帧内预测就是利用这个特点来进行的。即帧内预测通过利用已经编码的相邻像素值来预测待编码的像素值,最后达到减少空间冗余的目的。
在h264标准,块分为宏块和子块。宏块大小是16x16(YUV420图像亮度块是16x16,色度块为8x8)。帧内预测,亮度宏块可以继续划分成16个4x4的子块。
三点准则:
- 宏块大小是16x16,其中亮度块大小是16x16,色度块为8x8
- 帧内预测中亮度块和色度块是分开独立进行预测的
- 16x16的亮度块可以继续划分为16个4x4的子块
预测模式情况
- 4x4的亮度块帧内预测的预测模式有9种。其中8种方向和一种DC模式
- 16x16的亮度块的预测方式有4种。
- 8x8色度块鱼16x16一样是4种
帧内预测模式的选择
对于每一个块或者子块,我们可以得到预测块,再用实际待编码的块减去预测块就可以得到残差块。主要有以下三种方案得到最优预测模式
- 现对每一种预测模式的残差块的像素值求绝对值再求和,称之为cost,然后取其中残差块绝对值之和最小的预测模式为最优预测模式
- 对残差块先进行Hadamard变换,变换到频域之后再求绝对值之和,同样称为cost,然后取cost最小的预测模式为最优预测模式
- 也可以对残差块直接进行DCT变换、量化、熵编码,计算得到失真大小和编码后的码流大小,然后通过率失真优化的方法来选择最优预测模式
问题:
- H264标准,视频的第一帧的第一个块应该怎么选择预测模式?
视频的第一个块的左和上都是空,没法预测,所以设置成了一个约定值128
- 4x4、16x16不同大小的块为什么会有不同的预测模式
单纯从编码压缩来说是模式越多越好。但是要兼顾编码速度。16x16的块一般用在相对平坦的区域,也就是细节少一代呢的区域,模式少一点编码速度会好一些。而对于细节多的地方,需要更精细化编码,所以块也更小、模式也更多
- 是每个块都要做帧内预测吗?如果4x4的块,每个块都要计算9遍然后选出一个最优的?
所以一般有多种编码速度选择,在快速档会有一些快速算法根据块和周边块的像素特点提前判断编码模式,得到的不一定是最优解。在慢速档就可能每种模式都遍历得到的就是最优解(所以说不是码率高就清晰度高)
四、帧间预测
块大小
相比于帧内预测,帧间预测的块的划分类别会多很多,宏块大小16x16,可以划分为16x8,8x16,8x8三种,其中8x8可以继续划分为8x4,4x8和4x4,这事亮度块的划分。
运动搜索
运动搜索的目标就是在参考帧中找到一个块,称之为预测块,且这个预测块与编码块的差距最小。
全搜索算法
每一个宏块和子块进行全搜索
钻石搜索算法(菱形搜索算法)
以一个菱形的模式去寻找最优预测块。以亮度16x16的块运动搜索为例,将算法的思想分为以下几步:
- 从搜索的起点开始,已起始点作为菱形的中心点。首先已该中心点为左上角像素的16x16的块的预测块,求的残差块并求的像素绝对值之和,也就是SAD。之后对菱形4个角的4个点分别做同样的操作求的SAD。得到最小的SAD,最小的SAD值对应的点就是当前最佳匹配点。
- 如果最佳匹配点是菱形的中心点,那我们就找到了预测块了,搜索结束。
- 如果最佳匹配点不是菱形的中心点,则用当前最佳匹配点为中心点的菱形继续搜索,重复之前的步骤直到菱形的中心点为最佳匹配点。
例如,上图中:
第一步,以绿色点(起点)为中心点,搜索绿色点和旁边蓝色线连接的4个点,得到的最佳匹配点为橙色点,非中心点。
第二步,再以橙色点为中心点,搜索橙色点和旁边黄色线连接的4个点,最佳匹配是中心点橙色点,搜索完毕,橙色点为最佳匹配点。
六边形搜索算法
搜索模式是六边形。还是以亮度16x16的块为例介绍,主要有以下几个步骤:
-
- 从搜索的起始点开始,以起始点作为六边形的中心点。求的中心点作为左上角像素的预测块的值。之后对六边形的角上的6个点做同样的操作求的SAD值。得到最小的SAD值,而最小的SAD值对应的点就是当前最佳匹配点。
- 如果最佳匹配点是六边形的中心点,则用以当前最佳匹配点为中心点的六边形继续搜索,重复之前的步骤直到中心点为最佳匹配点。
第一步:以绿色点(起点)为中心点,搜索中心点和旁边蓝色线连接的6个点,得到最佳匹配点为橙色点,非中心点
第二步:再以橙色点为中心点,搜索橙色点和旁边黄色连线的6个点,最佳匹配点是中心点橙色点。
第三步:再以橙色点为中心点,搜索橙色点和旁边蓝色线连接菱形的4个点,最佳匹配点是黑色点
第四步:还是以橙色点为中心点,搜索旁边红色线连接正方形的4个点,并与菱形搜索得到的最佳匹配点黑色块比较,找到最后的最佳匹配点为红色点,搜索完毕。
运动矢量
以最佳匹配点为左上角像素的块就是预测块,并且预测块左上角像素在参考帧中的坐标(x1,y1)与编码块在当前编码帧中的坐标(x0,y0)的差值就是运动矢量。
搜索的起始点确认
搜索的起始点可以使用当前编码块的左边块、右边块、左上角块、右上角块的运动矢量预测得到。
总体思路:一般一个块最大就是16x16的大小,而运动的物体一般远大于这个大小,所以相邻块的运动方向大多数是很相似的。因此,一般会通过相邻已经编码块的运动矢量来预测当前块的运动矢量。这个预测的运动矢量也经常用作搜索的起点。
注意:快速搜索的预测块不一定是全局最优预测块(实验表明快速搜索相比全搜索压缩性能下降非常小,速度却可以提升十几倍到几十倍)
运动矢量预测
步骤如下:
- 取当前编码宏块的左边块A、上边块B、右上块C。如果右上块不存在或者参考帧与当前编码宏块不同(多参考的时候会存在),则使用左上块D替换C,即C=D。
- 求的A、B、C块的参考帧有多少个与当前编码块的参考帧相同,记为count。
- 如果count>1,取A、B、C块的运动矢量的中值(就是A、B、C块运动矢量的3个x和3个y分别取中间值作为MVP的x和y)。
- 如果count=1,则直接将这个块的运动矢量作为MVP。
- 如果count=0,并且B、C都不存在,A存在的话,则直接将A的运动矢量作为MVP
- 如果上述条件都不满足,则取A、B、C块运动矢量的中值
SKIP模式
如果运动矢量就是MVP,那么当前编码块的模式就是SKIP。
相比于SKIP模式,其他模式要不就是MVD不为0,要不就是量化后的残差系数不为0,或者俩者都不为0.所以说SKIP模式是一种特例,由于MVD和残差块都是等于0,因此压缩效率特别高。
五、变换量化