文章目錄

  • 思維導圖
  • 前言
  • 一、添加水印原理
  • 1. 水印掩模的生成邏輯
  • 2. 目標圖像ROI區域選擇
  • 3. 位運算的作用
  • 4. 圖像融合
  • 二、開發環境準備
  • 1. 核心庫安裝
  • 2. 素材準備
  • 三、分步實現圖像添加水印
  • 1. 圖像讀取與預處理
  • 代碼實現:
  • 關鍵説明:
  • 2. 水印掩模生成(灰度化+二值化)
  • 代碼實現:
  • 關鍵説明:
  • 3. 目標圖像ROI區域提取與位運算
  • 代碼實現:
  • 關鍵説明:
  • 4. 圖像融合與水印嵌入
  • 代碼實現:
  • 關鍵説明:
  • 四、效果優化方案:解決常見問題
  • 1. 掩模優化:調整二值化閾值與形態學操作
  • 2. 水印透明度調整:實現半透明效果
  • 3. 水印位置與尺寸優化
  • 4. 彩色水印優化:保留原始色彩
  • 五、完整代碼整合與運行説明
  • 1. 完整代碼
  • 2. 運行説明
  • 七、總結

思維導圖

Opencv(十三):圖像添加水印 - 教程_二值化

前言

本文將從技術原理出發,詳細講解如何用Python實現圖像添加水印功能,包括灰度化、二值化、位運算、圖像融合等核心步驟,同時提供完整可運行的代碼和效果優化方案,適合Python初學者和機器視覺入門者學習實踐。

一、添加水印原理

整個過程的核心邏輯可概括為:提取水印掩模→定位目標區域→圖像位運算→融合輸出結果,具體原理如下:

1. 水印掩模的生成邏輯

水印掩模是實現水印嵌入的關鍵,它的作用是“標記”出水印圖案的有效區域(即需要嵌入目標圖像的部分)。生成掩模的核心步驟是灰度化二值化

  • 灰度化:將彩色的水印圖像(如Logo、圖案)轉換為單通道的灰度圖像,消除顏色干擾,只保留亮度信息。灰度化後,圖像的每個像素值範圍為0-255(0代表黑色,255代表白色)。
  • 二值化:對灰度圖像進行閾值處理,將圖像轉換為僅含黑白兩種顏色的二值圖像。通過設置合適的閾值,讓水印圖案的主體部分變為黑色(像素值0),背景部分變為白色(像素值255),從而得到清晰的水印輪廓掩模。

2. 目標圖像ROI區域選擇

ROI(Region of Interest,感興趣區域)指的是目標圖像中需要添加水印的特定區域。通常選擇圖像的左上角、右上角等不影響主體內容的位置,且ROI區域的大小必須與水印圖像的大小保持一致(否則無法進行後續的像素級運算)。例如,若水印圖像的尺寸為200×200像素,則需要在目標圖像中截取同樣大小的矩形區域作為ROI。

3. 位運算的作用

位運算中的“與運算”是提取水印輪廓的核心操作。在二進制層面,與運算的規則是“全1則1,有0則0”。將目標圖像的ROI區域與水印掩模進行與運算時:

  • 掩模中黑色部分(像素值0,二進制00000000)與ROI區域對應像素進行與運算,結果為0(黑色),從而在ROI區域中“挖”出與水印形狀一致的黑色區域。
  • 掩模中白色部分(像素值255,二進制11111111)與ROI區域對應像素進行與運算,結果保持ROI區域原像素值不變,保留背景信息。

通過這一步操作,我們可以在目標圖像的ROI區域中得到一個與水印形狀完全匹配的“鏤空”區域,為後續嵌入水印做好準備。

4. 圖像融合

圖像融合是將水印圖案的像素信息填充到ROI區域“鏤空”部分的過程。這裏採用簡單高效的“像素相加”融合方式:將原始水印圖像與經過位運算處理後的ROI區域進行像素級相加,讓水印圖案的顏色和細節填充到“鏤空”區域中。由於ROI區域的“鏤空”部分像素值為0,相加後不會改變水印圖案的原始色彩;而其他區域保持原背景不變,最終實現水印與目標圖像的自然融合。

