之前看到一篇技术性的文章有提到:隐性水印的概念。也有叫盲水印的

你还在傻乐,别人已经顺着网线找上了门

https://ningbocat.com/post/1607.html?page=3

就专门查找并下载了三个软件试用现在全网差不多能做隐水印的也就这些了。

我试了一款真正能还原的就一款:2.  数字盲水印&隐形水印制作工具Watermark   还不支持所有长宽比例的图片或是批量文件的处理,不知道以后会不会有大神出新款。

我简单测试了一下:https://ningbocat.com/post/1613.html?page=7


总共我找到的是以下三款:


1. ImageSigner(频域水印签名工具) v1.0 2018最新版 含完整源代码


下载地址:

ImageSigner: https://url55.ctfile.com/d/14928255-51482991-bf9871?p=7242

(访问密码:7242)



网上的介绍已经很全面了我先引用:

mageSigner是一款功能强大的频域水印签名工具,采用信号处理的方法在数据中嵌入隐蔽标记,无论你是旋转、剪切、缩放、涂抹,都无法破坏图片中的隐藏水印,使用能够帮助用户快速给你的原创图片添加版权隐形水印,防止被他人盗图,签名后的图片与原始图片表面上没有任何区别,但是无论盗图的人怎么修改,隐形水印都无法去除,可以检测出来,非常不错,软件绿色无需安装,解压即可使用


1.什么是频域
现在你看到的这个图片就是空域(左图),和它对应的频域(有图)


频域水印简单而言就是将水印加到该图的频域中


下图是已经进行签名后的图片(已经将水印加在红色通道),和原始图片比较,基本没啥差别


2.功能特色

这个是重点,现在对签名后的图像改改(左图)然后再看它水印怎么样了
1.


2.

3.


4.

 
5.
 

简单来说给你的图片加版权水印,防止盗图狗侵权

3.使用教程


step1.加载原始图片
115834irslpoqx0qqpopxx.gif 

step2.加载签名图片


115834irslpoqx0qqpopxx.gif 


step3.签名

115834irslpoqx0qqpopxx.gif 

step4.点击save to file保存你的签名图片



4.下载地址

   

   github 开源地址:https://github.com/matrixcascade/ImageSigner  (编译环境 visual studio 2010+ Qt4.8.6)


  Release 版本下载: https://github.com/matrixcascade/ImageSigner/tree/master/Win32/Release


  Release 版本+Qt4.8.6 运行库:https://pan.baidu.com/s/1Uf3lNE2zC9fhBV6A5P3EdA

5.使用说明

1.算法使用的是基2FFT,因此图像的长度和宽度必须是以2为基数的指数,如256 ,512 ,1024,2048....
2.签名图长宽必须是源图长宽的一半,这个和频域共轭对称性相关
3.水印图尽量使用右下角部分,因为图像的重要频率主要在低频部分(左上角),高频(右下角)不容易对源图造成太大干扰

