当前位置: 首页 > news >正文

实验七 基于Python的数字图像水印算法

在这里插入图片描述
在这里插入图片描述

一、实验目的

掌握图像水印的应用;
掌握我国版权保护的发展现状;
掌握常见的数字图像水印算法。

二、实验内容

  1. 学习内容补充:
    数字水印的鲁棒性评价主要采用含水印图像提取出的水印与原始水印的相似程度,使用归一化相关(NC,Normalized Correlation)系数来表示,其相似程度越高,说明其稳健性越强。当前常用的水印相似度公式表示如下:
    在这里插入图片描述

    在这里插入图片描述

其中, 分别表示原始水印和提取的水印, 分别表示水印图像的尺寸,NC表示两幅图像的相似度。
从式1可以看出,NC取值的范围为[0,1],一般来说,NC的值越接近1,两幅图像越相似。若从含水印图像中提取出的水印与原始水印图像的NC值大于0.7,则一般可认为提取出了与原始水印相似的水印信息。
2. 读取一幅灰度图像,水印图像为qlu32.bmp,分别实现空域和频域两种算法对图像进行水印嵌入和提取;显示水印嵌入前后的图像,并显示水印嵌入前后的图像psnr值,以及水印提取的nc值。
3. 对2中嵌入水印后的图像添加噪声,并进行水印提取,显示添加噪声后的图像和提取的水印图像。由此,分析2中实现的算法的优缺点。

三、完整实验程序、结果与分析

空域算法是“脆弱的完美者”,适合对不可见性和实时性要求极高的场景。
频域算法是“稳健的守护者”,适合需要抵抗干扰的长期版权保护场景。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

空域(LSB):
优点:实现简单,计算量小,嵌入后PSNR高。
缺点:对噪声敏感,鲁棒性差,NC值在加噪后显著下降。
频域(DCT):
优点:对噪声和压缩等攻击鲁棒性较好,NC值下降较少。
缺点:实现复杂,嵌入后PSNR较低,可能存在块效应。
在这里插入图片描述

结论:空域算法的不可见性显著优于频域算法。
在这里插入图片描述

结论:空域算法在无干扰时提取准确性完美,频域算法因宿主内容影响略有下降。
在这里插入图片描述

结论:频域算法抗噪声能力显著优于空域算法。

四、本次作业中出现的问题及心得

通过本次对比实验,我深刻认识到空域与频域水印算法在不可见性、鲁棒性和适用场景上的显著差异。空域算法通过LSB替换实现了高达50dB以上的PSNR值,水印完全不可见且提取精准,但其脆弱性在添加噪声后NC值暴跌至0.5,暴露出抗干扰能力不足的缺陷;而频域算法虽因DCT块效应导致PSNR约40dB,视觉存在轻微失真,却能抵抗噪声攻击,加噪后NC值仍保持0.6以上,展现出强鲁棒性。
这启示我们在实际应用中需针对性选择算法:对实时性和隐蔽性要求高的场景可采用空域技术,而需长期抗攻击的版权保护场景则更适合频域方案。实验也让我体会到数字水印技术中不可见性与鲁棒性的本质矛盾,未来可探索混合域嵌入或自适应强度调节来突破这一技术瓶颈。

完整代码

