用Python的OpenCV从二值图像中去除噪声

26

我正在尝试获取图像框的角落。以下是示例图像,它们的阈值结果,在箭头右侧的是我需要的结果。你可能以前也在Slack上看到过这些图像,因为我在Slack上使用这些图像作为我的示例问题。

输入图像描述

以下是允许我到达中间图像的代码。

import cv2
import numpy as np

img_file = 'C:/Users/box.jpg'
img = cv2.imread(img_file, cv2.IMREAD_COLOR)
img = cv2.blur(img, (5, 5))

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

thresh0 = cv2.adaptiveThreshold(s, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
thresh1 = cv2.adaptiveThreshold(v, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
thresh2 = cv2.adaptiveThreshold(v, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
thresh = cv2.bitwise_or(thresh0, thresh1)

cv2.imshow('Image-thresh0', thresh0)
cv2.waitKey(0)
cv2.imshow('Image-thresh1', thresh1)
cv2.waitKey(0)
cv2.imshow('Image-thresh2', thresh2)
cv2.waitKey(0)
在opencv中有没有可以帮我做到这一点的方法?我尝试过膨胀cv2.dilate()和腐蚀cv2.erode(),但在我的情况下它不起作用。如果没有其他方法,那么还有哪些替代方法可以做到这一点呢?谢谢。
图像的Canny版本...左边是低阈值,右边是高阈值。enter image description here

据我所知,canny + 腐蚀应该可以工作。您能上传图片以便查看为什么对您无效吗? - Rick M.
3
如果您成功解决了问题,可以考虑接受一个答案;或者,您可以发布自己的解决方案,这样其他人也可以受益。请注意,在翻译过程中我尽可能使语言通俗易懂,但并没有改变原意,并且不会提供任何额外的解释或内容。 - Michał Gacka
4个回答

21

以下是@dhanushka方法的Python实现。

import cv2
import numpy as np

# load color image
im = cv2.imread('input.jpg')

# smooth the image with alternative closing and opening
# with an enlarging kernel
morph = im.copy()

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 1))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))

# take morphological gradient
gradient_image = cv2.morphologyEx(morph, cv2.MORPH_GRADIENT, kernel)

# split the gradient image into channels
image_channels = np.split(np.asarray(gradient_image), 3, axis=2)

channel_height, channel_width, _ = image_channels[0].shape

# apply Otsu threshold to each channel
for i in range(0, 3):
    _, image_channels[i] = cv2.threshold(~image_channels[i], 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)
    image_channels[i] = np.reshape(image_channels[i], newshape=(channel_height, channel_width, 1))

# merge the channels
image_channels = np.concatenate((image_channels[0], image_channels[1], image_channels[2]), axis=2)

# save the denoised image
cv2.imwrite('output.jpg', image_channels)
以上代码在处理发票(或白色背景上有大量文本的图片)时效果不佳。 为了在这样的图片上获得良好的结果,请移除

gradient_image = cv2.morphologyEx(morph, cv2.MORPH_GRADIENT, kernel)

并将morph对象传递给分割函数,并在for循环中删除~符号。


1
非常感谢,这节省了我很多时间。 - singrium
5
谢谢!这比所有垃圾方法(如Canny)好太多了,它们会留下很多多余的噪音。 - Osi
如果我想将这些边缘转换为形状,例如整个方框变成黑色形状,我应该怎么做? - Umer Waqas - Python expert

16

通过使用扩大的结构元素和应用替代形态学闭运算和开运算操作,您可以在一定程度上平滑图像。以下是原始版本和平滑版本。

imsmooth im2smooth2

然后取图像的形态梯度。

grad grad2

然后对每个通道应用大津阈值,并合并这些通道。

merged merged2

如果您的图像大小不同(更大),您可能需要更改代码的某些参数或将图像大致调整为此处使用的大小。代码使用 c++编写,但转换到python不难。

/* load color image */
Mat im = imread(INPUT_FOLDER_PATH + string("2.jpg"));
/* 
smooth the image with alternative closing and opening
with an enlarging kernel
*/
Mat morph = im.clone();
for (int r = 1; r < 4; r++)
{
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(2*r+1, 2*r+1));
    morphologyEx(morph, morph, CV_MOP_CLOSE, kernel);
    morphologyEx(morph, morph, CV_MOP_OPEN, kernel);
}
/* take morphological gradient */
Mat mgrad;
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(morph, mgrad, CV_MOP_GRADIENT, kernel);

