盲水印、暗水印(Blind Watermark)算法简明教程:算法原理、流程以及基于C/C++ 的代码实现

这篇具有很好参考价值的文章主要介绍了盲水印、暗水印(Blind Watermark)算法简明教程:算法原理、流程以及基于C/C++ 的代码实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

由于工作需要,最近学习了盲水印相关的知识,本文对学习过程中做一个整理和总结。主要内容包括:

  1. 对盲水印相关概念做基本介绍
  2. 对开源的 python 算法 blind_watermark 进行解析,给出算法流程
  3. 基于 blind_watermark,给出了对应的 C/C++ 实现代码,你可以在 cpp_blind_watermark 找到所有代码

一、Blind Watermark 简介

盲水印(blind watermark)算法是一种将数字水印嵌入到数字媒体中的技术,而不需要原始媒体文件。与传统的数字水印技术不同,盲水印算法不需要原始媒体文件来提取数字水印,因此更加安全和隐私保护。

盲水印算法的基本原理是将数字水印嵌入到数字媒体的频域或空域中,使得数字水印能够在不影响原始媒体质量的情况下被提取出来。盲水印算法通常包括两个主要步骤:嵌入和提取。

在嵌入阶段,数字水印被嵌入到数字媒体中。这通常涉及到将数字水印转换为频域或空域信号,并将其嵌入到数字媒体中。嵌入过程需要考虑数字水印的鲁棒性和不可见性,以确保数字水印能够在不影响原始媒体质量的情况下被提取出来。

在提取阶段,数字水印被提取出来。这通常涉及到对数字媒体进行一些处理,以提取数字水印。提取过程需要考虑数字水印的鲁棒性和准确性,以确保数字水印能够被正确地提取出来。

盲水印算法在数字版权保护、数字身份认证和数字隐私保护等领域具有广泛的应用。它可以帮助数字内容提供商保护其版权,防止盗版和侵权行为;也可以帮助用户保护其数字身份和隐私,防止个人信息被泄露。

二、算法流程

本章分析 blind_watermark 中算法逻辑

2.1 水印嵌入

整体流程如下图,接下来对各个阶段做详细的解释。
如何用cpp实现在ppt中添加暗水印,音视频,图像处理,音视频,图像处理

2.1.1 水印二进制化

水印可以是各种形式的信息,包括字符串、图片等。为了嵌入这些信息,我们首先需要将水印转换为二进制形式,即由0和1组成的数组。例如,我们可以将字符串转换为二进制数组。以下是一种实现方法:

byte = bin(int(wm_content.encode('utf-8').hex(), base=16))[2:]
self.wm_bit = (np.array(list(byte)) == '1')

在上面的代码中,首先将字符串类型的水印信息 wm_content 转换为十六进制格式,然后使用 int() 函数将其转换为整数。接着,使用 bin() 函数将整数转换为二进制格式,并去掉前缀 ‘0b’,得到一个字符串类型的二进制数。最后,使用 np.array() 函数将字符串转换为一个由 ‘0’ 和 ‘1’ 组成的数组,并将其与 ‘1’ 做比较,得到一个布尔类型的数组 self.wm_bit,其中 True 表示对应位置为 ‘1’,False 表示对应位置为 ‘0’。

除了上述方法外,如果水印信息由 ASCII 码组成,也可以直接将每个字符转换为 8 位的二进制字符串,然后将这些字符串拼接成一个二进制数组。这个过程可以使用 Python 内置的 bin() 函数和字符串操作来实现。

2.1.2 图像预处理

读取图片后,我们进行图像预处理,包括:

  1. 如果图片包含 Alpha 通道,那么忽略它,只保留 RGB 颜色通道
  2. 如果图片的大小不是偶数的,那么对图片进行填充,使其大小为偶数。做这一步是因为后续需要对图片进行分块处理,块大小是 4x4,如果图片大小不是偶数的话,处理起来比较麻烦
  3. 将 RGB 转换为 YUV444 格式