4.power越大,图像抗攻击能力越强,与此同时的,对源图的影响也越大


    2.  数字盲水印&隐形水印制作工具Watermark 

    作者之前发在这里:https://www.52pojie.cn/thread-709668-1-1.html

         本软件可以通过对图像添加肉眼看不见的水印,便于追溯图片来源
           原理是在频域添加数字水印,具有一定的隐秘性,抗破坏能力,且基本不破坏原图图像。灵感来源于前段时间阿里凭截图查到了月饼事件的泄密者,才发现,哇原来还有这种操作!自己在想,如果在传身份证这类比较隐私的图片之前打上对应平台的隐形水印,以后有什么问题也好知道是谁泄露的。
          可以添加普通水印和盲水印,添加盲水印的原图长和宽最好是2的N次方,如1024*1024,1024*512等,否则添加水印的时候会自动调整,如果添加水印后图片干扰严重,可以尝试降低水印强度,提取水印时可以调整水印的亮度,运行程序需要.net framework 4.5
          一、软件界面
          163504gfos3ouzey4ho505.png 
          二、破坏测试
           349293-20181210180803107-1471114347.jpg 
          349293-20181210180803107-1471114347.jpg 
          349293-20181210180803107-1471114347.jpg 
          349293-20181210180803107-1471114347.jpg 
          349293-20181210180803107-1471114347.jpg


    最新版本下载 :WaterMake 2018 V10.62 最新版

    http://www.greenxf.com/soft/222101.html



    3.有意思的数字盲水印的简单的实现 SSE_Optimization_Demo

    https://blog.csdn.net/weixin_33774308/article/details/86265544

      早期大约是10年前从一本数字图像处理上看到过数字水印的概念,觉得确实一种很有意思的东西,那个时候主要就是基于LSB的图像信息的隐藏,这种在空域里的方法有较大的缺陷,鲁棒性是比较差的。随便一个后期的都会造成水印的丢失,因此,虽然是一种盲水印,但是不具有很好的推广性。

      前段时间一个朋友给了我一段使用Opencv的盲水印代码,是基于FFT变换的, 抽空看了下,对其中部分的实现过程进行了替换和分解,也实现了一个最简单的基于频域的盲水印效果。

      我在寻找相关资料的时候在网络上看到有几个这方面的文章和工具,现在分享如下:

      https://blog.csdn.net/chenxiao_ji/article/details/52875199https://blog.csdn.net/chenxiao_ji/article/details/52875199

      https://www.sdbeta.com/wg/2018/0903/225358.html

           https://blog.csdn.net/weiyiweiyiweiyiyi/article/details/82847756

           https://blog.csdn.net/linyacool/article/details/71506638

      好像还有一个写的比较详细,而且有工具,在github上也有分享代码。

      但是似乎这些工具大部分只支持文字水印,而不支持图像水印,文字我不熟悉,因此我还是用图像做水印模板,核心的代码如下所示:

    int IM_AddBlindWaterMark(unsigned char *Src, unsigned char *WaterMark, unsigned char *Dest, int Width, int Height, int Stride, int WidthW, int HeightW, int StrideW)
    {    int Channel = Stride / Width, ChannelW = StrideW / WidthW;    if ((Src == NULL) || (Dest == NULL))                        return IM_STATUS_NULLREFRENCE;    if ((Width <= 0) || (Height <= 0))                            return IM_STATUS_INVALIDPARAMETER;    if ((Channel != 1) && (Channel != 3) && (Channel != 4))        return IM_STATUS_INVALIDPARAMETER;    if ((ChannelW != 1) && (ChannelW != 3) && (ChannelW != 4))    return IM_STATUS_INVALIDPARAMETER;    if ((WidthW >= Width / 4) || (HeightW >= Height / 4))        return IM_STATUS_INVALIDPARAMETER;        //    水印图不能大于原图尺寸的一半
                
        int Status = IM_STATUS_OK;    
        int OptimalW = IM_GetOptimalDftSize(Width),    OptimalH = IM_GetOptimalDftSize(Height);    int OffsetX = (OptimalW - Width) / 2,        OffsetY = (OptimalH - Height) / 2;    int HalfW = OptimalW / 2,                    HalfH = OptimalH / 2;    
        if (Channel == 1)
        {
            Complex    *Data = (Complex *)malloc(OptimalW * OptimalH * sizeof(Complex));        if ((Data == NULL))    return IM_STATUS_OUTOFMEMORY;        
            for (int Y = 0; Y < Height; Y++)                                //    我们把数据居中布置,边缘用重复像素的方式        {
                unsigned char *LinePS = Src + Y * Stride;
                Complex *LinePD = Data + (Y + OffsetY) * OptimalW;            for (int X = 0; X < OffsetX; X++)
                {
                    LinePD[X].Real = LinePS[0];
                    LinePD[X].Imag = 0;
                }            for (int X = OffsetX; X < OffsetX + Width; X++)
                {
                    LinePD[X].Real = LinePS[X - OffsetX];            
                    LinePD[X].Imag = 0;
                }            for (int X = OffsetX + Width; X < OptimalW; X++)
                {
                    LinePD[X].Real = LinePS[Width - 1];
                    LinePD[X].Imag = 0;
                }
            }        for (int Y = 0; Y < OffsetY; Y++)
            {
                memcpy(Data + Y * OptimalW, Data + OffsetY * OptimalW, OptimalW * sizeof(Complex));
            }        for (int Y = OffsetY + Height; Y < OptimalH; Y++)
            {
                memcpy(Data + Y * OptimalW, Data + (OffsetY + Height - 1) * OptimalW, OptimalW * sizeof(Complex));
            }
    
            IM_FFT2D(Data, Data, OptimalW, OptimalH, false, 0, 0);
            IM_FFTShift(Data, Data, OptimalW, OptimalH);                //    数据偏移到中心
    
            for (int Y = 0; Y < HeightW; Y++)
            {
                Complex *LineLT = Data + (Y + OffsetY + Height / 16) * OptimalW + OffsetX + Width / 16;            //    确保在可见的范围内添加,左上角和右下角都镜像添加
                Complex *LineRB = Data + (OffsetY + Height - 1 - Height / 16 - Y) * OptimalW + OffsetX + Width - 1 - Width / 16;    //    再稍微往内部移动一点,可以适当增强抵抗变形的能力,但是越往中心其对最终结果的影响越大。
                unsigned char *LinePS = WaterMark + Y * StrideW;            if (ChannelW == 1)
                {                for (int X = 0; X < WidthW; X++)
                    {                    float Cof = ((LinePS[X] * 4) >> 8) + 1;
                        LineLT[X].Real *= Cof;    LineLT[X].Imag *= Cof;
                        LineRB[-X].Real *= Cof;    LineRB[-X].Imag *= Cof;
                    }
                }            else if (ChannelW == 3)
                {                for (int X = 0; X < WidthW; X++)
                    {                    float Cof = ((((LinePS[0] + LinePS[1] + LinePS[1] + LinePS[2]) >> 2) * 4) >> 8) + 1;
                        LineLT[X].Real *= Cof;    LineLT[X].Imag *= Cof;
                        LineRB[-X].Real *= Cof;    LineRB[-X].Imag *= Cof;
                        LinePS += 3;
                    }
                }            else if (ChannelW == 4)
                {                for (int X = 0; X < WidthW; X++)
                    {                    float Cof = ((IM_Div255(((LinePS[0] + LinePS[1] + LinePS[1] + LinePS[2]) >> 2) * LinePS[3]) * 4) >> 8) + 1;
                        LineLT[X].Real *= Cof;    LineLT[X].Imag *= Cof;
                        LineRB[-X].Real *= Cof;    LineRB[-X].Imag *= Cof;
                        LinePS += 4;
                    }
                }
            }
            
            IM_IFFTShift(Data, Data, OptimalW, OptimalH);
            IM_FFT2D(Data, Data, OptimalW, OptimalH, true, 0, 0);        for (int Y = 0; Y < Height; Y++)
            {
                Complex *LinePS = Data + (Y + OffsetY) * OptimalW + OffsetX;
                unsigned char *LinePD = Dest + Y * Stride;            for (int X = 0; X < Width; X++)
                {
                    LinePD[X] = IM_ClampToByte(LinePS[X].Real);
                }
            }        if (Data != NULL)        free(Data);        return IM_STATUS_OK;
        }    else
        {
            
        
        }
    }

      首先,把图像变换到频域,这里采用了opencv的有关FFT计算的过程,使用IM_GetOptimalDftSize计算最佳的DFT算法的大小,然后将图像数据居中分布,周边的空白像素采用镜像填充方式填充,虚部数据填0。

            FFT变换完成后,对FFT数据进行移位,把高频数据放置到图像的中心,低频的数据放置到图像的边缘。为了将水印的图像嵌入到目标图像,我们在适当位置根据水印图像的强度或内容来修改这些频域值,为了不影响最终的目标图像的视觉效果,嵌入的数据放置到边缘的低频数据中(靠近边缘的部位),我这里也没有放置在最边缘,而是边缘靠中的部位。

      常用的水印图像可能是8位灰度、24位彩色或32位透明图,因此,我在程序里对不同位数的水印图都做了处理,如果是32位图,则把Alpha也考虑进去了,使用的嵌入方式就是最简单的更具水印图的颜色强度值将目标图像的频域系数放大。这里的放大程度我做了固定的设计,测试效果还比较好,如果过度放大,则最后处理的结果将会严重的失真,这就失去了算法本身的意义了。当然还有一种方式就是缩小系数,也可以去尝试下。

      之后,我们需要将平移后的数据再次进行移位,然后就是进行IFFT计算了,并将计算结果返回到图像域。

      本例只给出了针对灰度目标图像的代码,那么彩色图像其实是一样的过程,将他们分解成三个通道单独处理就OK了。 

      同时,为了保证水印对结果图不会造成太大的影响,我们程序对水印图大小做了限制,长和宽都不得大于目标图像的1/4。

      另外,从嵌入的代码可以看到,我们希望水印图像尽量是黑色的背景(8位或24位)或纯背景部位是透明的(32位),这样对目标图像的影响也比较小。

      我们来做一些测试,以下是一张原图(原图缩小显示了)及两个水印图进行测试:

    349293-20181210180803107-1471114347.jpg 163504gfos3ouzey4ho505.png  349293-20181210180803107-1471114347.jpg

      分别查看其结果图和频谱图:

    163504gfos3ouzey4ho505.png 163504gfos3ouzey4ho505.png

    163504gfos3ouzey4ho505.png 163504gfos3ouzey4ho505.png

        可见,添加水印后基本未对原始图像造成视觉上的损失,在处理后的图像的频谱上可以明显看到添加后的水印的样式。


      如果对添加水印后的图像进行一些处理,看看水印是否还能有效保存。

      一、乱七八糟的增强

    163504gfos3ouzey4ho505.png 163504gfos3ouzey4ho505.png

    2、有局部裁切的旋转

    163504gfos3ouzey4ho505.png  163504gfos3ouzey4ho505.png

    3、含有模糊性质的算法

     163504gfos3ouzey4ho505.png 163504gfos3ouzey4ho505.png

      可见,这个时候水印信息就基本丢失了,这主要是因为我们的水印信息是加在图像的低频的,而模糊会对低频进行处理,所以就看不到水印了,但是如果是锐化算法就不成问题的。

      因此,这个盲水印的功能还是比较初级的,但是如果在自己的比较重要的图里隐藏个水印有的时候还是值得的,假如某个坏人是直接使用你的图而没有做任何更改呢。

      另外,还有一种基于FFT比较常见的水印技术,需要嵌入水印的图片以及未嵌入水印的原始图这样才可以获得水印,理论上讲这种应该不叫做盲水印了,但是他有个好处就是可以对水印进行加密,这样别人就比较难以知道你对图像是否嵌入了水印了。需要做的额外工具就是一定需要保留原始的未加水印的图像了。

      我将这个 小工具也集成到了我的SSE做的DEMO里了,有需要的朋友可以试下:SSE_Optimization_Demo.rar

      163504gfos3ouzey4ho505.png

    我找到的就以上三款,最实用的还是第二款2.  数字盲水印&隐形水印制作工具Watermark 

    这款支持直接打文字加上隐形水印,并且加过隐形水印的图片还可以在这款软件上面打开之后显示出你打的隐形水印。

    其它两款只能打水印,但是我不知道如何利用原软件查看所打的水印。

    第1,第3款软件打的水印,我用第2款还原也是看不到任何隐性水印的,估计都是各自有自己的算法的是独门独用的。


    做测试之前首先要找到一个2的N次方计算器:

    2的N次方在线计算

    http://www.99cankao.com/algebra/exponent2.php

    2N.jpg



    因为图片的长宽比例只支持2的N次方,也就是图片的长和宽只能上以上数值中的一个,

    我把测试的图片长和宽转换成:2048 x 2048

    用的软件是:Light Image Resizer v5.1.4.1 精简绿色版 

    https://www.appcgn.com/light-image-resizer.html

    这个软件很小又不用安装还功能强大:

    我是这么设置的,因为我不想图片变形,反正留白的地方等加上水印之后还可以处理掉的:

    123.png


    因为ImageSigner 和 SSE_Optimization_Demo不支持直接打文字,我还做了这样一个图片水印文件:

    1024 x 1024

    水印.jpg



    先用用:ImageSigner 

    Open source image就是主图,Open sign image 就要进行签名的图。

    然后Do sign 一下,最后保存就可。

    33.jpg


    一开始全部是默认的:Power 强度也是默认的,选择的是R,G,B 三个通道,但是效果全乱,后来选择B通道,对画质也有极大的影响:

    B通道:

    后来选择了R通道,又降了强度,看看还行:

    1.jpg

    关键是:我怎么从以上这张生成的图片里看出来我的加密水印呢?把生成的图片导入之后并不能看到任何东西,只能放弃。

    随后用了:SSE_Optimization_Demo

    QQ图片20190817091356.jpg

    原图片和水印图片也是可以加上的,但是我点:ShowFFT之后我看不到任何信息。


    最后还是2.  数字盲水印&隐形水印制作工具Watermark 

    这款除了没有批量处理之外,别的基本还是实用的,包括可以另存为JPG 另外两款只能保存为BMP,文件又大又没用还要转换成JPG

    a.jpg


    强度越小对画质的影响越小,我试了一下基本上 10左右差不多吧。

    否则我提取亮度100也快看不出来了,如果视力够好,显示器够亮说不定数值还可以再小一点吧,我就不试了。

    然后图片另存一下就可。

    DSCN5125.JPG





    可以直接打开图片提取盲水印:


    b.jpg



    后来又试了一上图片水印,我才发现我原来那个水印文件太大了,我做成512 x 512差不多可以。

    并且我用了黑底白字


    11.jpg




    如果要实现批量处理目前来说只能使用类似:按键精灵的软件录制一下动作,然后一直重复,虽然慢一点但是毕竟还是可以自动把所有图片加上隐性水印的。

    按键精灵的用法他们家主页上或是百度一下一堆一堆的,我也不用多写了:

    主页:http://www.anjian.com/  最新的版本就2014版,只不过升级一直在做,这东西技术成熟了也没大的更新了。

    教学在这里: xue.anjian.com/jiaocheng2014/


    最后就是要把图片的白边再处理掉,还要用到photoshop或别的软件,这几点就是有点烦。

    如果有大神能把上面这款软件加上批处理功能,然后再解决转成2的N次方之后,恢复原图大小的问题,那就完美了!

    目前来说就这么用用吧,也不错了!