Python使用OpenCV图片去水印多种方案实现
1. 前言
本文为作者学习记录,使用Python结合OpenCV,总结了几种常见的水印去除方式,简单图片去水印效果良好,但是复杂图片有点一言难尽,本文部分代码仅供参考,并不能针对所有水印通用,需要根据具体水印颜色、位置等情况进行分析调整代码。
2. 颜色介绍
本文总共使用了两种格式的颜色,一种是BGR,另一种是HSV。
关于如何获取BGR的颜色,可以直接使用截图或者吸取颜色的工具吸取即可。如下图:
有一点需要注意:Python里面用的是BGR,截图工具给的是RGB,这个使用的使用需要调整下顺序

HSV是由色调(H),饱和度(S),亮度(V)组成,如何获取HSV的值,这里提供一段Python的代码获取。其中'image/3_water.jpg'替换成你的图片路径。详细如下:
import cv2
import numpy as np
from matplotlib import pyplot as pltimage=cv2.imread('image/3_water.jpg')
HSV=cv2.cvtColor(image,cv2.COLOR_BGR2HSV)
def getpos(event,x,y,flags,param):if event==cv2.EVENT_LBUTTONDOWN: #定义一个鼠标左键按下去的事件print(HSV[y,x])cv2.imshow("imageHSV",HSV)
cv2.imshow('image',image)
cv2.setMouseCallback("imageHSV",getpos)
cv2.waitKey(0)
效果图如下,左边的为原图,右边的为转为HSV的图片,点击你需要获取颜色的位置,会在控制台打印对应的颜色值。

3. 添加水印
先准备几张测试图片,给这些图片打上水印。这里以下面的图片经行演示。

添加水印代码如下:
import cv2
import numpy as npdef create_watermark(image_path):# 读取图像image = cv2.imread(image_path)# 设置水印文本watermark_text = 'www.xiaoxiaofeng.com'# 设置字体font = cv2.FONT_HERSHEY_SIMPLEX# 设置字体大小font_scale = 0.5# 注意,这里是BGR 蓝色(B)、绿色(G)和红色(R)font_color = (62, 62, 187) # 设置字体粗细thickness = 1# 获取图像的高度和宽度height, width = image.shape[:2]# 设置文本位置(右下角)text_position = ( width - 200, height - 20 ) # 例如,在右下角# 添加水印文本cv2.putText(image, watermark_text, text_position, font, font_scale, font_color, thickness, cv2.LINE_AA)# 显示图像cv2.imshow('Watermarked Image', image)cv2.waitKey(0)cv2.destroyAllWindows()# 保存图像cv2.imwrite('image/2_water.jpg', image)create_watermark('image/2.jpg')
添加完水印的效果如下图:

4. 去除水印
关于去除水印,效果最好的应该还是训练模型,用模型去除效果肯定比较好,但是对于简单的水印,也没必要去训练模型,而且训练模型的门槛比较高,自己的"超配00年代"的电脑就别想了。
这里针对几种常见的水印,简单的描述下去水印的思想,以及实现代码。
4.1 文档类图片去水印
关于图片准备,这里直接用word创建了一个带水印的文档,然后截图,图片如下:

实现目标:去除中间背景的中的【笑小枫】的水印。
实现思想:背景为白色,字体颜色为黑色,水印颜色为灰色,因此可以将水印的灰色替换为白色即可
方案一:
通过观测,水印的颜色大多为(214, 214, 214),但是边缘的锯齿处有部分颜色为214-245之间,这里定义3个色彩相加之和位于 640-740之间的,全部替换为白色,当然这样可能会误伤一部分颜色,具体需要针对多种方案比较选用。
import numpy as np
import cv2def remote_water_mark_1(image):# 读取图像image = cv2.imread(image)# 显示原始图像cv2.imshow('Original Image', image)# 设置替换颜色为白色replace_color = (255, 255, 255)# 获取图片大小height, width = image.shape[:2]for i in range(height):for j in range(width):# 获取当前像素点的颜色值varP = image[i, j]# 如果当前像素点的颜色值总和在640到760之间,则替换为白色if sum(varP) > 640 and sum(varP) < 740:image[i, j] = replace_color# 显示处理后的图像cv2.imshow('Result Image', image)cv2.waitKey(0)remote_water_mark_1('image/3.jpg')
去水印后的效果图如下:

可以看到,图片底部的水印去掉了,仔细看图片中的文字颜色有点细微变化,这就是因为部分像素颜色被误伤了导致的。下面看下方案二,可以解决这个问题。
方案二:
和方案一的思想一致,但是不再使用BGR的颜色处理图片,而是使用HSV对图片进行处理。通过创建颜色范围的掩码,替换掉对应像素的颜色,具体代码如下:
import numpy as np
import cv2def remote_water_mark_2(image):# 读取图像image = cv2.imread(image)# 显示原始图像cv2.imshow('Original Image', image)# 将图像从BGR颜色空间转换为HSV颜色空间hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)# 定义要替换的颜色范围(在 HSV 空间中)lower_blue = np.array([0, 0, 214])upper_blue = np.array([0, 0, 245])# 在HSV图像中,根据定义的颜色范围创建掩码mask = cv2.inRange(hsv_image, lower_blue, upper_blue)# 显示掩码图像cv2.imshow('mask Image', mask)# 将掩码中对应位置的颜色替换为红色(在 HSV 空间中),白底在HSV空间就是红色。hsv_image[mask > 0] = [0, 0, 255]# 如果需要,将图片转换回 BGR 颜色空间result_image = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)# 显示结果图像cv2.imshow('Result Image', result_image)cv2.waitKey(0)remote_water_mark_2('image/3.jpg')
去水印后的效果图如下,可以通过mask掩码图像(黑色背景,白色水印图)清晰的看到水印的形状。这里可以根据mask掩码调整颜色区间的参数。可以看到右边图片成功去除水印,并且文字颜色一致。

4.2 固定位置水印去除方式
上文提到了,水印背景颜色如果和图片颜色类型,可能会误伤,出现意向不到问题。所有针对可以确认固定位置的水印,我们尽量处理固定位置,减少对图片的损伤。
如下图:水印固定在右下角,颜色和字体颜色基本一致,这样如果全图处理,整张图片就面目全非了。所以我们可以针对右下角固定的位置,特殊处理。

代码如下:
import numpy as np
import cv2def remote_water_mark_3(image):# 读取图像image = cv2.imread(image)# 显示原始图像cv2.imshow('Original Image', image)# 获取图像的高度和宽度height, width = image.shape[:2]# 定义 ROI 的坐标和大小x, y, w, h = width-280, height-40, 280, 40roi = image[y:y+h, x:x+w]# 将 ROI 从 BGR 颜色空间转换为 HSV 颜色空间hsv_image = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)# 定义要替换的颜色范围,范围尽量越小越好(在 HSV 空间中)lower_blue = np.array([0, 0, 0])upper_blue = np.array([0, 0, 240])# 创建掩码mask = cv2.inRange(hsv_image, lower_blue, upper_blue)# 替换颜色hsv_image[mask > 0] = [0, 0, 255] # 将匹配的颜色替换为红色(在 HSV 空间中)# 如果需要,将图片转换回 BGR 颜色空间roi = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)# 将处理后的 ROI 放回原图image[y:y+h, x:x+w] = roi# 显示处理后的图像cv2.imshow('Result Image', image)cv2.waitKey(0)remote_water_mark_2('image/1_water.jpg')
处理后的效果如下图所示

