H264解码器实现-帧间预测

前言

本文所说的帧间预测是指根据当前预测块的MV向量和预测所需的参考块数据,计算出当前预测块的预测像素值的过程。该过程得到的预测像素值经过运动补偿后(与反变换量化之后得到的残差像素值相加)可以得到滤波前的重建像素值。

原理说明

一般来说,只要知道MV向量,就可以将MV向量所指向参考块的像素值作为当前块的预测值,但是H264采用1/4亚像素级运动估计方法,MV向量并不是以整数像素点为单位的,而是以1/4像素为单位的。这也就意味着我们需要将MV指向的参考块进行像素内插操作,进而找到MV向量具体指向的位置,同时得到预测像素值。
假设当前4x4块的参考关系如下图所示:
图1:4x4块参考关系示意图
从图中可以看出当前块的左上角P0的坐标为(128,128)MV向量为(-19,-5)参考块左上角G的坐标为(123,126)。由于MV向量以1/4像素为单位,我们可以先将该MV向量做整像素单位对齐,找到该整数像素点的坐标,对齐后的MV向量我们暂时称为MVz,然后计算实际参考的分像素点相对与该整数像素点的向量MVf,可以看出MVf = MV - MVz。如下图所示,MVz为(-20,-8),刚好指向参考块的G像素,MVf为(1,3)指向参考块做了像素内插后的p像素点。同理可得P4的参考的整像素点为M点,实际参考点同样也是像素内插后相对与M点的(1,3)位置,其他像素点依次类推。
4x4块像素内插
图中的a,b,c,d,e,f,g,h,i,j,k,m,n,p,q,r,s等小写字母表示的都是通过像素内插得到的分像素点。那么像素内插过程是如何的呢,H264标准协议第8.4.2.2.1章节有详细描述,这里简单说明一下原理,请看下图:
像素内插示意图
图中灰色方块代表整数像素点,其余为分数像素点。像素内插遵循以下规则:
(1)首先生成参考图像亮度成分半像素像素。
半像素点(如b,h,m,s)通过对相应整像素点进行6 抽头滤波得出,权重为(1/32 ,-5/32 ,5/8, 5/8, -5/32, 1/32)。例如b 像素点计算如下:

b = round((E - 5F + 20G + 20H - 5I + J) / 32)

类似的,h 由A、C、G、M、R、T 滤波得出。

h = round((A − 5C + 20G + 20M − 5R + T) / 32) 

一旦邻近(垂直或水平方向)整像素点的所有像素都计算出,剩余的半像素点便可以通过对6 个垂直或水平方向的半像素点滤波而得。例如,j 由cc, dd, h,m,ee,ff 滤波或者由aa,bb,b,s,gg,hh得出,两者计算出的结果应该是相同的。

j = round((cc − 5dd + 20h + 20m − 5ee + ff) / 1024)
//或者
j = round((aa − 5bb + 20b + 20s − 5gg + hh) / 1024)

(2)半像素点计算出来以后,1/4 像素点就可通过线性内插得出.。
1/4像素点内插

1/4 像素点(如a,c, i, k, d, f, n, q)由邻近像素内插而得,如a像素计算如下:

a = round((G + b) / 2)

注意有4个特殊的1/4像素点e,g,p,r,他们是使用对角线上的两个半像素点得到,如:

e = round((h + b) / 2)

至此像素内插的过程就全部介绍完了。

代码实现

帧间预测需要使用三个输入数据:
(1)当前预测块的MV向量。
(2)当前预测块的参考块的像素重建数据。
(3)当前预测块左上角P0位置坐标。
输出:
(1)当前预测块的预测值。

下列代码展示了当前4x4块左上角P0坐标为(4,4),运动向量MV为(-7,-5)的帧间预测过程。
根据原理可知:
MVz = (-8,-8),MVf = (1,3),因此P0参考的像素点坐标G为(2,2)根据这些信息我们可以计算出整个当前4x4块的预测像素值。

#include <stdio.h>
#include <stdlib.h>

