一起來看看視頻隱形水印都能藏在哪些地方。
把水印加到封裝結構裏
圖種的生成與解壓
那麼,圖種畢竟只是圖片,如果是視頻,是否也存在利用封裝結構來隱藏數據的方法呢?那可太多了。事實上,大部分視頻格式都有存儲附屬信息的結構,我們只要把數據藏在那裏就行了。下面簡單舉兩個例子:
flv 文件由一個 header 和大量的 tag 組成,這些 tag 分爲三類:audio
、video
和 script data
。其中 script data
的格式標準允許我們插入各種自定義數據[3],我們可以對照官方文檔來實現代碼修改這個 tag 的數據。嫌麻煩的話,一些開源軟件也支持在一定程度上的修改它,比如 flvmeta:
使用 flvmeta 修改 flv 文件,加入自定義數據
實際存儲在文件中的數據
H.264 碼流
x264生成的SEI數據
封裝結構層的水印是所有隱形水印中運算量最小的,因爲它不會對視頻原始數據進行處理。但是其缺點也很明顯。因爲視頻在被盜用時極可能被人重新編碼存儲,在這個過程中,事先添加在這一層的水印一般都會丟失。因此這類方法僅在一些特殊的場景被使用。接下來,本文將介紹直接添加在畫面內容中的隱形水印。
把水印加到像素裏
通過修改像素值添加隱形水印的方法非常之多,本節僅介紹其中最簡單一種方法,即直接修改 LSB 數據。所謂 LSB,指的是最低有效位(Least Significant Bit),可以認爲是像素值中最無關緊要的一個比特。直接修改它對視覺影響很小。下圖的十個方塊,藍色分量的像素值依次由246遞增至255,相鄰的兩個方塊相當於修改了 LSB 數據。
import cv2
# 讀取圖像,將水印縮放至目標圖像大小,並二值化
ori_img = cv2.imread('Lenna.jpg')
watermark = cv2.imread('watermark.jpg')
resized_watermark = cv2.resize(watermark, ori_img.shape[:2],
interpolation=cv2.INTER_NEAREST) #既然是二值圖像,使用最近鄰方式來拉伸比較合適
binary_watermark = resized_watermark >> 7
# 嵌入水印並保存結果
output_img = ori_img & 0xFE | binary_watermark
cv2.imwrite('Lenna_with_watermark.png', output_img)
# 提取水印
img_with_watermark = cv2.imread('Lenna_with_watermark.png')
extracted_watermark = ((target_img & 0x01) * 255)
cv2.imwrite('extracted_watermark.jpg', extracted_watermark)
待添加水印
提取出的水印
細心的讀者會發現,代碼中其他圖像都是使用 jpg 格式存儲的,唯獨添加完水印的圖片代碼中使用了 png 格式。這是因爲最低有效位的數據非常脆弱,極容易被有損壓縮算法修改,導致水印無法正常提取。而 png 格式是無損壓縮格式,不會引入這個干擾。如果把上述代碼中的 png 換成 jpg,你會看到提取出的水印變得完全無法辨認。
這樣看來,似乎 LSB 方法也不是那麼保險。畢竟視頻編碼基本都是有損壓縮,更別提被盜取的視頻被重新發布時一般都會經歷二次編碼,LSB 的損失會更加嚴重。所以,現在主流的隱形水印算法,大多選擇變換後的數據進行處理。由於這部分內容過多,將放在下一篇中介紹。
參考文獻
[1] CCITT Rec. T.81
[2] APPNOTE.TXT - .ZIP File Format Specification
[3] Adobe Flash Video File Format Specification
[4] Recommendation ITU-T H.264
本文分享自微信公衆號 - 音視頻開發進階(glumes_blog)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。