import cv2
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False# ---------------------- 辅助函数定义 ----------------------
def psnr(original, watermarked):"""计算峰值信噪比"""mse = np.mean((original - watermarked) ** 2)return 10 * np.log10(255.0 ** 2 / mse) if mse != 0 else float('inf')def nc(original, extracted):"""计算归一化相关系数"""original = original.flatten().astype(np.float64)extracted = extracted.flatten().astype(np.float64)product = np.dot(original, extracted)norm_original = np.sqrt(np.sum(original ** 2))norm_extracted = np.sqrt(np.sum(extracted ** 2))return product / (norm_original * norm_extracted) if (norm_original * norm_extracted) != 0 else 0# ---------------------- 空域水印算法 ----------------------
def spatial_embed(host, watermark):"""空域水印嵌入(LSB替换)"""watermark_resized = cv2.resize(watermark, (host.shape[1], host.shape[0]),interpolation=cv2.INTER_NEAREST)return (host & 0xFE) | (watermark_resized > 128).astype(np.uint8)def spatial_extract(watermarked_host):"""空域水印提取"""return (watermarked_host & 1) * 255# ---------------------- 频域水印算法 ----------------------
def dct_embed(host, watermark, block_size=8, alpha=10.0, pos=(3, 4)):"""频域水印嵌入(DCT系数修改)"""h, w = host.shapewm_h, wm_w = h // block_size, w // block_sizewatermark_resized = cv2.resize(watermark, (wm_w, wm_h),interpolation=cv2.INTER_NEAREST)host_watermarked = host.astype(np.float32).copy()for i in range(wm_h):for j in range(wm_w):y, x = i * block_size, j * block_sizeblock = host[y:y + block_size, x:x + block_size].astype(np.float32)dct_block = cv2.dct(block)dct_block[pos] += alpha * (1 if watermark_resized[i, j] > 128 else -1)host_watermarked[y:y + block_size, x:x + block_size] = cv2.idct(dct_block)return np.clip(host_watermarked, 0, 255).astype(np.uint8), watermark_resizeddef dct_extract(watermarked_host, target_shape, block_size=8, pos=(3, 4)):"""频域水印提取"""h, w = watermarked_host.shapewm_h, wm_w = h // block_size, w // block_sizeextracted = np.zeros((wm_h, wm_w), dtype=np.uint8)for i in range(wm_h):for j in range(wm_w):block = watermarked_host[i * block_size:(i + 1) * block_size,j * block_size:(j + 1) * block_size].astype(np.float32)coeff = cv2.dct(block)[pos]extracted[i, j] = 255 if coeff > 0 else 0return cv2.resize(extracted, target_shape, interpolation=cv2.INTER_NEAREST)# ---------------------- 主程序(关键修改点)--------------------
if __name__ == "__main__":# 读取水印图像(添加错误检查)watermark_path = "D:/tuxiang/qlu32.bmp"watermark = cv2.imread(watermark_path, cv2.IMREAD_GRAYSCALE)if watermark is None:raise FileNotFoundError(f"水印文件不存在:{watermark_path}")_, watermark_bin = cv2.threshold(watermark, 128, 255, cv2.THRESH_BINARY)# 宿主图像列表host_paths = ['D:/tuxiang/bird.gif','D:/tuxiang/barbara.jpg']# 修改宿主图像读取部分for path in host_paths:# 读取宿主图像if path.endswith('.gif'):try:host = np.array(Image.open(path).convert('L'))except Exception as e:print(f"读取 GIF 失败 [{path}]:{str(e)}")continueelse:# 尝试用 OpenCV 读取host = cv2.imread(path, cv2.IMREAD_GRAYSCALE)if host is None:# 尝试用 PIL 读取try:host = np.array(Image.open(path).convert('L'))except Exception as e:print(f"无法读取图像 [{path}]:{str(e)}")# 输出调试信息import osprint(f"  路径是否存在:{os.path.exists(path)}")print(f"  绝对路径:{os.path.abspath(path)}")if os.path.exists(path):print(f"  文件大小:{os.path.getsize(path)} 字节")continue# 动态调整水印尺寸(添加尺寸验证)try:wm_resized = cv2.resize(watermark_bin, (host.shape[1], host.shape[0]),interpolation=cv2.INTER_NEAREST)except cv2.error as e:print(f"调整水印尺寸失败:{str(e)}")continue# ================= 空域处理 =================host_spatial = spatial_embed(host, wm_resized)extracted_spatial = spatial_extract(host_spatial)psnr_spatial = psnr(host, host_spatial)nc_spatial = nc(wm_resized, extracted_spatial)# ================= 频域处理 =================host_dct, wm_dct = dct_embed(host, wm_resized)extracted_dct = dct_extract(host_dct, wm_resized.shape)psnr_dct = psnr(host, host_dct)nc_dct = nc(wm_resized, extracted_dct)# ================= 显示结果 =================plt.figure(figsize=(15, 8))plt.subplot(231), plt.imshow(host, cmap='gray'), plt.title('原始图像')plt.subplot(232), plt.imshow(host_spatial, cmap='gray'),plt.title(f'空域水印嵌入\nPSNR: {psnr_spatial:.2f} dB')plt.subplot(233), plt.imshow(host_dct, cmap='gray'),plt.title(f'频域水印嵌入\nPSNR: {psnr_dct:.2f} dB')plt.subplot(235), plt.imshow(extracted_spatial, cmap='gray'),plt.title(f'空域水印提取\nNC: {nc_spatial:.4f}')plt.subplot(236), plt.imshow(extracted_dct, cmap='gray'),plt.title(f'频域水印提取\nNC: {nc_dct:.4f}')plt.tight_layout()plt.show()# ================= 噪声测试 =================noisy_spatial = host_spatial + np.random.normal(0, 25, host_spatial.shape).astype(np.uint8)noisy_dct = host_dct + np.random.normal(0, 25, host_dct.shape).astype(np.uint8)noisy_extracted_spatial = spatial_extract(noisy_spatial)noisy_extracted_dct = dct_extract(noisy_dct, wm_resized.shape)nc_spatial_noisy = nc(wm_resized, noisy_extracted_spatial)nc_dct_noisy = nc(wm_resized, noisy_extracted_dct)plt.figure(figsize=(10, 4))plt.subplot(121), plt.imshow(noisy_spatial, cmap='gray'),plt.title(f'加噪空域图像\nNC: {nc_spatial_noisy:.4f}')plt.subplot(122), plt.imshow(noisy_dct, cmap='gray'),plt.title(f'加噪频域图像\nNC: {nc_dct_noisy:.4f}')plt.tight_layout()plt.show()

