揭秘版权保护下的视频隐形水印算法(上篇)

视频水印,作为保护知识产权的重要手段 ,早已被大众习惯且接受,但是这种方法仍然存在着多方面的不足。对于观众来说,盖在画面一角的 logo 多少会影响到他们的观赏体验。对于视频所有者来说,这种直接显示在画面上的水印也很容易被定位和攻击。一些厂家为了应对这些攻击,将水印时不时地从随机的方向插入到画面里,从而增加 delogo 的难度,但这就更进一步降低了观众的观看体验。
针对这些问题,隐形水印这门技术被提出并逐渐发展了起来。作为实时音视频技术引领者,拍乐云在这一领域不断探索,为用户提供极致的体验。 我们将通过两篇文章,带你详细了解版权保护背后的视频隐形水印算法技术。

一起来看看视频隐形水印都能藏在哪些地方。

01

把水印加到封装结构里

首先,我们用一个最简单的例子来引入一类完全不会修改画面内容的隐形水印,也即封装结构层的水印。这个例子叫图种。一部分读者也许听说过这种东西。在国内版权意识逐渐普及的时期,网络上公开可下载的无授权资源逐渐被封禁,图种开始作为资源传播的手段小范围流行起来。它看上去就是一张普通的 jpeg 图片,但是下载下来后,zip 解压软件居然能对它进行解压,之后就能得到隐藏在图片背后的其他数据。其原理,就是利用了文件的封装结构来隐藏数据。 说得详细一点,jpeg 格式的文件,是以一串固定数据(0xFFD9)结尾的 [1] ,而 zip 格式的文件,也有其固定的起始码(0x04034B50) [2] 。通过将一个 jpeg 文件和一个 zip 文件直接拼接,就可以产生图种。大部分图片浏览器和解压软件都有对数据首尾位置不正常的情况做兼容,所以它们可以正常处理这类文件。

图种的生成与解压

图种可以被正常显示

那么,图种毕竟只是图片,如果是视频,是否也存在利用封装结构来隐藏数据的方法呢?那可太多了。事实上,大部分视频格式都有存储附属信息的结构,我们只要把数据藏在那里就行了。下面简单举两个例子:

flv 文件  

flv 文件由一个 header 和大量的 tag 组成,这些 tag 分为三类:audiovideo script data其中 script data 的格式标准允许我们插入各种自定义数据[3],我们可以对照官方文档来实现代码修改这个 tag 的数据。嫌麻烦的话,一些开源软件也支持在一定程度上的修改它,比如 flvmeta:

使用 flvmeta 修改 flv 文件,加入自定义数据

实际存储在文件中的数据

H.264 码流

在 flv、mp4、mkv 等文件格式上添加数据,意味着视频必须以离线文件的形式存在。如果我们希望在直播或者视频通话中的视频数据上添加水印呢?这时我们可以在更下一层,也就是视频码流层下手。可能有读者对码流这个概念不是很清楚。视频数据原本只是一个个像素值,经过编码器压缩后的数据一般就称为码流或比特流(bitstream)。视频码流虽然也可以直接解码播放,但是只能得到一帧一帧的画面,不包含准确的时间信息,也不包含给播放器跳转用的数据偏移量,更没有音频和字幕。所以它需要更上一级的封装格式提供给播放器附加信息以保证正常播放。视频编码有很多标准,现在使用最广泛的H.264 标准中,有一段数据叫做 SEI(补充增强信息,supplemental enhancement information),它被用来存储辅助解码与显示的信息,支持添加用户的自定义数据[4]。著名的开源编码器x264就在这段数据里写了自己的版本信息以及编码参数。我们可以参照格式标准生成自己定义的SEI数据,再嵌入视频码流中,从而实现隐形水印。

自定义 SEI 的语法标准

x264生成的SEI数据

封装结构层的水印是所有隐形水印中运算量最小的,因为它不会对视频原始数据进行处理。但是其缺点也很明显。因为视频在被盗用时极可能被人重新编码存储,在这个过程中,事先添加在这一层的水印一般都会丢失。因此这类方法仅在一些特殊的场景被使用。接下来,本文将介绍直接添加在画面内容中的隐形水印。

02

把水印加到像素里

通过修改像素值添加隐形水印的方法非常之多,本节仅介绍其中最简单一种方法,即直接修改 LSB 数据。所谓 LSB,指的是最低有效位(Least Significant Bit),可以认为是像素值中最无关紧要的一个比特。直接修改它对视觉影响很小。下图的十个方块,蓝色分量的像素值依次由246递增至255,相邻的两个方块相当于修改了 LSB 数据。

修改 LSB 数据较难被肉眼分辨
有了这个认识,再来做水印就很简单了。我们先把水印数据转化成只有 0 和1 的二值图像,然后直接写到目标图像的最低位上,这样就完成了水印的嵌入。除了可以用这种方法嵌入纯黑白的 logo,二维码也是个不错的选择。毕竟二维码本身就有纠错的能力,能在一定程度上防止水印被破坏。下面我们用python 简单实现一下:
import cv2
# 读取图像,将水印缩放至目标图像大小,并二值化ori_img = cv2.imread('Lenna.jpg')watermark = cv2.imread('watermark.jpg')resized_watermark = cv2.resize(watermark, ori_img.shape[:2], interpolation=cv2.INTER_NEAREST) #既然是二值图像,使用最近邻方式来拉伸比较合适binary_watermark = resized_watermark >> 7
# 嵌入水印并保存结果output_img = ori_img & 0xFE | binary_watermarkcv2.imwrite('Lenna_with_watermark.png', output_img)
# 提取水印img_with_watermark = cv2.imread('Lenna_with_watermark.png')extracted_watermark = ((target_img & 0x01) * 255)cv2.imwrite('extracted_watermark.jpg', extracted_watermark)
原始图片

待添加水印

添加完水印的图片

提取出的水印

细心的读者会发现,代码中其他图像都是使用 jpg 格式存储的,唯独添加完水印的图片代码中使用了 png 格式。这是因为最低有效位的数据非常脆弱,极容易被有损压缩算法修改,导致水印无法正常提取。而 png 格式是无损压缩格式,不会引入这个干扰。如果把上述代码中的 png 换成 jpg,你会看到提取出的水印变得完全无法辨认。 

图片经过有损压缩后提取的水印

这样看来,似乎 LSB 方法也不是那么保险。毕竟视频编码基本都是有损压缩,更别提被盗取的视频被重新发布时一般都会经历二次编码,LSB 的损失会更加严重。所以,现在主流的隐形水印算法,大多选择变换后的数据进行处理。由于这部分内容过多,将放在下一篇中介绍。 

本篇 大致介绍了在 封装层和在变换前的原始像素数据 上进行处理的隐形水印嵌入方法,内容 比较集中在格式标准上。 在下一篇中,我们将给大家介绍更多图像处理相关的内容,包括 DCT(离散余弦变换)、DWT(离散小波变换)以及SVD(奇异值分解)在隐形水印上的应用,这些方法能够大幅提高隐形水印的鲁棒性,从而在有损压缩以及人为攻击后仍能在一定程度上保证水印的内容。

参考文献

[1] CCITT Rec. T.81

[2] APPNOTE.TXT - .ZIP File Format Specification

[3] Adobe Flash Video File Format Specification

[4] Recommendation ITU-T H.264

本文分享自微信公众号 - 音视频开发进阶(glumes_blog)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部