[1.0.2]PHP 盲水印库:实现不可见图像水印

迷路的小孩

BlindWatermark

PHP 盲水印库:实现不可见图像水印

GitHub仓库:https://github.com/kid1296/BlindWatermark

License
PHP
Platform

特性对比

指标 旧版 新版
嵌入耗时 (940×940) ~4.9 s ~2.5 s(提速 2 倍)
提取耗时 ~3.8 s ~1.5 s(提速 2.5 倍)
嵌入+提取峰值内存 (940×940) 必须设置 memory_limit=2G ~61 MB(默认 128 MB 即可运行)
嵌入+提取峰值内存 (1358×780) 128 MB 限制下直接 OOM ~81 MB
图像画质 (PSNR, 940×940) ~36.9 dB 35.6 dB
鲁棒性 (椒盐/亮度/遮挡/裁剪) PASS PASS
鲁棒性 (缩放攻击, 普通照片) PASS PASS

PSNR 35–40 dB 在一般照片上被公认为「视觉无损」。
默认 d1/d2 由 36/20 降低为 27/15,在保留对椒盐、亮度、遮挡、裁剪攻击的鲁棒性的前提下,进一步提升画质。

环境要求

  • PHP >= 8.1
  • GD 扩展(多数 PHP 发行版默认已启用)

GD 扩展用于所有图像 I/O(读取 JPEG/PNG、写入 PNG,以及缩放/椒盐噪声/亮度等攻击模拟)。
可通过 php -m | grep gd 检查是否已启用。

安装

composer require kid1296/blind-watermark

使用示例

所有示例均可在 PHP 默认内存限制下直接运行,无需再加 -d memory_limit=2G
(即使是 130 万像素图像,峰值内存也远低于 128 MB)。

01 字符串水印的嵌入与提取

原始图片 + 水印文字 = 携带盲水印的不可见水印图片。

origin.jpg + 'This is a test sentence.'embedded_str.png

完整示例:01_embed_extract_str.php

<?php
require __DIR__ . '/../vendor/autoload.php';
use Kid1296\BlindWatermark\WaterMark;

// ========== 嵌入 ==========
$bwm1 = new WaterMark(passwordWm: 1, passwordImg: 1);
$bwm1->read_img('examples/pics/origin.jpg');
$wm = 'This is a test sentence.';
$bwm1->read_wm($wm, 'str');
$bwm1->embed('examples/output/embedded_str.png');
$lenWm = $bwm1->wmSize;                    // 记得保存这个长度

// ========== 提取 ==========
$bwm2 = new WaterMark(passwordWm: 1, passwordImg: 1);
$extracted = $bwm2->extract(
    filename: 'examples/output/embedded_str.png',
    wmShape:  $lenWm,
    mode:     'str'
);
// $extracted === 'This is a test sentence.'

运行输出:

embed done in 2.51s
Put down the length of wm_bit 191
extract done in 1.49s
extracted: This is a test sentence.
RESULT: PASS

02 位(bit)水印的嵌入与提取

嵌入任意 0/1 数组,适合自定义协议或二进制载荷。

origin.jpg + 1011010011010110embedded_bit.png

完整示例:02_embed_extract_bit.php

<?php
require __DIR__ . '/../vendor/autoload.php';
use Kid1296\BlindWatermark\WaterMark;

$bits = [1,0,1,1,0,1,0,0,1,1,0,1,0,1,1,0];

// 嵌入
$bwm1 = new WaterMark(1, 1);
$bwm1->read_img('examples/pics/origin.jpg');
$bwm1->read_wm($bits, 'bit');
$bwm1->embed('examples/output/embedded_bit.png');
$lenWm = $bwm1->wmSize;

// 提取
$bwm2 = new WaterMark(1, 1);
$extracted = $bwm2->extract(
    filename: 'examples/output/embedded_bit.png',
    wmShape:  $lenWm,
    mode:     'bit'                   // 返回 int[],每个元素为 0 或 1
);
// $extracted === $bits

运行输出:

original bits: 1011010011010110
embed done in 2.64s, wm_size = 16
extract done in 1.55s
extracted bits: 1011010011010110
RESULT: PASS

03 内存中处理(不落盘)

适合 Web 服务场景:从请求/数据库读图→嵌入→直接返回 BGR 数据,全程不写临时文件。

完整示例:03_in_memory.php

<?php
require __DIR__ . '/../vendor/autoload.php';
use Kid1296\BlindWatermark\WaterMark;
use Kid1296\BlindWatermark\Image\ImageIo;

$img = ImageIo::read('examples/pics/origin.jpg');
// $img = ['data'=>string, 'h'=>int, 'w'=>int, 'channels'=>int]

// 嵌入 — 不传入 filename,直接返回内存结果数组
$bwm1 = new WaterMark(1, 1);
$bwm1->read_img($img);                       // 传数组而不是文件路径
$bwm1->read_wm('This is a test sentence.', 'str');
$embedded = $bwm1->embed();                  // 不传 filename → 返回数组
$lenWm = $bwm1->wmSize;
// $embedded 结构:['data'=>string, 'h'=>int, 'w'=>int, 'channels'=>int]
// 可以稍后用 ImageIo::write(...) 落盘,或直接流式返回给客户端。

// 提取 — 传 embedImg 数组(而不是 filename)
$bwm2 = new WaterMark(1, 1);
$extracted = $bwm2->extract(embedImg: $embedded, wmShape: $lenWm, mode: 'str');

运行输出:

host image: 940x940, channels=3
embed done in 2.16s
embedded: 940x940, channels=3, data size = 21206400 bytes
extract done in 1.18s
extracted: This is a test sentence.
RESULT: PASS

04 鲁棒性测试(攻击 / 裁剪还原)

演示水印在常见后处理攻击下的存活能力,以及裁剪 + 参数估计 + 还原的完整流程。

嵌入后的水印图(宿主 = origin.jpg,水印 = 'This is a attack test'):

embedded_attacks.png

各攻击结果图(以下每张图均能成功提取水印):

椒盐噪声 2% 亮度 ×0.9 缩放 940²→800×600
att_sp att_bright att_resize
遮挡 5块@5% 裁剪右侧 20% 裁剪还原后
att_shelter att_crop att_crop_rec

完整示例:04_with_attacks.php

$ php examples/04_with_attacks.php
embedded wm_size = 167

-- attacks --
  salt-pepper 2%                 -> 'This is a attack test' [PASS]
  brightness 0.9                 -> 'This is a attack test' [PASS]
  resize 940x940 -> 800x600 -> back -> 'This is a attack test' [PASS]
  shelter 5 blocks @5%           -> 'This is a attack test' [PASS]

-- crop + recover --
  crop right 20% + recover       -> 'This is a attack test' [PASS]
  estimate_crop_parameters       -> [0,0,752,940] (与期望值一致)  [PASS]

关于缩放攻击的鲁棒性说明:缩放攻击内部使用 GD 的双线性重采样。
对具有良好动态范围的普通照片(如 origin.jpg,均值约 120、标准差约 40)能稳定通过;
对极度均匀/接近纯色的宿主图像(例如均值 > 250/255)可能失败,因为此时水印信号的
相对强度太低,无法经受重采样的低通滤波。这是 DWT-DCT-SVD 盲水印范式的固有局限,
不是本库的 bug。验证缩放鲁棒性请使用 origin.jpg 作为宿主图。

📮联系邮箱

kkkkkkid3172@yeah.net

184 1 0
1个评论

mincon

支持

  • 暂无评论

迷路的小孩

170
积分
0
获赞数
0
粉丝数
2025-12-22 加入
🔝