【OpenCV】Python图像处理之数字水印

OpenCV-Python 实现数字水印的核心是将水印信息(文本、图像) 嵌入到原始图像中,且不影响原始图像的视觉效果,同时保证水印可被准确提取。根据水印的可见性,分为可见水印 (如 LOGO 叠加)和不可见水印 (隐藏在像素中);根据提取方式,分为盲水印 (无需原始图像即可提取)和非盲水印(需原始图像辅助提取)。以下详细介绍常用水印方案的实现及原理:

可见水印直接叠加在原始图像上,用于版权声明(如图片 LOGO、文字标识),核心是通过图像混合Alpha 通道控制透明度实现。

python 复制代码
import cv2
import numpy as np
import matplotlib.pyplot as plt

def add_visible_logo_watermark(img, logo_path, alpha=0.3, position='bottom-right'):
    """
    添加可见LOGO水印
    :param img: 原始图像
    :param logo_path: 水印LOGO路径(推荐PNG带Alpha通道)
    :param alpha: 水印透明度(0-1,越小越透明)
    :param position: 水印位置(top-left/top-right/bottom-left/bottom-right)
    :return: 带水印的图像
    """
    # 读取LOGO并保留Alpha通道
    logo = cv2.imread(logo_path, cv2.IMREAD_UNCHANGED)
    if logo is None:
        raise ValueError("LOGO读取失败!")
    
    # 调整LOGO尺寸(默认占原图1/5)
    h, w = img.shape[:2]
    logo_h, logo_w = logo.shape[:2]
    scale = min(w//5 / logo_w, h//5 / logo_h)  # 缩放比例
    logo = cv2.resize(logo, (int(logo_w*scale), int(logo_h*scale)))
    
    # 分离LOGO的RGB和Alpha通道
    logo_rgb = logo[:, :, :3]
    logo_alpha = logo[:, :, 3] / 255.0  # 归一化到0-1
    
    # 计算水印位置
    lh, lw = logo_rgb.shape[:2]
    if position == 'bottom-right':
        x = w - lw - 20  # 右偏移20像素
        y = h - lh - 20  # 下偏移20像素
    elif position == 'bottom-left':
        x = 20
        y = h - lh - 20
    elif position == 'top-right':
        x = w - lw - 20
        y = 20
    else:  # top-left
        x = 20
        y = 20
    
    # 提取原图对应区域并混合
    roi = img[y:y+lh, x:x+lw]
    # 混合公式:roi * (1 - alpha*logo_alpha) + logo_rgb * (alpha*logo_alpha)
    for c in range(3):
        roi[:, :, c] = np.uint8(
            roi[:, :, c] * (1 - alpha * logo_alpha) + logo_rgb[:, :, c] * (alpha * logo_alpha)
        )
    
    return img

# 测试
img = cv2.imread('image.jpg')
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('Original Img'), plt.axis('off')
watermarked_img = add_visible_logo_watermark(img, 'logo.png', alpha=0.4)

# 显示
plt.subplot(122), plt.imshow(cv2.cvtColor(watermarked_img, cv2.COLOR_BGR2RGB)), plt.title('LOGO Img'), plt.axis('off')
plt.show()

# 保存结果
cv2.imwrite('watermarked_logo.jpg', watermarked_img)
运行效果图:
2. 文本水印(添加版权文字)
python 复制代码
def add_text_watermark(img, text, font_scale=1.0, color=(255, 255, 255), alpha=0.5, position='bottom-right'):
    """
    添加文本水印
    :param img: 原始图像
    :param text: 水印文本(如"© 2025 版权所有")
    :param font_scale: 字体大小
    :param color: 文本颜色(BGR)
    :param alpha: 透明度(0-1)
    :param position: 位置
    :return: 带水印的图像
    """
    h, w = img.shape[:2]
    # 定义字体
    font = cv2.FONT_HERSHEY_SIMPLEX
    # 计算文本尺寸
    text_size, _ = cv2.getTextSize(text, font, font_scale, thickness=2)
    tw, th = text_size
    
    # 计算文本位置
    if position == 'bottom-right':
        x = w - tw - 20
        y = h - th - 10
    else:  # top-left
        x = 20
        y = th + 20
    
    # 创建透明文本层
    text_layer = np.zeros_like(img, dtype=np.uint8)
    cv2.putText(text_layer, text, (x, y), font, font_scale, color, thickness=2)
    
    # 混合文本层和原图
    watermarked = cv2.addWeighted(img, 1.0, text_layer, alpha, 0)
    return watermarked

# 测试文本水印(添加版权文字)
text_watermarked = add_text_watermark(img, "@ 2025 CSDN:BangBangDePiPi", font_scale=0.8, color=(0, 0, 255), alpha=0.4)
plt.subplot(122), plt.imshow(cv2.cvtColor(text_watermarked, cv2.COLOR_BGR2RGB)), plt.title('Watermark'), plt.axis('off'), plt.show()
效果运行图:
3.完整代码:
python 复制代码
import cv2
import numpy as np
import matplotlib.pyplot as plt


def add_visible_logo_watermark(img, logo_path, alpha=0.3, position='bottom-right'):
    """
    添加可见LOGO水印
    :param img: 原始图像
    :param logo_path: 水印LOGO路径(推荐PNG带Alpha通道)
    :param alpha: 水印透明度(0-1,越小越透明)
    :param position: 水印位置(top-left/top-right/bottom-left/bottom-right)
    :return: 带水印的图像
    """
    # 读取LOGO并保留Alpha通道
    logo = cv2.imread(logo_path, cv2.IMREAD_UNCHANGED)
    if logo is None:
        raise ValueError("LOGO读取失败!")

    # 调整LOGO尺寸(默认占原图1/5)
    h, w = img.shape[:2]
    logo_h, logo_w = logo.shape[:2]
    scale = min(w // 5 / logo_w, h // 5 / logo_h)  # 缩放比例
    logo = cv2.resize(logo, (int(logo_w * scale), int(logo_h * scale)))

    # 分离LOGO的RGB和Alpha通道
    logo_rgb = logo[:, :, :3]
    logo_alpha = logo[:, :, 2] / 255.0  # 归一化到0-1

    # 计算水印位置
    lh, lw = logo_rgb.shape[:2]
    if position == 'bottom-right':
        x = w - lw - 20  # 右偏移20像素
        y = h - lh - 20  # 下偏移20像素
    elif position == 'bottom-left':
        x = 20
        y = h - lh - 20
    elif position == 'top-right':
        x = w - lw - 20
        y = 20
    else:  # top-left
        x = 20
        y = 20

    # 提取原图对应区域并混合
    roi = img[y:y + lh, x:x + lw]
    # 混合公式:roi * (1 - alpha*logo_alpha) + logo_rgb * (alpha*logo_alpha)
    for c in range(3):
        roi[:, :, c] = np.uint8(
            roi[:, :, c] * (1 - alpha * logo_alpha) + logo_rgb[:, :, c] * (alpha * logo_alpha)
        )

    return img


def add_text_watermark(img, text, font_scale=1.0, color=(255, 255, 255), alpha=0.5, position='bottom-right'):
    """
    添加文本水印
    :param img: 原始图像
    :param text: 水印文本(如"© 2025 版权所有")
    :param font_scale: 字体大小
    :param color: 文本颜色(BGR)
    :param alpha: 透明度(0-1)
    :param position: 位置
    :return: 带水印的图像
    """
    h, w = img.shape[:2]
    # 定义字体
    font = cv2.FONT_HERSHEY_SIMPLEX
    # 计算文本尺寸
    text_size, _ = cv2.getTextSize(text, font, font_scale, thickness=2)
    tw, th = text_size

    # 计算文本位置
    if position == 'bottom-right':
        x = w - tw - 20
        y = h - th - 10
    else:  # top-left
        x = 20
        y = th + 20

    # 创建透明文本层
    text_layer = np.zeros_like(img, dtype=np.uint8)
    cv2.putText(text_layer, text, (x, y), font, font_scale, color, thickness=2)

    # 混合文本层和原图
    watermarked = cv2.addWeighted(img, 1.0, text_layer, alpha, 0)
    return watermarked

# 测试
img = cv2.imread('panorama.jpg')
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('Original Img'), plt.axis('off')
# 测试文本水印(添加版权文字)
text_watermarked = add_text_watermark(img, "@ 2025 CSDN:BangBangDePiPi", font_scale=0.8, color=(0, 0, 255), alpha=0.4)
plt.subplot(122), plt.imshow(cv2.cvtColor(text_watermarked, cv2.COLOR_BGR2RGB)), plt.title('Watermark'), plt.axis('off'), plt.show()


# # 测试图像 LOGO 水印(带透明度控制)
# watermarked_img = add_visible_logo_watermark(img, 'Python-OpenCV.png', alpha=0.4)
# plt.subplot(122), plt.imshow(cv2.cvtColor(watermarked_img, cv2.COLOR_BGR2RGB)), plt.title('LOGO Img'), plt.axis('off')

# 显示
plt.show()

# 保存结果
# cv2.imwrite('watermarked_logo.jpg', watermarked_img)

二、不可见水印(LSB 算法)

不可见水印隐藏在图像的最低有效位(LSB) 中,人眼无法察觉,核心原理是:图像像素的低位(如第 0 位、第 1 位)对视觉影响极小,可替换为水印信息。

1. 原理
  • 水印预处理:将二值水印图像(0/255)转为 0/1 的二进制序列;
  • 嵌入:将原始图像像素的最低位(LSB)替换为水印的二进制位;
  • 提取:读取含水印图像的最低位,恢复水印二进制序列,重构水印图像。
2. 实现代码(图像水印)
python 复制代码
def embed_lsb_watermark(img, watermark_path):
    """
    LSB不可见水印嵌入(非盲水印)
    :param img: 原始图像(灰度图或彩色图)
    :param watermark_path: 水印图像路径(建议二值图,尺寸小于原图1/8)
    :return: 含水印图像
    """
    # 读取并预处理水印(转为二值图)
    watermark = cv2.imread(watermark_path, 0)
    _, watermark_bin = cv2.threshold(watermark, 127, 1, cv2.THRESH_BINARY)  # 0/1二值化
    wm_h, wm_w = watermark_bin.shape
    
    # 检查水印尺寸(需小于原图1/8,避免溢出)
    h, w = img.shape[:2]
    if wm_h > h//2 or wm_w > w//2:
        raise ValueError("水印尺寸过大!请缩小水印至原图1/2以内")
    
    # 若为彩色图,转为灰度图处理(或选择单个通道)
    if len(img.shape) == 3:
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    else:
        img_gray = img.copy()
    
    # 嵌入水印(替换最低位)
    watermarked = img_gray.copy()
    for i in range(wm_h):
        for j in range(wm_w):
            # 清空像素最低位,嵌入水印位
            watermarked[i, j] = (watermarked[i, j] & ~1) | watermark_bin[i, j]
    
    # 若为彩色图,将处理后的灰度图合并回彩色通道
    if len(img.shape) == 3:
        watermarked_color = img.copy()
        watermarked_color[:, :, 0] = watermarked  # 嵌入到蓝色通道
        return watermarked_color
    else:
        return watermarked

def extract_lsb_watermark(watermarked_img, wm_size):
    """
    提取LSB水印
    :param watermarked_img: 含水印图像
    :param wm_size: 水印尺寸(h, w)
    :return: 提取的水印图像
    """
    wm_h, wm_w = wm_size
    # 若为彩色图,提取嵌入通道(如蓝色通道)
    if len(watermarked_img.shape) == 3:
        img_gray = watermarked_img[:, :, 0]
    else:
        img_gray = watermarked_img
    
    # 提取最低位作为水印
    watermark = np.zeros((wm_h, wm_w), dtype=np.uint8)
    for i in range(wm_h):
        for j in range(wm_w):
            watermark[i, j] = img_gray[i, j] & 1  # 提取最低位
    watermark = watermark * 255  # 恢复为0/255二值图
    return watermark

# 测试
img = cv2.imread('image.jpg')
watermark = cv2.imread('watermark.png', 0)
wm_h, wm_w = watermark.shape

# 嵌入水印
watermarked = embed_lsb_watermark(img, 'watermark.png')

# 提取水印
extracted_wm = extract_lsb_watermark(watermarked, (wm_h, wm_w))

# 显示结果
plt.figure(figsize=(15, 5))
plt.subplot(131), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原始图像'), plt.axis('off')
plt.subplot(132), plt.imshow(cv2.cvtColor(watermarked, cv2.COLOR_BGR2RGB)), plt.title('含水印图像'), plt.axis('off')
plt.subplot(133), plt.imshow(extracted_wm, cmap='gray'), plt.title('提取的水印'), plt.axis('off')
plt.show()

# 保存结果
cv2.imwrite('watermarked_lsb.jpg', watermarked)
cv2.imwrite('extracted_watermark.jpg', extracted_wm)
3. 优化:文本水印嵌入(LSB)

将文本转为二进制序列嵌入图像:

python 复制代码
def text_to_bin(text):
    """文本转二进制序列"""
    return ''.join([format(ord(c), '08b') for c in text])

def embed_text_lsb(img, text):
    """嵌入文本水印"""
    bin_text = text_to_bin(text) + '11111111'  # 结束标志(8个1)
    text_len = len(bin_text)
    
    # 检查文本长度(需小于图像像素数)
    h, w = img.shape[:2]
    pixel_count = h * w
    if text_len > pixel_count:
        raise ValueError("文本过长!请缩短文本或使用更大图像")
    
    # 嵌入文本
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
    idx = 0
    for i in range(h):
        for j in range(w):
            if idx < text_len:
                # 替换最低位为文本二进制位
                img_gray[i, j] = (img_gray[i, j] & ~1) | int(bin_text[idx])
                idx += 1
            else:
                break
        if idx >= text_len:
            break
    
    # 转回彩色图
    if len(img.shape) == 3:
        img_color = img.copy()
        img_color[:, :, 0] = img_gray
        return img_color
    return img_gray

def extract_text_lsb(watermarked_img):
    """提取文本水印"""
    img_gray = watermarked_img[:, :, 0] if len(watermarked_img.shape) == 3 else watermarked_img
    h, w = img_gray.shape
    bin_text = ''
    
    # 提取最低位,直到遇到结束标志
    for i in range(h):
        for j in range(w):
            bin_text += str(img_gray[i, j] & 1)
            # 检查是否到达结束标志
            if len(bin_text) >= 8 and bin_text[-8:] == '11111111':
                bin_text = bin_text[:-8]  # 去掉结束标志
                # 二进制转文本
                text = ''
                for k in range(0, len(bin_text), 8):
                    byte = bin_text[k:k+8]
                    if len(byte) == 8:
                        text += chr(int(byte, 2))
                return text
    return ""

# 测试
text = "© 2025 MyCopyright - Confidential"
watermarked_text = embed_text_lsb(img, text)
extracted_text = extract_text_lsb(watermarked_text)

print("提取的文本水印:", extracted_text)
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原始'), plt.axis('off')
plt.subplot(122), plt.imshow(cv2.cvtColor(watermarked_text, cv2.COLOR_BGR2RGB)), plt.title('文本水印'), plt.axis('off')
plt.show()

三、鲁棒性优化(结合加密)

LSB 水印易受图像压缩、噪声、裁剪等攻击,可结合之前的异或加密提升鲁棒性:

python 复制代码
def embed_robust_lsb(img, watermark_path, key=123):
    """带加密的LSB水印"""
    # 1. 水印加密(异或)
    watermark = cv2.imread(watermark_path, 0)
    _, wm_bin = cv2.threshold(watermark, 127, 1, cv2.THRESH_BINARY)
    wm_encrypted = (wm_bin ^ (key % 2))  # 简单异或加密(可替换为混沌加密)
    
    # 2. 嵌入加密后的水印
    h, w = img.shape[:2]
    wm_h, wm_w = wm_encrypted.shape
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
    
    for i in range(wm_h):
        for j in range(wm_w):
            img_gray[i, j] = (img_gray[i, j] & ~1) | wm_encrypted[i, j]
    
    # 3. 转回彩色图
    if len(img.shape) == 3:
        img_color = img.copy()
        img_color[:, :, 0] = img_gray
        return img_color
    return img_gray

def extract_robust_lsb(watermarked_img, wm_size, key=123):
    """提取带加密的LSB水印"""
    wm_h, wm_w = wm_size
    img_gray = watermarked_img[:, :, 0] if len(watermarked_img.shape) == 3 else watermarked_img
    wm_extracted = np.zeros((wm_h, wm_w), dtype=np.uint8)
    
    # 1. 提取水印
    for i in range(wm_h):
        for j in range(wm_w):
            wm_extracted[i, j] = img_gray[i, j] & 1
    
    # 2. 水印解密
    wm_decrypted = (wm_extracted ^ (key % 2)) * 255
    return wm_decrypted

四、注意事项

  1. 水印尺寸:不可见水印尺寸不宜过大,否则会影响原始图像质量;
  2. 鲁棒性:LSB 水印对图像压缩(如 JPG)、噪声、裁剪敏感,需结合加密、冗余嵌入等提升鲁棒性;
  3. 视觉隐蔽性:嵌入深度不宜过深(如仅修改第 0 位,避免修改第 1-2 位),否则会出现视觉失真;
  4. 通道选择:彩色图建议嵌入到亮度通道(如 YUV 的 Y 通道)或蓝色通道(人眼对蓝色敏感度较低)。

五、总结

数字水印的核心是隐蔽性可提取性

  • 可见水印:用于显性版权声明,通过透明度控制平衡视觉效果;
  • 不可见水印:用于隐性版权验证,LSB 算法简单高效,适合轻量级场景;
  • 鲁棒性优化:结合加密、冗余嵌入、变换域(如 DCT、DWT)算法,可提升水印抗攻击能力。

根据实际需求选择合适的水印方案,例如:LOGO 水印用于图片版权标注,文本水印用于文档追踪,LSB 水印用于隐蔽版权验证。

相关推荐
tomeasure1 小时前
INTERNAL ASSERT FAILED at “/pytorch/c10/cuda/CUDACachingAllocator.cpp“:983
人工智能·pytorch·python·nvidia
追光天使1 小时前
装饰器的作用
python·装饰器
一瞬祈望2 小时前
【环境配置】Windows 下使用 Anaconda 创建 Python 3.8 环境 + 安装 PyTorch + CUDA(完整教程)
pytorch·windows·python
小兔崽子去哪了2 小时前
RFM 模型 项目实战
python
昨天那个谁谁2 小时前
ROS2运行时报无法加载create_key等符号错误
c++·python·ros2
nju_spy3 小时前
python 算法题基础常用总结(比赛 or 机试 or 面试)
python·记忆化搜索·位运算·二分查找 - bisect·排序与lambda·最短路和最小生成树·堆与优先队列
Deng8723473483 小时前
自动化极验3点选验证码的识别与验证方案
运维·python·自动化
川石课堂软件测试3 小时前
自动化测试的基本概念及常用框架
数据库·python·功能测试·测试工具·单元测试·自动化·流程图
灰勒塔德3 小时前
jetson orin nano super开发指南
linux·服务器·python