从图像中删除透明水印 - python
Removing transparent watermark from an image - python
我正在尝试从图像中删除透明水印。
这是我的示例图片:
我想从图像中删除文本“水印”。如您所见,文本是透明的。所以我想把那个文字替换成原来的背景。
我想要这样的输出:
我尝试了一些例子(我目前正在使用cv2,如果其他库可以解决问题也请推荐),但是none其中离成功还很近。我知道要走的路是戴面具(比如 post),但他们都已经有蒙版图像,但我没有。
这是我试图做的蒙版,我将饱和度调低为黑白,并创建了一个图像“imagemask.jpg”,然后尝试使用 [=12] 遍历像素=]循环:
mask = cv2.imread('imagemask.jpg')
new = []
rows, cols, _ = mask.shape
for i in range(rows):
new.append([])
#print(i)
for j in range(cols):
k = img[i, j]
#print(k)
if all(x in range(110, 130) for x in k):
new[-1].append((255, 255, 255))
else:
new[-1].append((0, 0, 0))
cv2.imwrite('finalmask.jpg', np.array(new))
之后想使用掩码的代码,但我意识到“finalmask.jpg”是一团糟...所以我没有尝试使用掩码的代码。
这真的可能吗?我已经尝试了大约 3 个小时,但没有成功...
这可不简单,我的朋友。雪上加霜的是,您的图像分辨率非常低、压缩并且有令人讨厌的眩光——这根本无助于处理。请查看您的输入并相应地设定您的期望。话虽如此,让我们尝试用我们拥有的东西获得最好的结果。这些是我建议的步骤:
- 尝试从图像分割水印文本
- 过滤分割掩码并尝试获得尽可能干净的二进制掩码
- 使用文本遮罩画图使用输入图像作为参考的违规区域
现在,如您所见,棘手的部分是对文本进行分段。在尝试了一些技术和色彩空间之后,我发现 CMYK
color space - 特别是 K 通道 - 提供了有希望的结果。文字比较清楚,我们可以尝试Adaptive Thresholding
,让我们看一下:
# Imports
import cv2
import numpy as np
# Read image
imagePath = "D://opencvImages//"
img = cv2.imread(imagePath+"0f5zZm.jpg")
# Store a deep copy for the inpaint operation:
originalImg = img.copy()
# Convert to float and divide by 255:
imgFloat = img.astype(np.float) / 255.
# Calculate channel K:
kChannel = 1 - np.max(imgFloat, axis=2)
OpenCV
不提供 BGR
到 CMYK
的直接转换,因此我不得不使用 conversion formula 手动获取 K
频道。这非常简单。 K
(或Key)通道表示最低强度(黑色)的像素,颜色为白色。这意味着几乎是白色的文本将呈现为黑色...这是输入的 K
通道:
你看到输入中较暗的像素在这里几乎是白色的了吗?很好,它似乎在文本和其他所有内容之间得到了清晰的区分。很遗憾,我们在右侧有一些令人讨厌的大眩光。反正转换涉及float
个操作,所以要小心data types
。也许我们可以通过稍微 brightness/contrast 调整来改善此图像。只是一点点,我只是想把更多的文字从那讨厌的眩光中分离出来:
# Apply a contrast/brightness adjustment on Channel K:
alpha = 0
beta = 1.2
adjustedK = cv2.normalize(kChannel, None, alpha, beta, cv2.NORM_MINMAX, cv2.CV_32F)
# Convert back to uint 8:
adjustedK = (255*adjustedK).astype(np.uint8)
这是调整后的图像:
文本和眩光之间似乎有更多的分离。好吧,让我们对这个坏男孩应用一个 Adaptive Thresholding
以获得初始分割掩码:
# Adaptive Thresholding on adjusted Channel K:
windowSize = 21
windowConstant = 11
binaryImg = cv2.adaptiveThreshold(adjustedK, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, windowSize, windowConstant)
你看到我在这里使用了一个不太大的 windowSize
作为阈值吗?如果您愿意,可以随意调整这些参数。这是我得到的二进制图像:
是的,有很多噪音。这是我建议获得更干净的蒙版的方法:有一些明显的斑点比文本大。同样,还有其他比文本 小 的斑点。让我们找到大斑点和小斑点,然后 减去 它们。如果我们正确设置参数,生成的图像应该包含文本。让我们看看:
# Get the biggest blobs on the image:
minArea = 180
bigBlobs = areaFilter(minArea, binaryImg)
# Filter the smallest blobs on the image:
minArea = 20
smallBlobs = areaFilter(minArea, binaryImg)
# Let's try to isolate the text:
textMask = smallBlobs - bigBlobs
cv2.imshow("Text Mask", textMask)
cv2.waitKey(0)
这里我使用了一个名为 areaFilter
的辅助函数。此函数 returns 图像中所有高于 最小面积阈值 的斑点。我将 post 函数放在答案的末尾。与此同时,看看这些很酷的图片:
大斑点:
已过滤的小斑点:
它们的区别:
遗憾的是,似乎部分字符无法通过过滤操作。那是因为眩光和文本的交集太多,算法无法清晰地分离出来。可能有利于内部绘画结果的东西是此蒙版上的微妙模糊,以摆脱压缩别名。让我们应用一些 Gaussian Blur
来稍微平滑蒙版:
# Blur the mask a little bit to get a
# smoother inpanting result:
kernelSize = (3, 3)
textMask = cv2.GaussianBlur(textMask, kernelSize, cv2.BORDER_DEFAULT)
内核没有那么大,我只是想要一个微妙的效果。这是结果:
最后,让我们应用内画:
# Apply the inpaint method:
inpaintRadius = 10
inpaintMethod = cv2.INPAINT_TELEA
result = cv2.inpaint(originalImg, textMask, inpaintRadius, inpaintMethod)
cv2.imshow("Inpaint Result", result)
cv2.waitKey(0)
这是最终结果:
好吧,考虑到输入图像,还不错。您可以尝试通过调整一些值来进一步改善结果,但现实是,老兄,输入图像一开始并不是那么好。这是 areaFilter
函数:
def areaFilter(minArea, inputImage):
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(inputImage, connectivity=4)
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
return filteredImage
我正在尝试从图像中删除透明水印。
这是我的示例图片:
我想从图像中删除文本“水印”。如您所见,文本是透明的。所以我想把那个文字替换成原来的背景。
我想要这样的输出:
我尝试了一些例子(我目前正在使用cv2,如果其他库可以解决问题也请推荐),但是none其中离成功还很近。我知道要走的路是戴面具(比如
这是我试图做的蒙版,我将饱和度调低为黑白,并创建了一个图像“imagemask.jpg”,然后尝试使用 [=12] 遍历像素=]循环:
mask = cv2.imread('imagemask.jpg')
new = []
rows, cols, _ = mask.shape
for i in range(rows):
new.append([])
#print(i)
for j in range(cols):
k = img[i, j]
#print(k)
if all(x in range(110, 130) for x in k):
new[-1].append((255, 255, 255))
else:
new[-1].append((0, 0, 0))
cv2.imwrite('finalmask.jpg', np.array(new))
之后想使用掩码的代码,但我意识到“finalmask.jpg”是一团糟...所以我没有尝试使用掩码的代码。
这真的可能吗?我已经尝试了大约 3 个小时,但没有成功...
这可不简单,我的朋友。雪上加霜的是,您的图像分辨率非常低、压缩并且有令人讨厌的眩光——这根本无助于处理。请查看您的输入并相应地设定您的期望。话虽如此,让我们尝试用我们拥有的东西获得最好的结果。这些是我建议的步骤:
- 尝试从图像分割水印文本
- 过滤分割掩码并尝试获得尽可能干净的二进制掩码
- 使用文本遮罩画图使用输入图像作为参考的违规区域
现在,如您所见,棘手的部分是对文本进行分段。在尝试了一些技术和色彩空间之后,我发现 CMYK
color space - 特别是 K 通道 - 提供了有希望的结果。文字比较清楚,我们可以尝试Adaptive Thresholding
,让我们看一下:
# Imports
import cv2
import numpy as np
# Read image
imagePath = "D://opencvImages//"
img = cv2.imread(imagePath+"0f5zZm.jpg")
# Store a deep copy for the inpaint operation:
originalImg = img.copy()
# Convert to float and divide by 255:
imgFloat = img.astype(np.float) / 255.
# Calculate channel K:
kChannel = 1 - np.max(imgFloat, axis=2)
OpenCV
不提供 BGR
到 CMYK
的直接转换,因此我不得不使用 conversion formula 手动获取 K
频道。这非常简单。 K
(或Key)通道表示最低强度(黑色)的像素,颜色为白色。这意味着几乎是白色的文本将呈现为黑色...这是输入的 K
通道:
你看到输入中较暗的像素在这里几乎是白色的了吗?很好,它似乎在文本和其他所有内容之间得到了清晰的区分。很遗憾,我们在右侧有一些令人讨厌的大眩光。反正转换涉及float
个操作,所以要小心data types
。也许我们可以通过稍微 brightness/contrast 调整来改善此图像。只是一点点,我只是想把更多的文字从那讨厌的眩光中分离出来:
# Apply a contrast/brightness adjustment on Channel K:
alpha = 0
beta = 1.2
adjustedK = cv2.normalize(kChannel, None, alpha, beta, cv2.NORM_MINMAX, cv2.CV_32F)
# Convert back to uint 8:
adjustedK = (255*adjustedK).astype(np.uint8)
这是调整后的图像:
文本和眩光之间似乎有更多的分离。好吧,让我们对这个坏男孩应用一个 Adaptive Thresholding
以获得初始分割掩码:
# Adaptive Thresholding on adjusted Channel K:
windowSize = 21
windowConstant = 11
binaryImg = cv2.adaptiveThreshold(adjustedK, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, windowSize, windowConstant)
你看到我在这里使用了一个不太大的 windowSize
作为阈值吗?如果您愿意,可以随意调整这些参数。这是我得到的二进制图像:
是的,有很多噪音。这是我建议获得更干净的蒙版的方法:有一些明显的斑点比文本大。同样,还有其他比文本 小 的斑点。让我们找到大斑点和小斑点,然后 减去 它们。如果我们正确设置参数,生成的图像应该包含文本。让我们看看:
# Get the biggest blobs on the image:
minArea = 180
bigBlobs = areaFilter(minArea, binaryImg)
# Filter the smallest blobs on the image:
minArea = 20
smallBlobs = areaFilter(minArea, binaryImg)
# Let's try to isolate the text:
textMask = smallBlobs - bigBlobs
cv2.imshow("Text Mask", textMask)
cv2.waitKey(0)
这里我使用了一个名为 areaFilter
的辅助函数。此函数 returns 图像中所有高于 最小面积阈值 的斑点。我将 post 函数放在答案的末尾。与此同时,看看这些很酷的图片:
大斑点:
已过滤的小斑点:
它们的区别:
遗憾的是,似乎部分字符无法通过过滤操作。那是因为眩光和文本的交集太多,算法无法清晰地分离出来。可能有利于内部绘画结果的东西是此蒙版上的微妙模糊,以摆脱压缩别名。让我们应用一些 Gaussian Blur
来稍微平滑蒙版:
# Blur the mask a little bit to get a
# smoother inpanting result:
kernelSize = (3, 3)
textMask = cv2.GaussianBlur(textMask, kernelSize, cv2.BORDER_DEFAULT)
内核没有那么大,我只是想要一个微妙的效果。这是结果:
最后,让我们应用内画:
# Apply the inpaint method:
inpaintRadius = 10
inpaintMethod = cv2.INPAINT_TELEA
result = cv2.inpaint(originalImg, textMask, inpaintRadius, inpaintMethod)
cv2.imshow("Inpaint Result", result)
cv2.waitKey(0)
这是最终结果:
好吧,考虑到输入图像,还不错。您可以尝试通过调整一些值来进一步改善结果,但现实是,老兄,输入图像一开始并不是那么好。这是 areaFilter
函数:
def areaFilter(minArea, inputImage):
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(inputImage, connectivity=4)
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
return filteredImage