jjjzw 发表于 2022-8-30 18:44

【Python】图像处理之看不见的水印

本帖最后由 jjjzw 于 2022-8-30 20:23 编辑

一直对各大互联网大厂传说中揪出内鬼的能力感到好奇,机缘巧合下了解到其中一种数字水印的方法,在员工操作的设备上添加隐藏的水印,通过解密可以得到附加的信息,因此学习复现一下。



### 原理

现阶段常见的,使用最多的水印技术,就是用水印图片覆盖原有像素,虽然简洁明了,但容易被PS干掉,也不可逆地破坏了原图片的内容,因此隐藏数字水印的技术只能作用于频域中

为了获得图像的频域,需要使用二维的快速傅立叶变换(FFT2),将灰度图转化为频率的分布

一张频谱图如下所示:

![原图频谱](https://s1.328888.xyz/2022/08/30/aOW2n.png)

其中,频谱图反映了图像中突变、边缘的变化等信息,对其进行细微修改不会显著地变化原图

因此,将一张水印直接添加到待加密的图片的频谱上,再进行傅立叶逆变换,恢复到空间域的图像,得到一张加密过的图像

解密时,将图像进行傅立叶变换,减去原图的频谱,得到隐藏在其中的信息



### 代码验证

|                           待加密图                           |                           水印                           |
| :----------------------------------------------------------: | :----------------------------------------------------------: |
| ![](https://s1.328888.xyz/2022/08/30/aOxd6.jpg) | ![](https://s1.328888.xyz/2022/08/30/aOtBX.jpg) |
|                         52pojie.jpg                        |                        watermark.jpg                         |

代码:需要`opencv-python` + `matplotlib`

```python
import cv2
import random
import numpy as np
import matplotlib.pyplot as plt

# 随机序列表
key1 = []
key2 = []


def read(name):
    img0 = cv2.imread(name)
    return cv2.cvtColor(img0, cv2.COLOR_BGR2GRAY)


# 读取图片和水印
raw_pic = read("52pojie.jpg")
watermark = read("watermark.jpg")
if raw_pic.shape != watermark.shape & raw_pic.shape != watermark.shape:
    print("水印尺寸错误")

# 水印随机序列编码
if key1 == key2 == []:
    key1 = random.sample(range(watermark.shape), watermark.shape)
    key2 = random.sample(range(watermark.shape), watermark.shape)
    with open("key.txt", "w") as f:
      f.write(str(key1) + "\n" + str(key2))
temp = np.zeros((watermark.shape, watermark.shape))
for i in range(watermark.shape):
    for j in range(watermark.shape):
      temp = watermark]]
watermark_encoded = temp
cv2.imwrite("watermark_encoded.jpg", watermark_encoded)

# 添加水印
FR = np.fft.fft2(raw_pic)
FA = FR + watermark_encoded
fshift = np.fft.fftshift(FA)
s = np.log(np.abs(fshift))
fimg = np.abs(s)

# 从频域恢复图片
ifshift = np.fft.ifftshift(fshift)
back = np.fft.ifft2(ifshift)
back = np.abs(back)
cv2.imwrite("picture.jpg", back)

# 提取水印
F_encoded = np.fft.fft2(back)
F_watermark = (F_encoded - FR)
plt.savefig("watermark_extracted.jpg")

# 水印解码
FF = np.zeros((F_watermark.shape, F_watermark.shape))
for i in range(F_watermark.shape):
    for j in range(F_watermark.shape):
      FF]] = F_watermark
cv2.imwrite("watermark_decoded.jpg", FF)

```

运行结果如下:

|                         1. 原图频谱                        |                         2. 水印编码                        |
| :----------------------------------------------------------: | :----------------------------------------------------------: |
| ![](https://s1.328888.xyz/2022/08/30/aOW2n.png) | ![](https://s1.328888.xyz/2022/08/30/aOdrs.jpg) |
|                     **3. 加密频谱**                        |                     **4. 加密图片**                        |
| ![](https://s1.328888.xyz/2022/08/30/ajL1p.png) | ![](https://s1.328888.xyz/2022/08/30/ajNyo.jpg) |
|                     **5. 提取水印**                        |                     **6. 水印解码**                        |
| ![](https://s1.328888.xyz/2022/08/30/ajlvF.jpg) | ![](https://s1.328888.xyz/2022/08/30/ajDbS.jpg) |



### 测试1:水印不编码

将水印直接加到原图的频域中,频域之差即水印,观察提取出来的水印

![](https://s1.328888.xyz/2022/08/30/ajOAN.png)

发现一正一反重叠了起来,和上面的图有差距,这是由于傅立叶变换的对称性,在本图中表现为出现了相反的两张水印

但水印的总能量是不变的,两张水印各占有一半的能量,因此水印的灰度相比原水印都发生了变化

因此,随机序列编码的优势在于,只恢复了想要的正确水印,将相反的水印散布在背景中,使得水印看起来清晰可见

该问题很好解决,只要做一张对称的水印即可

|                        对称水印                         |                        水印提取                         |
| :-----------------------------------------------------: | :-----------------------------------------------------: |
| ![对称水印](https://s1.328888.xyz/2022/08/30/aju4y.jpg) | ![对称解码](https://s1.328888.xyz/2022/08/30/ajTDU.jpg) |

在对称水印的情况下,是否编码的结果没有任何差别(可以完全取出水印)

我只是简单把像素加到频域中,是否编码不影响矩阵作差的准确性,在一些使用滤波器的数字水印加密中,编码可以使得图片的频域与原图的频域相对分离,可以做到不需要原图也可以提取水印



### 测试2:鲁棒性测试

对图片造成一点破坏——添加一块噪声上去:`原图 + 噪声系数 * 噪声像素`

![](https://s1.328888.xyz/2022/08/30/aj0IC.jpg)

再次测试结果:

(这里应当有一张全白的图片.jpg)

信息完全丢失了

修改噪声系数小于0.1时能看到被破坏过的结果:

![](https://s1.328888.xyz/2022/08/30/aj4rd.jpg)

可以看出这种加密对像素攻击有一定的抵御能力(涂改的地方已经挺多了,一般图片不会这样损坏)



### 优缺点

1. 优点:足够隐蔽(完全看不出来-**见4. 加密图片**),可以抵御一定程度的攻击
2. 缺点:信息直接加在图片的频域上,在复频域中,但是在保存加密图片时只能储存0-255的像素值,过大的误差导致计算出来的加密图片和保存的图片频域差距较大,无法正常隐藏信息,暂时没有想到好方法解决该问题



### 总结

去年实验课的时候有个题目是在音频中隐藏信息,没做成功,后来发现图像领域有类似的鲁棒性水印,在生活中有一定用处,便学习了一下。各位看到水印的文字大概能想到它原来的用途(笑)可惜没能解决保存加密图片的问题,只能提前放出。待我再研究研究使用滤波器的数字水印技术,下次原创发布区见!

jjjzw 发表于 2022-8-30 22:47

三滑稽甲苯 发表于 2022-8-30 22:41
能抵抗截屏/拍屏攻击吗

可以抵抗截屏,对抗拍摄的能力稍差

vethenc 发表于 2022-8-30 19:00

感谢分享,没太看懂

Gif 发表于 2022-8-30 19:06

不明觉厉:虽然没看懂,但是觉得很厉害

likaiaixuexi 发表于 2022-8-30 19:30

我也没咋看懂

Chenmozero 发表于 2022-8-30 19:35

用matlab做过,过了一年还是照着葫芦画瓢,楼主厉害

smartfind 发表于 2022-8-30 19:43

楼主厉害了,感谢分享

Piz.liu 发表于 2022-8-30 22:02

厉害厉害,虽然看不懂,{:1_926:}
想知道如果对图做了修改,比如裁剪,或再添加一层水印,还能获取到信息么

xsw1000 发表于 2022-8-30 22:13

没看懂,厉害..感谢分享,

Airey 发表于 2022-8-30 22:35

快速傅里叶变换

三滑稽甲苯 发表于 2022-8-30 22:41

能抵抗截屏/拍屏攻击吗
页: [1] 2
查看完整版本: 【Python】图像处理之看不见的水印