三、使用HM进行简单的视频隐写

  • 前言
  • 一、实验环境
  • 二、实验思路
  • 三、实验过程
    • 3.1 提取原始载体
    • 3.2 使用LSB隐写算法进行隐写
    • 3.3 放回含密载体
    • 3.4 提取含密载体
    • 3.5 使用LSB提取算法进行比对
  • 四、碰到的问题以及解决思路
    • 4.1 块的结构体发生变化
      • 4.1.1 产生的原因
      • 4.1.2 解决方法
    • 4.2 一个有趣的现象
      • 4.2.1 产生的原因
    • 4.3 视频产生较大失真
      • 4.3.1 产生的原因
      • 4.3.2 解决方法
  • 五、总结

前言

在前两篇博客中介绍了基于HEVC/H.265视频编解码器的视频隐写的基本思路,本文将详细记录我在使用HM做视频隐写的简单实验中遇到的各种问题和解决的思路等。

本次实验参考的博客是dy学长的两篇博客:第一篇博客、第二篇博客

感谢dy学长、另外一位网友和徐老师对我实验过程中的帮助和解答。


一、实验环境

在Windows10 x64的笔记本上,用VS2010对HM12.0进行实验。

HM12.0网盘分享(提取码:zdeu)


二、实验思路

本次实验是我对视频隐写的第一个实验,意在熟悉视频隐写的整个流程,以便将来进行其它的视频隐写实验。所以,这次实验选择了比较简单的对4x4块使用LSB算法,对帧内预测过程中的IPM进行隐写,且不论最终实验评价指标(即PSNR、SSIM等)的优劣。

本次实验是在上一篇博客的基础上进行的,按照上一篇博客的思路,将使用三个HM编解码器:
1、在第一个编码器HM_1中提取出所有的原始载体original_cover涉及到的相关信息——深度depth、划分模式partitionsize和original_IPM值——并保存下来;
2、在外部编写LSB隐写算法,对4x4块的original_IPM进行隐写。这里需要注意的是:并不是所有的4x4块的IPM都适合隐写,对IPM=0、1和34的块,不进行隐写——IPM为0或1时,块是没有预测方向的角度的,此时如若进行隐写修改,则将导致从无角度变成有角度,产生较大失真。IPM为34时,使用LSB进行隐写,若secret_msg==“1”,会使得隐写后的stego_IPM变成35,而在HM中,IPM的取值范围是[0,34],导致越界。为避免这些影响,简单地将这三种情况排除,不进行隐写。最终,得到经过LSB算法隐写好后的stego_IPM;
3、在第二个编码器HM_2中将stego_IPM放回,并编码视频,得到嵌密视频。这里需要注意的是:HM_1和HM_2的视频参数应该是一模一样的;
4、在第三个解码器HM_3中对嵌密视频进行提取,提取所有隐写后的载体以及相关信息——深度depth、划分模式partitionsize和extract_IPM值;
5、在外部编写LSB提取算法,对4x4块的IPM进行提取,得到extract_msg,并与original_msg进行比对;


三、实验过程

3.1 提取原始载体

使用码流分析器我们可以得知:8x8的块与4x4的块它们的深度都为3,而其它更大的块的深度为2、1、0等,8x8的块与4x4的块之间的差别在于,8x8的块的划分模式是2Nx2N的,而4x4的块的划分模式是NxN的,而其它更大的块的划分模式都是2Nx2N。所以,对于所有深度depth=3、划分模式partitionsize=NxN的块,就是4x4的块,即就是我们要找的原始载体original_cover。

接下来查看TComDataCU.h中的TComDataCU类,这个类中定义了一个CU的各种属性,如该CU在Slice中的位置、Z-order、横坐标、纵坐标、深度、PU类型、编码模式和帧内预测模式等等。(TComDataCU类解释)

