• FFmpeg学习(四)视频基础


    一:视频入门

    (一)视频定义(什么是视频)

    (二)图像

    1.像素:图像由像素组成(如下图图片中的一个个小格子)。

    对于每个像素,还有位深的概念:用多少个位来表示位深。类似于音频中的采样大小

    RGB888:对于R、G、B中每个元素占8位

    RGBA:同上,多了一个A(透明度)

    2.RGB:每个像素是由RGB三元素组成;如下图格子表示一个像素,内部由RGB(三个发光二极管)组成。

    通过RGB(三个发光二极管不同亮度组成)可以组合成多种色彩:

    3.分辨率:用来表示图像在X、Y轴上各有多少个像素点。

    (三)屏幕显示器

    1.显示器和图像都具有像素!!!

    对于屏幕而言,每一个像素如下所示:

    根据图像的数据,来控制屏幕中每一个像素点的发光二极管的强度。从而实现不同颜色点,拼接为最终图像!! 

    2.RGB的色彩问题(部分情况使用的BGR顺序,使得与显示器的驱动程序处理顺序不一致,导致图像渲染出错

    3.屏幕指标

    PPI:每英寸下的像素数 
    DPI:每英寸下的点数
    基本上两者相等,极少情况下DPI每个点中存在多个像素
    PPI > 300 :视网膜级别(人眼无法区别是由像素组成,认为是一整块图像)

    (四)码流的计算

    1.分辨率(清晰度)

    常见的视频宽高比是16:9,部分老式显示器可能是4:3;如果一个视频的宽高比不是这两种,则需要进行转换。
    对于360P,16:9中是指高360P,宽640P

    2.帧率(平滑、流畅度)

    对于直播系统中,为了减少所占码流的大小,通常采用15帧/S;
    录课:30帧/S
    视频播放60帧/S

    3.未编码视频的RGB码流

    其中3Byte表示一个像素RGB所占大小

    (五)图像的显示(图像和显示器分辨率不一致情况)

    二:YUV了解

    各种格式解析:https://www.fourcc.org/rgb.php

    (一)什么是YUV(有了RGB图像之后,为什么还需要YUV?)

    由电视系统发展而来,逐步从黑白--->彩色;YUV可以兼容这两种模式。当获取Y信号之后就可以正常的进行播放,此时颜色为黑白(黑白电视机可以播放)。UV用于描述影像的色彩和饱和度(彩色电视机播放需要)。最标准YUV格式为YUV4:2:0

    在DVD、摄像机、数字电视等消费类视频产品中,常用的色彩编码方案是YCbCr,其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量。

    人的肉眼对视频的Y分量更敏感,因此在通过对色度分量进行子采样来减少色度分量后,肉眼将察觉不到的图像质量的变化。

    1.YUV图像各个分量

    YUV原始图像:

    YUV图像的Y分量(只有明亮度、没有色彩和饱和度):

    YUV图像的U分量(只有Cb分量,图片蓝色分量):

    YUV图像的V分量(只有Cr分量,图片红色分量):

    只有在电视中使用了YUV格式,在手机、屏幕、显示器中使用RGB格式,所以YUV显示在屏幕需要进行转换!

    2.RGB与YUV的关系

    RGB转YUV:

    YUV转RGB:

    3.YUV常见格式

    YUV4:44 一个Y元素对应一个U元素对应一个V元素,对于1280×720图像,数据量为:1280×720×3---其中3同RGB888一致,表示3个字节(24位)
    YUV4:22 对于每一个横行,隔一列有一个UV,数据量为1280×720×1+2×1280×720/2 = 1280×720×2--- ×1表示Y占1字节,/2是对于UV来说是每隔一列才有一个UV
    YUV4:20 最标准,最广泛; 隔行分U或者V分量,隔列分是否有UV分量;数据量为1280×720×1+2×1280×720/4 = 1280×720×1.5
    各个格式详细见下面图片!!!

    YUV4:2:0

    对于YUV4:2:0而言满足下面的计算:

    YUV4:2:0优点:

    1.YUV兼容以前的图像
    2.相比较RGB而言,数据量减少1半,存储更加具有优势!

    补充:对齐知识!!!!

    对于已经对齐的分辨率如:1280*720,640*480这样的分辨率它的YUV420P数据格式是完全对齐(16位对齐)
    
    而像176*144这样的size它的YUV420P不是16位对齐,需要补空白位(0补齐),使得176*144这样的size能16位对齐。

    YUV4:2:2

    此格式的打包格式为,YUYV YUYV YUYV ……(YUYV422格式);此格式的存储格式为:首先全是Y,然后全是U,最后全是V。

    YUV4:4:4

    更多格式见:https://blog.csdn.net/cgwang_1580/article/details/79595958

    (二)YUV4:2:0存储格式

    YUV数据是分层存储的,所以可以更加方便的与黑白电视机相兼容,只需要读取前面的Y数据即可,后面的UV数据舍弃即可。对于彩色电视机,将后面的UV数据一块读取。
    4:2:0,是指4个Y对应纵向1个U,1个V;如下图(色彩对应)

     

    不同系统下,存储格式可能不同:比如IOS使用YV12,android使用NV21

    YUV4:2:0 码流计算:

    (三)YUV命令行采集数据

    原始mp4参数:

    1.根据参数进行命令行采集YUV数据

    ffmpeg -i gfxm.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv

    ffplay -pix_fmt yuv420p -s 864*486 out.yuv   #-s是说明播放时的分辨率 -pix_fmt指定格式 如果两个参数不一致,会导致播放出现花屏等问题

    播放YUV图像的Y分量(其他分量也可以播放):

    ffplay -pix_fmt yuv420p -s 864*486 -vf extractplanes='y' out.yuv    #-vf 表示滤波器,vf属于简单滤波

    提取单独分量:

    ffmpeg -i gfxm.mp4 -filter_complex 'extractplanes=y+u+v[y][u][v]' -map '[y]' y.yuv -map '[u]' u.yuv -map '[v]' v.yuv   
    #其中[]表示别名,map可以引用别名

    播放出现问题:

    ffplay -s 864*486 y.yuv

    需要指定格式pix_fmt为单色,而不是按默认YUV去读取

    ffplay y.yuv -pix_fmt gray -s 864*486

     

    注意:对于U、V分量,由于是YUV4:2:0,其中U、V分量只占1/2(即X,Y轴分辨率各自减少一半)

    ffplay u.yuv -pix_fmt gray -s 432*243

    (四)FFmpeg编程采集 

    x11grab见:https://www.cnblogs.com/ssyfj/p/14576359.html

    #include <stdio.h>
    #include <libavutil/log.h>
    #include <libavcodec/avcodec.h>
    #include <libavdevice/avdevice.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    
    void rec_video(){
        char* devicename = ":0.0";    //:前面为空,表示录制本地;:后面,如果NumA为0, 则表示连接到6000端口; 使用unix socket方式连接时则表示连接的unix socket的路径, 如果为0, 则表示连接到/tmp/.X11-unix/X0 . NumB则几乎总是0.
        char errors[1024];
        int ret,count=0,len;
        FILE* fp = NULL;
    
        AVFormatContext* fmt_ctx=NULL;    //格式上下文获取-----av_read_frame获取packet
        AVDictionary* options=NULL;
        AVInputFormat *iformat=NULL;
        AVPacket packet;    //包结构
    
        //获取输入(采集)格式
        iformat = av_find_input_format("x11grab");    //驱动,用来录制桌面
        //设置参数
        av_dict_set(&options,"video_size","1024*768",0);
        av_dict_set(&options,"framerate","15",0);
    
        //打开输入设备
        ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options);    //----打开输入设备,初始化格式上下文和选项
        if(ret<0){
            av_strerror(ret,errors,1024);
            av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s
    ",ret,errors);
        }
        
        av_log(NULL,AV_LOG_INFO,"Success to open video device
    ");
        //打开文件
        fp = fopen("./video.yuv","wb");
        if(fp==NULL){
            av_log(NULL,AV_LOG_ERROR,"Failed to open out file
    ");
            goto fail;
        }
    
        //开始从设备中读取数据
        while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){
            av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d
    ",packet.size,packet.data,count);
    
            fwrite(packet.data,1,packet.size,fp);
            fflush(fp);
    
            //释放空间
            av_packet_unref(&packet);
        }
    fail:
    
        if(fp)
            fclose(fp);
    
        //关闭设备、释放上下文空间
        avformat_close_input(&fmt_ctx);
        return ;
    }
    
    int main(int argc,char* argv)
    {
    
        av_register_all();
        av_log_set_level(AV_LOG_DEBUG);
        //注册所有的设备,包括我们需要的音频设备
        avdevice_register_all();
    
        rec_video();
        return 0;
    }
    View Code
    gcc -o gd 01GetData.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample

    由于没有摄像头,所以使用了屏幕录制,所以采集的数据是BGR0类型

    ffplay video.yuv -s 1024*768 -pix_fmt bgr0

    注意:在下面代码中直接写入packet.size大小,可能在处理摄像头YUV数据时出错

    fwrite(packet.data,1,packet.size,fp);

    写入时包大小:3145728

    读取时数据采用BGR0(最后0是指A透明度为不透明)大小4字节:1024×768×4=3145728;

    和前面的读取包大小packet.size一致,所以不会出错,如果两者不一致,那么需要修改packet.size为我们想要的数据大小!!

    另外:主动选择像素格式

    如果驱动允许我们自己设置pixel_format,那么最好进行主动设置:https://ffmpeg.org/ffmpeg-devices.html#avfoundation

    补充:使用摄像头(借到了)

    1.查看摄像头所支持的视频格式和分辨率

    ffmpeg -hide_banner -f v4l2 -list_formats all -i /dev/video0

    发现只支持yuyv422格式....

    2.测试摄像头

    ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 320*240  -framerate 15 -i /dev/video0 out.yuv
    ffplay out.yuv -s 320*240 -pixel_format yuyv422

    3.代码实现

    #include <stdio.h>
    #include <libavutil/log.h>
    #include <libavcodec/avcodec.h>
    #include <libavdevice/avdevice.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    
    void rec_video(){
        char* devicename = "/dev/video0";    //设备文件描述符
        char errors[1024];
        int ret,count=0,len;
        FILE* fp = NULL;
    
        AVFormatContext* fmt_ctx=NULL;    //格式上下文获取-----av_read_frame获取packet
        AVDictionary* options=NULL;
        AVInputFormat *iformat=NULL;
        AVPacket packet;    //包结构
    
        //获取输入(采集)格式
        iformat = av_find_input_format("video4linux2");    //驱动,用来录制视频
        //设置参数 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480  -framerate 15 -i /dev/video0 out.yuv
        av_dict_set(&options,"video_size","640*480",0);
        av_dict_set(&options,"framerate","30",0);
        av_dict_set(&options,"pixel_format","yuyv422",0);
    
        //打开输入设备
        ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options);    //----打开输入设备,初始化格式上下文和选项
        if(ret<0){
            av_strerror(ret,errors,1024);
            av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s
    ",ret,errors);
        }
        
        av_log(NULL,AV_LOG_INFO,"Success to open video device
    ");
        //打开文件
        fp = fopen("./video.yuv","wb");
        if(fp==NULL){
            av_log(NULL,AV_LOG_ERROR,"Failed to open out file
    ");
            goto fail;
        }
    
        //开始从设备中读取数据
        while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){
            av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d
    ",packet.size,packet.data,count);
    
            fwrite(packet.data,1,packet.size,fp);
    
            fflush(fp);
    
            //释放空间
            av_packet_unref(&packet);
        }
    fail:
    
        if(fp)
            fclose(fp);
    
        //关闭设备、释放上下文空间
        avformat_close_input(&fmt_ctx);
        return ;
    }
    
    int main(int argc,char* argv)
    {
    
        av_register_all();
        av_log_set_level(AV_LOG_DEBUG);
        //注册所有的设备,包括我们需要的音频设备
        avdevice_register_all();
    
        rec_video();
        return 0;
    }
    View Code

    三:H264编码

    (一)H264压缩码率

    YUV420下的码流大小为:640×480×1.5(RBG为3字节,YUV420为1.5字节)×15= 6912000 bytes = 55.296 mbps(换算为bits)
    H264编码而言,建议的码流大小为500kbps,约为YUV420下的1/100!!!

    参考码率:https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1video_1_1_video_encoder_configuration.html#a4b090cd0e9f6d98bcf89cb1c4c2066e8

    通信实时性要求高,所以码率相比较于直播会低很多,从而实现流畅度增加

    (二)GOP(Group of Pictures)

    原始视频数据1s内,包含25帧,帧间隔时间为40ms:

    当视频时间跨度为10min后,数据帧数变多25×10×60=15000帧;不易压缩!!----引入分组(按照相关性),如下图所有帧中只包含“玩电脑”、“望远镜”两个动作,在此两个动作上进行微调;所以每一个组都是描述一个图像目标的细微差别。所以,同一个组中的视频是强相关的。而在不同组之间的相关性非常小。以此来进行分组。

    由下图可以看出,GOP中帧与帧之间差别很小。将无差别的背景图用一张图表示,将中间人物相同部分放置到一张图,将人物中间望远镜位置等差值进行存储。由此使得GOP这一组帧存储空间被压缩得很小!!!

    (三)I/P/B帧

    举例:I帧,是关键帧,如果缺失了该帧,则可能后面无法解析

    对于前面所提到的GOP中,一组强相关帧中的第一帧,我们一般称为IDR帧(一种特殊的I帧);
    对于GOP中,如果包含过多的帧(超过一定范围),H264会强制加入一些I帧到GOP中,用来防止一旦出现错误,导致错误串联的情况!!
    对于I帧,不依赖其他任何帧,所以属于帧内压缩!!(编码、解码都只靠自己)

    举例:P帧,参考前面的帧,前面解码,当前帧才可以解码;前面编码,当前帧才可以编码。P帧只参考前面,不参考后面!!!属于帧间压缩技术,与前面的帧有关系。

    举例:B帧,双向参考帧,压缩率最高,耗时严重,延迟大;所以对于实时性要求高的应用中,基本使用I帧和P帧;对于音视频转码应用,为了节省空间,使用B帧多

    在GOP中,如果含有B帧数据,那么B帧一定是最后才进行解码的。但是播放的时候是按照帧出现的顺序来播放的!!!

    对于B帧来说,后面的P帧一定优于B帧先进行解码的???见下面补充:帧与分组的关系(默认H264下)

    补充:IDR帧和I帧的区别和联系

    补充:帧与分组的关系(默认H264下)

    注意,如下图:对于在I帧和P帧之间的B帧,需要参考前面的I帧和后面的P帧。所以解码顺序依次是I帧-->P帧-->中间3个B帧

    如下图,对于在两个P帧之间的B帧,参考前后的P帧即可

    如下图,对于的B帧,参考前面GOP的P帧和后面GOP的I帧

    注意:GOP中所有P帧率都是先于B帧的!!

    补充:SPS与PPS

    (四)H264压缩技术(一系列压缩技术集合) 

     

    帧内压缩(有损压缩技术):解决空域数据冗余问题;  ----- 空域:一张图片是占用一定的空间,帧内压缩技术,是用来解决图片内的数据压缩问题。 (对于背景,主体等采用不同方法压缩,使得总数据下降)
    帧间压缩(有损压缩技术):解决时域数据冗余问题;  ----- 时域:帧与帧之间的数据之间有参考,利用残差值
    -------帧内压缩,是将不必要的数据去除,相比于亮度,色度要求更低,所以压缩时可以除去部分色度数据,达到减少数据的要求!!
    -------帧间压缩,由于依赖前一帧的数据,而前一帧已经是有损的,所以参考前一帧的当前帧也是有损的!!
    整数离散余弦变换(无损压缩技术): ----- 经过上面两种压缩后,数据已经很小了,但是还可以通过DCT变换,将有用数据集中,其他位置为0,进行压缩。可以减少复杂度,利于后面的无损压缩 CABAC压缩(无损压缩技术):    ----- 根据上下文进行数据的压缩

    (五)宏块---最基础知识点

    原始图像:

    H264宏块划分:如下图,按像素划分一个8×8像素的宏块,由于每个像素包含一个值,所以可以看作右侧的3维图像!!!

    宏块划分完成后:

    对于每个宏块,我们还可以进一步划分为子块:如下图:宏块大小16×16,划分为4个8×8,进一步划分为4×4、4×8、8×4一系列子块(注意:子块也是宏块)。

    如果宏块特别大,则控制力弱,但是处理速度快;

    对于细节多,纹理强的图,宏块需要划分较小,控制力更强,压缩比会更高。

    可以对比下图右图,H264划分子块后的压缩比要高于MPEG2的压缩比。H264数据量小于MPEG。

    宏块尺寸:(可以进一步划分,用来增强压缩率)

    四:I/P/B帧和音视频同步

    转载:https://m.imooc.com/mip/article/91381

    (一)I/P/B帧

    对于 I帧,B帧,P帧,前面三中已经进行了讲解。

    • I帧是关键帧,它采用帧内压缩技术;
    • B帧是前后参考帧,它属由帧间压缩技术。也就是说在压缩成 B帧前,它会参考它前面的非压缩视频帧,和后面的非压缩的视频帧,记录下前后两帧都不存放的“残差值”,这样可以达到更好的压缩率;
    • P帧是向前参考帧,也就是它参考的是前一个关键帧的数据。P帧也属于帧间压缩技术,相对于 B帧来说,P帧的压缩率要比B帧低。

    但在实时互动直播系统中,很少使用B帧。主要的原因是压缩和解码B帧时,由于要双向参考,所以它需要缓冲更多的数据,且使用的CPU也会更高。由于实时性的要求,所以一般不使用它。不过对于播放器来说,遇到带有B帧的H264数据是常有的事儿。

    (二)PTS/DTS

    有了上面 I/B/P帧的概念,我们再来理解 PTS/DTS 就非常容易了。

    PTS(Presentation TimeStamp)是渲染用的时间戳,也就是说,我们的视频帧是按照 PTS 的时间戳来展示的。
    
    DTS(Decoding TimeStamp)解码时间戳,是用于视频解码的。

    那为什么有了 PTS 还要有 DTS呢?这就与我们上面所讲的 I/B/P帧有关了。

    如果我们的视频中没有B帧,那显示的帧的顺序与存放的帧的顺序是一样的,此时PTS与DTS 的值就是一样的,也就没有存在两个时间戳的必要了。

    但有了B帧之后,就不是这个样子了。我们举个简单的例子:I B B P

    实际应展示的顺序:I B B P 
    实际在解码的顺序:I P B B
    按实际顺序号解码:1 4 2 3
    按实际顺序号展示:1 2 3 4

    对于上面这个例子我们作下说明:

    1. 我们实际应该展示的帧的顺序是 I, B, B, P 帧解码后的视频帧。
    2. 但实际上,这些帧到达之后,在缓冲区里就按照第二行的样子存放。为什么会这样呢?这是由于我上面所讲的,P帧参考的是 I帧,B帧是双向参考帧。也就是说,如果 I帧和P帧没有解码的话,B帧是无法进行解码的。基于此,为了解决这个问题就出现了 PTS和DTS两个时间戳。
    3. 第三行是视频帧真正的解码顺序,先解 I帧,然后是P帧,然后是第一个B帧,最后是第二个B帧。
    4. 最终的展示顺序是 I帧解码后的视频帧,第一个B帧解码后的视频帧,第二个B帧解码后的视频帧,最后是P帧解码后的视频帖。

    (三)时间基

    有了时间戳之后,最终进行展示时还要需要将 PTS时间戳转成以秒为单位的时间。那这里需要向大家介绍一下 ffmpeg的时间基。

    我们在执行 ffmpeg/ffplay命令时,可以通过控制台看到几个参数,分别是 tbr, tbn, tbc。这几个值是什么含义呢?其实就是不同的时间基。

    tbr: 是我们通常所说的帧率time base of rate
    tbn: 视频流的时间基。    time base of stream
    tbc: 视频解码的时间基。    time base of codec

    在ffmpeg中,不同的时间戳对应不同的时间基。对于视频的渲染我们使用的是视频流的时间基,也就是 tbn。

    那我们如何理解时间基呢?时间刻度(间隔)

    其实非常简单,就是时间刻度。我们以帧率为例,如果每秒钟的帧率是 25帧,那么它的时间基(时间刻度)就是 1/25。也就是说每隔1/25 秒后,显示一帧。

    所以如我们当前的时间是 100, 时间基是 1/25,那么转成秒的时间是多少呢?

    100*时音基(1/25),也就是100 * 1/25 = 4秒。

    (四)ffmpeg内部时间基

    除了我上面所讲的几个时间基之外,ffmpeg内部还有一个时间基。即我们通过所见到的 AV_TIME_BASE。它在ffmpeg内部定义如下:

    #define AV_TIME_BASE 1000000

    它还有一种分数所表式法:(这里就是时间间隔的数据存放结构,时间基由下面的函数av_q2d获取)

    #define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}

    在 ffmpeg中进行换算,将不同时间基的值转成按秒为单位的值计算如下:

    timestamp(秒) = pts * av_q2d(time_base)  #可以看出pts是以自己的时间基为间隔×pts

    这里引入了 av_q2d 这个函数,它的定义非常简单:(获取时间基)

    typedef struct AVRational{
    int num; //numerator
    int den; //denominator
    } AVRational;
    
    static inline double av_q2d(AVRational a){
    /**
    * Convert rational to double.
    * @param a rational to convert
    **/
        return a.num / (double) a.den;
    }

    从这里我们可以看到,它与我上面所讲的公式是一样的。

    (五)不同时间基的换算

    在上面我向大家介绍了 ffmpeg有好几种不同的时间基,有时候我们需要在不同的时间基之间做换算。ffmpeg为我们提供了非常方便的函数。即

    av_rescale_q()  把时间戳从一个时基调整到另外一个时基时候用的函数。其中,a 表式要换算的值;b 表式原来的时间基;c表式要转换的时间基。其计算公式为 a * b / c

    既然公式这么简单,我们自己写就OK了,为什么ffmpeg还要单独提供一个函数呢?其实这个看似简单的方法,还要考虑数值溢出的问题。所以把这块的逻辑加上之后,就没我们看到的这么简单了。不过没关系,我们只要清楚 av_rescale_q 是做什么的,怎么用就可以了。

    下面我再给出两个算计公式:

    • 时间戳转秒
      time_in_seconds = av_q2d(AV_TIME_BASE_Q) * timestamp #时间戳×时间基=n秒
    • 秒转时间戳
      timestamp = AV_TIME_BASE * time_in_seconds #秒/时间基 = 时间戳

    五:H264压缩技术

    (一)帧内压缩

    1.相邻宏块之间的像素差别不大,可以进行预测。(以宏块为单位,而不是以像素为单位处理,后者效率太低;宏块最小为4×4小块
    2.黑白即可了解图片信息,色度为辅助(如黑白电视)。所以即便颜色有所偏差也无所谓。
    3.YUV的优点就是直接获取Y亮度数据,UV数据进行单独处理。

    通过预测的方法进行压缩:

    当我们不知道下一个宏块的数据(如4×4像素大小),可以根据之前的像素或者当前宏块的像素,来推断出下一个宏块的像素具体是多少!!!

    帧内预测(9种模式):详解H.264之帧内预测

    4×4亮度块的上方和左方像素A~M为已编码和重构像素,用作编解码器中的预测参考像素(临近像素,也就是前面已经编码完成的)
    a~p为待预测像素,利用A~M值和9种模式(0~8)实现预测

    其中模式2(DC预测)根据A~M中已编码像素预测,而其余模式只有在所需预测像素全部提供才能使用。
    图2.b箭头表明了每种模式预测方向。
    对模式3~8,预测像素由A~M加权平均而得。
    例如,模式4中,d=round(B/4+C/2+D/4)。

    0 1 2
    3 4 5
    6 7 8

    模式0:垂直预测,每列数据都与头部数据相同
    a b c d
    a b c d
    a b c d
    a b c d

    模式1:横向预测,每行数据与左侧数据相同
    I I I I
    J J J J
    K K K K
    L L L L

    模式2:求平均值,每个值都是(头部a b c d + 左侧 I J K L)的平均值

    对于预测模式3-8,倾斜方向的,各个像素是由A到L像素通过权重不等的公式加权计算的
    ....详见https://blog.csdn.net/u014253011/article/details/79970582

    模式选取:在上面9种模式选取时,会先进行预判;以某点(宏块)为基础,推算其相邻的下一个宏块是哪一种模式。

    选取方式(一种快速算法):在9种预测方法,看哪一种模式最接近原来宏块像素,则选取该预测模式!!

    如下图右侧中,快速获取每一个模块应该使用的预测方式:3表示使用模式3处理,....

    帧内预测举例:(与原始图结果相似)

    但是有一定区别:

    因此需要获取预测值与原始值之间获取残差值:(下灰色图)

    预测模式信息残差值进行压缩:

    对方根据预测模式及残差值和前面的参考宏块,就可以还原为原始数据(有损压缩)!!

    帧内压缩的帧类型:

    (二)帧间压缩技术

    1.GOP:帧间压缩一定是在同一个GOP之内的相邻帧之间进行帧间压缩(差异性小、强相关)
    2.参考帧:后面的帧要参考前面的帧进行帧间压缩
    3.运动估计:宏块匹配+运动矢量 通过前者去找到后者
    4.运动补偿:通过残差值,在解码时进行补偿

    参考帧:  在一组GOP中,后面的帧要参考前面的帧(下图中首个图片标红为I帧)

    帧与帧之间的区别很小,实际压缩中,只需要存储望远镜的运动矢量。解码时根据原来的基本图像和运动矢量就可以还原该图片!

    宏块查找:以下图黄色球所在宏块进行查找(实际会查找所有宏块)。右侧为帧1,左侧为帧2。

    通过逐行扫描(实际算法不是这个),查找在下一帧(帧2)中相似度最高的宏块(或者达到阈值);记录新的坐标,同时有原始帧1中的坐标信息。由此获取了距离和方向信息(运动矢量)

    宏块查找算法

    运动估计:左侧为上面帧1帧2之间的运动矢量,我们获取一组GOP中所有相邻帧之间的运动矢量,从而获取右侧红色箭头运动轨迹数据

    实际解码过程还需要残差值,通过运动矢量(先大致还原)+残差值(进行补偿)

    帧间压缩的帧类型:

    实际应用中出现问题:

    有帧丢失,会导致残差值和运动矢量丢失;如果I帧丢失,后面GOP中数据都无法解析;但这里主要是P、B帧丢失

    因此,花屏和卡顿无法兼容....,通过插入I帧来减少卡顿时间,但是I帧是帧内压缩,压缩比没有帧间压缩效率高!!(考虑带宽)

    (三)无损压缩 

    DCT变换,使得数据从分散--->到集中的过程。方便后面进行进一步压缩!

    VLC压缩(MPEG2使用):可变长编码。Huffman编码是可变字长编码(VLC)的一种。按频率编码

    上面图中使用字符进行编码,实际中使用数据块进行编解码

    CABAC压缩(H264使用):上下文适配的二进制算术编码 

    上图对比了VLC压缩:左侧白色块为原始图像,右侧为压缩后的数据。下面只讨论由此压缩后的数据

    右侧为压缩后的数据,越靠近右侧,是越早压缩的数据。所以对比两种方法,发现最右侧压缩都较长,而随着时间推移,CABAC向左侧(后面开始压缩的)压缩数据较少。

    因为CABAC由于有上下文的原因,所以后面压缩的数据压缩率非常高 

    (四)H264编解码流程

    ME:运动评估---对每一个宏块进行匹配查找
    MC:通过运动评估获取得到运动矢量
    
    T、Q是无损压缩中的DCT和CABAC等转换和压缩算法

  • 相关阅读:
    37. 解数独
    皮尔逊相关系数的计算以及数据的描述性统计
    商业微信小程序开发实战---1
    51. N皇后
    拟合算法
    216. 组合总和 III
    打印心性
    指针
    第五天
    循环 和 宏
  • 原文地址:https://www.cnblogs.com/ssyfj/p/14671862.html
Copyright © 2020-2023  润新知