You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DWT-DCT-SVD水印提取结果与嵌入水印不一致问题求助

问题排查结果

你的代码存在5个核心逻辑/数据类型错误,是导致无攻击场景下水印提取SSIM远低于论文值的原因,按影响程度从高到低排列:

  • 图像存储/读取的类型错误(影响最大)
    cv2.imwrite对浮点型数组的存储规则为:仅当数组值范围在[0,1]区间时,会自动乘255映射为8位像素值;若数组值是0-255范围的浮点数,所有大于1的像素都会被截断为255,直接导致含水印图像大面积过曝、像素值完全失真。同时你读取含水印图像时未将PIL对象转为灰度numpy数组,小波变换输入数据格式不匹配。
    修正方式:

    1. 嵌入流程最后保存图像前,增加数值截断和类型转换:
    # 替换原代码 cv2.imwrite('Watermarked_img.png', IDWT)
    IDWT = np.clip(IDWT, 0, 255).astype(np.uint8)
    cv2.imwrite('Watermarked_img.png', IDWT)
    
    1. 提取流程读取含水印图像时,显式转为灰度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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:54:37