用X265对HEVC/H.265视频进行隐写steganography---二、基本思路

用X265对HEVC/H.265视频进行隐写steganography---二、基本思路

概述

继续上一篇。主要写一下HEVC视频隐写实现的主体思路。

1.HEVC视频隐写算法实现的基本思路

从代码角度,可以把目前视频隐写算法的实现大致分为两大类。一类以LSB为代表,特点是可以只使用一个编码器完成嵌入,过程基本是隐写一个数值,在视频对应载体里嵌入一个数值,载体前后不关联。

以下图为例,比如将该视频前9个帧内预测模式作为载体,其数值按z字形顺序排序是:16.17.12.13.23.24.27.16.1。如果一个基本的奇偶替换算法,其载体对应的二进制比特即是:0,1,0,1,1,0,1,0,1。如果密文是,0,0,0,1,1,0,1,0,1。相当于只需要修改第二个17这个模式,而且不影响前后载体,那么这样就是个典型的第一种隐写算法实现。即只需要在编码器编码到第二个17的时候修改即可。

在这里插入图片描述
然而这种算法实现过程并不能适用于目前大多数的HEVC视频隐写算法。原因主要是,一、一般算法都会考虑前后的载体影响,采用例如STC等方式进行隐写,如果只在一个编码过程中修改,比如编码17的时候,这时你并不知道12,13及以后的编码信息,就无法利用之后的编码信息进行隐写(如果在一帧编码后,直接在熵编码前进行修改替换,则会导致编码过程中的参数,如预测值,残差值等,与最终载体,如运动向量,DCT/DST,帧内模式等,的数值不匹配,最终隐写视频将产生极大的失真)。二,HEVC是递归的编码过程,其递归过程也限制了编码一个隐写一个这种实现流程的实用性(如图中最后一个帧内模式为1的块,递归过程会先算其小分块的编码,因为无法知道哪一层次分块会最终保留,因此如果隐写了小分块,当1这一大块覆盖小分块时,需要将原来隐写的密文退格,实现起来比较麻烦)。

因此,对于目前的隐写算法,主要采用第二类实现思路。代码实现过程的基本是:一、准备A编码器,负责提取对应载体和对应的编码信息。二、准备B隐写程序,负责根据A的信息和具体的算法,修改或生成载体,从而嵌入密文。三、准备C编码器,将B程序的带密载体隐写入视频(比如要将第一个16修改为17,则在C编码器递归的预测过程中,让编码器只保留17的编码信息,如对应的残差,预测值等等均匹配。这里面主要涉及如何在递归过程保留想要的递归层次的信息)。基本隐写嵌入流程如下图所示。(提取过程较为简单就不画图了,两种方式都是265视频文件进入解码器,输出载体信息,将载体信息输入隐写提取程序,得到密文即可)
在这里插入图片描述

还是以第一个图为例,载体对应的二进制比特是:0,1,0,1,1,0,1,0,1。第一个A编码器将所有的帧内模式提取并保存,并同时输出如分块深度,skip等信息。如果是个stc嵌入,那么在B程序里读取,并计算代价函数等一系列参数,最终生成带密载体,可能是:0,1,1,0,1,0,1,1,0。如果只修改帧内模式的话,将此带密载体和之前的信息一起送入C编码器,在编码视频过程中隐写,最终完成嵌入。

2.A编码器的基本思路

首先,HEVC编码一帧的函数时frameencoder.cpp里面的void FrameEncoder::compressFrame()这个函数。这个函数流程基本是:1、初始化。2、并行按行、CTU编码。3、熵编码。

需要涉及的代码部分是第2部分和第3部分。思路是在第2部分的过程中,将信息保存到某结构体,并且在完成一帧编码后,在第3部分熵编码之前输出该帧的所有需要的信息。如果算法不涉及一些X265自身没保存的信息,那么可以直接在第三部分熵编码前输出对应的变量即可。

第2部分代码开始可以查找以下代码注释:

/* Analyze CTU rows, most of the hard work is done here. Frame is
* compressed in a wave-front pattern if WPP is enabled. Row based loop
* filters runs behind the CTU compression and reconstruction */

第3部分可以查找以下代码及注释:

// finish encode of each CTU row, only required when SAO is enabled
if (m_param->bEnableSAO)
encodeSlice(0);

第3部分前输出用到的常见变量可在cuda.h里面查看。举例来说,(num开头变量仅是个人用于循环指针数组所用,不是X265原有的)

slice->m_sliceType 该帧类型
m_frame->m_encData->m_picCTU[numctu].m_lumaIntraDir[numintra]; 帧内模式
m_frame->m_encData->m_picCTU[numctu].m_log2CUSize[numintra];CU大小
m_frame->m_encData->m_picCTU[numctu].m_partSize[numintra];PU结构

如果需要一些例如RDCOST之类的额外信息,基本思路是在自己建立一个二维数组,如rd[i][j],i是保存每一个CU深度下的信息,j则是保存对应深度下的信息。在第2部分里面保存编码器临时变量进对应位置。然后通过在熵编码前,读取最终编码的CU深度,对数组进行取值,决定输出哪个深度i下的信息,如(保存了最终保留的PU中35种帧内模式每个模式的最优cost,对于一个ctu,j的范围是255*35)

rd[m_frame->m_encData->m_picCTU[numctu].m_cuDepth[numintra]][numintra* 35 +i];

这个rd数组可放在一个以CTU数量为长度的自定义结构体指针中,即可实现全帧遍历,即参考x265里m_picCTU的定义方式(虽然它是class,但自己定义的变量不需要函数,struct即可),自己定义一个的结构体指针即可

以帧内隐写为例的话,无论是DCT/DST还是帧内模式,主要修改函数在compressIntraCU(递归函数),estIntraPredQT等地方。对于目前大部分算法,A编码器基本只需要在这些部分将对应需要的临时变量保存进自己的二维数组,最终输出即可。而这些函数也是最终C编码器覆盖修改的主要函数,建议仔细阅读和理解过程。

B程序可按具体算法进行实现,下一篇主要讲C编码器的主要思路。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值