class TComDataCU
{private:// 绝大部分已经省略UInt          m_uiCUAddr;           ///< CU address in a slice                                 在 slice 中的位置,用的是 raster 扫描(从左到右,从上到下)UInt          m_uiAbsIdxInLCU;      ///< absolute address in a CU. It's Z scan order           当前 CU 在 LCU 中的位置,位置用 Z 扫描顺序UInt          m_uiCUPelX;           ///< CU position in a pixel (X)                            CU 的横坐标UInt          m_uiCUPelY;           ///< CU position in a pixel (Y)                            CU 的纵坐标UChar*        getDepth              ()                        { return m_puhDepth;        } //返回深度信息UChar         getDepth              ( UInt uiIdx )            { return m_puhDepth[uiIdx]; } //返回深度信息Char*         getPartitionSize      ()                        { return m_pePartSize;        } //返回划分模式信息PartSize      getPartitionSize      ( UInt uiIdx )            { return static_cast<PartSize>( m_pePartSize[uiIdx] ); } //返回划分模式信息UChar*        getLumaIntraDir       ()                        { return m_puhLumaIntraDir;           }//返回帧内预测模式UChar         getLumaIntraDir       ( UInt uiIdx )            { return m_puhLumaIntraDir[uiIdx];    }//返回帧内预测模式
};
// TComDataCU类定义结束

再看一下PartSize类,这是一个枚举类型的类,其中2Nx2N的值为0,NxN的值为3。

/// supported partition shape
enum PartSize
{SIZE_2Nx2N,           ///< symmetric motion partition,  2Nx2N                     0SIZE_2NxN,            ///< symmetric motion partition,  2Nx N                     1SIZE_Nx2N,            ///< symmetric motion partition,   Nx2N                     2SIZE_NxN,             ///< symmetric motion partition,   Nx N                     3SIZE_2NxnU,           ///< asymmetric motion partition, 2Nx( N/2) + 2Nx(3N/2)     4SIZE_2NxnD,           ///< asymmetric motion partition, 2Nx(3N/2) + 2Nx( N/2)     5SIZE_nLx2N,           ///< asymmetric motion partition, ( N/2)x2N + (3N/2)x2N     6SIZE_nRx2N,           ///< asymmetric motion partition, (3N/2)x2N + ( N/2)x2N     7SIZE_NONE = 15        //                                                          15,指的是没有了
};

所以,把所有块的深度depth、划分模式partitionsize和预测模式IPM保存下来,那些depth=3&&partitionsize=3的IPM就是可供隐写的载体。

for(int i = 0 ; i < pcCU->getTotalNumPart() ; i ++){// printf("i = %d,预测模式 = %d,深度 = %d,模式 = %d\n", i, pcCU->getLumaIntraDir(i), pcCU->getDepth(i), pcCU->getPartitionSize(i));fout_IPM << int(pcCU->getLumaIntraDir(i)) << ",";fout_depth << int(pcCU->getDepth(i)) << ",";fout_partition << int(pcCU->getPartitionSize(i)) << ",";
}

3.2 使用LSB隐写算法进行隐写

首先需要对所有块进行统计,计算出4x4块的个数,以便确定隐秘信息的长度,来生成隐秘信息original_msg。

# 统计一下4x4的个数
count_4x4_IPM = 0
for i in range(len(depth_num_list)):for j in range(len(depth_num_list[i])):if (depth_num_list[i])[j] == 3 and (partitionsize_num_list[i])[j] == 3:count_4x4_IPM += 1
print("count_4x4_IPM = ", count_4x4_IPM)

根据计算得到的个数count_4x4_IPM,生成隐秘信息original_msg。遍历所有块,对4x4块的合适IPM进行隐写。

# 对所有4x4块隐写
if depth_num_list[i][j] == 3 and partitionsize_num_list[i][j] == 3:msg = original_msg_list[0][idx_msg]# 如果IPM==34或者IPM==1或者IPM==0,则不隐写if IPM == 34 or IPM == 0 or IPM == 1:temp_IPM_list.append(IPM)else:IPM_binary_str = Integer_To_BinaryStr(IPM)for k in range(len(IPM_binary_str)):# msg==1时,LSB=1if msg == "1":IPM_binary_str = IPM_binary_str[:7] + "1"# msg==0时,LSB=0else:IPM_binary_str = IPM_binary_str[:7] + "0"IPM_num_stego = BinaryStr_To_Integer(IPM_binary_str)temp_IPM_list.append(IPM_num_stego)idx_msg += 1
# 不是4x4的块
else:temp_IPM_list.append(IPM)

