【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的像素值,过大的误差导致计算出来的加密图片和保存的图片频域差距较大,无法正常隐藏信息,暂时没有想到好方法解决该问题
### 总结
去年实验课的时候有个题目是在音频中隐藏信息,没做成功,后来发现图像领域有类似的鲁棒性水印,在生活中有一定用处,便学习了一下。各位看到水印的文字大概能想到它原来的用途(笑)可惜没能解决保存加密图片的问题,只能提前放出。待我再研究研究使用滤波器的数字水印技术,下次原创发布区见!
三滑稽甲苯 发表于 2022-8-30 22:41
能抵抗截屏/拍屏攻击吗
可以抵抗截屏,对抗拍摄的能力稍差 感谢分享,没太看懂 不明觉厉:虽然没看懂,但是觉得很厉害 我也没咋看懂 用matlab做过,过了一年还是照着葫芦画瓢,楼主厉害 楼主厉害了,感谢分享 厉害厉害,虽然看不懂,{:1_926:}
想知道如果对图做了修改,比如裁剪,或再添加一层水印,还能获取到信息么 没看懂,厉害..感谢分享, 快速傅里叶变换 能抵抗截屏/拍屏攻击吗
页:
[1]
2