揭秘版权保护下的视频隐形水印算法(上篇)
共 3342字,需浏览 7分钟
·
2021-07-12 01:58
一起来看看视频隐形水印都能藏在哪些地方。
把水印加到封装结构里
图种的生成与解压
那么,图种毕竟只是图片,如果是视频,是否也存在利用封装结构来隐藏数据的方法呢?那可太多了。事实上,大部分视频格式都有存储附属信息的结构,我们只要把数据藏在那里就行了。下面简单举两个例子:
flv 文件由一个 header 和大量的 tag 组成,这些 tag 分为三类:audio
、video
和 script data
。其中 script data
的格式标准允许我们插入各种自定义数据[3],我们可以对照官方文档来实现代码修改这个 tag 的数据。嫌麻烦的话,一些开源软件也支持在一定程度上的修改它,比如 flvmeta:
使用 flvmeta 修改 flv 文件,加入自定义数据
实际存储在文件中的数据
H.264 码流
x264生成的SEI数据
封装结构层的水印是所有隐形水印中运算量最小的,因为它不会对视频原始数据进行处理。但是其缺点也很明显。因为视频在被盗用时极可能被人重新编码存储,在这个过程中,事先添加在这一层的水印一般都会丢失。因此这类方法仅在一些特殊的场景被使用。接下来,本文将介绍直接添加在画面内容中的隐形水印。
把水印加到像素里
通过修改像素值添加隐形水印的方法非常之多,本节仅介绍其中最简单一种方法,即直接修改 LSB 数据。所谓 LSB,指的是最低有效位(Least Significant Bit),可以认为是像素值中最无关紧要的一个比特。直接修改它对视觉影响很小。下图的十个方块,蓝色分量的像素值依次由246递增至255,相邻的两个方块相当于修改了 LSB 数据。
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_watermark
cv2.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 的损失会更加严重。所以,现在主流的隐形水印算法,大多选择变换后的数据进行处理。由于这部分内容过多,将放在下一篇中介绍。
参考文献
[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