DWT-DCT-SVD水印提取结果与嵌入水印不一致问题求助
你的代码存在5个核心逻辑/数据类型错误,是导致无攻击场景下水印提取SSIM远低于论文值的原因,按影响程度从高到低排列:
图像存储/读取的类型错误(影响最大)
cv2.imwrite对浮点型数组的存储规则为:仅当数组值范围在[0,1]区间时,会自动乘255映射为8位像素值;若数组值是0-255范围的浮点数,所有大于1的像素都会被截断为255,直接导致含水印图像大面积过曝、像素值完全失真。同时你读取含水印图像时未将PIL对象转为灰度numpy数组,小波变换输入数据格式不匹配。
修正方式:- 嵌入流程最后保存图像前,增加数值截断和类型转换:
# 替换原代码 cv2.imwrite('Watermarked_img.png', IDWT) IDWT = np.clip(IDWT, 0, 255).astype(np.uint8) cv2.imwrite('Watermarked_img.png', IDWT)- 提取流程读取含水印图像时,显式转为灰度numpy数组:
# 替换原代码 Watermarked_Image = Image.open('Watermarked_img.png') Watermarked_Image = np.array(Image.open('Watermarked_img.png').convert('L'))奇异值顺序破坏未修正
numpy的SVD函数返回的奇异值s默认按降序排列(s[0]>=s[1]>=s[2]>=s[3]),你修改s[1]后未校验顺序,可能出现s[1]>s[0]的情况,此时逆SVD重构的子块系数失真,且后续提取时SVD会重新对奇异值排序,导致你取的系数位置完全错位。
修正方式:修改完s[1]后立刻保证奇异值降序,调整Watermark_Embedding函数内的对应逻辑:s[1] = s[1] + delta # 新增:保证奇异值降序 if s[1] > s[0]: s[0], s[1] = s[1], s[0] k1.append(-1) else: k1.append(1) if s[2] > s[1]: s[1], s[2] = s[2], s[1]原有提取逻辑的分支不需要修改,
key1标记会自动匹配奇异值交换后的位置。DCT逆变换后取整方式错误
逆DCT输出的是浮点数,你合并水印时直接用int()做向下取整,会引入最大1个灰度级的截断误差,累积后拉低SSIM。
修正方式:合并水印时用四舍五入取整,同时增加数值截断避免越界,修改Merge_W1_W2函数内的对应代码:# 替换原代码中bw1、bw2的计算逻辑 bw1 = '{:0>4}'.format(bin(int(np.clip(np.round(abs(w1[i])),0,15)))[2:]) bw2 = '{:0>4}'.format(bin(int(np.clip(np.round(abs(w2[i])),0,15)))[2:])代码格式错误
你贴出的提取代码第一行import pywt前有多余空格,SSIM计算代码除导入行外有多余的全局缩进,运行时会触发缩进报错,直接修正即可。
完成以上修正后,无攻击场景下提取水印的SSIM可以达到0.98以上,符合论文给出的指标。
附修正后带中文注释的完整代码:
水印嵌入代码
import pywt import numpy as np import cv2 from PIL import Image from math import sqrt, log10 from scipy.fftpack import dct, idct def Get_MSB_LSB_Watermark(): # 将水印拆分为最高有效位(MSB)和最低有效位(LSB)两张子图 MSBs = [] LSBs = [] for i in range(len(Watermark)): binary = '{:0>8}'.format(str(bin(Watermark[i]))[2:]) MSB = binary[0:4] LSB = binary[4:] MSB = int(MSB, 2) LSB = int(LSB, 2) MSBs.append(MSB) LSBs.append(LSB) MSBs = np.array(MSBs) LSBs = np.array(LSBs) return MSBs.reshape(64,64), LSBs.reshape(64,64) def split(array, nrows, ncols): # 将数组拆分为nrows*ncols大小的子块 r, h = array.shape return (array.reshape(h//nrows, nrows, -1, ncols) .swapaxes(1, 2) .reshape(-1, nrows, ncols)) def unblockshaped(arr, h, w): # 分块操作的逆函数,将子块拼回原尺寸数组 n, nrows, ncols = arr.shape return (arr.reshape(h//nrows, -1, nrows, ncols) .swapaxes(1,2) .reshape(h, w)) def ISVD(U,S,V): # 奇异值分解的逆运算 s = np.zeros(np.shape(U)) for i in range(4): s[i, i] = S[i] recon_image = U @ s @ V return recon_image def Watermark_Embedding(blocks, watermark): Watermarked_blocks = [] k1 = [] k2 = [] # 将水印展平为列表 w = list(np.ndarray.flatten(watermark)) for i in range(len(blocks)): B = blocks[i] # 对图像块做奇异值分解 U, s, V = np.linalg.svd(B) # 修改图像块的奇异值嵌入水印 P = s[1] - s[2] delta = abs(w[i]) - P s[1] = s[1] + delta # 保证奇异值降序排列 if s[1] > s[0]: s[0], s[1] = s[1], s[0] k1.append(-1) else: k1.append(1) if s[2] > s[1]: s[1], s[2] = s[2], s[1] # 水印嵌入后做奇异值分解逆变换重构块 recunstructed_B = ISVD(U, s, V) Watermarked_blocks.append(recunstructed_B) for j in range(len(w)): if w[j] >= 0: k2.append(1) else: k2.append(-1) return k1,k2, np.array(Watermarked_blocks) def apply_dct(image_array): size = len(image_array[0]) all_subdct = np.empty((size, size)) for i in range(0, size, 4): for j in range(0, size, 4): subpixels = image_array[i:i+4, j:j+4] subdct = dct(dct(subpixels.T, norm="ortho").T, norm="ortho") all_subdct[i:i+4, j:j+4] = subdct return all_subdct def inverse_dct(all_subdct): size = len(all_subdct[0]) all_subidct = np.empty((size, size)) for i in range(0, size, 4): for j in range(0, size, 4): subidct = idct(idct(all_subdct[i:i+4, j:j+4].T, norm="ortho").T, norm="ortho") all_subidct[i:i+4, j:j+4] = subidct return all_subidct # 读取水印图像 Watermark = Image.open('Copyright.png').convert('L') Watermark = list(Watermark.getdata()) # 将水印拆分为MSB和LSB两张子图 Watermark1, Watermark2 = Get_MSB_LSB_Watermark() # 对两张子水印图做离散余弦变换 DCT_Watermark1 = apply_dct(Watermark1) DCT_Watermark2 = apply_dct(Watermark2) # 读取载体图像 Cover_Image = Image.open('10.png').convert('L') Cover_Image = np.array(Cover_Image) # 对载体图像做1级haar离散小波变换 LL1, (LH1, HL1, HH1) = pywt.dwt2(Cover_Image, 'haar') # 将LH1、HL1子带拆分为4*4大小的子块 blocks_LH1 = split(LH1,4,4) blocks_HL1 = split(HL1,4,4) # 分别在LH1、HL1子带嵌入水印,同时生成密钥 Key1, Key3, WatermarkedblocksLH1 = Watermark_Embedding(blocks_LH1,DCT_Watermark1) Key2 ,Key4, WatermarkedblocksHL1 = Watermark_Embedding(blocks_HL1,DCT_Watermark2) # 拼接嵌入水印后的子块 reconstructed_LH1 = unblockshaped(WatermarkedblocksLH1, 256,256) reconstructed_HL1 = unblockshaped(WatermarkedblocksHL1, 256,256) # 做离散小波逆变换得到含水印图像 IDWT = pywt.idwt2((LL1, (reconstructed_LH1, reconstructed_HL1, HH1)), 'haar') IDWT = np.clip(IDWT, 0, 255).astype(np.uint8) cv2.imwrite('Watermarked_img.png', IDWT)
水印提取代码
import pywt import numpy as np import cv2 from PIL import Image from Watermark_Embedding import split, inverse_dct, Key1, Key2, Key3, Key4 def Watermark_Extraction(blocks,key1, key2): Extracted_Watermark = [] for i in range(len(blocks)): B = blocks[i] # 对图像块做奇异值分解 U, s, V = np.linalg.svd(B) if key1[i] == 1: P = (s[1] - s[2]) Extracted_Watermark.append(P) else: P = (s[0] - s[2]) Extracted_Watermark.append(P) for j in range(len(Extracted_Watermark)): if key2[j] == 1: Extracted_Watermark[j] = Extracted_Watermark[j] else: Extracted_Watermark[j] = - (Extracted_Watermark[j]) return np.array(Extracted_Watermark) def Merge_W1_W2(IDCTW1, IDCTW2): Merged_watermark = [] w1 = list(np.ndarray.flatten(IDCTW1)) w2 = list(np.ndarray.flatten(IDCTW2)) for i in range(len(w2)): bw1 = '{:0>4}'.format(bin(int(np.clip(np.round(abs(w1[i])),0,15)))[2:]) bw2 = '{:0>4}'.format(bin(int(np.clip(np.round(abs(w2[i])),0,15)))[2:]) P = bw1+bw2 pixel = int(P,2) Merged_watermark.append(pixel) return Merged_watermark # 读取含水印图像 Watermarked_Image = np.array(Image.open('Watermarked_img.png').convert('L')) LL1, (LH1, HL1, HH1) = pywt.dwt2(Watermarked_Image, 'haar') blocks_LH1 = split(LH1,4,4) blocks_HL1 = split(HL1,4,4) W1 = Watermark_Extraction(blocks_LH1, Key1,Key3) W2 = Watermark_Extraction(blocks_HL1, Key2, Key4) W1 = W1.reshape(64,64) W2 = W2.reshape(64,64) IDCTW1 = inverse_dct(W1) IDCTW2 = inverse_dct(W2) Merged = np.array(Merge_W1_W2(IDCTW1, IDCTW2)) Merged = Merged.reshape(64,64) cv2.imwrite('Extracted_Watermark.png', Merged)
SSIM计算代码
from skimage.metrics import structural_similarity import cv2 original_Watermark = cv2.imread('Copyright.png') extracted_watermark = cv2.imread('Extracted_Watermark.png') # 转换为灰度图 original_watermark = cv2.cvtColor(original_Watermark, cv2.COLOR_BGR2GRAY) extracted_Watermark = cv2.cvtColor(extracted_watermark, cv2.COLOR_BGR2GRAY) # 计算两张图的SSIM值 (score, diff) = structural_similarity(original_watermark, extracted_Watermark, full=True) print("SSIM = ", score)
内容的提问来源于stack exchange,提问作者Ghofrane

