思维导图

在这里插入图片描述

前言

本文将从技术原理出发,详细讲解如何用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()

输出结果为:
在这里插入图片描述

关键说明:
  • 图像读取:使用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)}")

输出结果
在这里插入图片描述
注意:这里原始图片和水印图片我是选择我自己的路径,你可以把路径改成你自己的

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的图像添加水印技术,从核心原理(灰度化、二值化、位运算、图像融合)到分步实现,再到效果优化和拓展应用,提供了完整的技术方案和可运行代码。通过本文的学习,你可以掌握:

  • 图像预处理的基本操作(读取、尺寸调整、通道转换)。
  • 水印掩模的生成方法(灰度化+二值化+形态学优化)。
  • 图像位运算和融合的核心逻辑。
  • 水印效果的优化技巧(透明度调整、位置选择、尺寸优化)。