揭祕版權保護下的視頻隱形水印算法(上篇)

視頻水印,作爲保護知識產權的重要手段 ,早已被大衆習慣且接受,但是這種方法仍然存在着多方面的不足。對於觀衆來說,蓋在畫面一角的 logo 多少會影響到他們的觀賞體驗。對於視頻所有者來說,這種直接顯示在畫面上的水印也很容易被定位和攻擊。一些廠家爲了應對這些攻擊,將水印時不時地從隨機的方向插入到畫面裏,從而增加 delogo 的難度,但這就更進一步降低了觀衆的觀看體驗。
針對這些問題,隱形水印這門技術被提出並逐漸發展了起來。作爲實時音視頻技術引領者,拍樂雲在這一領域不斷探索,爲用戶提供極致的體驗。 我們將通過兩篇文章,帶你詳細瞭解版權保護背後的視頻隱形水印算法技術。

一起來看看視頻隱形水印都能藏在哪些地方。

01

把水印加到封裝結構裏

首先,我們用一個最簡單的例子來引入一類完全不會修改畫面內容的隱形水印,也即封裝結構層的水印。這個例子叫圖種。一部分讀者也許聽說過這種東西。在國內版權意識逐漸普及的時期,網絡上公開可下載的無授權資源逐漸被封禁,圖種開始作爲資源傳播的手段小範圍流行起來。它看上去就是一張普通的 jpeg 圖片,但是下載下來後,zip 解壓軟件居然能對它進行解壓,之後就能得到隱藏在圖片背後的其他數據。其原理,就是利用了文件的封裝結構來隱藏數據。 說得詳細一點,jpeg 格式的文件,是以一串固定數據(0xFFD9)結尾的 [1] ,而 zip 格式的文件,也有其固定的起始碼(0x04034B50) [2] 。通過將一個 jpeg 文件和一個 zip 文件直接拼接,就可以產生圖種。大部分圖片瀏覽器和解壓軟件都有對數據首尾位置不正常的情況做兼容,所以它們可以正常處理這類文件。

圖種的生成與解壓

圖種可以被正常顯示

那麼,圖種畢竟只是圖片,如果是視頻,是否也存在利用封裝結構來隱藏數據的方法呢?那可太多了。事實上,大部分視頻格式都有存儲附屬信息的結構,我們只要把數據藏在那裏就行了。下面簡單舉兩個例子:

flv 文件  

flv 文件由一個 header 和大量的 tag 組成,這些 tag 分爲三類:audiovideo script data其中 script data 的格式標準允許我們插入各種自定義數據[3],我們可以對照官方文檔來實現代碼修改這個 tag 的數據。嫌麻煩的話,一些開源軟件也支持在一定程度上的修改它,比如 flvmeta:

使用 flvmeta 修改 flv 文件,加入自定義數據

實際存儲在文件中的數據

H.264 碼流

在 flv、mp4、mkv 等文件格式上添加數據,意味着視頻必須以離線文件的形式存在。如果我們希望在直播或者視頻通話中的視頻數據上添加水印呢?這時我們可以在更下一層,也就是視頻碼流層下手。可能有讀者對碼流這個概念不是很清楚。視頻數據原本只是一個個像素值,經過編碼器壓縮後的數據一般就稱爲碼流或比特流(bitstream)。視頻碼流雖然也可以直接解碼播放,但是隻能得到一幀一幀的畫面,不包含準確的時間信息,也不包含給播放器跳轉用的數據偏移量,更沒有音頻和字幕。所以它需要更上一級的封裝格式提供給播放器附加信息以保證正常播放。視頻編碼有很多標準,現在使用最廣泛的H.264 標準中,有一段數據叫做 SEI(補充增強信息,supplemental enhancement information),它被用來存儲輔助解碼與顯示的信息,支持添加用戶的自定義數據[4]。著名的開源編碼器x264就在這段數據裏寫了自己的版本信息以及編碼參數。我們可以參照格式標準生成自己定義的SEI數據,再嵌入視頻碼流中,從而實現隱形水印。

自定義 SEI 的語法標準

x264生成的SEI數據

封裝結構層的水印是所有隱形水印中運算量最小的,因爲它不會對視頻原始數據進行處理。但是其缺點也很明顯。因爲視頻在被盜用時極可能被人重新編碼存儲,在這個過程中,事先添加在這一層的水印一般都會丟失。因此這類方法僅在一些特殊的場景被使用。接下來,本文將介紹直接添加在畫面內容中的隱形水印。

02

把水印加到像素裏

通過修改像素值添加隱形水印的方法非常之多,本節僅介紹其中最簡單一種方法,即直接修改 LSB 數據。所謂 LSB,指的是最低有效位(Least Significant Bit),可以認爲是像素值中最無關緊要的一個比特。直接修改它對視覺影響很小。下圖的十個方塊,藍色分量的像素值依次由246遞增至255,相鄰的兩個方塊相當於修改了 LSB 數據。

修改 LSB 數據較難被肉眼分辨
有了這個認識,再來做水印就很簡單了。我們先把水印數據轉化成只有 0 和1 的二值圖像,然後直接寫到目標圖像的最低位上,這樣就完成了水印的嵌入。除了可以用這種方法嵌入純黑白的 logo,二維碼也是個不錯的選擇。畢竟二維碼本身就有糾錯的能力,能在一定程度上防止水印被破壞。下面我們用python 簡單實現一下:
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_watermarkcv2.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 的損失會更加嚴重。所以,現在主流的隱形水印算法,大多選擇變換後的數據進行處理。由於這部分內容過多,將放在下一篇中介紹。 

本篇 大致介紹了在 封裝層和在變換前的原始像素數據 上進行處理的隱形水印嵌入方法,內容 比較集中在格式標準上。 在下一篇中,我們將給大家介紹更多圖像處理相關的內容,包括 DCT(離散餘弦變換)、DWT(離散小波變換)以及SVD(奇異值分解)在隱形水印上的應用,這些方法能夠大幅提高隱形水印的魯棒性,從而在有損壓縮以及人爲攻擊後仍能在一定程度上保證水印的內容。

參考文獻

[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源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章