有意思的數字盲水印的簡單的實現。

  早期大約是10年前從一本數字圖像處理上看到過數字水印的概念,以爲確實一種頗有意思的東西,那個時候主要就是基於LSB的圖像信息的隱藏,這種在空域裏的方法有較大的缺陷,魯棒性是比較差的。隨便一個後期的都會形成水印的丟失,所以,雖然是一種盲水印,可是不具備很好的推廣性。html

  前段時間一個朋友給了我一段使用Opencv的盲水印代碼,是基於FFT變換的, 抽空看了下,對其中部分的實現過程進行了替換和分解,也實現了一個最簡單的基於頻域的盲水印效果。git

  我在尋找相關資料的時候在網絡上看到有幾個這方面的文章和工具,如今分享以下:github

  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/82847756ide

       http://www.javashuo.com/article/p-syxlhtgd-he.html工具

  好像還有一個寫的比較詳細,並且有工具,在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。spa

        FFT變換完成後,對FFT數據進行移位,把高頻數據放置到圖像的中心,低頻的數據放置到圖像的邊緣。爲了將水印的圖像嵌入到目標圖像,咱們在適當位置根據水印圖像的強度或內容來修改這些頻域值,爲了避免影響最終的目標圖像的視覺效果,嵌入的數據放置到邊緣的低頻數據中(靠近邊緣的部位),我這裏也沒有放置在最邊緣,而是邊緣靠中的部位。

  經常使用的水印圖像多是8位灰度、24位彩色或32位透明圖,所以,我在程序裏對不一樣位數的水印圖都作了處理,若是是32位圖,則把Alpha也考慮進去了,使用的嵌入方式就是最簡單的更具水印圖的顏色強度值將目標圖像的頻域係數放大。這裏的放大程度我作了固定的設計,測試效果還比較好,若是過分放大,則最後處理的結果將會嚴重的失真,這就失去了算法自己的意義了。固然還有一種方式就是縮小系數,也能夠去嘗試下。

  以後,咱們須要將平移後的數據再次進行移位,而後就是進行IFFT計算了,並將計算結果返回到圖像域。

  本例只給出了針對灰度目標圖像的代碼,那麼彩色圖像實際上是同樣的過程,將他們分解成三個通道單獨處理就OK了。 

  同時,爲了保證水印對結果圖不會形成太大的影響,咱們程序對水印圖大小作了限制,長和寬都不得大於目標圖像的1/4。

  另外,從嵌入的代碼能夠看到,咱們但願水印圖像儘可能是黑色的背景(8位或24位)或純背景部位是透明的(32位),這樣對目標圖像的影響也比較小。

  咱們來作一些測試,如下是一張原圖(原圖縮小顯示了)及兩個水印圖進行測試:

   

  分別查看其結果圖和頻譜圖:

 

 

    可見,添加水印後基本未對原始圖像形成視覺上的損失,在處理後的圖像的頻譜上能夠明顯看到添加後的水印的樣式。

  若是對添加水印後的圖像進行一些處理,看看水印是否還能有效保存。

  1、亂七八糟的加強

 

二、有局部裁切的旋轉

  

三、含有模糊性質的算法

  

  可見,這個時候水印信息就基本丟失了,這主要是由於咱們的水印信息是加在圖像的低頻的,而模糊會對低頻進行處理,因此就看不到水印了,可是若是是銳化算法就不成問題的。

  所以,這個盲水印的功能仍是比較初級的,可是若是在本身的比較重要的圖裏隱藏個水印有的時候仍是值得的,假如某個壞人是直接使用你的圖而沒有作任何更改呢。

  另外,還有一種基於FFT比較常見的水印技術,須要嵌入水印的圖片以及未嵌入水印的原始圖這樣才能夠得到水印,理論上講這種應該不叫作盲水印了,可是他有個好處就是能夠對水印進行加密,這樣別人就比較難以知道你對圖像是否嵌入了水印了。須要作的額外工具就是必定須要保留原始的未加水印的圖像了。

  我將這個 小工具也集成到了個人SSE作的DEMO裏了,有須要的朋友能夠試下:SSE_Optimization_Demo.rar

  

相關文章
相關標籤/搜索