本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、设计思想
-
概述 利用Arnold变换(又称猫脸变换)可以对图像进行置乱,使得原本有意义的图像变成一张无意义的图像。该变换可以在其它图像处理前对图像做预处理,例如在数字水印嵌入前对水印进行置乱。也可以用于普通的图像加密。 通常一次Arnold变换达不到理想效果,需要对图像进行连续多次的变换。Arnold变换具有周期性,即对图像连续进行Arnold变换,最终又能得到原图像。变换的周期和图像的尺寸有关。 当图像是一张方形的图像时,Arnold变换存在逆变换。经过N次Arnold变换后的数据可以通过N次逆变换恢复数据。 Arnold变换不仅可以用于图像置乱,也可以用于其它数据的置乱和加密。
-
狭义的猫脸变换 2.1 公式 狭义的猫脸变换即最简单的一种变换。网络上绝大部分关于Arnold变换的博客都是狭义Arnold变换。 其矩阵运算公式为:
转化为多项式为:
其中mod()是取模运算,N是正方形图像的边长,(x', y')是像素点(x, y)变换后的坐标。
注意求模运算(mod) ≠ 求余运算(%) 。在被除数是负数时两者存在差别,例如: -5 mod(6) = 1, 但 -5 % 6 = -5。 /**
- 求模运算 */ private int mod(int number, int mod) { return (number % mod + mod) % mod; }
2.2 物理意义和示意图
置乱的实质是新位置与旧位置的映射,且该映射是一一对应的。下图是一次猫脸变换的示意图:
(a)是原图
(b)是先做水平方向的错切
(c)是在(b)的基础上再做一次竖直方向的错切
(d)是对图像求模,即切割回填操作,得到变换后的图像。
如果你想知道为什么要这样变换,为什么是水平错切一个单位,竖直错切两个单位:
实际上这里水平错切的长度是一倍图像的高度,竖直错切的长度是一倍图像的高度加一倍图像的宽度。由于图像的宽高相等,所以这里看起来是水平错切一个单位,竖直两个单位。
为什么这样子错切,是因为置乱的实质是新位置与旧位置的映射,且该映射是一一对应的。
也就是说,其它错切形式可能造成多个点移动到同一个位置,导致图像信息的丢失。例如下面两种错切方式:
其他错切情形
第一种是水平和竖直方向都错切一个单位,第二种是水平一个单位,竖直三个单位。可以看出,取模后两种错切方式都有部分区域重叠了。因此错切的单位是有一定要求的,详见广义的Arnold变换。
- 狭义猫脸变换的逆变换
当一张图片的宽度和高度相同时,Arnold变换具有逆变换。虽然Arnold变换具有周期性,可以通过一直变换下去得到原图,但是周期越长,恢复原图也越长。通过逆变换可以较为方便地把变换后的图像恢复。
3.1 逆变换公式
二、程序清单
狭义猫脸正变换:
ArnoldDisorder.java
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
/**
* @author ArtemisKhryso
* @time 2021/4/21
*/
public class ArnoldDisorder {
public static void main(String[] args) throws IOException {
//变换500次
for (int i = 0; i<500; i++){
anod("img/hui/"+i+".png","img/hui/"+(i+1)+".png");
}
}
public static void anod(String src,String res) throws IOException {
System.out.println(src);
//图像操纵对象
BufferedImage imgSrc = ImageIO.read(new File(src));
//图片类型
int imgType = 5;
int imgHight = imgSrc.getHeight();
int imgWidth = imgSrc.getWidth();
//猫脸矩阵
// int[][] arnold = {{1,1},{1,2}};
//原像素矩阵
int[][] source = new int[imgWidth][imgHight];
//置乱后像素矩阵
int[][] result = new int[imgWidth][imgHight];
//记录原像素信息到source矩阵里
for (int i = 0; i < imgWidth; i++) {
for (int j = 0; j < imgHight; j++) {
source[i][j] = imgSrc.getRGB(i, j);
}
}
//将原像素点移动到猫脸变换后的位置构造新矩阵
for (int i = 0; i < imgWidth; i++) {
for (int j = 0; j < imgHight; j++) {
int x1 = mod((i + j),imgWidth);
int y1 = mod((i + 2*j),imgWidth);
result[x1][y1] = source[i][j];
}
}
//按新矩阵取像素,添加像素点到图片对象相应位置生成新图片
BufferedImage imgRes = new BufferedImage(imgWidth, imgHight, imgType);
for (int i = 0; i < imgWidth; i++) {
for (int j = 0; j < imgHight; j++) {
imgRes.setRGB(i,j,result[i][j]);
}
}
File imgOut = new File(res);
//输出文件
try {
ImageIO.write(imgRes, "png", imgOut);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 求模运算
*/
private static int mod(int number, int mod) {
return (number % mod + mod) % mod;
}
}
狭义猫脸逆变换: ArnoldDisorderNi.java
主体与正变换相同,仅需修改猫脸矩阵部分代码既可,改为狭义猫脸逆变换矩阵
//将原像素点移动到猫脸变换后的位置构造新矩阵
for (int i = 0; i < imgWidth; i++) {
for (int j = 0; j < imgHight; j++) {
int x1 = mod((2*i - j),imgWidth);
int y1 = mod((j - i),imgWidth);
result[x1][y1] = source[i][j];
}
}
三、调试记录
1.首先测试灰度级图片Arnold变换
图片像素为125x125
//变换1次
for (int i = 0; i<1; i++){
anod("img/hui/"+i+".png","img/hui/"+(i+1)+".png");
}
//变换300次
for (int i = 0; i<300; i++){
anod("img/hui/"+i+".png","img/hui/"+(i+1)+".png");
}
测试证明矩阵边长N=125的猫脸变换周期T为250。
2.再测试彩色图片Arnold变换
图片像素为220x220
//变换1次
for (int i = 0; i<1; i++){
anod("img/qid/"+i+".png","img/qid/"+(i+1)+".png");
}
//变换300次
for (int i = 0; i<300; i++){
anod("img/qid/"+i+".png","img/qid/"+(i+1)+".png");
}
测试证明矩阵边长N=220的猫脸变换周期T为30。
3.再测试彩色图片Arnold逆变换
图片像素为220x220
以正变换的随机一张置乱图片为源图
//变换100次
for (int i = 0; i<100; i++){
anod("img/ni/"+i+".png","img/ni/"+(i+1)+".png");
}
测试证明已置乱图片也可通过逆矩阵变换一定次数恢复初始状态。
四、结果及其分析
Arnold变换具有周期性,即经过若干次变换后,矩阵回到最初状态,且周期T与N的大小有关。
Arnold变换直观、简单、具有周期性,使用非常方便。