基于 DWT 的盲数字水印实现(嵌入与提取)

一、原理

盲数字水印(Blind Watermarking)指提取水印时无需原始载体图像,仅依靠含水印图像和密钥即可完成。

DWT(离散小波变换) 将图像分解为:

  • LL:低频近似分量(能量集中,稳定性强,适合嵌入水印)
  • LH/HL/HH:高频细节分量(边缘/纹理,不适合嵌入,易失真)

嵌入逻辑 :修改 LL 分量的系数(如奇偶性、幅度偏移),隐藏水印信息;
提取逻辑:对含水印图像做相同 DWT 分解,从 LL 分量反推水印信息。


二、完整 C# 实现(使用 AForge.NET

2.1 环境配置

xml 复制代码
<!-- NuGet 依赖 -->
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
<PackageReference Include="AForge.Math" Version="2.2.5" />

2.2 核心类:DwtBlindWatermark.cs

csharp 复制代码
using AForge.Imaging;
using AForge.Imaging.Filters;
using AForge.Math;
using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace DwtWatermark
{
    public class DwtBlindWatermark
    {
        // 配置参数(嵌入/提取必须一致!)
        private const string WaveletName = "haar"; // 小波基(haar 计算最快)
        private const int DecompositionLevels = 1; // DWT 分解层数(1层足够)
        private const double EmbedStrength = 0.05; // 嵌入强度(0.01~0.1,平衡不可见性)
        private const int WatermarkSize = 32; // 水印尺寸(32x32 二值图)

        /// <summary>
        /// 嵌入水印(盲水印:仅修改 LL 分量)
        /// </summary>
        public Bitmap EmbedWatermark(Bitmap carrier, Bitmap watermark, int secretKey)
        {
            // 1. 预处理:转为灰度图
            var grayCarrier = Grayscale.CommonAlgorithms.BT709.Apply(carrier);
            var grayWatermark = Grayscale.CommonAlgorithms.BT709.Apply(watermark);

            // 2. 载体 DWT 分解
            ComplexImage complexCarrier = ComplexImage.FromBitmap(grayCarrier);
            complexCarrier.ForwardDWT(WaveletName, DecompositionLevels);
            Complex[,] ll = complexCarrier.Image; // LL 分量(复数数组,取实部)

            // 3. 水印二值化并转为序列
            bool[,] watermarkBits = BinarizeWatermark(grayWatermark);
            int bitIndex = 0;

            // 4. 嵌入水印(修改 LL 系数的奇偶性)
            Random rnd = new Random(secretKey);
            int stride = ll.GetLength(1);
            for (int y = 0; y < ll.GetLength(0); y++)
            {
                for (int x = 0; x < ll.GetLength(1); x++)
                {
                    if (bitIndex >= WatermarkSize * WatermarkSize) break;

                    // 随机选择 LL 系数(增强安全性)
                    int rx = rnd.Next(ll.GetLength(0));
                    int ry = rnd.Next(ll.GetLength(1));
                    double coeff = ll[rx, ry].Re;

                    // 嵌入规则:水印位 1 → 系数调整为奇数;0 → 偶数
                    bool watermarkBit = watermarkBits[bitIndex / WatermarkSize, bitIndex % WatermarkSize];
                    double modifiedCoeff = coeff;
                    if (watermarkBit)
                        modifiedCoeff = Math.Ceiling(coeff) % 2 == 0 ? coeff + EmbedStrength : coeff;
                    else
                        modifiedCoeff = Math.Floor(coeff) % 2 == 1 ? coeff - EmbedStrength : coeff;

                    ll[rx, ry] = new Complex(modifiedCoeff, ll[rx, ry].Im);
                    bitIndex++;
                }
            }

            // 5. 逆 DWT 重构含水印图像
            complexCarrier.BackwardDWT(WaveletName, DecompositionLevels);
            return complexCarrier.ToBitmap();
        }

        /// <summary>
        /// 提取水印(无需原始载体)
        /// </summary>
        public Bitmap ExtractWatermark(Bitmap watermarkedCarrier, int secretKey)
        {
            // 1. 预处理
            var grayCarrier = Grayscale.CommonAlgorithms.BT709.Apply(watermarkedCarrier);

            // 2. 含水印图像 DWT 分解
            ComplexImage complexCarrier = ComplexImage.FromBitmap(grayCarrier);
            complexCarrier.ForwardDWT(WaveletName, DecompositionLevels);
            Complex[,] ll = complexCarrier.Image;

            // 3. 提取水印比特
            bool[,] extractedBits = new bool[WatermarkSize, WatermarkSize];
            Random rnd = new Random(secretKey);
            int bitIndex = 0;

            for (int y = 0; y < ll.GetLength(0); y++)
            {
                for (int x = 0; x < ll.GetLength(1); x++)
                {
                    if (bitIndex >= WatermarkSize * WatermarkSize) break;

                    int rx = rnd.Next(ll.GetLength(0));
                    int ry = rnd.Next(ll.GetLength(1));
                    double coeff = ll[rx, ry].Re;

                    // 提取规则:系数为奇数 → 1;偶数 → 0
                    extractedBits[bitIndex / WatermarkSize, bitIndex % WatermarkSize] = 
                        Math.Abs(coeff % 2) > 0.5;
                    bitIndex++;
                }
            }

            // 4. 比特数组转水印图像
            return BitsToBitmap(extractedBits);
        }

        #region 辅助方法
        // 水印二值化(阈值 128)
        private bool[,] BinarizeWatermark(Bitmap watermark)
        {
            bool[,] bits = new bool[WatermarkSize, WatermarkSize];
            Bitmap resized = new ResizeNearestNeighbor(WatermarkSize, WatermarkSize).Apply(watermark);
            for (int y = 0; y < WatermarkSize; y++)
                for (int x = 0; x < WatermarkSize; x++)
                    bits[y, x] = resized.GetPixel(x, y).R > 128;
            return bits;
        }

        // 比特数组转图像
        private Bitmap BitsToBitmap(bool[,] bits)
        {
            Bitmap bmp = new Bitmap(WatermarkSize, WatermarkSize);
            for (int y = 0; y < WatermarkSize; y++)
                for (int x = 0; x < WatermarkSize; x++)
                    bmp.SetPixel(x, y, bits[y, x] ? Color.White : Color.Black);
            return bmp;
        }
        #endregion
    }
}