3.3 放回含密载体

在HM_2中,把stego_IPM读取进来,并赋值给uiBestPUMode,替代它原来的IPM值。

这里需要注意的是:HM_1和HM_2的视频参数信息——例如QP、编码帧数等——应是一模一样的。

//=== update PU data ====
// 20210415周四 对所有块赋值stego_IPM
stego_num = atoi(stego_vectorString[order_num].c_str());// stego_IPM的值
uiBestPUMode = stego_num;
pcCU->setLumaIntraDirSubParts     ( uiBestPUMode, uiPartOffset, uiDepth + uiInitTrDepth ); // 这行代码是它本来就有的
pcCU->copyToPic                   ( uiDepth, uiPU, uiInitTrDepth );

3.4 提取含密载体

配置HM12.0解码端相关信息,按照3.1的方法,把深度depth、划分模式partitionsize和预测模式IPM保存下来。(HM解码端配置教程)

3.5 使用LSB提取算法进行比对

使用LSB的提取算法,把4x4块的合适IPM的LSB提取出来,并与原始的隐秘信息进行比对。

# 5、提取所有4x4的LSB
extract_msg = ""
for i in range(len(extract_IPM_list)):for j in range(len(extract_IPM_list[i])):# 判断是否是4x4块if depth_num_list[i][j] == 3 and partitionsize_num_list[i][j] == 3: # 是4x4块,提取LSBif extract_IPM_list[i][j] == 34 or extract_IPM_list[i][j] == 0 or extract_IPM_list[i][j] == 1: # IPM=34、0、1则不提取continueelse:temp_str = Integer_To_BinaryStr(extract_IPM_list[i][j])if temp_str[-1] == "1":extract_msg += "1"else:extract_msg += "0"else: # 不是4x4块,则继续continue
print("extract_msg = ", extract_msg)# 6、判断extract_msg和original_msg是否一致
count = 0
for i in range(len(extract_msg)):if extract_msg[i] == original_msg_list[0][i]:continueelse:count += 1print(i, extract_msg[i], original_msg_list[0][i])
print("提取正确率 = ", format((1 - count / len(extract_msg)) * 100, '.4f'), "%")

四、碰到的问题以及解决思路

4.1 块的结构体发生变化

在第二个编码器HM_2中,把stego_IPM放回后,编码视频,结果发现隐写后的视频的块结构发生了很大变化——例如4个4x4的块合并成了1个8x8的块——使得4x4块的数量发生了变化,4x4块的位置也发生了变化。这也就意味着,利用LSB算法提取出来的隐秘信息必定是错误的。

4.1.1 产生的原因

由于块的划分选择是一个递归的过程,一个块递归至8x8时,会计算当前划分模式的cost值,并继续向下递归至4个4x4,计算4个4x4划分模式的cost值,最终选择cost值小的那种划分模式。

例如(1016, 752)这个块,它应是4个4x4的块(IPM值分别为13、13、2、11),但是却合并成了1个8x8的块(IPM为0),说明是由于8x8块的cost值小于4个4x4块的cost值之和导致的。

4.1.2 解决方法

参考dy学长的博客,在递归过程中,通过修改cost来控制它的划分模式:判断当前递归层次的深度和划分模式,如果和HM_1中保存下来的该CU的原始深度、原始划分模式一致,则修改cost为-9999.9999。

// 20210415周四 在这里对递归中的depth和partitionsize进行判断,如果符合判断条件,则修改它的cost,使其结构体不发生变化
depth_num = atoi(depth_vectorString[j].c_str());
partitionsize_num = atoi(partitionsize_vectorString[j].c_str());
if(pcCU->getDepth(uiPartOffset) == depth_num && pcCU->getPartitionSize(uiPartOffset) == partitionsize_num){uiPUDistY = 0;uiPUDistC = 0;dPUCost   = -999999.9999;
}

