用 OpenCV 去除图片中的水印,骚操作!

简介: 这两天在 Stackoverflow 上面看到了一个有趣的案例,是关于OpenCV 的一个讨论,讨论的主题就是如何用 OpenCV 来去除下面图片中的水印,原图如下;

这两天在 Stackoverflow 上面看到了一个有趣的案例,是关于OpenCV 的一个讨论,讨论的主题就是如何用 OpenCV 来去除下面图片中的水印,原图如下;

题主想把纸张中的 黑色圆环去掉只留下背景,因此一些感兴趣的 CV 爱好者在下面写上自己的想法、并贴上自己的解决代码

看到关于这个主题的答案后,只能感叹真正的大佬,都是从实践场景出发来解决问题,

因为篇幅有限,在文章中只贴上得票最高的两个问答思路及代码, 让我们感受下他们思路的巧妙之处!


这老哥的思路,总体为五部分


1,首先将图像转化为灰度图记为 A;


2,利用霍夫圆在 A 中检测最大的椭圆,然后在新的图像中创建相同半径的圆得到 B;


3,对灰度图和绘制圆的图像,应用OpenCV 的 bitwise_and 与运算,在原灰度图像 A 中提取只包含椭圆图像区域记为 C;


4,对图像 C 设置合适的阈值进行文字提取最终得到 D;


5, 对 图像 A 和 D 做bitwise_or 操作,即能够得到最终图像 E;


以下是在自己机子上跑出来的结果,从左到右依次对应上面的 A,C,D,E;效果如下


这个方法整体大概思想,先提取图像中圆环部分区域,对圆环内的文字做阈值分割进行提取,最后将提取到的图像区域在初始图像中进行替换,


这里答主主要用到了三种重要算法:图像位运算(和、或)、阈值分割 、霍夫圆检测,


下面就是这个思路的代码部分,原答主用的是 C++ ,因为我做的是 Python 教程,就用 Python 转换了一下

import cv2
import numpy as np
if __name__ =='__main__':
    img_path = "F:/Data/Ceshi1/shuiyin.jpg"
    img1 = cv2.imread(img_path)
    cv2.namedWindow('img1',cv2.WINDOW_FREERATIO)
    cv2.imshow('img1',img1)
    # 转化为 灰度图
    gray = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
    # 创建一个白画布
    ellipse_img = np.full((img1.shape[0],img1.shape[1],3),0,dtype = np.uint8)
    print(ellipse_img.shape,ellipse_img[0][0])
    gray = cv2.GaussianBlur(gray,(5,5),0) # 高斯处理
    # 应用霍夫圆检测,检测出所有圆
    circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,gray.shape[0]/8,100,100,100,0)
    # 找到最大的圆
    measure = 0.0
    x = 0.0
    y = 0.0
    for circle in (circles[0]):
        if circle[2] > measure:
            measure = circle[2]
            x = circle[0]
            y = circle[1]
    # 绘制圆
    cv2.circle(img1,(x,y),3,(0,255,0),-1,8,0)
    cv2.circle(img1,(x,y),int(measure),(0,255,0),2,8,0)
    # 绘制相同大小的圆
    ellipse_img =  cv2.ellipse(ellipse_img,(x,y),(int(measure),int(measure)),0,0,360,(255,255,255),-1,8)
    print(f'center x is {x} ,y is {y}, radius is {measure}')
    ellipse_img = cv2.cvtColor(ellipse_img,cv2.COLOR_BGR2GRAY)
    result = cv2.bitwise_and(gray,ellipse_img)
    cv2.namedWindow('bitwise and',cv2.WINDOW_FREERATIO)
    cv2.imshow('bitwise and',result)
    # 估计圆图像像素强度
    x = result[int(x+30)][int(y)]
    print(f'intensity is  {x}')
    # 阈值分割
    _,ellipse_img = cv2.threshold(result,int(x) - 10,250,cv2.THRESH_BINARY)
    # print('ellipse_img shape is {}'.format(ellipse_img.shape))
    cv2.namedWindow('threshold',cv2.WINDOW_FREERATIO)
    cv2.imshow('threshold',ellipse_img)
    # 使用 bitwise_or 方法
    print('shape ------------\n')
    print(ellipse_img.shape,gray.shape)
    res = cv2.bitwise_or(gray,ellipse_img)
    cv2.namedWindow('bitwise_or',cv2.WINDOW_FREERATIO)
    cv2.imshow('bitwise_or',res)
    cv2.waitKey(0)

