幻影坦克简介
对于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); AddSuffix (PICB); 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); cout << "DONE" << endl; }
参考教程
『整活』幻影坦克
基础版
介绍几种使用C/C++语言判断一个文件是否存在的方法
从CSDN上搬运的我的博客,并进行更新