相关文章:

  • 【SpringBoot】MyBatisPlus(MP | 分页查询操作
  • CSP 2024 提高级第一轮(CSP-S 2024)单选题解析
  • Java异常、泛型与集合框架实战:从基础到应用
  • 用飞帆做一个网页,并假装是自己写的
  • C++跨平台开发:挑战与应对策略
  • 时间筛掉了不够坚定的东西
  • 基于C#的MQTT通信实战:从EMQX搭建到发布订阅全解析
  • 题单:递归求和
  • 复旦微FMQL调试笔记:PS网口
  • 【漫话机器学习系列】263.线性插值(Interpolation)
  • 数据库3——视图及安全性
  • 《算法导论(第4版)》阅读笔记:p82-p82
  • 【Linux网络】ARP协议
  • Redis学习专题(二)事务和锁机制
  • Linux——shell编程
  • 基于 Leaflet 地图库的强大线条、多边形、圆形、矩形等绘制插件Leaflet-Geoman
  • 【背包dp-----分组背包】------(标准的分组背包【可以不装满的 最大价值】)
  • 【双指针】供暖器
  • 2025春训第二十场
  • 【51】快速获取数码管段选表(含小数点)及字母表的工具(分享)
  • 解锁儿时愿望!潘展乐战胜孙杨,全国冠军赛男子400自夺冠
  • 江西4人拟任县(市、区)委书记,其中一人为“80后”
  • 自强!助残!全国200个集体和260名个人受到表彰
  • 深圳南澳码头工程环评将再次举行听证会,项目与珊瑚最近距离仅80米
  • 车建兴被留置:跌落的前常州首富和红星系重整迷路
  • 泰山、华海、中路等山东险企综合成本率均超100%,承保业务均亏损