在递归返回过程中,通过设置flag来控制它的划分模式:判断当前递归返回时的深度和划分模式,如果当前层次的信息与原始信息一致,则设置flag=1,并在TEncCU::xCheckBestMode()的判断中新增一个刷新条件“ || flag == 1”。

 // 20210419 判断深度和划分模式是否一致//printf("x = %d,y = %d,depth = %d,partitionsize = %d,z-order = %d\n", rpcTempCU->getCUPelX(), rpcTempCU->getCUPelY(), uiDepth, eSize, rpcTempCU->getZorderIdxInCU());z_order_num = rpcTempCU->getZorderIdxInCU();// z-ordero_num = rpcTempCU->getZorderIdxInCU() + rpcTempCU->getAddr() * 256 + rpcTempCU->getPic()->getPOC() * rpcTempCU->getPic()->getNumCUsInFrame() * 256;// 块的索引序号p_num = atoi(p_vectorString[o_num].c_str());// 原始的partitionsize的值d_num = atoi(d_vectorString[o_num].c_str());// 原始的depth的值if(eSize == p_num && uiDepth == d_num)flag = 1;elseflag = 0;

4.2 一个有趣的现象

每次试验,在编码视频的过程中,都是第一帧(第0个POC)编码时间较长,接下来几帧的编码时间与原始的HM12.0压缩视频时间相同。

例如在编码BQSquare.yuv时,第1帧(第0个POC)编码的时间为48秒,剩余几帧编码的时间为9秒——这与使用HM12.0在QP=28时压缩BQSquare.yuv所用时间一样。

4.2.1 产生的原因

猜测:由于在HM_2中需要读取所有保存在硬盘上的载体相关信息,所以在第一帧中,需要将这些保存在硬盘上的信息读取进来保存到缓存中,导致了花费时间较长,而在接下来的递归中,由于是直接从缓存中读取数据,所以花费的时间较短。

用代码测试了一下,发现只有第一次的runtime是一个较大的数,接下来的runtime都为0,即证明了我的猜测。

 clock_t begin_time = clock();// 开始时间// 20210412 周一 把stego_IPM从.txt文件中读取进来,保存在一个栈中while(getline(stego_in, stego_str, ',')){// 1、把file_str按照空格分成一个个字符串型的数字stringstream stego_input(stego_str);while(stego_input >> stego_result)stego_vectorString.push_back(stego_result); // vectorString中已经有了一个个按空格分开来的字符串型的数字了}clock_t end_time = clock();// 结束时间printf("runtime = %lf\n", float(end_time - begin_time) / 1000); // 判断了一下,发现只有第一次读的时候是花费时间的,后面的runtime都是0了system("pause");

在我看来,这是很重要的一点,因为之前写代码的时候一直担心在递归函数中读取信息,会不会每递归一次,就要花很长时间去读信息。现在看来,除了第一次递归读取信息时花费较多时间以外,接下来都是瞬时完成的。

也就是说,视频编码时间只是在第一次递归读取时增加了,接下来都是按正常的速率进行编码的。对于那些分辨率大的视频,第一帧编码时间较长,只是单纯的因为它们的数据量大而已,而不是由于递归重复读取导致的时间过长。

4.3 视频产生较大失真

最终,利用软件分析隐写后的视频质量,发现最终的PSNR值非常低,也就是说视频质量很差,有肉眼可见的较大失真。

4.3.1 产生的原因

经分析,可能导致PSNR值过低的原因有两点:

一、在LSB隐写时,没有考虑IPM=0以及IPM=1的情况,对这两种无角度的IPM都进行了隐写,导致IPM从无角度预测变成了有角度预测,产生较大失真;
二、在HM_2中,对uiBestPUMode强行赋值的行为,可以视作在写码流时修改IPM,这种修改方式由于没有对修改后的IPM进行再编码,使得接下来的块产生巨大失真;
三、在HM_2中,虽然保证了所有4x4的位置不发生变化,但是没有保证更大块的结构不变,那么就有可能4个8x8的块合并成了1个16x16的块,4个无角度的IPM合并成了1个有角度的IPM,导致这个大块(平坦区域)产生较大失真;