二、開發環境準備

1. 核心庫安裝

本文實現圖像水印功能主要依賴OpenCV庫(用於圖像處理)和NumPy庫(用於數組運算),安裝命令如下:

pip install opencv-python numpy matplotlib
  • OpenCV(cv2):用於圖像讀取、灰度化、二值化、位運算、圖像融合等核心操作,是Python中最常用的機器視覺庫。
  • NumPy:用於處理圖像的數組表示(圖像在Python中以NumPy數組形式存儲),支持高效的像素級運算。
  • Matplotlib:用於圖像的顯示和結果對比,方便觀察每一步處理效果。

2. 素材準備

需要準備兩張圖像素材,建議提前放在同一目錄下:

  • 目標圖像(target_image.jpg):需要添加水印的原始圖像,格式建議為JPG或PNG,分辨率無強制要求。
  • 水印圖像(watermark.png):用於嵌入的水印圖案,建議選擇背景簡單、輪廓清晰的圖像(如透明背景的Logo),格式優先選擇PNG(支持透明通道,後續可優化掩模生成效果)。

三、分步實現圖像添加水印

1. 圖像讀取與預處理

首先需要讀取目標圖像和水印圖像,並進行初步的預處理(如尺寸調整、通道匹配),確保兩張圖像滿足後續運算的要求。

代碼實現:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 設置圖像顯示的中文字體(避免中文標籤亂碼)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 讀取目標圖像和水印圖像
def read_images(target_path, watermark_path):
# 讀取目標圖像(默認讀取彩色圖像,保留BGR通道)
target_img = cv2.imread('lena.png')
if target_img is None:
raise ValueError("目標圖像讀取失敗,請檢查文件路徑是否正確")
# 讀取水印圖像(保留Alpha通道,用於後續透明背景處理)
watermark_img = cv2.imread('logo.png', cv2.IMREAD_UNCHANGED)
if watermark_img is None:
raise ValueError("水印圖像讀取失敗,請檢查文件路徑是否正確")
return target_img, watermark_img
# 調整水印圖像尺寸(確保不超過目標圖像的1/4,避免遮擋主體)
def resize_watermark(target_img, watermark_img, scale=0.25):
# 獲取目標圖像和水印圖像的尺寸
target_height, target_width = target_img.shape[:2]
watermark_height, watermark_width = watermark_img.shape[:2]
# 計算水印圖像的最大允許尺寸(目標圖像的scale比例)
max_watermark_width = int(target_width * scale)
max_watermark_height = int(target_height * scale)
# 保持水印圖像的寬高比,調整尺寸
scale_ratio = min(max_watermark_width / watermark_width, max_watermark_height / watermark_height)
new_watermark_size = (int(watermark_width * scale_ratio), int(watermark_height * scale_ratio))
resized_watermark = cv2.resize(watermark_img, new_watermark_size, interpolation=cv2.INTER_AREA)
return resized_watermark
# 主函數中調用
if __name__ == "__main__":
# 圖像文件路徑(請根據實際情況修改)
target_path = "target_image.jpg"
watermark_path = "watermark.png"
# 讀取圖像
target_img, watermark_img = read_images(target_path, watermark_path)
# 調整水印尺寸
resized_watermark = resize_watermark(target_img, watermark_img, scale=0.2)
# 顯示原始圖像和調整後的水印圖像
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
# OpenCV讀取的圖像為BGR通道,Matplotlib顯示需要轉換為RGB通道
axes[0].imshow(cv2.cvtColor(target_img, cv2.COLOR_BGR2RGB))
axes[0].set_title("目標原始圖像")
axes[0].axis("off")
# 處理水印圖像的通道顯示(如果有Alpha通道,只顯示RGB部分)
if resized_watermark.shape[-1] == 4:
axes[1].imshow(cv2.cvtColor(resized_watermark[:, :, :3], cv2.COLOR_BGR2RGB))
else:
axes[1].imshow(cv2.cvtColor(resized_watermark, cv2.COLOR_BGR2RGB))
axes[1].set_title("調整尺寸後的水印圖像")
axes[1].axis("off")
plt.tight_layout()
plt.show()

