幻影坦克简介

对于png透明图片,在QQ,贴吧等平台上,看缩略图时默认是以白色为背景,而点开看原图时默认是以黑色为背景,基于此显示逻辑可以实现一种图像,看缩略图时是表图,看原图时是里图,这就是幻影坦克。

基于图像线性融合原理,公式:g(x) = α×f(x) + (1-α)×h(x) ,可以假设最后生成图与白色叠加得到表图,最后生成图与黑色叠加得到里图,套用公式即可得到幻影坦克的理论公式:

公式和原理都源自B站视频: 『整活』幻影坦克 基础版,讲的很细致。


幻影坦克实现过程

先将表图缩放为里图尺寸,再遍历表图和里图的每个像素,算出幻影坦克图对应位置像素的灰度透明度,赋值给一张空图,这张图就是最后的幻影坦克了


Debug记录

需要注意的是,公式里α透明度有一项是表图的灰度值减里图灰度值,必须保证这个值时刻大于0,不然会出现α值大于255的情况,造成错误,即里图显示时出现表图部分,如图:

在我把背景换成橙色以后,图中错误部分也显示为橙色,所以判断为这些地方透明度计算出现问题。

经过初步分析得到:显示错误的地方在原图中的灰度值非常低,导致P表-P里小于0,使α超出上限,造成完全透明。

如何保证P表-P里大于0?我的想法是里图调暗一些,最后保证表图每一像素的灰度大于里图灰度即可。

经过一段时间的调试,我得到了我的解决办法:以里图尺寸为基准,遍历全部像素点,分别读取表图和里图的灰度,将读取到的里图灰度乘0.35,灰度的范围是0-255,因此最大值255*0.35=89.25。对于表图的灰度,如果出现小于上面这个值的灰度,就给他强行赋值为100,这样一定能够保证P表-P里大于0,α正常,里图显示正常。

可能会有人问:改变了表图灰度,会不会对最终结果造成影响?这个问题我还不清楚,但是根据生成的幻影坦克来看,这点影响可以忽略不计。

希望大神指点更好的方法以保证α不出问题。


读取文件夹内图片

解决了最基础的幻影坦克功能问题,接着就解决读取图片问题

图片有许多后缀名,如jpg, png, jpeg, jfif等,虽然我们可以规定表图命名为A,里图命名为B,但是OpenCV读图片必须精确到后缀名

问老师后得到了一个思路:C++检验文件是否存在

查阅他人博客得到了一个简单的方法:用ifstream的good函数可以判断文件是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
#include <fstream>
using namespace std;

bool isFileExists_ifstream(string& name) {
ifstream f(name.c_str());
return f.good();
}
int main()
{
string filename = "1.txt";

bool ret = isFileExists_ifstream(filename);
if (ret)
{
cout << "文件存在" << endl;
}
else
{
cout << "文件不存在" << endl;
}
}

这个方法实现起来比较省事,就采用这种方法

大体思路是检查同一文件夹中是否存在jpg, png, bmp, jfif, jpeg这些常见图片后缀名,再通过string加法将存在的后缀名加到图片名后,因为先前固定了表图命名为A,里图命名为B,所以这样可以直接实现

检查后缀名的总函数采用了C++值传递的特征,传入参数string后,在函数里随便修改也不会影响string本身,于是就利用这点构造了一系列返回值bool的函数,进行文件存在判断,最后再利用C++传入引用的参数,修改string,就得到了实际存在的两张图,再对他们缩放、生成幻影坦克



最后附上源码

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include<opencv2/opencv.hpp>
#include<iostream>
#include<fstream>
#include<string>

using namespace cv;
using namespace std;

bool IsJPG(string name)
{
name += ".jpg";
ifstream f(name.c_str());
return f.good();
}
bool IsPNG(string name)
{
name += ".png";
ifstream f(name.c_str());
return f.good();
}
bool IsBMP(string name)
{
name += ".bmp";
ifstream f(name.c_str());
return f.good();
}
bool IsJFIF(string name)
{
name += ".jfif";
ifstream f(name.c_str());
return f.good();
}
bool IsJPEG(string name)
{
name += ".jpeg";
ifstream f(name.c_str());
return f.good();
}

void AddSuffix(string& name)
{
if (IsPNG(name))
{
name += ".png";
return;
}
if (IsJPG(name))
{
name += ".jpg";
return;
}
if (IsJPEG(name))
{
name += ".jpeg";
return;
}
if (IsBMP(name))
{
name += ".bmp";
return;
}
if (IsJFIF(name))
{
name += ".jfif";
return;
}
}

int main()
{
string PICA = "A";
string PICB = "B";
AddSuffix(PICA); //给A图添加后缀名
AddSuffix(PICB); //给B图添加后缀名

Mat A_origin = imread(PICA, 0);
Mat A;
Mat B = imread(PICB, 0);

resize(A_origin, A, B.size()); //将表图按里图尺寸缩放

Mat C = Mat::zeros(B.size(), CV_8UC4); //生成一张空图,尺寸为里图尺寸

int PA, PB, PC;
int Alpha;

for (int i = 0; i < B.rows; i++)
{
for (int j = 0; j < B.cols; j++)
{
PA = (int)A.at<uchar>(i, j);
PB = ((int)B.at<uchar>(i, j)) * 0.35;

if (PA < 100) //解决阈值问题的关键
{
PA = 100;
}

Alpha = 255 - (PA - PB); //遍历每一个像素点,获得对应参数

if (Alpha == 0)
{
Alpha = 1;
}

PC = (int)(255 * PB / Alpha); //按公式计算出幻影坦克对应位置像素的灰度值


C.at<Vec4b>(i, j) = Vec4b(PC, PC, PC, Alpha); //赋值给空图

}
}

imwrite("Mirage Tank.png", C); //储存为png的幻影坦克

cout << "DONE" << endl;

}





参考教程

『整活』幻影坦克 基础版

介绍几种使用C/C++语言判断一个文件是否存在的方法

从CSDN上搬运的我的博客,并进行更新