因为前段时间跟CG方面打了不少交道,所以产生了今天的问题.
对于视频的合成,我们可能都需要用到很多地方的素材,来源于各个地方,包括电视台,大部分素材都有一个共同点,都加了水印或台标.可以想像,在制作我们自己的作品时,肯定不能出现别人的标志,所以我们得去除原来的水印(注:在不侵犯别人的权利的前提下).今天我们只讨论对简单水印的处理.
先看一张加了水印的图:
<img src=http://www.bezier.com.cn/study/1.jpg>
其实我们可以看出,这个最终的图(C)其实是用另一张标志图(B)与原图(A)叠加而得到(叠加的方式这里就不作讨论,有兴趣的可以去参看参考书),叠加时赋予了一定的透明度(Tran).
这时,我们可以大概的形成一个公式:
A+ B*Tran=C
但是按此公式的话,原图像没有任何修改,标志图的象素值全部叠加原图像上,最张图像应该变亮,而事实上却没有,那肯定原图像在叠加过程中也有了一定的衰减过程(Atten)
即:
A*Atten + B*Tran=C
推测标志图增加了多少值,原图像应该减少多少,即Atten=1-Tran.
经测试确实是如此(测试过程省略)
即A*(1-Tran) + B*Tran=C
由此,我们如果想把加过水印的图像恢复到原样,只需要:
A=(C-B*Tran)/(1-Tran).
公式已经出来了,下面我们就开时做了。
这时候我们引入一个常用且普通的图像文件类型:TGA
这种文件格式简单,它由头文件、数据文件、附加文件构成。
TGA头文件结构
struct TargaHeader
{
BYTE IDLength;
BYTE ColormapType;
BYTE ImageType;//如果为02则表示未压缩,(A0)表示压缩
BYTE ColormapSpecification[5];
WORD XOrigin;
WORD YOrigin;
WORD ImageWidth;//图像宽度
WORD ImageHeight;//图像高度
BYTE PixelDepth;//图像色彩位数
BYTE ImageDescriptor;//应该是表示图像反正的,08表示反,其他表示正(或者反着来)
} tga;
//总共18个字节
我们进行处理时,只要先跳过前18个字节,直接对其数据部分进行处理。数据部分是按BGRA来排列,即蓝色、绿色、红色、Alpha通道值来分布。所以我们要处理的数据部分的大小为ImageWidth* ImageHeight*4。之所以用这种文件格式,因为其格式简单,同时,这种文件包含了图像的Alpha通道信息,有了这层Alpha值信息,我们可很容易的把图像中所需要的部分提取出来,这里就不多讲了,有兴趣的朋友可以跟我联系。
现在我们来读原图像文件(代码中,padfile,tran都是另外定义):
int RemoveMask(char *filename)
{
//这里读入加了水印的图像
FILE *fp=fopen(filename, "rb");
fseek(fp, 0, SEEK_END);
int len=ftell(fp);
unsigned char *tgadata;
tgadata=new unsigned char[len+1];
tgadata[len]=0;
fseek(fp, 0, SEEK_SET);
fread(tgadata, len, 1, fp);
fclose(fp);
if(!((tgadata[16]==24 || tgadata[16]==32) && (tgadata[2]==2 || tgadata[2]==10)))//判断图像色彩位数和压缩属性是否正确
return 0;
int sx=*((unsigned short *)&tgadata[12]);//获得图像x大小
int sy=*((unsigned short *)&tgadata[14]);// 获得图像y大小
///这里读入单纯的水印图像文件。这里不免有人问,这个文件哪来呢?本例子中因为是我自己做的,所以有原始的标志文件,但用别人的加了水印的文件时,哪来的这个文件呢?我这里只可以说的是,可以用Photoshop做一个,当然怎么做这些问题我就不提了。
FILE *fp2=fopen(psdfile, "rb");
fseek(fp2, 0, SEEK_END);
int len2=ftell(fp2);
unsigned char *psddata;
psddata=new unsigned char[len2+1];
psddata[len2]=0;
fseek(fp2, 0, SEEK_SET);
fread(psddata, len2, 1, fp2);
fclose(fp2);
if(!((psddata[16]==24 || psddata[16]==32) && (psddata[2]==2 || psddata[2]==10)))
return 0;
//////////////去除标志运算//////////////////////////////
int pixel=1;
int allpixel=sx*sy;
for(int j=18;j<allpixel*4+18;j+=4)//跳过头文件进行检测,每四个象素值为一个单元
{
if(psddata[j+3]!=0)//此值为通道值,如果通道值为0,表示该单元的色彩信息是不显示的,这里就涉及到了刚才所说的Alpha值的应用
{
pixel=(int)((tgadata[j]-psddata[j]*tran*psddata[j+3]/255)/(1-tran*psddata[j+3]/255));//这里就为刚才的公式的应用,对原象素的值进行还原,依次对BGA各值进行还原,此处为蓝色值
if(pixel>255)
tgadata[j]=255;
else if (pixel<0) tgadata[j]=0;
else
tgadata[j]=(unsigned char)pixel;
/////////GGGG////////
pixel=(int)((tgadata[j+1]-psddata[j+1]*tran*psddata[j+3]/255)/(1-tran*psddata[j+3]/255));// 此处为绿色值
if(pixel>255)
tgadata[j+1]=255;
else if (pixel<0) tgadata[j+1]=0;
else
tgadata[j+1]=(unsigned char)pixel;
pixel=(int)((tgadata[j+2]-psddata[j+2]*tran*psddata[j+3]/255)/(1-tran*psddata[j+3]/255)); //此处为红色值
if(pixel>255)
tgadata[j+2]=255;
else if (pixel<0) tgadata[j+2]=0;
else
tgadata[j+2]=(unsigned char)pixel;
}
}
//////////////////保存为文件////////////////////////////////
if ((fp=fopen(filename,"wb"))!=0)
{
fwrite(tgadata,1,len,fp);//每个值已经进行了还原,写入覆盖原文件
}
fclose(fp);
delete []tgadata;
delete []psddata;
return 1;
}
<img src=http://www.bezier.com.cn/study/2.jpg>
值得说明的是,此法只针对用图像进行叠加所产生的水印,如用Photoshop或后期软件进行轨道的合成,比如Adobe Premiere, Sony Vegas等。在对视频文件时,我们可以用这些软件把其导出成TGA序列,然后对每帧序列文件进行处理,最后再合成,就形成了原始的视频文件了。
同时,由公式可以看出,当水印是不用透明度,完全叠加时,就失去原图的信息,此法就不合适了.同时,水印透明度的指定也是先依照估计,然后逐渐取最近值的做法
警告:此法只供学习交流使用,禁止用于非法途径,尊重知识产权!
有兴趣的朋友可以跟我继续交流。大家也可以尝试采用BMP,JPG文件结构。