輸出結果為:

Opencv(十三):圖像添加水印 - 教程_位運算_02

關鍵説明:
  • 圖像讀取:使用cv2.imread()讀取圖像,目標圖像默認讀取為BGR通道(OpenCV的默認通道順序),水印圖像使用cv2.IMREAD_UNCHANGED讀取,保留可能存在的Alpha通道(透明背景)。
  • 尺寸調整:通過cv2.resize()調整水印圖像尺寸,保持寬高比的同時,確保水印不超過目標圖像的20%(可通過scale參數調整),避免遮擋主體。
  • 圖像顯示:由於OpenCV讀取的圖像為BGR通道,而Matplotlib默認使用RGB通道顯示,因此需要通過cv2.cvtColor()進行通道轉換,否則會出現顏色失真。

2. 水印掩模生成(灰度化+二值化)

生成清晰的水印掩模是水印嵌入成功的關鍵,需要通過灰度化和二值化兩步操作,提取水印圖案的輪廓。

代碼實現:
# 生成水印掩模(灰度化+二值化)
def generate_watermark_mask(watermark_img, threshold=127):
# 如果水印圖像有Alpha通道,先提取RGB通道(忽略透明背景)
if watermark_img.shape[-1] == 4:
watermark_rgb = watermark_img[:, :, :3]
else:
watermark_rgb = watermark_img
# 步驟1:灰度化(將彩色圖像轉換為單通道灰度圖)
gray_watermark = cv2.cvtColor(watermark_rgb, cv2.COLOR_BGR2GRAY)
# 步驟2:二值化(根據閾值將灰度圖轉換為黑白二值圖)
# THRESH_BINARY_INV:反相二值化,背景變為白色(255),水印主體變為黑色(0)
_, binary_mask = cv2.threshold(gray_watermark, threshold, 255, cv2.THRESH_BINARY_INV)
# 可選:對二值化圖像進行形態學操作,去除噪聲,優化掩模輪廓
kernel = np.ones((3, 3), np.uint8)
binary_mask = cv2.erode(binary_mask, kernel, iterations=1)  # 腐蝕:去除細小噪聲
binary_mask = cv2.dilate(binary_mask, kernel, iterations=1)  # 膨脹:恢復水印輪廓
return gray_watermark, binary_mask
# 主函數中添加掩模生成和顯示
if __name__ == "__main__":
# (承接上文代碼)讀取圖像並調整水印尺寸後...
# 生成水印掩模(閾值可根據水印圖像的亮度調整,默認127)
gray_watermark, binary_mask = generate_watermark_mask(resized_watermark, threshold=150)
# 顯示灰度化和二值化結果
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(gray_watermark, cmap="gray")
axes[0].set_title("水印灰度圖")
axes[0].axis("off")
axes[1].imshow(binary_mask, cmap="gray")
axes[1].set_title("水印二值化掩模")
axes[1].axis("off")
plt.tight_layout()
plt.show()
關鍵説明:
  • 通道處理:如果水印圖像有Alpha通道(透明背景),先提取RGB通道,避免透明背景對掩模生成的干擾。
  • 灰度化:使用cv2.COLOR_BGR2GRAY將彩色圖像轉換為灰度圖,簡化後續處理。
  • 二值化:使用cv2.threshold()進行反相二值化(cv2.THRESH_BINARY_INV),讓水印主體變為黑色(0),背景變為白色(255),這樣生成的掩模在後續位運算中能準確“挖”出目標區域。
  • 形態學優化:通過腐蝕(cv2.erode)和膨脹(cv2.dilate)操作,去除二值化圖像中的細小噪聲(如背景中的雜點),同時修復水印輪廓的斷裂部分,讓掩模更清晰。

3. 目標圖像ROI區域提取與位運算

在目標圖像中選擇ROI區域,並通過位運算“挖”出與水印形狀一致的鏤空區域,為嵌入水印做準備。

