如果你还是理解不了,看这个:图中红色虚线全出的部分是超出原图右边界(蓝色激光柱)的,我们将其向左移动,平移到原图内。应当注意的是,这里的“平移”其实是指“对边长取模”,所以不是随便模的。还有一个很重要的一点图中所有的坐标都是刚开始时的编号,而不是真正的坐标。比如,右上角,用红色虚线框起来的格子,原图中$(3,3)$坐标的那个,在现在这张图上的实际坐标其实是$(9,6)$,那么平移后的新坐标必须是$(9 \bmod 4,6 \bmod 4)$,也就是$(1,2)$,哦,在这张图上我们只处理了 x 坐标的情况,y 坐标与之类似。
好了,最终我们能得到如下的图片。
那么这就是简单的 Arnold 变换啦!下面我们动手实践一下吧!
# Arnold 变换
import numpy as np
import cv2 as cv
def catmap(img):
img = np.rot90(img)
img = np.rot90(img) # 原始图片的存储数组是行列不是上文所讲的横纵坐标,需要旋转-90°的转换
img = np.rot90(img)
n = img.shape[0] # n 为图像的边长
result = np.zeros((n, n, 3), dtype=np.uint8) # 结果最开始为空白图像
for x in range(n):
for y in range(n):
result[(2*x + y)%n][(x + y)%n] = img[x][y]
result = np.rot90(result) # 旋转90°将横纵坐标转换为行列
return result
def main():
img = cv.imread('img/city.png') # 读取图片
cv.imshow('result', catmap(img)) # 显示结果
if __name__ == '__main__':
main()
cv.waitKey(0)
显然,一次 Arnold 变换是不够的。如果我们多做几次呢?
可以看到,图像越来越杂乱无章,到最后已经看不出来原图了。
解密与加密基本一致,只不过将过程反着操作。这是刚才加密得到的图片,我们从现在开始以这个为原图。
只不过这一次,我们将每一个像素$(x,y)$移到$(x-y,2y-x)$去。
啊,完好如初,简直就是破镜重圆,覆水再收的感觉。
完整过程如下:
于是我们很容易写出它的代码:
def decode_catmap(img):
img = np.rot90(img)
img = np.rot90(img)
img = np.rot90(img)
n = img.shape[0]
result = np.zeros((n, n, 3), dtype=np.uint8)
for x in range(n):
for y in range(n):
result[(x - y)%n][(2*y - x)%n] = img[x][y]
result = np.rot90(result)
return result
小结
一次 Arnold 变换:对于图像中的每一个像素 $(x,y)$,将其移动至 $(2x+y,x+y)$。
(用线性代数表述:$\begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} 2 & 1 \\ 1 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \end{bmatrix} \mod n$)
容易发现,矩阵$\begin{bmatrix} 1 & -a \\ -b & ab+1 \end{bmatrix}$为$\begin{bmatrix} ab+1 & a \\ b & 1 \end{bmatrix}$的逆矩阵。这一点很好理解。
可以看出,刚才我们所探讨的情况属于$a=b=1$的特殊情况。
好,这样我们就能写出最终的代码了:
# Arnold 变换
import numpy as np
import cv2 as cv
def catmap(img, times=1, a=1, b=1):
for i in range(times):
img = np.rot90(img)
img = np.rot90(img)
img = np.rot90(img)
n = img.shape[0]
result = np.zeros((n, n, 3), dtype=np.uint8)
for x in range(n):
for y in range(n):
result[(a*b*x + x + a*y)%n][(b*x + y)%n] = img[x][y]
result = np.rot90(result)
img = result
return result
def decode_catmap(img, times=1, a=1, b=1):
for i in range(times):
img = np.rot90(img)
img = np.rot90(img)
img = np.rot90(img)
n = img.shape[0]
result = np.zeros((n, n, 3), dtype=np.uint8)
for x in range(n):
for y in range(n):
result[(x - a*y)%n][(a*b*y + y - b*x)%n] = img[x][y]
result = np.rot90(result)
img = result
return result
def main(t, a, b):
img = cv.imread('img/city.png')
coded = catmap(img, t, a, b)
decoded = decode_catmap(coded, t, a, b)
cv.imshow("coded", coded)
cv.imshow("decoded", decoded)
cv.imwrite('temp/output.png', coded)
cv.imwrite('temp/output2.png', decoded)
if __name__ == '__main__':
main(1, 1, 1)
cv.waitKey(0)