#define PIC_WIDTH 16 //图像宽度
#define PIC_HIGHT 16 //图像高度

#define BLK_WIDTH 4 //当前块宽度
#define BLK_HIGHT 4 //当前块高度

#define REF_POS_X 2 //参考块左上角像素点G的横坐标
#define REF_POS_Y 2 //参考块左上角像素点G的纵坐标

#define MAX_PIX_VALUE 32 //像素点最大像素值

typedef char imgpel;

static inline int imin(int a, int b)
{
  return ((a) < (b)) ? (a) : (b);
}

static inline int imax(int a, int b)
{
  return ((a) > (b)) ? (a) : (b);
}

static inline int iClip1(int high, int x)
{
  x = imax(x, 0);
  x = imin(x, high);

  return x;
}

/* 该函数以MVf的值命名,因为所有MVf相同的帧内差值过程都是一样的,只是使用的整数像素点不一样而已 */
/* 参数说明:                                                                            */
/* (1) block: 当前块左上角P0像素地址                                                      */
/* (2) cur_imgY: 参考块左上角G像素相对与整幅图像所在行地址                                  */
/* (3) block_size_y: 当前块高度                                                          */
/* (4) block_size_y: 当前块宽度                                                          */
/* (5) x_pos: 参考块左上角G像素的横坐标x                                                  */
/* (6) shift_x: 整个图像Buffer的宽度,注意这里不一定和图像宽度一致                          */
/* (7) max_imgpel_value: 像素值最大值                                                    */
static void get_luma_13(imgpel **block, imgpel **cur_imgY, int block_size_y, int block_size_x, int x_pos, int shift_x, int max_imgpel_value)
{
  /* Diagonal interpolation */
  int i, j;
  imgpel *p0, *p1, *p2, *p3, *p4, *p5;
  imgpel *orig_line;
  int result;

  int jj = 1;

  /* 由于MVf(1,3)表示分像素p位置,根据原理可知它是由对角线上两个半像素计算而来 */
  /* 因此这里先横向内插 */
  for (j = 0; j < block_size_y; j++)
  {
    p0 = &cur_imgY[jj++][x_pos - 2];
    p1 = p0 + 1;
    p2 = p1 + 1;
    p3 = p2 + 1;
    p4 = p3 + 1;
    p5 = p4 + 1;

    //printf("ver %02d %02d %02d %02d %02d %02d\n", *p0, *p1, *p2, *p3, *p4, *p5);
    orig_line = block[j];

    for (i = 0; i < block_size_x; i++)
    {
      //计算s分像素点,公式见原理说明
      result  = (*(p0++) + *(p5++)) - 5 * (*(p1++) + *(p4++)) + 20 * (*(p2++) + *(p3++));
      *(orig_line++) = (imgpel) iClip1(max_imgpel_value, ((result + 16)>>5));
    }
  }

  /* 这里开始进行垂直方向内插 */
  p0 = &(cur_imgY[-2][x_pos]);
  for (j = 0; j < block_size_y; j++)
  {
    p1 = p0 + shift_x;
    p2 = p1 + shift_x;
    p3 = p2 + shift_x;
    p4 = p3 + shift_x;
    p5 = p4 + shift_x;
    orig_line = block[j];
    //printf("hor %02d %02d %02d %02d %02d %02d\n", *p0, *p1, *p2, *p3, *p4, *p5);

    for (i = 0; i < block_size_x; i++)
    {
      //计算h分像素点,公式见原理说明
      result  = (*(p0++) + *(p5++)) - 5 * (*(p1++) + *(p4++)) + 20 * (*(p2++) + *(p3++));
      //计算p分像素点,注意这里将h的取平均操作放在这里了
      *orig_line = (imgpel) ((*orig_line + iClip1(max_imgpel_value, ((result + 16) >> 5)) + 1) >> 1);
      orig_line++;
    }
    p0 = p1 - block_size_x ;
  }
}