2.1.3 YUV 数据进行小波变换(DTW)

得到 YUV 数据后,接着对各通道进行二维小波变换。

for channel in range(3):
   self.ca[channel], self.hvd[channel] = dwt2(self.img_YUV[:, :, channel], 'haar')

对于小波变换,具体原理我们暂时不进行深究,只需要了解:

  1. 在二维离散小波变换中,将图像分解为四个子图像,分别表示原图像的近似部分(Approximation,简称 cA)和水平(Horizontal,简称 cH)、垂直(Vertical,简称 cV)以及对角线(Diagonal,简称 cD)方向的细节部分。具体地,cA 表示原图像的低频部分,即近似部分,包含了图像的大部分能量;cH、cV 和 cD 分别表示原图像在水平、垂直和对角线方向上的高频部分,包含了图像的细节信息。这些子图像可以通过多级小波分解得到,其中每一级分解都将近似部分进一步分解为更低频的近似部分和更高频的细节部分。
  2. 如果输入图像为 M x N,那么四个子图像大小为 M/2 * N/2。
  3. 通过二维离散小波逆变换,将四个子图像恢复为原来图像。

2.1.4 在 cA 图像中分块嵌入数据

YUV 通道进行小波变换后得到 cA 子图像,接着对 cA 矩阵进行分块处理(block),块大小为 4x4。对于每个 block,我们嵌入一个 bit 的信息。

在 blind_watermark 中,提供了两种嵌入 bit 的算法:

def block_add_wm_slow(self, arg):
    block, shuffler, i = arg
    # dct->(flatten->加密->逆flatten)->svd->打水印->逆svd->(flatten->解密->逆flatten)->逆dct
    wm_1 = self.wm_bit[i % self.wm_size]
    block_dct = dct(block)
    # 加密(打乱顺序)
    block_dct_shuffled = block_dct.flatten()[shuffler].reshape(self.block_shape)
    u, s, v = svd(block_dct_shuffled)
    s[0] = (s[0] // self.d1 + 1 / 4 + 1 / 2 * wm_1) * self.d1
    if self.d2:
        s[1] = (s[1] // self.d2 + 1 / 4 + 1 / 2 * wm_1) * self.d2
    block_dct_flatten = np.dot(u, np.dot(np.diag(s), v)).flatten()
    block_dct_flatten[shuffler] = block_dct_flatten.copy()
    return idct(block_dct_flatten.reshape(self.block_shape))
def block_add_wm_fast(self, arg):
    # dct->svd->打水印->逆svd->逆dct
    block, shuffler, i = arg
    wm_1 = self.wm_bit[i % self.wm_size]
    u, s, v = svd(dct(block))
    s[0] = (s[0] // self.d1 + 1 / 4 + 1 / 2 * wm_1) * self.d1
    return idct(np.dot(u, np.dot(np.diag(s), v)))

block_add_wm_fast 更简单,我们做一个简短的说明:

  1. 首先对 block 进行 DCT(离散余弦变换),block 大小为 4x4,DCT 输出大小也是 4x4。
  2. 对 DCT 输出矩阵进行奇异值分解(SVD),返回值 S 中包含奇异值。通过 (s[0] // self.d1 + 1 / 4 + 1 / 2 * wm_1) * self.d1 将 1bit 数据嵌入第一个奇异值中。
  3. 嵌入数据后,恢复 block。 将 SVD 分解后的矩阵重新构造为原始矩阵。 接着,使用 idct() 函数对这个矩阵进行逆离散余弦变换(IDCT),得到重构后的矩阵。

重复上述过程,将每一个 bit 都嵌入到对应 block 中,则完成了数据嵌入,伪代码如下:

# Y_ca,U_ca 和 V_ca 即 YUV 经过小波变换后得到的 ca 矩阵
for ca in (Y_ca, U_ca, V_ca):
	i = 0
	# 遍历每一个 block
	for block_index in range(0, ca.get_block_count()):
		block = ca.getBlock(block_index) # 获取 block 数据
		bit_data = wm_bit[i % wm_size] # 获取 1 bit数据
		embeded_block = block_add_wm(block, bit_data) # 在当前 block 上嵌入 1 bit 数据
		ca.setBlock(block_index, embeded_block) # 将嵌入后的 block 写入 ca 矩阵中

根据这个嵌入的逻辑,我们可以计算下一张图片最多能够嵌入多少 bit 数据:

  1. 假设输入图片大小为 W x H,图片矩阵则是 H x W,转换为 YUV 后其矩阵大小仍然是 H x W
  2. 进行小波变换后,ca 矩阵大小是输入矩阵的一半,即 (H/2) x (W/2)
  3. 假设 block 大小为 (4, 4),那么 ca 矩阵上一共有 ( H / 2 ∗ W / 2 ) 4 ∗ 4 = H ∗ W 64 \frac{(H/2 * W/2)}{4*4} = \frac{H*W}{64} 44(H/2W/2)=64HW 个 block;每个 block 嵌入 1 bit 数据,那么最多能够嵌入 H ∗ W 64 \frac{H*W}{64} 64HW 个 bit
  4. 以 512 x 512 图片大小为例,最多嵌入 4096 个 bit,也就是一张 64x64 的黑白图片,或者 512 个 ASCII 字符

2.1.5 恢复为 RGB 数据

在 ca 矩阵上嵌入数据后,通过逆小波变换转换为 YUV 数据,接着 YUV 数据可以转为 RGB 以便保存为图片文件,或者用于其他处理,伪代码如下:

for channel in range(3):
	embed_YUV[channel] = idwt2(embed_ca[channel], hvd[channel])

embed_img = yuv2rgb(embed_YUV)
save_image("embed.jpg", embed_img)

至此,我们完成了盲水印的嵌入。

2.1.6 乱序

值得注意的是,我在这里省略了blind_watermark中关于password参数的描述。这个参数用于设置一个随机种子,以便在嵌入过程中打乱嵌入数据的顺序。我认为这是一个可选的部分,对于不熟悉该算法的人来说,这部分代码可能会让人感到困惑。因此,我想先把其他核心流程讲清楚,而password的乱序功能只是对整个过程的一个补充。

2.2 水印提取

水印的提取过程是嵌入的逆过程。整体流程如下图,对各流程做详细的解释。
如何用cpp实现在ppt中添加暗水印,音视频,图像处理,音视频,图像处理

2.2.1 图片数据预处理

与嵌入流程类似,读取图片进行预处理:

  1. RGB 转到 YUV
  2. 对 YUV 数据进行二维小波变换,得到四个子图像

2.2.2 在 cA 图像分块中提取数据

接着对 cA 矩阵进行分块处理(block),块大小为 4x4。对于每个 block,我们提取一个 bit 的信息。提取的算法为嵌入的逆过程:

def block_get_wm_slow(self, args):
    block, shuffler = args
    # dct->flatten->加密->逆flatten->svd->解水印
    block_dct_shuffled = dct(block).flatten()[shuffler].reshape(self.block_shape)
    u, s, v = svd(block_dct_shuffled)
    wm = (s[0] % self.d1 > self.d1 / 2) * 1
    if self.d2:
        tmp = (s[1] % self.d2 > self.d2 / 2) * 1
        wm = (wm * 3 + tmp * 1) / 4
    return wm
def block_get_wm_fast(self, args):
    block, shuffler = args
    # dct->flatten->加密->逆flatten->svd->解水印
    u, s, v = svd(dct(block))
    wm = (s[0] % self.d1 > self.d1 / 2) * 1

2.2.3 取平均

在嵌入过程中,我们实际上是在冗余地嵌入数据。这包括在YUV的三个通道中嵌入相同的数据,以及在数据长度较短的情况下,例如只有4个比特,我们会重复地将这4个比特写入到块中。这种冗余性可以增强盲水印的抵抗攻击能力。因此,在提取数据时,我们可以采取取平均值的方法来获取实际的数据。伪代码如下:

# 从 YUV 数据中,提取 01 数组
for channel in range(0):
	wm_block_bit[channel] = get_wm_block_bit(embed_YUV[channel])

# wm_block_bit 是一个 3xN 的数组,其中 N 为 bit 的个数
# 我们对它取平均,得到 N 个数据
wm_block_channel_avg = np.average(wm_block_bit, axis=0)

# 此时 wm_block_channel_avg 长度为 N,假设 wm_bit_size = m
# 对于循环嵌入的 bit 数据取平均
wm_avg = np.zeros(shape=self.wm_size)
for i in range(self.wm_size):
	wm_avg[i] = wm_block_bit[:, i::self.wm_size].mean()

# 得到 wm_avg 是一个长度为 m 的数组

2.2.4 Kmeans 聚类

wm_avg 是一个浮点数组,如果你要完全地恢复数据到 01 的状态,那么需要做进一步计算。blind_watermark 作者给出的方法是使用 kmeans 进行二分类。本人实测这种方法确实比较准确。

但如果你嵌入的是图片,那么可以不用做 kmeans,因为浮点值可以对应亮度。

三、其他问题

3.1 关于水印长度的问题

可以看到在提取水印时,我们需要知道水印的长度,以便对 01 数组去平均值。这就带来一个问题:在提取水印时,你无法知道该图片水印的长度。例如 A 图片嵌入 4 bit 数据,B 图片嵌入了 5 bit 数据,当你要提取 A 和 B 图片暗水印时,要如何处理?

我们可以固定水印长度,例如约定都是 64 个 bit,如果水印数据超过了这个长度,那么对不起,算法没法嵌入;如果不足 64 bit,那么剩余 bit 全部置为 0 即可。

3.2 关于抗攻击的说明

在 example_str 中,作者展示了 blind_watermark 抗攻击的能力,包括截屏、缩放等等。

但需要说明的是,这里的测试是有要求的:无论做了哪种攻击,在提取水印前你需要将图片位置正确的排放。例如:

  • 缩放攻击。假设图片从 512 * 512 缩放到 256 * 256,那么首先将图片恢复至 512 * 512,再送给算法
  • 截屏攻击。从 (x, y) 点截取 200*200 的图片,你不能直接用着 200 * 200 的数据送给提取水印的算法,而是创建一个与原图一致的矩阵,将 200 * 200 的数据填充回 (x, y) 点,矩阵其他地方数据可以为 0。完成了这项操作后,再把数据输入到算法

之所以有这种要求,算法在嵌入和提取的过程中,隐式地依赖了 block 的位置信息。在实际使用中,上述条件比较难满足,因为你不知道用户会对图片做什么操作。这也是盲水印算法的某种局限和难点。

四、关于 C/C++ 算法实现

你可以在 cpp_blind_watermark 仓库中看到对应 C/C++ 版本,它的依赖项目有:

  1. opencv,提供了处理图片、矩阵操作、离散余弦变换等能力
  2. wavelib,提供了小波变换能力

在 main.cpp 中有具体的代码示例;在 tests 下有各个模块的单元测试,如果你对某个接口不了解,可以在这里找到一些信息。文章来源地址https://www.toymoban.com/news/detail-840596.html

参考

  • blind_watermark
  • cpp_blind_watermark

到了这里,关于盲水印、暗水印(Blind Watermark)算法简明教程:算法原理、流程以及基于C/C++ 的代码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • dig 简明教程

    哈喽大家好,我是咸鱼 不知道大家在日常学习或者工作当中用 dig 命令多不多 dig 是 Domain Information Groper 的缩写,对于网络管理员和在域名系统(DNS)领域工作的小伙伴来说,它是一个非常常见且有用的工具。 无论是简单的 DNS 解析查找还是更高级的故障排除和分析, dig 都能够

    2024年02月08日
    浏览(55)
  • Husky使用简明教程

    Husky 是一个流行的 Git 钩子工具,用于在不同的 Git 操作(如提交和推送)前自动运行脚本。比如代码格式化、静态检查等。这有助于保持代码库的质量和一致性。本教程将详细介绍 Husky 的原理、使用方式、配置方法以及如何在开发中集成 Husky。 Husky 原理 安装 Husky 配置 Hus

    2024年04月10日
    浏览(32)
  • HuggingFace简明教程

    视频链接:HuggingFace简明教程,BERT中文模型实战示例.NLP预训练模型,Transformers类库,datasets类库快速入门._哔哩哔哩_bilibili 什么是huggingface?huggingface是一个开源社区,它提供了先进的NLP模型,数据集,以及其他便利的工具。 数据集:Hugging Face – The AI community building the future.  这

    2024年01月25日
    浏览(34)
  • SAP报表简明教程

    SAP 报表简明教程   一、 报表需求,根据物料编码和物料类型 查询报表。用户输入界面要求如下:     二、 开始写代码。先进入 TCODE:SE38 ,新建一个程序。      点击创建按钮,如下图:      输入标题,写明 此程序的功能 作者,创建时间,点保存,     输入自己事先建

    2024年02月04日
    浏览(37)
  • SSH 隧道简明教程

    本章主要介绍了什么是 SSH 隧道以及如何使用 SSH 隧道,包括 SSH 隧道加密数据传输以及绕过防火墙。 SSH 隧道是 SSH 中的一种机制,它能够将其他 TCP 端口的网络数据通过 SSH 连接来转发,并且自动提供了相应的加密及解密服务。因为 SSH 为其他 TCP 链接提供了一个安全的通道来

    2024年02月06日
    浏览(41)
  • Docker入门简明教程

    Docker 是基于 Go 语言实现的云开源项目,是基于 Linux 的多项开源技术提供高效、敏捷和轻量级的容器方案。创建于 2013 年初,自从开源后就受到了广泛的关注,从长远的眼光来看,Docker 是未来虚拟化的一个发展的趋势。带来了更轻量快捷的的体验,一台主机可以同时运行数千

    2024年01月23日
    浏览(41)
  • AI绘画工具简明教程

    官方地址 首先需要邮箱注册,等待邀请(可能需要等待一两天) 能成功登录后会进入这样一个界面 https://app.scenario.com/generators 创建模型 提供的图片集上传的时候得是jpg,还需要裁剪成正方形。批量修改图片在线网站:https://www.birme.net/ 根据图集生成图片 官方网址:https://

    2024年02月11日
    浏览(58)
  • 电商3D产品渲染简明教程

    3D 渲染让动作电影看起来更酷,让建筑设计变得栩栩如生,现在还可以帮助营销人员推广他们的产品。 从最新的《阿凡达》电影到 Spotify 的上一次营销活动,3D 的应用让一切变得更加美好。 在营销领域,3D 产品渲染可帮助品牌创建产品的高分辨率图像和视频,这些图像和视

    2024年02月13日
    浏览(28)
  • shell简明教程3函数

    在本章中,您将了解为什么以及何时需要使用函数。 你将学习如何创建函数以及如何使用函数。 我们将讨论变量及其作用域。 学习如何使用参数访问传递给函数的参数。 最后,您还将学习如何使用函数处理退出状态和返回代码。 计算机编程和应用程序开发中有一个概念叫

    2024年02月11日
    浏览(36)
  • WebGPU开发简明教程【2023】

    WebGPU 是一种全新的现代 API,用于在 Web 应用程序中访问 GPU 的功能。 在 WebGPU 之前,有 WebGL,它提供了 WebGPU 功能的子集。 它启用了新一类丰富的网络内容,开发人员用它构建了令人惊叹的东西。 然而,它基于 2007 年发布的 OpenGL ES 2.0 API,而该 API 又基于更旧的 OpenGL API。

    2024年02月16日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包