可以看见水印已经去掉了。因为这里背景是白色,所以很好处理,但如果背景不是白色,而是一些其他颜色改怎么处理呢?接下来看下面
4.3 复杂背景色的水印处理
针对复杂背景色的图片,处理时肯定会一定程度的损伤图片了,如果需要相对精准,可以训练模型处理,单纯使用代码,还是有一定程序限制。如果水印位置不固定,数量不固定,大小不固定等等,代码局限性就很大了,纯openCV代码暂还没找到方案,这里还是以简单的示例。
先看下面这张相对简单的图片

方案一:
使用inpaint函数修复图片,代码如下:
import numpy as np
import cv2def remote_water_mark_6(image):# 读取图像image = cv2.imread(image)# 显示原始图像cv2.imshow('Original Image', image)height, width = image.shape[:2]# 创建一个掩码,标记水印区域mask = np.zeros(image.shape[:2], np.uint8)height, width = image.shape[:2]# 假设水印在这个区域,绘制矩形掩码cv2.rectangle(mask, (width-220, height-50), (width, height), 255, -1)# 使用inpaint函数修复图像denoised_image = cv2.inpaint(image, mask, 1, cv2.INPAINT_TELEA)# 显示结果图像cv2.imshow('Result Image', denoised_image)cv2.waitKey(0)remote_water_mark_6('image/2_water.jpg')
处理后的效果图如下,可以看到水印处理掉了,但是图片水印处有略微变形。

针对下面这张图片进行测试:

可以看到水印虽然去掉了,但是水印出糊的很厉害

方案二:
此方案为自己写的,暂未经大量图片测试,但测试了部分图片,效果还可,因此放在这里做个比较
实现思想:实现步骤基本于上面一致,针对水印颜色区间像素进行颜色替换,替换的颜色值,这里取周边像素颜色出现次数最多的颜色做替换。然后对区域图片进行噪点处理,尽量中和处理部分于原图像的匹配度,如果水印下方是文字类型,不可以进行噪点处理。
具体代码如下:
具体使用时需要根据水印位置和颜色调整对应的参数
import numpy as np
import cv2
from collections import Counterdef find_most_frequent_color(color_list):"""找到颜色列表中出现频率最高的颜色。Args:color_list (list): 颜色列表,每个颜色可以是一个包含三个整数的列表,分别表示RGB值。Returns:tuple: 出现频率最高的颜色,以元组形式返回,包含三个整数,分别表示RGB值。"""# 将颜色列表中的每个颜色转换为元组形式color_list = [tuple(color) for color in color_list] # 使用 Counter 统计每种颜色出现的次数color_counts = Counter(color_list)# 获取出现次数最多的颜色,并返回该颜色return color_counts.most_common(1)[0][0]def remote_water_mark_5(image):# 读取图像image = cv2.imread(image)# 显示原始图像cv2.imshow('Original Image', image)height, width = image.shape[:2]# 定义 ROI(水印的位置,需要根据实际情况调整)x, y, w, h = width-200, height-35, 200, 35 # ROI 的坐标和大小roi = image[y:y+h, x:x+w]for yy in range(25, -1, -1):for xx in range(195):# 获取当前像素的颜色pixel_color = roi[yy, xx]pixel_color_sum = sum(pixel_color[:3])# 检查当前像素的颜色是否与要替换的颜色匹配,需要根据实际情况调整if (pixel_color_sum > 300 and pixel_color_sum < 650):if sum(roi[yy, xx]) == sum(roi[yy, xx-1]) and sum(roi[yy, xx]) == sum(roi[yy+1, xx]):continueis_fix = Falsecolor_set = [roi[yy, xx]]for i in range(5, -1, -1):if is_fix:breaktemp = roi[yy + i, xx + i]temp1 = roi[yy - i, xx - i]color_set.append(temp)color_set.append(temp1)color_set.append( roi[yy + i, xx + 2])color_set.append( roi[yy + 2, xx + i])color_set.append( roi[yy - i, xx - 2])color_set.append( roi[yy - 2, xx - i])color_set.append( roi[yy + i, xx - i])color_set.append( roi[yy - i, xx + i])if sum(temp) == sum(temp1):roi[yy, xx] = tempis_fix = Trueelif i == 1:most_frequent_color = find_most_frequent_color(color_set)roi[yy, xx] = most_frequent_color# 滤波窗口大小,如果背景处是文字,不可以进行噪点处理,不然会糊掉kernel_size = 3# 对 ROI 进行中值滤波roi = cv2.medianBlur(roi, kernel_size)# 将处理后的 ROI 放回原图image[y:y+h, x:x+w] = roi# 显示处理后的图像cv2.imshow('Result Image', image)cv2.waitKey(0)remote_water_mark_5('image/4_water.jpg')
测试效果如下图所示:


下面这个图,去除了噪点处理的代码。

4.4 训练模型(未完成)
作者尝试过进行训练模型处理,但在训练数据时,电脑CPU咔咔100%,因此放弃。这里就不附代码供大家参考,因为没有训练出模型,不知道效果如何,也不知道对不对。
5. 本文总结
本文写了几种去水印的方案,具体如何选择,小伙伴们可以根据实际情况去选择,本文也是我使用python去水印一路研究过来的总结。
因为刚刚接触python不多,如有错误之处,大家可以帮忙指出来,感谢!
希望有帮助的朋友帮忙点个赞赞,作者后续会陆续学习分享Java和Python相关知识,有兴趣的朋友可以添加好友一起研究学习。
相关文章:
Python使用OpenCV图片去水印多种方案实现
1. 前言 本文为作者学习记录,使用Python结合OpenCV,总结了几种常见的水印去除方式,简单图片去水印效果良好,但是复杂图片有点一言难尽,本文部分代码仅供参考,并不能针对所有水印通用,需要根据具…...
论文阅读2——S波段宽波束圆极化天线设计
论文结构 研究背景,基于当前天线通信的应用基础进行分析,为了适应更多的应用环境和更复杂的通信信号提出宽波束圆极化的天线设计要求,圆极化天线可以接收到任意极化的电磁波且其辐射波也可以由其他任意极化天线接收到圆极化天线的特性&#…...
【java】基本数据类型和引用数据类型
在 Java 中,数据类型分为 基本数据类型 和 引用数据类型。它们的本质区别在于存储方式和操作方式。下面我会详细解释这两种数据类型,并用通俗易懂的语言帮助你理解。 1. 基本数据类型(Primitive Data Types) 基本数据类型是 Java…...
基于角色访问控制的UML 表示02
一个用户可以成为很多角色的成员,一个角色可以有许多用户。类似地,一个角色可以有多个权限,同一个权限可以被指派给多个角色。每个会话把一个用户和可能的许多角色联系起来。一个用户在激发他或她所属角色的某些子集时,建立了一个…...
CEF132 编译指南 Linux 篇 - 获取 CEF 源代码:源码同步详解(五)
1. 引言 在完成所有必要工具的安装和配置之后,我们来到了整个 CEF 编译流程中至关重要的环节:获取 CEF 源代码。CEF 源码的获取过程需要我们特别关注同步策略和版本管理,以确保获取的代码版本正确且完整。本篇将详细指导你在 Linux 系统上获…...
Golang关于结构体组合赋值的问题
现在有一个结构体,其中一个属性组合了另外一个结构体,如下所示: type User struct {Id int64Name stringAge int64UserInfo }type UserInfo struct {Phone stringAddress string }如果要给 User 结构体的 Phone 和 Address 赋值的话&am…...
django上传文件
1、settings.py配置 # 静态文件配置 STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR /static, ]上传文件 # 定义一个视图函数,该函数接收一个 request 参数 from django.shortcuts import render # 必备引入 import json from django.views.decorators.http i…...
Springboot核心:统一异常处理
概述 统一异常处理机制在 Spring Boot 应用中是非常重要的核心点,因为它带来了多个方面的优势,能够显著提升应用的质量和开发效率。 为什么要优雅的处理异常 在后端发生异常或者是请求出错时,前端通常直接显示异常信息,而且对于…...
【银河麒麟高级服务器操作系统】服务器卡死后恢复系统日志丢失-分析及处理全过程
了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://document.kylinos.cn 服务器环境以及配置 【机型】 处理器ÿ…...
【deepseek api 第三方平台使用参考】
1.硅基流动 1.1硅基流动网页版使用 打开硅基流动的官网 https://siliconflow.cn/zh-cn/ 点右上角登陆 在模型广场这里找到这个deepseek的r1 注意,一定选择下面标记【671B】的,这个是满血版 这个满血版收费标准是 4块钱/百万token输入 16块钱/百万token…...
通过 VBA 在 Excel 中自动提取拼音首字母
在excel里面把表格里的中文提取拼音大写缩写怎么弄 在Excel中,如果你想提取表格中的中文字符并转换为拼音大写缩写(即每个汉字的拼音首字母的大写形式),可以通过以下步骤来实现。这项工作可以分为两个主要部分: 提取拼…...
动态规划dp_4
一.背包 如果求组合数就是外层for循环遍历物品,内层for遍历背包。 如果求排列数就是外层for遍历背包,内层for循环遍历物品。 二.题 1. 思路:dp五部曲,思路在注释 /* dp[i]表示:到达第 i 个台阶有dp[i]种方法 状态转…...
对贵司需求的PLC触摸的远程调试的解决方案
远程监控技术解决方案 一、需求痛点分析 全球设备运维响应滞后(平均故障处理周期>72小时)客户定制化需求频繁(每月PLC程序修改需求超50次)人力成本高企(单次跨国差旅成本约$5000)多品牌PLC兼容需求&am…...
Python用PyMC3马尔可夫链蒙特卡罗MCMC对疾病症状数据贝叶斯推断
全文链接:https://tecdat.cn/?p39937 本文聚焦于马尔可夫链蒙特卡罗(MCMC)方法在贝叶斯推断中的Python实现。通过介绍MCMC的基础原理、在贝叶斯推断中的应用步骤,展示了其在解决复杂分布采样问题上的强大能力。同时,借…...
网络工程师 (39)常见广域网技术
一、HDLC 前言 HDLC(High-level Data Link Control,高级数据链路控制)是一种面向比特的链路层协议。 (一)定义与历史背景 HDLC是由国际电信联盟(ITU)标准化的,它基于IBM公司早期的同…...
每日Attention学习23——KAN-Block
模块出处 [SPL 25] [link] [code] KAN See In the Dark 模块名称 Kolmogorov-Arnold Network Block (KAN-Block) 模块作用 用于vision的KAN结构 模块结构 模块代码 import torch import torch.nn as nn import torch.nn.functional as F import mathclass Swish(nn.Module)…...
基于Python的Optimal Interpolation (OI) 方法实现
前言 Optimal Interpolation (OI) 方法概述与实现 Optimal Interpolation (OI) 是一种广泛应用于气象学、海洋学等领域的空间数据插值方法。该方法通过结合观测数据与模型预测数据,最小化误差方差,从而实现对空间数据的最优插值。以下是OI方法的一般步骤…...
学习数据结构(10)栈和队列下+二叉树(堆)上
1.关于栈和队列的算法题 (1)用队列实现栈 解法一:(参考代码) 题目要求实现六个函数,分别是栈初始化,入栈,移除并返回栈顶元素,返回栈顶元素,判空࿰…...
.NET版Word处理控件Aspose.Words教程:使用 C# 删除 Word 中的空白页
Word 文档中的空白页会使其看起来不专业并扰乱流程。用户会遇到需要删除 Word 中的空白页的情况,但手动删除它们需要时间和精力。在这篇博文中,我们将探讨如何使用 C# 删除 Word 中的空白页。 本文涵盖以下主题: C# 库用于删除 Word 中的空…...
《代码随想录》刷题笔记——回溯篇【java实现】
文章目录 组合组合总和 III电话号码的字母组合组合总和组合总和II思路代码实现 分割回文串※思路字符串分割回文串判断效率优化※ 复原 IP 地址优化版本 子集子集 II使用usedArr辅助去重不使用usedArr辅助去重 递增子序列※全排列全排列 II重新安排行程题意代码 N 皇后解数独直…...
【JavaEE进阶】验证码案例
目 🌲实现说明 🎄Hutool介绍 🌳准备工作 🌴约定前后端交互接口 🚩接口定义 🚩实现服务器后端代码 🚩前端代码 🚩整体测试 🌲实现说明 随着安全性的要求越来越⾼…...
TCP/UDP 简介,三次握手与四次挥手
一、TCP 三次握手 目的:为了解决在不可靠的信道上建立可靠的网络连接 三次握手是连接请求的过程: A 发送连接请求的数据给 B(发送 SYN 包) B 同意连接,返回数据给 A(返回 SYNACK 包) A 收到后回…...
C++之线程池(Thread Pool)
1.介绍 线程池是一种并发编程的设计模式,用于管理和复用多个线程。以避免频繁创建和销毁线程的开销。线程池的核心思想是预先创建一组线程,并将任务分配给这些线程执行,从而提高程序的性能和资源利用率。 2.线程池的核心组件 一个经典的线程…...
Django中实现简单易用的分页工具
如何在Django中实现简单易用的分页工具?📚 嗨,小伙伴们!今天我们来看看如何在 Django 中实现一个超简单的分页工具。无论你是在处理博客文章、产品列表,还是用户评论,当数据量一大时,分页显得尤…...
【kafka系列】Exactly Once语义
目录 1. Exactly-Once语义的定义 2. Kafka实现Exactly-Once的机制 3. 端到端Exactly-Once示例 场景描述 3.1 生产者配置与代码 3.2 消费者配置与代码 4. 异常场景与Exactly-Once保障 场景1:生产者发送消息后宕机 场景2:消费者处理消息后宕机 场…...
export default与export区别
1.定义: export default:用于导出模块中的默认成员。一个模块中只能有一个export default,通常用于导出模块的主要功能或对象。导入时可以使用任意名称,因为它没有具体的名称 export:用于导出模块中的多个成…...
Qt Creator 5.0.2 (Community)用久了突然变得很卡
目录 1.现象 2.解决方案 1.现象 很久没有用Qt Creator开发项目了,刚刚结束的项目又是用VS2019开发的;这两天刚好有时间去学习一下Qt,刚好要用Qt Creator,结果一打开就没反应,主界面显示出来要好几分钟,最…...
Windows搭建CUDA大模型Docker环境
Windows搭建CUDA大模型Docker环境 一、安装Docker二、拉取镜像三、启动容器四、安装依赖环境五、安装Miniconda3六、设置pip源地址 一、安装Docker windows中docker安装教程 二、拉取镜像 系统:Ubuntu20.04CUDA版本:11.8.0 docker pull nvcr.io/nvid…...
阅读论文笔记《Efficient Estimation of Word Representations in Vector Space》
这篇文章写于2013年,对理解 word2vec 的发展历程挺有帮助。 本文仅适用于 Word2Vect 的复盘 引言 这篇论文致力于探索从海量数据中学习高质量单词向量的技术。当时已发现词向量能保留语义特征,例如 “国王 - 男人 女人≈女王”。论文打算借助该特性&am…...
初学PADS使用技巧笔记(也许会继续更新)
操作意图:网上找某个芯片封装又不想自己画,再加上没经验,怎么办? 就以AC-DC芯片PN8036为例,打开嘉立创的的DFM,打开立创商城,输入PN8036,点击数据手册,然后点击直接打开…...
