blind_watermark 盲水印工具:把版權記號藏進圖片,壓縮後仍讀得回

blind_watermark 是 MIT 開源的 Python 盲水印套件,用 DWT-DCT-SVD 把版權文字藏進圖片頻率域,提取不需要原圖。本文實測九種轉發傷害的復原邊界:JPEG 壓縮與縮放讀得回,裁切與旋轉讀不回,兩個密碼參數裡只有一個真的在鎖門。

用 AI 摘要這篇文章:

一段二十字元的版權文字,一點五秒藏進一張照片,肉眼完全看不出來;之後只拿這張照片、不用原圖,零點四秒就把整段文字一個不差讀回來。這是 2026 年 8 月在本機實際跑 blind_watermark 0.4.4 的結果,測試圖是官方倉庫附的範例照片。同樣這輪實測也量出了它的邊界:JPEG 再壓縮與縮圖過關,裁切與旋轉直接出局,而它提供的兩個密碼參數裡,只有一個真的在鎖門。

blind_watermark 是一個 MIT 開源的 Python 盲水印套件,作者郭飛從 2019 年維護到現在,GitHub 上累積一萬四千多顆星。所謂盲水印,就是把版權資訊藏進圖片的頻率域,圖片看起來跟原來一模一樣,事後卻能從圖片本身把資訊解出來,過程不需要原圖對照。先講結論:它值得放進你的發布流程,但要把它理解成事後指認的取證工具,理解成防盜機制會失望。

六行 Python 就能來回,但要先記下一個數字

實測環境是 Python 3.11 的乾淨虛擬環境,一行 pip install blind-watermark 裝妥,依賴只有 numpy、OpenCV 與 PyWavelets 三個。值得一提的是它在新版依賴上跑得很順:這次實測用的是 numpy 2.4 與 OpenCV 5.0,都是 2026 年的當前版本,沒有踩到舊套件相容性的坑。

Python 介面的嵌入流程就是六行,讀檔、讀水印、輸出各司其職:

from blind_watermark import WaterMark
bwm1 = WaterMark(password_img=1, password_wm=2)
bwm1.read_img('ori_img.jpeg')
bwm1.read_wm('TechMoon 2026 版權標記測試', mode='str')
bwm1.embed('embedded.png')
print(len(bwm1.wm_bit))

提取則是再建一個物件,把同兩個密碼與水印長度交給它,文字就回到眼前。不寫程式的人也有命令列版本,一行搞定:blind_watermark --embed --pwd 1234 ori_img.jpeg "水印文字" embedded.png,提取換成 --extract,同一組密碼加上水印長度。整個來回在 738 乘 1108 的照片上,嵌入 1.54 秒、提取 0.36 秒,速度對單張工作流綽綽有餘。真正的關鍵動作是嵌入後要印出 len(bwm1.wm_bit) 並記下來:這段文字對應的水印長度是 255,提取時這個數字是必要參數。忘了它,之後誰也解不出來,包括你自己。

第一次執行時套件會在終端印一段歡迎訊息,內容包含一句重要提醒:嵌入與提取的版本必須一致。這句話不是客套,版本鎖定是長期保存水印的真實風險,後面會再回到這裡。想把訊息關掉,官方也留了 bw_notes.close() 的開關,接進自動化流程時記得處理,否則管線日誌會多出這段輸出。

承載量也順手測了:一張 640 乘 480 的小圖,塞一句二十字元的識別文字沒問題,連三倍長度(767 位元)都完整讀回。要把作品編號、時間與作者名串成一句長識別碼放進去,容量是夠的。

畫質代價量得出來,看不出來

盲水印的第一個疑問通常是:圖會不會變醜。逐像素量測的答案是嵌入後的圖與原圖 PSNR 為 38.42dB,最大單一像素差 23(滿刻度 255)。翻成白話:儀器量得出差異,人眼在正常觀看距離下分辨不出來。官方倉庫附的範例對照圖也是同樣狀況,原圖與嵌入圖並排放著看,找不到任何痕跡。

blind_watermark 官方範例:已嵌入盲水印的照片,畫面與原圖肉眼無法分辨Pin
官方範例照片,這張圖裡藏著一句文字水印,肉眼找不到任何痕跡

