幻影坦克原理解析


PhantomTank Image 幻影坦克


什么是幻影坦克

幻影坦克是一类隐写图片,当放置它在白色背景下会显现出一张图,当放置在黑色背景下会显现出另一张图片。如下:

白色背景下效果

黑色背景下效果

原理

核心是利用透明度使得不同背景色下显示的效果不同,其本质上是数学问题。

引入:设背景色为,某个像素格的灰度,透明度为,我们易知肉眼实际看到的颜色

根据上式,我们可以知道,y是关于的三元函数。对于一张已经做好的幻影坦克图片,$x$和$alpha$是固定的,这样y就只是关于的函数。在黑白背景下我们有不同的显示效果,可以归纳为下面两式子:

在程序中灰度和透明度一般为0~255,如果我们把透明度转为0~1,即透明度/255,有固定为1,固定为0,上式可以转化为:

如果我们需要用图片1和图片2制作幻影坦克,那么实际上制作的过程可以等价于已知上式方程组的,求解

运用二年级学过的代入法解出结果为:

这样我们就得到了需要制作的图片的灰度值和透明度,不过还需要注意的是因为我们前面把透明度置换到0~1,所以得出的要限制范围在0~1间,有

化简得到

如果不对输入的两张图片进行处理,则灰度值不一定满足上面的不等式,我们需要变换两张图片使得对应位置的灰度恒有背景图的灰度大于前景图的灰度,其中的一个方法就是区间变换,即把灰度分别变到区间[0,127]和[128,255]:

实际操作中我们的值区间在,所以还需要把解出的乘255得到

最终我们得到完整的操作过程:

  1. 给前景图某个像素格的灰度,背景图灰度,进行区间变换:
  1. 计算新的灰度和透明度

对整个图像矩阵进行遍历,最后保存灰度矩阵和透明度矩阵为png格式图片即可

使用

github仓库:不放了,小东西直接扔最后吧

1
2
3
4
5
6
7
python phantom.py -f <faceImage> -b <backImage> -o <outputImage> -w <outputWidth> -h <outputHeight>

//faceImage:前景图路径
//backImage:背景图路径
//outputImage: 输出路径,拓展名请写png
//w:可选,输出文件的宽(px)
//h:可选,输出文件的高(px)

示例

1
py -3 phantom.py -f img1.jpg -b img2.jpg -o res.png

拓展

在如何满足不等式时,我们选择把灰度变换到均分的两个区间,实际上可以随意划分两个区间,比如[0,63]和[64,255],分割比例是,即为整数时,有

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import cv2 as cv2
import numpy as np
import sys, getopt

class PhantomTank():
def __init__(self, face_path, back_path):
'''
image1 is the front face of the image
image2 is the back face of the image
'''
self.img1 = cv2.imread(face_path, cv2.IMREAD_GRAYSCALE)
self.img2 = cv2.imread(back_path, cv2.IMREAD_GRAYSCALE)

def make(self, output_path, shape=None):
print("making...wait~")
m,n = self.resize(shape=shape)
alpha = np.zeros(self.img2.shape, dtype=np.uint8)
gray = np.zeros(self.img2.shape, dtype=np.uint8)
for i in range(m):
for j in range(n):
alpha[i, j], gray[i, j] = self.pixel(self.img1[i, j], self.img2[i, j])
res = cv2.merge((gray, gray, gray, alpha))
cv2.imwrite(output_path, res)
print("finish! saved at " + output_path)

def pixel(self, a, b):
'''calculate new pixel value and alpha value'''
ca = a / 255*127 + 128
cb = b / 255*127
alpha = cb - ca + 255
gray = 0 if alpha == 0 else (cb * 255 / alpha)
return alpha, gray

def resize(self, shape=None):
'''reshape images'''
m, n = self.img2.shape
if not shape==None:
self.img1 = cv2.resize(self.img1, shape)
self.img2 = cv2.resize(self.img2, shape)
return shape[1],shape[0]
else:
self.img1 = cv2.resize(self.img1, (n, m))
return m,n

def get_args(argv):
face = ''
back = ''
output = ''
h=0; w=0; shape = None
try:
opts, args = getopt.getopt(argv,"-f:-b:-o:-w:-h:", ["help", "face=","back=", "output=", "w=", "h="])
except getopt.GetoptError:
print('phantom.py -f <faceImage> -b <backImage> -o <outputImage> -w <outputWidth> -h <outputHeight>')
sys.exit(2)
for opt, arg in opts:
if opt in ("-f", "--face"):
face = arg
elif opt in ("-b", "--back"):
back = arg
elif opt in ("-o", "--output"):
output = arg
elif opt in ("-w", "--w"):
w = int(arg)
elif opt in ("-h", "--h"):
h = int(arg)
if not (w == 0 or h ==0):
shape = (w, h)
return face, back, output, shape
if __name__ == "__main__":
face, back, output, shape = get_args(sys.argv[1:])
pt = PhantomTank(face, back)
pt.make(output, shape)