代碼實現:
# 提取目標圖像的ROI區域並進行位運算
def extract_roi_and_bitwise(target_img, binary_mask, position="top-left"):
# 獲取水印掩模的尺寸(高度、寬度)
mask_height, mask_width = binary_mask.shape[:2]
# 獲取目標圖像的尺寸
target_height, target_width = target_img.shape[:2]
# 根據指定位置計算ROI區域的座標(默認左上角)
if position == "top-left":
roi_y1, roi_y2 = 0, mask_height
roi_x1, roi_x2 = 0, mask_width
elif position == "top-right":
roi_y1, roi_y2 = 0, mask_height
roi_x1, roi_x2 = target_width - mask_width, target_width
elif position == "bottom-left":
roi_y1, roi_y2 = target_height - mask_height, target_height
roi_x1, roi_x2 = 0, mask_width
elif position == "bottom-right":
roi_y1, roi_y2 = target_height - mask_height, target_height
roi_x1, roi_x2 = target_width - mask_width, target_width
else:
raise ValueError("位置參數錯誤,可選值:top-left/top-right/bottom-left/bottom-right")
# 檢查ROI區域是否超出目標圖像範圍
if roi_y2 > target_height or roi_x2 > target_width:
raise ValueError("水印尺寸過大,超出目標圖像範圍,請調整水印縮放比例")
# 提取ROI區域(目標圖像中需要添加水印的部分)
roi = target_img[roi_y1:roi_y2, roi_x1:roi_x2]
# 位運算:將ROI區域與掩模進行與運算,得到“鏤空”的ROI背景
# 由於掩模中水印部分為0,與運算後ROI對應位置變為0(黑色),其他位置保持不變
roi_bg = cv2.bitwise_and(roi, roi, mask=binary_mask)
return roi, roi_bg, (roi_y1, roi_y2, roi_x1, roi_x2)
# 主函數中添加ROI提取和位運算
if __name__ == "__main__":
# (承接上文代碼)生成水印掩模後...
# 提取ROI區域並進行位運算(位置選擇左上角)
roi, roi_bg, roi_coords = extract_roi_and_bitwise(target_img, binary_mask, position="top-left")
roi_y1, roi_y2, roi_x1, roi_x2 = roi_coords
# 顯示ROI區域和位運算後的結果
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(cv2.cvtColor(roi, cv2.COLOR_BGR2RGB))
axes[0].set_title("目標圖像ROI區域")
axes[0].axis("off")
axes[1].imshow(cv2.cvtColor(roi_bg, cv2.COLOR_BGR2RGB))
axes[1].set_title("位運算後的鏤空ROI背景")
axes[1].axis("off")
plt.tight_layout()
plt.show()
關鍵説明:
  • ROI位置選擇:支持左上角、右上角、左下角、右下角四種位置,可通過position參數指定,滿足不同的水印佈局需求。
  • 邊界檢查:在提取ROI區域前,檢查水印尺寸是否超出目標圖像範圍,避免出現數組索引錯誤。
  • 位運算原理:cv2.bitwise_and(roi, roi, mask=binary_mask)表示對ROI區域自身進行與運算,並以binary_mask為掩模。掩模中像素值為0的區域(水印主體)會將ROI對應位置的像素值置為0(黑色),而掩模中像素值為255的區域(背景)則保留ROI的原始像素值,從而實現“鏤空”效果。

4. 圖像融合與水印嵌入

將原始水印圖像與“鏤空”的ROI背景進行融合,實現水印的最終嵌入,並將融合後的ROI區域放回目標圖像的原位置。