4.3.2 解决方法

针对第一种可能性,在LSB隐写算法中,对IPM=0和IPM=1进行了特殊处理——即对IPM=0和IPM=1的块不进行隐写——但是效果不理想,视频仍有较大失真。

*针对第二种可能性,应对重新赋值后的CU进行再编码,但是不知道该如何去实现。

*针对第三种可能性,不知道该如何解决。这两点算作是留着优化的点吧。

20210423更新:
由于直接覆盖stego_IPM的方式,是在块计算DCT/DST系数等操作之后进行的,所以会产生巨大的失真。

所以,在HM_2中采用新的策略——在遍历候选列表的时候,判断递归的深度和递归的划分模式,若depth和partitionsize都和HM_1中保存下来的值相同时,则把候选列表长度设置成1,并把候选列表中的值设置成stego_IPM。这样一来,编码器就会自动地根据stego_IPM进行编码,从而直接省去了修改cost的操作。

UInt    uiBestPUMode  = 0;UInt    uiBestPUDistY = 0;UInt    uiBestPUDistC = 0;Double  dBestPUCost   = MAX_DOUBLE;// 20210423周五 在这里对递归中的depth和partitionsize进行判断,如果符合判断条件,使他的预测模式只有这个值order_num = pcCU->getPic()->getPOC() * pcCU->getPic()->getNumCUsInFrame() * 256 + pcCU->getAddr() * 256 + pcCU->getZorderIdxInCU() + uiPartOffset;// 块的索引值depth_num = atoi(depth_vectorString[order_num].c_str());partitionsize_num = atoi(partitionsize_vectorString[order_num].c_str());stego_num = atoi(stego_vectorString[order_num].c_str());// stego_IPM的值if(pcCU->getDepth(uiPartOffset) == depth_num && pcCU->getPartitionSize(uiPartOffset) == partitionsize_num){numModesForFullRD = 1;uiRdModeList[0] = stego_num;}// 遍历候选列表for( UInt uiMode = 0; uiMode < numModesForFullRD; uiMode++ ){// set luma prediction modeUInt uiOrgMode = uiRdModeList[uiMode];pcCU->setLumaIntraDirSubParts ( uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );// set context modelsif( m_bUseSBACRD ){m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );}// determine residual for partitionUInt   uiPUDistY = 0;UInt   uiPUDistC = 0;Double dPUCost   = 0.0;// 等等等,已略}

通过这种代码策略,最终隐写后的视频质量非常的高,隐写效果非常好,解决了失真严重的问题。


五、总结

通过这次demo的练习,对HM的代码有了初步的理解,对HEVC的一些知识有了更深的理解。

对于实验过程中碰到的种种问题,只有自己碰到了、解决了,才是真正的搞懂了。仅仅是参考别人的博客,纸上谈兵,看着好像是理解了,其实还是不然的。

虽然我写博客的初衷是希望能够给以后的人一些经验,但是有些东西还是得自己去试过了,尝试过了,错过了,才是最好的办法。