int main(int argc, char*argv[])
{
        int i;
        int j;
        char **img_y;
        char *img_y_vir;
        char *cur_block_y_vir;
        char **cur_block_y;

		// 分配参考图像空间,这样分配可以使用二维数组访问方式来访问数据,如img_y[0][0]
        img_y = (char **)malloc(sizeof(char*) * PIC_HIGHT);
        img_y_vir = (char*)malloc(PIC_WIDTH * PIC_HIGHT);
        for(i = 0; i < PIC_HIGHT; i++) {
                img_y[i] = img_y_vir + i * PIC_WIDTH;
        }

		// 分配当前块空间,这样分配可以使用二维数组访问方式来访问数据,如cur_block_y[0][0]
        cur_block_y = (char **)malloc(sizeof(char*) * BLK_HIGHT);
        cur_block_y_vir = (char*)malloc(BLK_WIDTH * BLK_HIGHT);
        for(i = 0; i < BLK_HIGHT; i++) {
                cur_block_y[i] = cur_block_y_vir + i * BLK_WIDTH;
        }

		// 这里使用随机函数随机生成参考图像数据
		printf("ref pic data:\n");
        for(i = 0; i < PIC_HIGHT; i++) {
                for(j = 0; j < PIC_WIDTH; j++) {
                        img_y[i][j] =  rand() % 32;
                        printf("%02d ", img_y[i][j]);
                }
                printf("\n");
        }

		//开始针对MVf为(1,3)的情况进行帧间预测,注意如果MV计算出来的MVf是其他值,这里暂不支持。
		printf("ref left top point G is (%d, %d)\n", REF_POS_X, REF_POS_Y);
        get_luma_13(cur_block_y, &img_y[REF_POS_X], BLK_HIGHT, BLK_WIDTH, REF_POS_Y, PIC_WIDTH, MAX_PIX_VALUE);

		printf("cur block data:\n");
        for(i = 0; i < BLK_HIGHT; i++) {
                for(j = 0; j < BLK_WIDTH; j++) {
                        printf("%02d ", cur_block_y[i][j]);
                }
                printf("\n");
        }

        free(img_y);
        free(img_y_vir);
        free(cur_block_y);
        free(cur_block_y_vir);

        return 0;
}

运行结果:

ref pic data:
07 06 09 19 17 31 10 12 09 13 26 11 18 27 03 06
28 02 20 24 27 08 07 13 22 26 14 03 19 31 09 26
06 18 13 23 17 24 03 26 05 29 05 23 24 09 30 20
11 18 13 06 27 20 20 17 14 02 20 01 01 29 28 07
16 09 30 01 01 01 28 07 30 01 30 23 10 28 11 22
15 24 28 10 12 16 27 27 18 15 28 20 12 24 27 28
02 26 30 03 27 26 10 26 27 09 17 06 05 28 28 20
21 24 30 01 09 25 28 27 08 25 15 21 17 11 17 19
05 15 23 00 09 01 26 05 10 11 11 16 08 07 04 29
31 03 30 08 28 27 04 05 20 19 26 05 30 11 25 03
27 16 04 04 17 30 09 28 10 20 12 18 27 16 15 27
19 13 03 16 08 07 21 28 27 15 02 25 26 27 29 21
11 01 26 28 31 03 24 09 24 04 27 19 21 10 14 08
24 18 24 00 25 13 29 20 28 31 14 23 26 11 12 05
12 06 01 11 10 26 21 02 30 16 21 19 27 04 28 19
22 20 19 15 02 16 04 30 15 18 21 09 29 02 14 09
ref left top point G is (2, 2)
cur block data:
08 16 25 24
20 00 08 13
25 07 07 14
21 10 28 19

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/447051.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

<Linux> 初识线程

目录 前言&#xff1a; 一、什么是线程 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;线程理解 &#xff08;三&#xff09;线程与进程的关系 &#xff08;四&#xff09;简单实用线程 &#xff08;五&#xff09;重谈虚拟地址空间 1. 页表的大小 2…

