图片混淆
打乱像素位置保护隐私,混淆两次可还原原图
1. Hilbert 空间填充曲线
Hilbert 曲线是一条连续的空间填充曲线,将一维索引 d ∈ [0, N²) 映射到二维坐标 (x, y) ∈ [0, N)²。 其关键性质是局部保持性:曲线上相邻的点在 2D 空间中也倾向于相邻。
坐标转换公式(Skilling 算法):
2. 对合排列(Involutive Permutation)
排列 σ 满足 σ(σ(i)) = i,即执行两次等于恒等。 构造方法:先 Fisher-Yates 洗牌得到随机序列,再两两配对交换。
正确性:每个交换 (a b) 满足 (a b)(a b) = id, 整个排列是不相交交换的组合,因此 σ² = id。
3. 填充与裁剪
将图像填充到 128 的整数倍后处理,输出时裁剪回原始尺寸。 这确保所有像素都被完整块覆盖,没有未处理的边缘。
填充不改变原始像素(填黑色),裁剪去掉填充像素。 由于排列仅在原始像素范围内操作,填充区域的黑色像素不参与置换。
4. 对合性证明
设 F 为混淆函数,F = crop ∘ obfuscatePadded ∘ pad。
对合性要求 F(F(x)) = x:
pad和crop互为逆操作obfuscatePadded使用对合排列 σ,满足σ² = id- 每个块独立处理,块间无依赖
- 排列仅在原始像素范围内操作,填充区域不参与
因此 F(F(x)) = crop ∘ pad ∘ crop ∘ obfuscatePadded ∘ pad ∘ obfuscatePadded ∘ pad(x) = x。 ∎
5. 种子与确定性
种子计算:seed = width × 114514 + height × 1919810
每个块的子种子:blockSeed = seed + bx × 7 + by × 13
确定性保证:同尺寸图像的相同块总是使用相同的排列。不同尺寸产生不同的排列。
为什么用 Hilbert 曲线?
Hilbert 曲线具有局部保持性:曲线上相邻的点在 2D 空间中也相邻。 这意味着混淆后的图像在视觉上呈现有机的流动扭曲效果,而非随机噪声。 相比之下,随机像素置换会产生类似电视雪花的无意义噪声。
为什么用对合排列?
对合排列 σ² = id 使得混淆和反混淆使用完全相同的函数。 不需要单独实现反混淆逻辑,减少了代码量和出错概率。 用户只需对混淆图再次执行混淆即可还原。
为什么分 128×128 块?
Hilbert 曲线要求块尺寸为 2 的幂。128 = 2⁷ 是合适的折中: 块足够大以产生明显的视觉混淆效果,又足够小使得每个块的排列生成开销可控(128² = 16384 个元素)。
为什么不考虑裁剪后反混淆?
裁剪后反混淆需要知道裁剪偏移和原始尺寸。这些信息必须从混淆图中提取(标记像素)或由用户提供。 标记像素会污染混淆图的视觉效果,而依赖用户输入增加了使用复杂度。 实际场景中,裁剪混淆图的需求极少——用户通常要么保存整张混淆图,要么在混淆前先裁剪。
为什么使用固定种子?
使用基于图像尺寸的确定性种子,保证同一张图每次混淆结果一致。 不依赖外部密钥,用户无需记忆或管理密码。 缺点是相同尺寸的不同图像使用相同的排列模式(但块内排列不同,因为像素值不同)。
✅ 特征
- 精确对合:混淆两次 = 还原原图(无信息损失)
- 所有像素都被混淆,包括图像边缘
- 色彩保留:块平均色彩不变,整体色调反映原图
- Hilbert 曲线的局部保持性产生有机扭曲效果
- 处理速度快:纯像素位置置换,无复杂计算
⚠️ 限制
- 不支持裁剪后反混淆(需要原始尺寸和偏移信息)
- 不支持加密(算法目标是混淆,不是保密)
- 块边界可能产生轻微的视觉接缝
🛡️ 抗干扰能力
- PNG(无损):完全无影响,精确还原
- JPEG Q90+:几乎完美还原
- JPEG Q80:轻微噪声,可接受
- JPEG Q60:明显噪声,原图可辨认
- WebP:同质量下优于 JPEG
- 亮度/对比度调整:全局调整后仍可还原
- 随机噪声:噪声被重新分布到全图,不被消除
- 缩放/裁剪:不可逆(像素信息丢失)