2.3 主程序测试(Program.cs

csharp 复制代码
using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace DwtWatermark
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("=== DWT 盲数字水印测试 ===\n");

            // 1. 读取图像(替换为你的路径)
            Bitmap carrier = new Bitmap("carrier.jpg"); // 载体图像(如风景照)
            Bitmap watermark = new Bitmap("logo.png");   // 水印图像(32x32 二值图)
            int secretKey = 123456; // 密钥(嵌入/提取必须一致!)

            var watermarker = new DwtBlindWatermark();

            // 2. 嵌入水印
            Console.WriteLine("嵌入水印...");
            Bitmap watermarked = watermarker.EmbedWatermark(carrier, watermark, secretKey);
            watermarked.Save("watermarked.jpg", ImageFormat.Jpeg);
            Console.WriteLine("含水印图像保存至:watermarked.jpg\n");

            // 3. 提取水印
            Console.WriteLine("提取水印...");
            Bitmap extracted = watermarker.ExtractWatermark(watermarked, secretKey);
            extracted.Save("extracted_watermark.jpg", ImageFormat.Jpeg);
            Console.WriteLine("提取的水印保存至:extracted_watermark.jpg\n");

            // 4. 评估(PSNR:峰值信噪比,>30dB 说明不可见性好)
            double psnr = CalculatePSNR(carrier, watermarked);
            Console.WriteLine($"载体与含水印图像的 PSNR: {psnr:F2} dB");
            Console.WriteLine("(PSNR > 30dB 表示水印不可见)");

            Console.WriteLine("\n测试完成!按任意键退出...");
            Console.ReadKey();
        }

        // 计算 PSNR(评估不可见性)
        static double CalculatePSNR(Bitmap original, Bitmap compressed)
        {
            double mse = 0;
            for (int y = 0; y < original.Height; y++)
            {
                for (int x = 0; x < original.Width; x++)
                {
                    Color c1 = original.GetPixel(x, y);
                    Color c2 = compressed.GetPixel(x, y);
                    mse += Math.Pow(c1.R - c2.R, 2) + Math.Pow(c1.G - c2.G, 2) + Math.Pow(c1.B - c2.B, 2);
                }
            }
            mse /= (original.Width * original.Height * 3);
            return 10 * Math.Log10(255 * 255 / mse);
        }
    }
}

三、参数说明

参数 作用 建议值
WaveletName 小波基 haar(计算快,稳定性好)
DecompositionLevels DWT 分解层数 1(层数过多易失真)
EmbedStrength 嵌入强度 0.01~0.1(越大鲁棒性越强,但易失真)
secretKey 密钥 整数(嵌入/提取必须一致)

参考 盲水印嵌入提取 www.youwenfan.com/contentcsu/63122.html

四、鲁棒性测试

对含水印图像进行以下攻击,验证水印是否可提取:

  1. JPEG 压缩(质量 50%)→ 水印应仍可识别;
  2. 高斯噪声(均值 0,方差 0.01)→ 轻微影响;
  3. 裁剪(裁剪 10% 边缘)→ 依赖 LL 分量,仍可能提取;
  4. 旋转(±5°)→ 需结合几何校正(本示例未实现,可扩展)。
相关推荐
鱼子星_1 小时前
最短路问题【图论】
数据结构·算法·贪心算法·动态规划·图论
研究点啥好呢1 小时前
dji机器人SLAM算法工程师 面试题精选:10道高频考题+答案解析
c++·算法·机器人·slam·dji
君万1 小时前
【LeetCode每日一题】3. 无重复字符的最长子串 560. 和为 K 的子数组
算法·leetcode·golang·go
代码地平线1 小时前
【排序】C语言实现八大排序算法(含完整源码与性能测试)
c语言·算法·排序算法
承渊政道1 小时前
【贪心算法】(经典实战应用解析(一):柠檬水找零、将数组和减半的最少操作次数、最大数、摆动序列)
数据结构·c++·学习·算法·leetcode·贪心算法·排序算法
初心未改HD1 小时前
机器学习之支持向量机SVM详解
算法·机器学习·支持向量机
he___H1 小时前
子串----
java·数据结构·算法·leetcode
05候补工程师2 小时前
【ROS 2 避坑指南】从 SLAM 实时建图到 Nav2 导航算法深度调优全过程
算法·ubuntu·机器人
Dlrb12113 小时前
C语言-函数传参
c语言·数据结构·算法