ARMv8/ARMv9架构入门到精通-学习方法

目录 1、学习ARM基础知识2、学习ARM异常(中断)3、学习MMU4、学习Cache5、学习Trustzone和安全架构6、学习ARM架构和各类IP推荐 本文转自 周贺贺&#xff0c;baron&#xff0c;代码改变世界ctw&#xff0c;Arm精选&#xff0c; 资深安全架构专家&#xff0c;11年手机安全/SOC底层…

WorldView卫星遥感影像数据/米级分辨率遥感影像

目前世界上最常用的高分辨率卫星影像莫过于WORLDVIEW系列了&#xff0c;在卫星遥感圈内可谓大名鼎鼎&#xff0c;不仅具有超高的分辨率还具有其他高分辨卫星所不具有的8波段&#xff0c;风光无限。在分辨率方面目前只有WORLDVIEW3和WORLDVIEW4能够达到0.3米的分辨率&#xff0c…

【神经网络与深度学习】LSTM(Long Short-Term Memory)神经网络模型

概述 LSTM&#xff08;Long Short-Term Memory&#xff09;是一种特殊的循环神经网络&#xff08;RNN&#xff09;结构&#xff0c;通常被用于处理和学习时间序列数据。因此&#xff0c;LSTM属于深度学习领域中的一种神经网络模型。 在深度学习中&#xff0c;LSTM被广泛应用于…

站库分离技术--反向代理技术-雷池云WAF-给自己搭建一个安全点的网站

文章目录 概要整体架构流程技术名词解释技术细节ssh-ubuntu服务器docker-映射-链接-通信nginx反代mysql设置数据库新密码 小结我的mysql映射目录我的wordpress映射目录 成果展示 概要 新买了一个云服务器&#xff0c;想搭建一个站库分离的wordpress为主的网站&#xff0c;采用d…

数据结构:图及相关算法讲解

图 1.图的基本概念2. 图的存储结构2.1邻接矩阵2.2邻接表2.3两种实现的比较 3.图的遍历3.1 图的广度优先遍历3.2 图的深度优先遍历 4.最小生成树4.1 Kruskal算法4.2 Prim算法4.3 两个算法比较 5.最短路径5.1两个抽象存储5.2单源最短路径--Dijkstra算法5.3单源最短路径--Bellman-…

CentOS 7安装MySQL及常见问题与解决方案(含JDBC示例与错误处理)

引言 MySQL是一个流行的开源关系型数据库管理系统&#xff0c;广泛应用于各种业务场景。在CentOS 7上安装MySQL后&#xff0c;我们通常需要使用JDBC&#xff08;Java Database Connectivity&#xff09;连接MySQL进行后端操作。 目录 引言 CentOS 7安装MySQL 使用JDBC连接My…

AI入门笔记(四)

深度学习是人工智能的一种实现方法。本文我将学习到的关于深度学习的代表卷积神经网络的数学结构分享给大家。 深度学习是重叠了很多层的隐藏层&#xff08;中间层&#xff09;的神经网络。我们以一个例题为例。 建立一个卷积神经网络&#xff0c;用来识别通过 66 像素的图像读…

基于VSCode安装Node.js开发环境

根据官网介绍&#xff0c;Node.js 是一个免费的、开源的、跨平台的JavaScript实时运行环境&#xff0c;允许开发人员在浏览器之外编写命令行工具和服务器端脚本. Node.js框架由于是采用JavaScript语法进行调用的&#xff0c;因此Node.js环境除了用来编写调试Node.js代码&#…

mybatis基础操作(三)

动态sql 通过动态sql实现多条件查询&#xff0c;这里以查询为例&#xff0c;实现动态sql的书写。 创建members表 创建表并插入数据&#xff1a; create table members (member_id int (11),member_nick varchar (60),member_gender char (15),member_age int (11),member_c…

视图【MySQL】

