H264视频编码原理
说到视频,我们首先想到的可能就是占内存。我们知道一个视频是由一连串图像序列组成的,视频中图像一般是 YUV 格式。假设有一个电影视频,分辨率是 1080P,帧率是 25fps,并且时长是 2 小时,如果不做视频压缩的话,YUV 420格式,它的大小是 1920 x 1080 x 1.5 x 25 x 2 x 3600 = 521.4G!如果是在视频通话场景下的话,按照这个大小去传输视频数据,对流量和带宽资源的消耗也是非常大的,并且如此大的数据发送对网络要求也非常高,很显然我们是接受不了的。因此,做视频编码压缩就非常有必要。
什么是编码
编码:就是按指定的方法,将信息从一种形式(格式),转换成另一种形式(格式)。视频编码:就是将一种视频格式,转换成另一种视频格式。编码的目的,通常是为了减少文件大小。
视频编码原理
频编码是对一帧帧图像来进行的。一般我们所熟知的彩色图像的格式是 RGB 的,即用红绿蓝三个分量的组合来表示所有颜色。但是,RGB 三个颜色是有相关性的,为了去掉这个相关性,减少需要编码的信息量,我们通常会把 RGB 转换成 YUV,也就是 1 个亮度分量和 2 个色度分量。另外,人眼对于亮度信息更加敏感,而对于色度信息稍弱,所以视频编码是将 Y 分量和 UV 分量分开来编码的。另外,人眼对于亮度信息更加敏感,而对于色度信息稍弱,所以视频编码是将 Y 分量和 UV 分量分开来编码的。
图像一般都是有数据冗余的,主要包括以下 4 种:
-
空间冗余。一帧图片的内容往往存在大量相似的内容。如果将一帧图像划分成一个个 16x16 的块之后,相邻的块很多时候都有比较明显的相似性,这种就叫空间冗余。

如上面这张图片,天空是蓝色的一大片,若果把它划分成一个一个的小块区域,那么相邻的区块之间的差别是比较小的(特别是天空部分)。

-
时间冗余。一个帧率为 30fps 的视频中前后两帧图像相差只有 33ms,两张图像的变化是比较小的,相似性很高,这种叫做时间冗余。
-

如上图,假设是视频流中的两帧图片,图片的背景都一样,只是小车在移动。那么这两帧图片大部分内容都是相同,只是小车的位置不一样,这个就是时间冗余。 -
视觉冗余。我们的眼睛是有视觉灵敏度这个东西的。人的眼睛对于图像中高频信息的敏感度是小于低频信息的。有的时候去除图像中的一些高频信息,人眼看起来跟不去除高频信息差别不大,这种叫做视觉冗余。
-
信息熵冗余。我们一般会使用 Zip 等压缩工具去压缩文件,将文件大小减小,这个对于图像来说也是可以做的,这种冗余叫做信息熵冗余。
对于一个 YUV 图像,H264把它划分成一个个 16x16 的宏块,Y、U、V 分量的大小分别是 16x16、8x8、8x8。这里我们只对 Y 分量进行分析(U、V 分量同理)。假设 Y 分量这 16x16 个像素就是一个个数字,我们从左上角开始之字形扫描每一个像素值,则可以得到一个“像素串”。如下图所示:

我们来看一个LeetCode编程题: 字符串压缩
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。
将 “aabcccccaaa” 压缩成 “a2b1c5a3”,字符串由 11个字节压缩到 78个字节,这个叫做行程编码。如果我们对图像宏块扫描出来的这个“像素串”做同样的行程编码操作,是不是也有可能减小图像块呢?
在对“像素串”行程编码之前,我们先回过头来看看另一个行程编码的例子。如果刚才编程题的字符串是 “abcdabcdabcd” 的话,那么编码之后就会是 “1a1b1c1d1a1b1c1d1a1b1c1d”。字符串的大小从 13 字节变成了 25 字节,还变大了。
我们发现如果想要达到压缩的目的,我们必须要使得编码前的字符串中出现比较多连续相同的字符。这对于图像块也是一样的。我们需要使得扫描出来的“像素串”,也尽量出现连续相同的像素值,最好是一连串数字很小(比如 0)的“像素串”,因为 0 在二进制中只占 1 个位就可以了。这个地方你可能会有疑惑,0 也是至少要一个字节存储,需要 8 个位,怎么会是 1 个位呢?这个有的编码算法是可以做到的,比如指数哥伦布编码,它就可以做到 0 只占用一个位。事实上,算术编码可以做到一个符号只占用 0 点几个位,连一个位都不用,这里不详细展开
那我们如何做到将这串像素值变成有很多 0 的“像素串”呢?首先第一步,我们通过减少图像块的空间冗余和时间冗余来接近这个目标。刚才我们也说到,图像内部相邻宏块之间有很多相似性,并且两张图像之间也有很多相似性。因此,根据图像的这个特点,我们可以在编码的时候进行帧内预测和帧间预测。
帧内预测就是在当前编码图像内部已经编码完成的块中找到与将要编码的块相邻的块。一般就是即将编码块的左边块、上边块、左上角块和右上角块,通过将这些块与编码块相邻的像素经过多种不同的算法得到多个不同的预测块。
然后我们再用编码块减去每一个预测块得到一个个残差块。最后,我们取这些算法得到的残差块中像素的绝对值加起来最小的块为预测块。而得到这个预测块的算法为帧内预测模式。