它能做到這點,靠的是把資訊藏進頻率域:先做小波分解,在中低頻係數上做離散餘弦轉換,再把水印位元散進奇異值裡。這串技術名詞不需要記,值得記的是背後的直觀:JPEG 這類破壞性壓縮在丟資料時,優先丟的是人眼本來就看不出來的高頻細節,而水印資訊刻意住在壓縮捨不得丟的中低頻帶,所以圖被再壓縮、被縮放,房子拆了一半,住在低頻裡的房客還在。反過來說,裁切與旋轉改變的是頻域結構的空間對齊,等於把整棟房子的隔間打掉重排,房客再穩也找不到自己的位置。資訊不是藏在某個位置,而是攤平到整張圖的頻域結構裡,局部改動傷不了它,全面性的幾何變形才是剋星。這直接解釋了下一節的實測結果。

順帶一提,很多人的發布流程本來就會過一次壓縮,例如用 本機圖片壓縮工具先壓再上傳。盲水印對此的相容性後面有實測數字,可以先放心一半。

哪些轉發傷害讀得回來,哪些讀不回

對嵌入完成的圖依序施加常見的壓縮、縮放、雜訊與幾何傷害,再逐一提取,結果分成兩邊,整理成表:

施加的傷害提取結果
JPEG 再壓縮,品質 75完整讀回
JPEG 再壓縮,品質 40完整讀回
JPEG 再壓縮,品質 20亂碼
縮小到一半再放大回原尺寸完整讀回
縮小到四分之一再放大回原尺寸完整讀回
亮度調降一成完整讀回
百分之一椒鹽雜訊完整讀回
中心裁掉一半(直接解與還原後再解)失敗,還原後再解時解碼器拋出例外
旋轉五度與四十五度亂碼
縱向裁掉一半失敗
縮到六成並存成品質 70 的 JPEG(複合轉發)失敗

表格裡最反常的一格是裁切:把裁剩的圖還原回原尺寸再解,結果連亂碼都不是,解碼器直接丟出例外終止。對使用者來說這代表一件事:失敗有兩種樣貌,一種是安靜的亂碼,一種是程式錯誤,把提取接進自動化流程的人要兩種都接住。

最值得記的是複合場景。縮小與壓縮單獨施加時都過關,但模擬真實社群轉發的組合拳,用較好的縮放演算法縮到六成再存成品質 70 的 JPEG,水印就讀不回來了。單項通關不等於組合通關,而真實世界的轉發很少只做一件事。

這裡有個對照要說清楚。官方 README 的攻擊展示表裡,旋轉四十五度與裁切後的提取都顯示成功,看起來與本次實測矛盾。裁切那幾張展示圖的檔名帶著還原與填補字樣,也就是先對被攻擊的圖做了幾何還原處理再提取;旋轉那張的檔名沒有這類線索,前提未載明。這不是造假,但至少裁切案例是帶前置處理的示範,與拿到一張被裁過的圖直接解的實務場景有距離。使用前對它的守備範圍認知要以此為準:全面性再壓縮、縮放、亮度與輕微雜訊,在範圍內;裁切、旋轉與幾何重製,在範圍外。

blind_watermark 官方 README 展示的旋轉四十五度攻擊後照片,屬於帶幾何還原前置處理的案例Pin
官方 README 的旋轉四十五度展示圖,屬於還原處理後再提取的成功案例

兩個密碼,只有一個真的鎖門

套件提供兩個密碼參數,實測打錯其中一個時出現了意外結果:把 image 密碼從 1 改成 9,水印文字照樣完整解出,一個字都不差。換成打錯 wm 密碼,得到的才是亂碼,有時解碼器乾脆拋出例外。對照原始碼可以確認原因:wm 密碼用在水印位元的加密上,image 密碼只負責擾亂嵌入位置的排列,換句話說,image 密碼打錯也不影響提取,它不提供保密。