最终结果预览比对

思路主要分为四部分

  • 1,源图像记为 A,用形态学滤波器删除图像中文字区域,得到的图像记为 B;

  • 2,获取A,B 图像的之差,用 A-B ,得到区别后再用阈值分割进行处理,得到 C;

  • 3,阈值分割背景图像,提取水印覆盖黑色部分记为 D,

  • 4,从 A 中提取在区域 D 中的像素,再用阈值分割方法分割像素,最终将提取到的像素贴到 B 中,得到最终去除水印的图像

代码贴在下方

import cv2
import numpy as np
if __name__ =='__main__':
    img_path = "F:/Data/Ceshi1/shuiyin.jpg"
    im = cv2.imread(img_path)
    gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    background = gray.copy()
    for i in range(1,5):
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(2*i+1,2*i+1))
        # print('kernel size is ',kernel)
        background = cv2.morphologyEx(background,cv2.MORPH_CLOSE,kernel)
        background = cv2.morphologyEx(background,cv2.MORPH_CLOSE,kernel)
    diff = background - gray # 计算差距
    cv2.namedWindow('diff',cv2.WINDOW_FREERATIO) # 获取图像中前景背景之差
    cv2.imshow('diff',background)
    # 阈值分割获取黑色字体
    _,bw = cv2.threshold(diff,0,255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
    # 阈值分割获取黑色区域
    cv2.namedWindow('bw_before', cv2.WINDOW_FREERATIO)
    cv2.imshow('bw_before', bw)
    _,dark = cv2.threshold(background,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
    darkpix = cv2.countNonZero(dark)# 获取 dark非0d图像像素个数
    darkpix = [0]*darkpix
    index = 0
    cv2.namedWindow('gray', cv2.WINDOW_FREERATIO)
    cv2.imshow('gray', gray)
    for r in range(dark.shape[0]):
        for c in range(dark.shape[1]):
            if(dark[r][c]):
                darkpix[index]  = gray[r][c]
                index = index +1
    # 阈值分割 dark 区域 因此我们在里面得到更深的像素
    darkpix = np.array(darkpix)
    _,darkpix = cv2.threshold(darkpix,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    cv2.namedWindow('darkpix', cv2.WINDOW_FREERATIO)
    cv2.imshow('darkpix', darkpix)
    # 把 取到的像素粘贴到 其渠道的 darker pixels
    cv2.namedWindow('dark',cv2.WINDOW_FREERATIO)
    cv2.imshow('dark',dark)
    index = 0
    for r in range(dark.shape[0]):
        for c in range(dark.shape[1]):
            if (dark[r][c]):
                bw[r][c] =  darkpix[index]
                index = index +1
    cv2.namedWindow('bw',cv2.WINDOW_FREERATIO)
    cv2.imshow('bw',bw)
    cv2.waitKey(0)

效果预览对比


相对第一种方法,第二种方法实用性更强一点,无论图像前景水印为什么形状的,这种方法都可适用(水印区域与其他背景像素强度差别大,且水印区域是连接在一起的),


如果考虑到商用途径,只用 OpenCV 来解决复杂场景的图片水印问题,是不现实的,还需人工的干涉;但不现实并不代表它没有用,对于前后像素值较大、简单场景的水印,OpenCV 是完全可行的,若是再加上一个批量操作,变得更可了,大大解放我们的双手!


并且这两种思路中用到的的一些方法,是值得我们借鉴的,比如 图像像素或与和操作、形态学过滤、霍夫圆检测等技术,可借助于这些方法应用到其它场景,例如提取图像中圆形区域、行人路上斑马线检测、去除不规则图像连接区域等。


相关文章
|
1月前
|
计算机视觉
Opencv学习笔记(十二):图片腐蚀和膨胀操作
这篇文章介绍了图像腐蚀和膨胀的原理、作用以及使用OpenCV实现这些操作的代码示例,并深入解析了开运算和闭运算的概念及其在图像形态学处理中的应用。
109 1
Opencv学习笔记(十二):图片腐蚀和膨胀操作
|
1月前
|
计算机视觉 Python
Opencv学习笔记(二):如何将整个文件下的彩色图片全部转换为灰度图
使用OpenCV库将一个文件夹内的所有彩色图片批量转换为灰度图,并提供了相应的Python代码示例。
31 0
Opencv学习笔记(二):如何将整个文件下的彩色图片全部转换为灰度图
|
1月前
|
计算机视觉 Python
Opencv学习笔记(一):如何将得到的图片保存在指定目录以及如何将文件夹里所有图片以数组形式输出
这篇博客介绍了如何使用OpenCV库在Python中将图片保存到指定目录,以及如何将文件夹中的所有图片读取并以数组形式输出。
132 0
Opencv学习笔记(一):如何将得到的图片保存在指定目录以及如何将文件夹里所有图片以数组形式输出
|
1月前
|
计算机视觉
Opencv错误笔记(一):通过cv2保存图片采用中文命名出现乱码
在使用OpenCV的cv2模块保存带有中文命名的图片时,直接使用cv2.imwrite()会导致乱码问题,可以通过改用cv2.imencode()方法来解决。
125 0
Opencv错误笔记(一):通过cv2保存图片采用中文命名出现乱码
|
3月前
|
计算机视觉 Windows Python
windows下使用python + opencv读取含有中文路径的图片 和 把图片数据保存到含有中文的路径下
在Windows系统中,直接使用`cv2.imread()`和`cv2.imwrite()`处理含中文路径的图像文件时会遇到问题。读取时会返回空数据,保存时则无法正确保存至目标目录。为解决这些问题,可以使用`cv2.imdecode()`结合`np.fromfile()`来读取图像,并使用`cv2.imencode()`结合`tofile()`方法来保存图像至含中文的路径。这种方法有效避免了路径编码问题,确保图像处理流程顺畅进行。
317 1
|
1月前
|
Serverless 计算机视觉
语义分割笔记(三):通过opencv对mask图片来画分割对象的外接椭圆
这篇文章介绍了如何使用OpenCV库通过mask图像绘制分割对象的外接椭圆。首先,需要加载mask图像,然后使用`cv2.findContours()`寻找轮廓,接着用`cv2.fitEllipse()`拟合外接椭圆,最后用`cv2.ellipse()`绘制椭圆。文章提供了详细的代码示例,展示了从读取图像到显示结果的完整过程。
45 0
语义分割笔记(三):通过opencv对mask图片来画分割对象的外接椭圆
|
3月前
|
存储 编解码 API
python多种方法压缩图片,opencv、PIL、tinypng、pngquant压缩图片
python多种方法压缩图片,opencv、PIL、tinypng、pngquant压缩图片
245 1
|
3月前
|
计算机视觉
使用QT显示OpenCV读取的图片
使用QT显示OpenCV读取的图片
69 1
|
5月前
|
算法 计算机视觉
【Qt&OpenCV QGraphicsView显示OpenCV读入的图片】
【Qt&OpenCV QGraphicsView显示OpenCV读入的图片】
75 0
|
6月前
|
计算机视觉 Python Windows
OpenCV自动裁剪图片
OpenCV自动裁剪图片
56 1