代碼實現:
# 圖像融合與水印嵌入
def fuse_images_and_embed(roi_bg, watermark_img, binary_mask):
# 如果水印圖像有Alpha通道,提取RGB通道(忽略透明背景)
if watermark_img.shape[-1] == 4:
watermark_rgb = watermark_img[:, :, :3]
else:
watermark_rgb = watermark_img
# 調整水印圖像的尺寸,確保與ROI背景一致
watermark_resized = cv2.resize(watermark_rgb, (roi_bg.shape[1], roi_bg.shape[0]), interpolation=cv2.INTER_AREA)
# 對水印圖像進行二值化反相處理,確保水印主體與背景分離
_, watermark_binary = cv2.threshold(cv2.cvtColor(watermark_resized, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)
watermark_foreground = cv2.bitwise_and(watermark_resized, watermark_resized, mask=watermark_binary)
# 圖像融合:將水印前景與ROI背景相加,水印填充到鏤空區域
fused_roi = cv2.add(roi_bg, watermark_foreground)
return fused_roi
# 替換目標圖像中的ROI區域,得到最終帶水印的圖像
def get_watermarked_image(target_img, fused_roi, roi_coords):
roi_y1, roi_y2, roi_x1, roi_x2 = roi_coords
# 創建目標圖像的副本,避免修改原始圖像
target_img_copy = target_img.copy()
# 將融合後的ROI區域放回原位置
target_img_copy[roi_y1:roi_y2, roi_x1:roi_x2] = fused_roi
return target_img_copy
# 主函數中添加圖像融合和水印嵌入
if __name__ == "__main__":
# (承接上文代碼)提取ROI和位運算後...
# 圖像融合與水印嵌入
fused_roi = fuse_images_and_embed(roi_bg, resized_watermark, binary_mask)
# 得到最終帶水印的圖像
watermarked_img = get_watermarked_image(target_img, fused_roi, roi_coords)
# 顯示融合後的ROI和最終結果
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
axes[0].imshow(cv2.cvtColor(fused_roi, cv2.COLOR_BGR2RGB))
axes[0].set_title("融合水印後的ROI區域")
axes[0].axis("off")
axes[1].imshow(cv2.cvtColor(watermarked_img, cv2.COLOR_BGR2RGB))
axes[1].set_title("最終帶水印的圖像")
axes[1].axis("off")
plt.tight_layout()
plt.show()
# 保存帶水印的圖像
cv2.imwrite("watermarked_image.jpg", watermarked_img)
print("帶水印的圖像已保存為 watermarked_image.jpg")
關鍵説明:
  • 水印前景提取:對水印圖像進行二值化處理,提取水印主體作為前景,避免水印背景對融合效果的干擾。
  • 圖像融合:使用cv2.add()進行像素級相加,由於ROI背景的“鏤空”部分像素值為0,相加後水印前景的像素值直接填充到該區域,而其他區域保持原背景不變,實現自然融合。
  • 結果保存:使用cv2.imwrite()將帶水印的圖像保存到本地,方便後續使用。

四、效果優化方案:解決常見問題

在實際應用中,可能會遇到水印模糊、顏色失真、遮擋主體、掩模輪廓不清晰等問題,以下是針對性的優化方案:

1. 掩模優化:調整二值化閾值與形態學操作

  • 閾值調整:如果水印掩模輪廓不清晰(如背景殘留或水印主體缺失),可調整generate_watermark_mask()函數中的threshold參數(範圍0-255)。亮度較高的水印圖像可適當提高閾值(如150-200),亮度較低的圖像可降低閾值(如80-127)。
  • 形態學操作增強:如果掩模中存在較多噪聲或輪廓斷裂,可增加形態學操作的迭代次數,或調整卷積核大小(如使用5×5的核):
kernel = np.ones((5, 5), np.uint8)
binary_mask = cv2.erode(binary_mask, kernel, iterations=2)
binary_mask = cv2.dilate(binary_mask, kernel, iterations=2)

2. 水印透明度調整:實現半透明效果

默認的圖像融合方式是完全不透明的,若想讓水印更隱蔽,可添加透明度調整功能:

# 帶透明度的圖像融合
def fuse_with_transparency(roi_bg, watermark_img, binary_mask, alpha=0.7):
# alpha:水印透明度(0-1,0完全透明,1完全不透明)
if watermark_img.shape[-1] == 4:
watermark_rgb = watermark_img[:, :, :3]
else:
watermark_rgb = watermark_img
watermark_resized = cv2.resize(watermark_rgb, (roi_bg.shape[1], roi_bg.shape[0]))
# 提取水印前景
_, watermark_binary = cv2.threshold(cv2.cvtColor(watermark_resized, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)
watermark_foreground = cv2.bitwise_and(watermark_resized, watermark_resized, mask=watermark_binary)
# 透明度融合:roi_bg * (1 - alpha) + watermark_foreground * alpha
fused_roi = cv2.addWeighted(roi_bg, (1 - alpha), watermark_foreground, alpha, 0)
return fused_roi

調用時傳入alpha參數(如0.5),即可實現半透明水印效果,既保證版權標識,又不影響原圖觀看。

3. 水印位置與尺寸優化

  • 位置選擇:根據目標圖像的主體內容選擇水印位置,避免遮擋關鍵信息(如人物面部、文字內容)。例如,風景圖可選擇右下角,人物圖可選擇左上角。
  • 尺寸調整:通過resize_watermark()函數的scale參數調整水印尺寸,建議水印寬度不超過目標圖像寬度的1/5,高度同理,確保水印不突兀。

4. 彩色水印優化:保留原始色彩

如果水印圖像是彩色的,為了避免融合後顏色失真,可在融合前確保水印圖像與目標圖像的通道數一致(均為3通道),並在提取水印前景時保留彩色信息(如上文代碼所示)。若水印顏色過亮或過暗,可通過調整水印圖像的亮度和對比度優化:

# 調整圖像亮度和對比度
def adjust_brightness_contrast(img, alpha=1.0, beta=0):
# alpha:對比度調整(1.0為默認,>1增強,<1減弱)
# beta:亮度調整(0為默認,正值增亮,負值變暗)
adjusted = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)
return adjusted
# 在融合前調用
watermark_resized = adjust_brightness_contrast(watermark_resized, alpha=1.2, beta=10)

五、完整代碼整合與運行説明

1. 完整代碼

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 設置中文字體
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 圖像讀取與預處理
def read_images(target_path, watermark_path):
target_img = cv2.imread(target_path)
if target_img is None:
raise ValueError("目標圖像讀取失敗,請檢查文件路徑是否正確")
watermark_img = cv2.imread(watermark_path, cv2.IMREAD_UNCHANGED)
if watermark_img is None:
raise ValueError("水印圖像讀取失敗,請檢查文件路徑是否正確")
return target_img, watermark_img
def resize_watermark(target_img, watermark_img, scale=0.25):
target_height, target_width = target_img.shape[:2]
watermark_height, watermark_width = watermark_img.shape[:2]
max_watermark_width = int(target_width * scale)
max_watermark_height = int(target_height * scale)
scale_ratio = min(max_watermark_width / watermark_width, max_watermark_height / watermark_height)
new_watermark_size = (int(watermark_width * scale_ratio), int(watermark_height * scale_ratio))
resized_watermark = cv2.resize(watermark_img, new_watermark_size, interpolation=cv2.INTER_AREA)
return resized_watermark
# 2. 水印掩模生成
def generate_watermark_mask(watermark_img, threshold=127):
if watermark_img.shape[-1] == 4:
watermark_rgb = watermark_img[:, :, :3]
else:
watermark_rgb = watermark_img
gray_watermark = cv2.cvtColor(watermark_rgb, cv2.COLOR_BGR2GRAY)
_, binary_mask = cv2.threshold(gray_watermark, threshold, 255, cv2.THRESH_BINARY_INV)
# 形態學優化
kernel = np.ones((3, 3), np.uint8)
binary_mask = cv2.erode(binary_mask, kernel, iterations=1)
binary_mask = cv2.dilate(binary_mask, kernel, iterations=1)
return gray_watermark, binary_mask
# 3. ROI提取與位運算
def extract_roi_and_bitwise(target_img, binary_mask, position="top-left"):
mask_height, mask_width = binary_mask.shape[:2]
target_height, target_width = target_img.shape[:2]
if position == "top-left":
roi_y1, roi_y2 = 0, mask_height
roi_x1, roi_x2 = 0, mask_width
elif position == "top-right":
roi_y1, roi_y2 = 0, mask_height
roi_x1, roi_x2 = target_width - mask_width, target_width
elif position == "bottom-left":
roi_y1, roi_y2 = target_height - mask_height, target_height
roi_x1, roi_x2 = 0, mask_width
elif position == "bottom-right":
roi_y1, roi_y2 = target_height - mask_height, target_height
roi_x1, roi_x2 = target_width - mask_width, target_width
else:
raise ValueError("位置參數錯誤,可選值:top-left/top-right/bottom-left/bottom-right")
if roi_y2 > target_height or roi_x2 > target_width:
raise ValueError("水印尺寸過大,超出目標圖像範圍,請調整水印縮放比例")
roi = target_img[roi_y1:roi_y2, roi_x1:roi_x2]
roi_bg = cv2.bitwise_and(roi, roi, mask=binary_mask)
return roi, roi_bg, (roi_y1, roi_y2, roi_x1, roi_x2)
# 4. 圖像融合(帶透明度)
def fuse_with_transparency(roi_bg, watermark_img, binary_mask, alpha=0.7):
if watermark_img.shape[-1] == 4:
watermark_rgb = watermark_img[:, :, :3]
else:
watermark_rgb = watermark_img
watermark_resized = cv2.resize(watermark_rgb, (roi_bg.shape[1], roi_bg.shape[0]), interpolation=cv2.INTER_AREA)
# 調整水印亮度和對比度
watermark_resized = cv2.convertScaleAbs(watermark_resized, alpha=1.2, beta=10)
# 提取水印前景
_, watermark_binary = cv2.threshold(cv2.cvtColor(watermark_resized, cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)
watermark_foreground = cv2.bitwise_and(watermark_resized, watermark_resized, mask=watermark_binary)
# 透明度融合
fused_roi = cv2.addWeighted(roi_bg, (1 - alpha), watermark_foreground, alpha, 0)
return fused_roi
# 5. 生成最終帶水印圖像
def get_watermarked_image(target_img, fused_roi, roi_coords):
roi_y1, roi_y2, roi_x1, roi_x2 = roi_coords
target_img_copy = target_img.copy()
target_img_copy[roi_y1:roi_y2, roi_x1:roi_x2] = fused_roi
return target_img_copy
# 6. 結果顯示與保存
def show_and_save_results(target_img, watermark_img, gray_watermark, binary_mask, roi, roi_bg, fused_roi, watermarked_img):
# 轉換通道用於顯示
target_rgb = cv2.cvtColor(target_img, cv2.COLOR_BGR2RGB)
if watermark_img.shape[-1] == 4:
watermark_rgb = cv2.cvtColor(watermark_img[:, :, :3], cv2.COLOR_BGR2RGB)
else:
watermark_rgb = cv2.cvtColor(watermark_img, cv2.COLOR_BGR2RGB)
roi_rgb = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)
roi_bg_rgb = cv2.cvtColor(roi_bg, cv2.COLOR_BGR2RGB)
fused_roi_rgb = cv2.cvtColor(fused_roi, cv2.COLOR_BGR2RGB)
watermarked_rgb = cv2.cvtColor(watermarked_img, cv2.COLOR_BGR2RGB)
# 創建子圖顯示所有步驟結果
fig, axes = plt.subplots(3, 2, figsize=(16, 18))
axes[0, 0].imshow(target_rgb)
axes[0, 0].set_title("原始目標圖像")
axes[0, 0].axis("off")
axes[0, 1].imshow(watermark_rgb)
axes[0, 1].set_title("原始水印圖像")
axes[0, 1].axis("off")
axes[1, 0].imshow(gray_watermark, cmap="gray")
axes[1, 0].set_title("水印灰度圖")
axes[1, 0].axis("off")
axes[1, 1].imshow(binary_mask, cmap="gray")
axes[1, 1].set_title("水印二值化掩模")
axes[1, 1].axis("off")
axes[2, 0].imshow(roi_bg_rgb)
axes[2, 0].set_title("位運算後的鏤空ROI")
axes[2, 0].axis("off")
axes[2, 1].imshow(watermarked_rgb)
axes[2, 1].set_title("最終帶水印圖像(透明度0.7)")
axes[2, 1].axis("off")
plt.tight_layout()
plt.show()
# 保存結果
cv2.imwrite("watermarked_image.jpg", watermarked_img)
cv2.imwrite("binary_mask.jpg", binary_mask)
print("帶水印的圖像已保存為:watermarked_image.jpg")
print("水印掩模已保存為:binary_mask.jpg")
# 主函數
if __name__ == "__main__":
# 配置參數(根據實際情況修改)
target_path = "target_image.jpg"    # 目標圖像路徑
watermark_path = "watermark.png"    # 水印圖像路徑
watermark_scale = 0.2               # 水印縮放比例(目標圖像的20%)
binarization_threshold = 150        # 二值化閾值
watermark_position = "bottom-right" # 水印位置(bottom-right:右下角)
transparency_alpha = 0.7            # 水印透明度(0-1)
try:
# 步驟1:讀取並調整圖像
print("正在讀取圖像...")
target_img, watermark_img = read_images(target_path, watermark_path)
resized_watermark = resize_watermark(target_img, watermark_img, scale=watermark_scale)
# 步驟2:生成水印掩模
print("正在生成水印掩模...")
gray_watermark, binary_mask = generate_watermark_mask(resized_watermark, threshold=binarization_threshold)
# 步驟3:提取ROI並進行位運算
print("正在處理目標圖像ROI區域...")
roi, roi_bg, roi_coords = extract_roi_and_bitwise(target_img, binary_mask, position=watermark_position)
# 步驟4:圖像融合
print("正在融合圖像並嵌入水印...")
fused_roi = fuse_with_transparency(roi_bg, resized_watermark, binary_mask, alpha=transparency_alpha)
# 步驟5:生成最終圖像
watermarked_img = get_watermarked_image(target_img, fused_roi, roi_coords)
# 步驟6:顯示並保存結果
print("正在顯示並保存結果...")
show_and_save_results(target_img, resized_watermark, gray_watermark, binary_mask, roi, roi_bg, fused_roi, watermarked_img)
print("水印添加完成!")
except Exception as e:
print(f"程序運行出錯:{str(e)}")

輸出結果

Opencv(十三):圖像添加水印 - 教程_位運算_03


注意:這裏原始圖片和水印圖片我是選擇我自己的路徑,你可以把路徑改成你自己的

2. 運行説明

  • 環境要求:Python 3.7+,OpenCV 4.0+,NumPy 1.18+,Matplotlib 3.0+。
  • 素材準備:將目標圖像命名為target_image.jpg,水印圖像命名為watermark.png,放在代碼同一目錄下;若路徑不同,需修改target_pathwatermark_path參數。
  • 參數調整:
  • watermark_scale:水印縮放比例,默認0.2(目標圖像的20%),可根據需求調整。
  • binarization_threshold:二值化閾值,默認150,可根據水印圖像的亮度調整。
  • watermark_position:水印位置,可選top-left(左上角)、top-right(右上角)、bottom-left(左下角)、bottom-right(右下角)。
  • transparency_alpha:水印透明度,默認0.7,0為完全透明,1為完全不透明。
  • 運行結果:運行代碼後,會彈出包含所有處理步驟的圖像窗口,並在當前目錄下保存watermarked_image.jpg(帶水印的圖像)和binary_mask.jpg(水印掩模)。

七、總結

本文詳細講解了基於Python和OpenCV的圖像添加水印技術,從核心原理(灰度化、二值化、位運算、圖像融合)到分步實現,再到效果優化和拓展應用,提供了完整的技術方案和可運行代碼。通過本文的學習,你可以掌握:

  • 圖像預處理的基本操作(讀取、尺寸調整、通道轉換)。
  • 水印掩模的生成方法(灰度化+二值化+形態學優化)。
  • 圖像位運算和融合的核心邏輯。
  • 水印效果的優化技巧(透明度調整、位置選擇、尺寸優化)。