文章目录 概念操作视图创建视图查询视图更新视图删除视图 视图规则和限制 概念 MySQL 中的视图&#xff08;View&#xff09;是一个虚拟表&#xff0c;其内容由查询定义。视图本身不包含数据&#xff0c;这些数据是从一个或多个实际表中派生出来的&#xff0c;通过执行视图定义…

简单了解TCP/IP四层模型

什么是计算机网络&#xff1f; 计算机网络我们可以理解为一个巨大的城市地图&#xff0c;我们想从A地前往B地&#xff0c;其中要走的路、要避开的问题都交给计算机网络解决&#xff0c;直到我们可以正常的到达目的地&#xff0c;那么我们会把其中的过程抽象成一个网络模型&…

练习01-登录注册(简单)

一、用户登录/注册实现 综合前面学的知识来实现简单的注册登录功能 1.准备工作 注册登录页面 数据库&#xff0c;数据表 mybatis 坐标引入&#xff0c;MySQL驱动 配置 映射文件 用户实体类 Servlet代码 2.页面 不想手写的可以看博主IT黄大大【带源码】 【炫酷登录界…

吴恩达机器学习-可选实验室:可选实验:使用逻辑回归进行分类(Classification using Logistic Regression)

在本实验中&#xff0c;您将对比回归和分类。 import numpy as np %matplotlib widget import matplotlib.pyplot as plt from lab_utils_common import dlc, plot_data from plt_one_addpt_onclick import plt_one_addpt_onclick plt.style.use(./deeplearning.mplstyle)jupy…

机器学习——PPO补充

On-policy vs Off-policy 今天跟环境互动&#xff0c;并学习是on-policy 只是在旁边看&#xff0c;就是Off-policy 从p中选q个重要的&#xff0c;需要加一个weight p(x)/q(x) p和q不能相差太多 采样数太少导致分布差很多&#xff0c;导致weight发生变化 On-Policy -&g…

STM32F103 CubeMX ADC 驱动 PS2游戏摇杆控制杆传感器模块

STM32F103 CubeMX ADC 驱动 PS2游戏摇杆控制杆传感器模块 1. 工程配置1.1 配置debug口1.2 配置时钟1.3 配置ADC1.4 配置串口1.5 配置时钟1.6 生成工程 2. 代码编写2.1 串口代码2.2 ADC读取数据的代码 1. 工程配置 1.1 配置debug口 1.2 配置时钟 1.3 配置ADC 1.4 配置串口 1.5 …

小迪安全37WEB 攻防-通用漏洞XSS 跨站权限维持钓鱼捆绑浏览器漏洞

#XSS跨站系列内容:1. XSS跨站-原理&分类&手法 XSS跨站-探针&利用&审计XSS跨站另类攻击手法利用 XSS跨站-防御修复&绕过策略 #知识点&#xff1a; 1、XSS 跨站-另类攻击手法分类 2、XSS 跨站-权限维持&钓鱼&浏览器等 1、原理 指攻击者利用…

JavaWeb-Maven基础

Maven是专门用于管理和构建Java项目的工具&#xff0c;是 Apache 下的一个纯 Java 开发的开源项目&#xff0c;基于项目对象模型&#xff08;POM&#xff09;概念。先来学习一下Maven基础&#xff0c;等后面学完开发框架后再学Maven高级&#xff0c;这次的内容如下 一、概述 …

leetcode 热题 100_搜索二维矩阵

题解一&#xff1a; 二叉搜索树&#xff1a;从矩阵右上角观察&#xff0c;结构类似二叉搜索树&#xff0c;因此可以用类似的解法来做。具体做法是双指针从右上角开始&#xff0c;向左下角逐步搜索&#xff0c;如果当前值比目标值大&#xff0c;则向下移动&#xff0c;如果当前值…

体系班第十三节

1判断完全二叉树递归做法 有四种情况&#xff1a;1 左树完全&#xff0c;右数满&#xff0c;且左高为右高加一 2左满 &#xff0c;右满&#xff0c;左高为右高加一 3左满&#xff0c;右完全&#xff0c;左右高相等 4左右均满且高相等 #include<iostream> #include&l…