這個結果對使用者的意義比看起來大。兩個密碼在程式裡都是整數種子,不是高強度金鑰,盲水印在這套設計裡從來就不是嚴格的保密機制。真正的鑰匙有三把:加密用的 wm 密碼、前面提過的水印長度參數,以及版本一致,套件自己在啟動訊息裡也明講了最後一點。落實到操作上,作品登記表至少該有四個欄位:水印文字本體、兩個密碼、位元長度,再加上嵌入當時的套件版本號。四個欄位齊全,兩年後回頭取證才不會卡在套件升級或紀錄缺角上;指望密碼擋住別人的眼睛,是對這個工具的誤解。

跟可見浮水印剛好是互補的兩層

傳統的可見浮水印走的是另一條路:用 批次浮水印工具在圖上蓋字或蓋標誌,宣告所有權,嚇阻路人。問題是這條路已經變成貓鼠遊戲,AI 去浮水印工具能自動抓出並抹掉畫面上的標記,可見的宣告同時也是可見的靶子。

盲水印的角色剛好相反。它不嚇阻任何人,搬運者根本不知道它的存在,所以不會被針對性處理;代價是它也不會阻止任何事情發生,只在事後提供這張圖出自於你的證據。兩層可以並用:可見浮水印做嚇阻與導流,盲水印做最後的取證保險。如果你的擔憂偏向圖片來源本身,例如想驗證一張圖是否為 AI 生成、有沒有內容簽章,那屬於圖片來源檢視工具的守備範圍,與盲水印是互補而非替代關係。

放進實際流程的建議很簡單:在發布前對外流版本嵌入一句含作者、作品編號與日期的識別文字,把水印長度、兩個密碼與套件版本寫進作品登記表,同時保留原始檔與首次發布的網頁存檔。水印讀得回來時它是證據,讀不回來時,原始檔與發布紀錄仍然是你的底牌。

授權與專案狀態:穩態維護,兩個安裝細節要知道

授權是乾淨的 MIT,商用與修改都沒有障礙。專案體質也夠厚:2019 年 7 月建立,一萬四千五百多顆星、一千四百多個 fork,至今未封存。不過維護節奏要如實說:主分支最後一次實質的程式碼變動停在 2024 年 6 月的尺寸檢查修正,之後唯一的提交是 2025 年 9 月加了一個徽章;開發分支上則在 2026 年 3 月出現準備 0.6 版的提交,作者還在慢慢動。這是低速穩態,對成熟工具不是壞事,而 0.6 版一旦推出,嵌入與提取的版本一致提醒就會變成真實的遷移課題。

安裝面有兩個細節。其一,PyPI 上的 0.4.4 發布於 2023 年 4 月,主分支在那之後又累積了八個提交,包含前述的尺寸檢查修正,全都沒有隨包發布。用 pip 裝到的是三年多前的版本,需要那個修正的話,得自己從原始碼安裝。其二,文件裡的平行處理是選配:建構子預設走單一程序的 common 模式,要明確指定 multiprocessing 或 multithreading 才會開,而且在 Windows 上 multiprocessing 會自動退回多執行緒。以為裝完就平行的人,跑批次時會低估它的速度潛力。另外建構子上還有一個 block_shape 參數,收了卻沒有傳進核心運算,調它不會有任何效果,知道這點可以省下白費的嘗試。

沒測到的三件事

有三個問題這輪實測沒有回答。面對刻意攻擊的存活性,例如攻擊者自己也嵌入一層水印覆蓋、或用專業工具加噪破壞頻域,本次沒有測,對抗惡意破壞的效果屬於未知。大規模批次的效能表現,例如一次處理上千張圖的耗時與記憶體,也超出這次的範圍。最後,它與商業盲水印方案的比較沒有做,兩者的對稱實測需要另外安排,這裡不對任何一方佔優下結論。

回到一開始的問題:要不要用。如果你的作品會公開流傳,而你想在別人搬運之後還能拿出這張圖出自於你的證據,blind_watermark 是最容易上手的開源選擇之一,MIT 授權、六行程式碼、對壓縮與縮放有實測背書的耐受度。如果你要的是讓搬運者知難而退,或應付裁切與旋轉的重製,它幫不上忙,把預算與期待放到別的地方去。

Sliven 褚崇名
Sliven 褚崇名

每日分享科技新知、免費資源以及 WordPress、虛擬主機相關主題,任何問題歡迎在科技月球下方留言,或是發送 Email 至 [email protected] 與我聯繫。

文章: 873

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


Share to...