由于这个残差块中像素的绝对值之和最小,这个残差块的像素值经过扫描之后的“像素串”是不是就比直接扫描编码块的“像素串”中的像素值更接近 0 了?
同理,帧间预测也是一样的。我们在前面已经编码完成的图像中,循环遍历每一个块,将它作为预测块,用当前的编码块与这个块做差值,得到残差块,取残差块中像素值的绝对值加起来最小的块为预测块,预测块所在的已经编码的图像称为参考帧。
预测块在参考帧中的坐标值 (x0, y0) 与编码块在编码帧中的坐标值 (x1, y1) 的差值 (x0 - x1, y0 - y1) 称之为运动矢量。

而在参考帧中去寻找预测块的过程称之为运动搜索。事实上编码过程中真正的运动搜索不是一个个块去遍历寻找的,而是有快速的运动搜索算法的。之后我们在帧间预测的课中会详细介绍。总之,通过预测得到的残差块的像素值相比编码块的像素值,去除了大部分空间冗余信息和时间冗余信息,这样得到的像素值更小。如果把这个残差块做扫描得到的像素串送去做行程编码,是不是相比直接拿编码块的像素串去做编码更有可能得到更大的压缩率?
但是我们的目标不只是将像素值变小,而是希望能出现连续的 0 像素,那怎么办呢?这就需要利用我们人眼的视觉敏感性的特点了。我们刚才说了人眼对高频信息不太敏感。因为人眼看到的效果可能差别不大,所以我们可以去除一些高频信息。这个就是接下来我们要讨论的 DCT 变换和量化。
为了分离图像块的高频和低频信息,我们需要将图像块变换到频域。常用的变换是 DCT 变换。DCT 变换又叫离散余弦变换。在 H264 里面,如果一个块大小是 16x16 的,我们一般会划分成 16 个 4x4 的块(当然也有划分成 8x8 做变换的,我们这里以 4x4 为例)。然后对每个 4x4 的块做 DCT 变换得到相应的 4x4 的变换块。变换块的每一个“像素值”我们称为系数。变换块左上角的系数值就是图像的低频信息,其余的就是图像的高频信息,并且高频信息占大部分。低频信息表示的是一张图的总体样貌。一般低频系数的值也比较大。而高频信息主要表示的是图像中人物或物体的轮廓边缘等变化剧烈的地方。高频系数的数量多,但高频系数的值一般比较小(注意不是所有的高频系数都一定小于低频,只是大多数高频系数比较小)。如下图所示(黄色为低频,绿色为高频):

这样做完了 DCT 变换之后,低频和高频信息就分离开来了。由于低频信息在左上角,其余的都是高频信息。那么如果我们对变换块的像素值进行“之字形”扫描,这样得到的像素串,前面的就是数值比较大的低频系数,后面就是数值比较小的高频部分。由于人眼对高频信息不太敏感,如果我们通过一种手段去除掉大部分高频信息,也就是将大部分高频信息置为 0,但又不太影响人的观感,是不是就可以达到我们最初的目标,即可以得到有一连串 0 的像素串?这就涉及到量化操作了。我们让变换块的系数都同时除以一个值,这个值我们称之为量化步长,也就是 QStep(QStep 是编码器内部的概念,用户一般使用量化参数 QP 这个值,QP 和 QStep 一一对应,你可以自行去网上查询一下转换表),得到的结果就是量化后的系数。QStep 越大,得到量化后的系数就会越小。同时,相同的 QStep 值,高频系数值相比低频系数值更小,量化后就更容易变成 0。这样一来,我们就可以将大部分高频系数变成 0。如下图所示:

