盲水印
一、演示
首先看 這是一張女朋友
解碼水印
接下來我們輸入一行神奇的命令:
python bwm.py --action decode --origin Demo.jpg --im ../Gakki.jpg --result res.jpg
可以得到這樣的一張圖:
以後誰再跟你搶女朋友就可以這樣聲明版權了嘿嘿.
(腳本和原圖都在最後的附錄裏, 有興趣的朋友只需要將上面的圖片保存為Demo.jpg,附錄裏的原圖保存為Gakki.jpg, 就可以解碼出上面的信息)
加密水印
通過今天的方法你可以將信息放入任意圖片,來達到加密信息的目的.
附錄裏的腳本, 加密用法:
python bwm.py --action encode --origin Gakki.jpg --im wm1.png --result Demo.jpg --alpha 2
二、用途
上面 的水印就叫做盲水印,隱藏式的水印是以數字數據的方式加入音頻、圖片或影片中,但在一般的狀況下無法被看見。隱藏式水印的重要應用之一是保護版權,期望能借此避免或阻止數字媒體未經授權的複製和拷貝。
1.不同人加相同水印
聲明版權
應用案例:
- 某些畫師、攝影師、設計師會在其作品中加入水印。
13年左右有位自稱是“超寫實主義”的畫家,聲稱自己純手工畫的畫寫實程度可以超過攝影機,並開辦培訓班斂財。
後被一位加了盲水印的攝影師戳穿,原來其“畫作”都是直接將照片用ps處理成手繪質感的圖。
- 淘寶防盜圖功能
淘寶賣家圖會被淘寶自動打上水印,如果有別的賣家存圖作為自己的圖上傳會被檢測出。
2.不同人加不同水印
將某份保密數字資料發送給不同人時,可加上不同標識,如果資料被複制、傳播可根據解碼出的唯一標識來追究責任人。
應用案例:
- 電影剛剛公映時,每個影院,影廳的 電影底片裏都會加入不同的不可見水印, 如果電影流出,就可追究相關影院責任。
-
阿里,華為等公司內部論壇、平台會在HTML頁面中加入足夠數量 及不被發現的唯一標識。當有內部敏感信息通過截圖等方式流出,也可追蹤到個人。
比如著名的“阿里月餅門”。
三、原理
原理圖
傅里葉變換
- 簡單複習下傅里葉變換
傅里葉變換簡單地説就是將信號在時域或空域的函數轉變到頻域表示,在和工程學中有許多應用。因其基本思想首先由法國學者約瑟夫·傅里葉系統地提出。
- 再理解下時域和頻域
.gif)
那麼,傅里葉變換有什麼用呢,
- 先在紙上畫一個sin(x),不一定標準,意思差不多就行。不是很難吧。
- 好,接下去畫一個sin(3x)+sin(5x)的圖形。這個就很難能畫得出來。
現在把sin(3x)+sin(5x)的曲線給你,只看圖是看不出這整個曲線的方程式是怎樣的,現在需要將把sin(5x)從圖裏拿出去,看看剩下的是什麼。這基本是不可能做到的。
但是在頻域呢?則簡單的很,無非就是幾條豎線而已。
這是最簡單的一種用法,其他複雜用法不在此贅述。
頻譜圖
一維信號的變換理解之後,那麼圖像的頻譜圖長什麼樣呢。
圖片中明亮的部分就是低頻(平緩)部分,暗點的是高頻(突變交界)部分。一般為了展示會把頻譜圖低頻的部分移到中心。頻譜圖上的點跟原圖不存在一一對應關係,頻譜圖的每一點都來自於全部的圖像(類似於時域曲線的點,和頻域圖的點)。
這樣可能還不夠直觀,接下來看這張圖。
這是一張400x400的圖,共有16 萬個像素點。
我們平時怎麼來表示一張圖片呢,首先是在笛卡爾座標系中用x,y來定位某一確定的點。那麼,我們怎麼來描述這個點呢?
我們知道,所有的色彩都是由三原色組成。生活中經常説的紅、黃、藍(青),其實是一種消減型的三原色,光學中的三原色是紅、綠、藍,也就是R、G、B。
通常我們用來描述圖像點的方法就是RGB的值,其實圖像處理中用的是灰度(Gray scale)來表示圖片,但是為了便於理解,下面用的是RGB演示 。
上圖是截取了某一行RGB的值做成的曲線圖,可以看到,每條曲線都在不停的上下波動,且波動的頻率是相同的。有些區域的波動比較小,有些區域突然出現了大幅波動。
對比一下圖像就能發現,曲線波動較大的地方,也是圖像出現突變的地方。
圖像的頻譜可以理解為將一維的頻譜繞着縱軸旋轉一圈,形成一個3維的數學函數圖(原圖中心對稱、鏡像對稱才可以這樣幹,其他類似),x、y軸代表兩個方向的頻率,z軸代表該頻率的幅值,只不過頻譜圖像是一個2維圖,所以用亮度來表示幅值了。
二維傅里葉變換的物理意義是將圖像的灰度分佈函數變換為圖像的頻率分佈函數。
盲水印的特性
魯棒性一般要能抗(壓縮 、裁剪、塗畫,旋轉)。
- 隱蔽性
由於不希望被察覺、不希望干擾用户體驗、不希望被模仿等等原因,我們的水印不可見,也就是隱匿性。
- 不易移除性
不易移除性跟魯棒性有些相似, 不同的是:
魯棒性更加強調的是數字資源在傳播過程中不要被不自覺地干擾和破壞。
不易移除性是在別有用心者察覺了盲水印的存在後,不被他們自覺地移除或者破壞。
- 強健性
強健性通常也被稱作魯棒性,來自於其英文名稱(Robustness)的音譯。
簡單地説就是耐操性。
需要説明的一點是,魯棒性和隱蔽性通常不可兼得。
- 明確性
沒什麼可説的,就是盲水印需要表示出明確的信息。
四、引申
圖種
例如這是一張相貌平平的圖片, 你可以保存下來,將後綴改為"rar"或者直接用解壓工具打開,就可以看到神秘福利.
|ू・ω・` )
製作方法也很簡單,在win下 入以下命令就可以做一張"圖種"了.
copy /b A.jpg + B.zip C.jpg
大約十年以前,圖種被廣泛上傳到論壇等地用來傳播資源。後來由於許多網站在上傳圖片時會判斷圖片結尾標識,其之後的全部丟棄,慢慢不再有人使用。(https://sm.ms/這個圖牀還是很給力的, 經測試還是可以解析種子)
隱藏文件
圖片可以跟種子文件結合,當然也可以和其他文件結合。
其實隱藏文件和盲水印都屬於圖片隱寫術。
圖片隱寫術
隱寫術(Steganography)也是數字水印的一種應用,雙方可利用隱藏在數字信號中的信息進行溝通。
數字照片中的註釋數據能記錄照片拍攝的時間、使用的光圈和快門,甚至是相機的廠牌等信息,這也是數字水印的應用之一。
某些文件格式可以包含這些稱為“metadata”的額外信息。
用途
規避敏感詞過濾
所謂的“敏感詞過濾”,常翻牆的同學,應該都很熟悉了。用圖片來隱藏信息,可以規避GFW的敏感詞過濾。
規避肉眼審查
國內的很多網站,對於上傳的圖片,都會進行人工審查。如果能通過技術手段把信息隱藏在圖片中,而圖片本身又看不出什麼異樣,人工審核就看不出來。
傳遞加密信息
不希望被別人看到的資料、信息等。
常見方法
原理
內容覆蓋法
通常來説,圖片文件都有包含2部分:文件頭和數據區。
而“內容覆蓋法”,就是把要隱藏的文件,直接【覆蓋】到圖片文件的【數據區】的【尾部】。
比方説,某圖片有 100KB,其中文件頭佔 1KB,那麼,數據區就是 99KB。也就是説,最多隻能隱藏 99KB 的文件。
切記:覆蓋的時候,千萬不可破壞文件頭。文件頭一旦破壞,這個圖片文件就不再是一個合法的圖片文件了。
使用這種方法,對圖片文件的格式,是有講究的——最好用【24位色的 BMP 格式】。
- BMP 格式本身比較簡單,數據區隨便覆蓋,問題不大;
- 24位色的 BMP 相對其它的格式 BMP,文件尺寸更大,可以隱藏更多內容。
import sys
def embed(container_file, data_file, output_file) :
"""代碼沒有嚴格計算 BMP 的文件頭尺寸,只是大致預留了 1024 字節"""
container = open(container_file, "rb").read()
data = open(data_file, "rb").read()
if len(data)+1024 >= len(container) :
print("Not enough space to save " + data_file)
else :
f = open(output_file, "wb")
f.write(container[ : len(container)-len(data)])
f.write(data)
f.close()
if "__main__" == __name__ :
try :
if len(sys.argv) == 4 :
embed(sys.argv[1], sys.argv[2], sys.argv[3])
else :
print("Usage:\n%s container data output" % sys.argv[0])
except Exception as err :
print(err)
LSB最低有效位
很多商業軟件使用的原理都是這個方法。
例如在PNG圖片的儲存中,每個顏色會有8bit,LSB(Least Significant Bit)隱寫就是修改了像數中的最低的1bit,在人眼看來是看不出來區別的,也把信息隱藏起來了。(每個像數可以攜帶3bit的信息。)
譬如我們想把’A’隱藏進來的話,如下圖,就可以把A轉成16進制的0x61再轉成二進制的01100001,再修改為紅色通道的最低位為這些二進制串。
最後
-
附上前面演示代碼的實現:
(參考了幾個git hub上的項目,不過魯棒性都不太好)
# coding=utf-8 import cv2 import numpy as np import random import os from argparse import ArgumentParser ALPHA = 5 class BlindWaterMark(): """盲水印加解密,無頻移簡單版""" def __init__(self): self.parser = ArgumentParser() self.parser.add_argument('--action', dest='action', required=True) self.parser.add_argument('--origin', dest='ori', required=True) self.parser.add_argument('--img', dest='img', required=True) self.parser.add_argument('--result', dest='res', required=True) self.parser.add_argument('--alpha', dest='alpha', default=ALPHA) def encode(self, ori_path, wm_path, res_path, alpha): img = cv2.imread(ori_path) img_f = np.fft.fft2(img) # 2維離散傅里葉變換 height, width, channel = np.shape(img) watermark = cv2.imread(wm_path) wm_height, wm_width = watermark.shape[0], watermark.shape[1] # 水印隨機編碼 x, y = range(height / 2), range(width) random.seed(height + width) # 隨機數解碼時可控 random.shuffle(x) random.shuffle(y) # 按目標圖片大小 對水印圖進行對稱 tmp = np.zeros(img.shape) # 根據圖片形狀,生成0填充的矩陣 for i in range(height / 2): for j in range(width): if x[i] < wm_height and y[j] < wm_width: tmp[i][j] = watermark[x[i]][y[j]] tmp[height - 1 - i][width - 1 - j] = tmp[i][j] res_f = img_f + alpha * tmp # 原圖頻域值 + 水印頻域值 res = np.fft.ifft2(res_f) # 傅里葉逆變換 res = np.real(res) # 轉換為實數 cv2.imwrite(res_path, res, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) def decode(self, ori_path, img_path, res_path, alpha): ori = cv2.imread(ori_path) img = cv2.imread(img_path) ori_f = np.fft.fft2(ori) img_f = np.fft.fft2(img) height, width = ori.shape[0], ori.shape[1] watermark = (ori_f - img_f) / alpha watermark = np.real(watermark) res = np.zeros(watermark.shape) random.seed(height + width) x = range(height / 2) y = range(width) random.shuffle(x) random.shuffle(y) for i in range(height / 2): for j in range(width): res[x[i]][y[j]] = watermark[i][j] res[height - i - 1][width - j - 1] = res[i][j] cv2.imwrite(res_path, res, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) def run(self): options = self.parser.parse_args() action = options.action ori = options.ori img = options.img res = options.res alpha = float(options.alpha) if not os.path.isfile(ori): parser.error("image %s does not exist." % ori) if not os.path.isfile(img): parser.error("watermark %s does not exist." % img) if action == "encode": self.encode(ori, img, res, alpha) elif action == "decode": self.decode(ori, img, res, alpha) if __name__ == '__main__': bwm = BlindWaterMark() bwm.run()
2.隱寫術是一門很深、應用很廣泛的學問,這裏講的很泛,權當做拋磚引玉。圖片隱寫術只是其中一種,有興趣的同學可以看下面這本書。