三、使用HM进行简单的视频隐写demo相关推荐

  1. 关于帧内预测模式的视频隐写代码介绍

    关于帧内预测模式的视频隐写代码介绍 前言 一.H.265/HEVC的帧内预测过程 二.论文[1]的介绍以及如何复现 前言 在早期的基于帧内预测模式(IPM)的H.265/HEVC视频中,大多是基于自定 ...

  2. CTF之misc-音视频隐写

    注释:音视频隐写主要是针对一些特定的音乐,在听或者查看的过程中会存在比较明显的特征,认真查看就能得到flag (1) MP3stego 当你拿到一个MP3文件时不要手足无措,先试试MP3stego 用 ...

  3. (3.2)【多媒体隐写】数字视频隐写介绍、MSU StegoVideo、TCStego

    目录 一.介绍: 二.MSU StegoVideo 2.1.简介 2.2.主要特性 2.3.下载.使用方法: 三.TCStego 3.1.简介: 3.2.使用方法: 一.介绍: 基于数字视频文件的数据 ...

  4. 由一道简单的图片隐写题总结思路

    题目来源:BUU [WUSTCTF2020]find_me 已我的经验拿到杂项图片题一般这个思路(以后自己变强了再补充): 右键看属性有无提示. binwalk分析文件是否隐写入其他文件有则forem ...

  5. 20220131--CTF刷题MISC方向--三道题--简单的图片隐写/与佛论禅解密/rot13解码---比较有意思的几道题

    标题20220131–CTF刷题MISC方向 刷题网站:攻防世界 https://adworld.xctf.org.cn/ MISC方向–新手场–第1题–this_is_flag 题目描述:Most ...

  6. 二、对HEVC/H.265视频编解码器进行隐写的基本思路

    二.对HEVC/H.265视频编解码器进行隐写的基本思路 概述 1.视频隐写的基本思路 2.视频隐写的举例说明 3.结尾 概述 其实对视频隐写.图像隐写或是音频隐写,基本的思路都是一样的:读取原始图像 ...

  7. 【学习总结】ctf隐写初阶解题思路与方法

    作为ctf中相对较为简单的题目,隐写题更适合初学者上手和提高初学者的兴趣.本人也对隐写术很感兴趣,于是尝试着对自己目前所学的隐写解题思路和工具的使用做一个总结.由于水平有限,总结可能会有错误的地方,希 ...

  8. CTF-MISC隐写总结

    文章首发于freebuf 地址:https://www.freebuf.com/articles/others-articles/266884.html 本文仅就个人练习过的misc题目所涉及的知识点 ...

  9. misc1-图片隐写

    Misc:杂项 一.分类 1.数据编码/图形密码 2.图片隐写 3.音频&视频隐写 4.流量分析 5.内存取证 6.游戏隐写(打通关获得一个flag) 二.基础知识 1.010editor(a ...

最新文章

  1. 云计算或将逐步被认可
  2. 分享一点python 编码设置的知识
  3. 浅析php反序列化字符串逃逸
  4. json_decode
  5. 操作系统复习笔记 06 CPU Scheduling CPU调度
  6. 02.2-元素定位(XPath)
  7. arcgis批量按掩膜提取栅格
  8. Mac下使用macdeployqt打包qt程序:
  9. 小程序自定义tabbar custom-tab-bar 6s出不来解决方案,cover-view不兼容
  10. linux清理磁盘空间的脚本,在Ubuntu和Linux Mint上释放空间的7种简单方法
  11. Oracle 数据文件(Datafile ) 大小 限制 说明
  12. Android Studio查看MD5
  13. 计算机图形学2-Liang-Barsky直线裁剪算法
  14. 惠普服务器ilo默认地址_使用ILO进行HP服务器管理的Docker容器
  15. 路由器当交换机用的设置方法
  16. 移动端怎么让底部固定_逆冬:移动端排名应该怎么做?两种匹配移动端实战排名干货分享!...
  17. Excel数字小写金额转换汉字大写金额公式的简单设置
  18. 【记录一次windows技术学习】使用笔记本DOS命令搭建WLAN热点
  19. 计算机网络技术中的单位换算,计算机存储/网络传输中单位换算1000还是1024
  20. python 技巧总结_python技巧汇总

热门文章

  1. linux 时间设置的坑,linux 配置定时任务crontab碰到的坑sendmail
  2. DOS的建文件夹,移动图片,多级文件夹建立
  3. Space Shooter
  4. 2012年第一批中关村高端领军人才公示公告
  5. Hexo Butterfly 主题功能拓展 - 标签云 云养猫
  6. AMC数学考试能用计算机吗,重大调整!今年美国数学竞赛AMC考试时间有变化!
  7. linux安装liinuxrar教程,linux操作系统下RAR的安装和使用
  8. Lync2013 升级Skype For Bussiness 2015 升级思路整理
  9. 智能音箱天猫精灵使用体验--写在前面的话
  10. 你能说更多关于崩坏3琪亚娜的细节吗