Mat ch[3], merged;
/* split the gradient image into channels */
split(mgrad, ch);
/* apply Otsu threshold to each channel */
threshold(ch[0], ch[0], 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
threshold(ch[1], ch[1], 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
threshold(ch[2], ch[2], 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
/* merge the channels */
merge(ch, 3, merged);

哇,这是一个聪明的方法。我对左右图片感到惊讶,其中所有内容都已经通过开闭操作平滑处理过了。 - bballdave025
不错的方法,确实,我能选择图像轮廓的最小和最大尺寸吗?我计划对图像进行分类。 - Daniel Saito
@DanielSaito 不清楚你所说的“轮廓的最小和最大尺寸”是什么意思?作为后处理,您可以找到轮廓并进行任何处理。 - dhanushka
@dhanushka 抱歉哈哈,我的意思是要删除被认为是噪声的小轮廓,但这个算法并没有像那些太大的轮廓一样好地去除。在获取轮廓后,只使用那些在范围min<X<max内的轮廓。我正在尝试理解如何限制这个范围。 - Daniel Saito

6
我不确定这种解决方案的健壮性,但是这个想法非常简单。盒子的边缘应该比这些图像上的所有其他高频更加明显。因此,使用一些基本的预处理可以强调它们。
我使用了你的代码制作了一个原型,但轮廓查找不一定是正确的路径。对于迭代的非锐化掩蔽,请原谅 - 没有时间调整参数。 result
import cv2
import numpy as np

def unsharp_mask(img, blur_size = (9,9), imgWeight = 1.5, gaussianWeight = -0.5):
    gaussian = cv2.GaussianBlur(img, (5,5), 0)
    return cv2.addWeighted(img, imgWeight, gaussian, gaussianWeight, 0)

img_file = 'box.png'
img = cv2.imread(img_file, cv2.IMREAD_COLOR)
img = cv2.blur(img, (5, 5))
img = unsharp_mask(img)
img = unsharp_mask(img)
img = unsharp_mask(img)

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

thresh = cv2.adaptiveThreshold(s, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
_, contours, heirarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(contours, key = cv2.contourArea, reverse = True)
#for cnt in cnts:
canvas_for_contours = thresh.copy()
cv2.drawContours(thresh, cnts[:-1], 0, (0,255,0), 3)
cv2.drawContours(canvas_for_contours, contours, 0, (0,255,0), 3)
cv2.imshow('Result', canvas_for_contours - thresh)
cv2.imwrite("result.jpg", canvas_for_contours - thresh)
cv2.waitKey(0)

0

方法1:使用AI模型

如果可行,始终尝试使用图像分割模型来完成您的项目,强大的模型将比任何阈值技术在更广泛的领域中表现更好。 例如Rembg,可以在Huggingface space上在线尝试。
以下是结果:
enter image description here enter image description here

方法2:

与其他答案几乎相似,但采用另一种方法。

  1. 我们使用 cv2.bilateralFilter 来代替关闭和打开以模糊“噪音”,这类似于 Photoshop 的表面模糊,阅读更多
im = cv2.imread('1.png')
blur = cv2.bilateralFilter(im,21,75,75)

enter image description here enter image description here

使用Sobel滤波器来查找边缘。
from skimage.filters import sobel
gray = cv2.cvtColor(blur,cv2.COLOR_BGR2GRAY)
mm = sobel(gray)
mm = ((mm/mm.max())*255).astype('uint8')

enter image description here

应用阈值处理,我在这里使用Sauvola阈值处理
from skimage.filters import threshold_sauvola

mm2 = np.invert(mm)
thresh_sauvola = threshold_sauvola(mm2, window_size=51)
th = mm2 < thresh_sauvola

enter image description here

  1. 膨胀和填充孔:
def fill_hole(input_mask):
  h, w = input_mask.shape
  canvas = np.zeros((h + 2, w + 2), np.uint8)
  canvas[1:h + 1, 1:w + 1] = input_mask.copy()
  mask = np.zeros((h + 4, w + 4), np.uint8)
  cv2.floodFill(canvas, mask, (0, 0), 1)
  canvas = canvas[1:h + 1, 1:w + 1].astype(np.bool)

  return ~canvas | input_mask

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
th2 =cv2.morphologyEx((th*255).astype('uint8'), cv2.MORPH_DILATE, kernel) 
filled = fill_hole(th2==255)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接