解码的时候,我们会将 QStep 乘以量化后的系数得到变换系数,很明显这个变换系数和原始没有量化的变换系数是不一样的,这个就是我们常说的有损编码。而到底损失多少呢?这由 QStep 来控制,QStep 越大,损失就越大。QStep 跟 QP 一一对应,也就是说确定了一个 QP 值,就确定了一个 QStep。所以从编码器应用角度来看,QP 值越大,损失就越大,从而画面的清晰度就会越低。同时,QP 值越大系数被量化成 0 的概率就越大,这样编码之后码流大小就会越小,压缩就会越高。以上就是视频编码的推理过程。总结一下就是,为了能够在最后熵编码的时候压缩率更高,我们希望送到熵编码(以行程编码为例)的“像素串”,是一串含有很多 0,并且最好连续为 0 的“像素串”。为了达到这个目标,我们先通过帧内预测或者帧间预测去除空间冗余和时间冗余,从而得到一个像素值相比编码块小很多的残差块。之后我们再通过 DCT 变换将低频和高频信息分离开来得到变换块,然后再对变换块的系数做量化。由于高频系数通常比较小,很容易量化为 0,同时人眼对高频信息不太敏感,这样我们就得到了一串含有很多个 0,大多数情况下是一串含有连续 0 的“像素串”,并且人的观感还不会太明显。这样,最后熵编码就能把图像压缩成比较小的数据,以此达到视频压缩的目的。这就是视频编码的原理。
总结
本文主要讲了视频编码的必要性,以及视频编码的原理。视频编码主要分为熵编码、预测、DCT 变换和量化这几个步骤。
- 熵编码(以行程编码为例):视频编码中真正实现“压缩”的步骤,主要去除信息熵冗余。在出现连续多个 0 像素的时候压缩率会更高。
- 帧内预测:为了提高熵编码的压缩率,先将当前编码块的相邻块像素经过帧内预测算法得到帧内预测块,再用当前编码块减去帧内预测块得到残差块,从而去掉空间冗余。
- 帧间预测:类似于帧内预测,在已经编码完成的帧中,先通过运动搜索得到帧间预测块,再与编码块相减得到残差块,从而去除时间冗余。
- DCT
变换和量化:将残差块变换到频域,分离高频和低频信息。由于高频信息数量多但大小相对较小,又人眼对高频信息相对不敏感,我们利用这个特点,使用
QStep 对 DCT 系数进行量化,将大部分高频信息量化为 0,达到去除视觉冗余的目的。
参考:编码原理
相关文章:
H264视频编码原理
说到视频,我们首先想到的可能就是占内存。我们知道一个视频是由一连串图像序列组成的,视频中图像一般是 YUV 格式。假设有一个电影视频,分辨率是 1080P,帧率是 25fps,并且时长是 2 小时,如果不做视频压缩的…...
UDP实现群聊
代码: import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.net.*; import java.io.IOException; import java.lang.String;public class liaotian extends JFrame{private static final int DEFAULT_PORT8899;private JLabel stateLB…...
服务器部署网易开源TTS | EmotiVoice部署教程
一、环境 ubuntu 20.04 python 3.8 cuda 11.8二、部署 1、docker方式部署 1.1、安装docker 如何安装docker,可以参考这篇文章 1.2、拉取镜像 docker run -dp 127.0.0.1:8501:8501 syq163/emoti-voice:latest2、完整安装 安装python依赖 conda create -n Emo…...
贪心算法和动态规划
目录 一、简介 二、贪心算法案例:活动选择问题 1.原理介绍 三、动态规划案例:背包问题 1.原理介绍 四、贪心算法与动态规划的区别 五、总结 作者其他文章链接 正则表达式-CSDN博客 深入理解HashMap:Java中的键值对存储利器-CSDN博客…...
jsp 设备预约管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
一、源码特点 JSP 设备预约管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0…...
Python:核心知识点整理大全10-笔记
目录 5.4 使用 if 语句处理列表 5.4.1 检查特殊元素 toppings.py 5.4.2 确定列表不是空的 5.4.3 使用多个列表 5.5 设置 if 语句的格式 5.6 小结 第6章 字 典 6.1 一个简单的字典 alien.py 6.2 使用字典 6.2.1 访问字典中的值 6.2.2 添加键—值对 6.2.3 先创建一…...
Hive数据库系列--Hive数据类型/Hive字段类型/Hive类型转换
文章目录 一、Hive数据类型1.1、数值类型1.2、字符类型1.3、日期时间类型1.4、其他类型1.5、集合数据类型1.5.1、Struct举例1.5.2、Array举例1.5.3、Map举例 二、数据类型转换2.1、隐式转换2.2、显示转换 三、字段类型的使用3.1、DECIMAL(precision,scale) 本章主要…...
在Spring Cloud中使用组件Ribbon和Feign,并分别创建子模块注册到Eureka中去
ok,在上篇文章中我们讲了在Spring cloud中使用Zuul网关,这篇文章我们将Spring Cloud的五大核心组件的Ribbon和Feign分别创建一个微服务模块。 题外话,本篇博客就是配置子模块,或者说是微服务,然后将微服务正式启动之前…...
(JAVA)-缓冲流
缓冲流能高效的读取数据 缓冲流底层自带了8192的缓冲区提高性能,他在原有的流上进行了包装,加上了缓冲效果 原理: 读入时首先会将内存中缓冲区大小的数据读入缓冲区中,接着下次读取直接从缓冲区中读取数据,当缓冲区…...
Autosar UDS-CAN诊断开发02-1(CAN诊断帧格式类型详解、CANFD诊断帧格式类型详解、15765-2(CANTP层)的意义)
目录 前言 CANTP层(15765-2协议)存在的意义 CANTP层(15765-2协议)帧类型详细解读(普通CAN格式) 四种诊断报文类型 单帧SingleFrame(SF) 首帧:FirstFrame(FF) 流控帧:FlowCont…...
swing快速入门(三)
解答一下上一篇关于留下的关于布局管理器的疑问 上一篇 几种常见的布局管理器 看不懂?看不懂没关系,这篇是概念篇,大概了解一下就行~ 1.FlowLayout(流式布局):按照从左到右、从上到下的顺序依次排列组件。…...
Swagger PHP Thinkphp 接口文档
安装 1. 安装依赖 composer require zircote/swagger-php 2. 下载Swagger UI git clone https://github.com/swagger-api/swagger-ui.git 3. 复制下载好的Swagger UI 中的dist目录到public目录中,修改目录名称 cp -rf swagger-ui/dist /home/htdocs/public/ m…...
12.9每日一题(备战蓝桥杯循环结构)
12.9每日一题(备战蓝桥杯循环结构) 题目 2165: 求平均年龄题目描述输入输出样例输入样例输出来源/分类 题解 2165: 求平均年龄题目 2166: 均值题目描述输入输出样例输入样例输出来源/分类 题解 2166: 均值题目 2167: 求整数的和与均值题目描述输入输出样…...
与时代共进退
还记得当初自己为什么选择计算机? 当初你问我为什么选择计算机,我笑着回答:“因为我梦想成为神奇的码农!我想像编织魔法一样编写程序,创造出炫酷的虚拟世界!”谁知道,我刚入门的那天࿰…...
Python 云服务器应用,Https,定时重启
Python 云服务器应用,Https,定时重启 环境搭建Python模块模块导入生成Flask实例GET处理启动服务器打开网页验证 GET接入证书 支持https申请证书下载证书保留 xxx.crt 和 xxx.key文件就可以了 copy到python项目目录ssl_context 配置 宝塔面板操作在www目录下新建python工作目录在…...
pytorch 笔记:dist 和 cdist
1 dist 1.1 基本使用方法 torch.dist(input, other, p2) 计算两个Tensor之间的p-范数 1.2 主要参数 input输入张量other另一个输入张量p范数 input 和 other的形状需要是可广播的 1.3 举例 import torchxtorch.randn(4) x #tensor([ 1.2698, -0.1209, 0.0462, -1.3271…...
Java的List中的各种浅拷贝和深拷贝问题
先来看一组代码 public class Temp{public static void main(String[] args) {List<Integer> list new ArrayList<>();list.add(1);list.add(2);list.add(3);List<Integer> temp list;list.add(4);System.out.println(list.toString());System.out.print…...
20231207_最新已测_Centos7.4安装nginx1.24.0_安装详细步骤---Linux工作笔记066
以前安装的太模糊了,干脆重新写一个: 1.首先下载对应的nginx-1.24.0.tar.gz安装文件 2.然后: 去执行命令 安装依赖 yum install -y gcc yum install -y pcre pcre-devel yum install -y zlib zlib-devel yum install -y openssl openssl-devel 3.然后:去解压 tar -zxvf ngi…...
前端知识笔记(二十六)———React如何像Vue一样将css和js写在同一文件
如果想在React中想要像Vue一样把css和js写到一个文件中,可以使用CSS-in-JS。 使用CSS-in-JS 下载 npm i styled-components使用 就像写scss一样,不过需要声明元素的类型 基本语法及展示如下 import styled from "styled-components"expor…...
Photoshop Circular Text
Ctrl N 新增 现学现卖...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
