去除图像中的虚假小噪声岛 - Python OpenCV

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30369031/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 08:20:24  来源:igfitidea点击:

Remove spurious small islands of noise in an image - Python OpenCV

pythonimageopencvimage-processingfiltering

提问by annena

I am trying to get rid of background noise from some of my images. This is the unfiltered image.

我试图从我的一些图像中去除背景噪音。这是未过滤的图像。

To filter, I used this code to generate a mask of what should remain in the image:

为了过滤,我使用此代码生成了图像中应保留的内容的掩码:

 element = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
 mask = cv2.erode(mask, element, iterations = 1)
 mask = cv2.dilate(mask, element, iterations = 1)
 mask = cv2.erode(mask, element)

With this code and when I mask out the unwanted pixels from the original image, what I get is:

使用此代码,当我从原始图像中屏蔽掉不需要的像素时,我得到的是:

As you can see, all the tiny dots in the middle area are gone, but a lot of those coming from the denser area are also gone. To reduce the filtering, I tried changing the second parameter of getStructuringElement()to be (1,1) but doing this gives me the first image as if nothing has been filtered.

如您所见,中间区域的所有小点都消失了,但是来自较密集区域的许多小点也消失了。为了减少过滤,我尝试将第二个参数更改getStructuringElement()为 (1,1) 但这样做会给我第一张图像,就好像没有过滤任何东西一样。

Is there any way where I can apply some filter that is between these 2 extremes?

有什么方法可以应用介于这两个极端之间的过滤器?

In addition, can anyone explain to me what exactly does getStructuringElement()do? What is a "structuring element"? What does it do and how does its size (the second parameter) affect the level of filtering?

另外,谁能向我解释一下究竟是getStructuringElement()做什么的?什么是“结构元素”?它做什么以及它的大小(第二个参数)如何影响过滤级别?

采纳答案by rayryeng

A lot of your questions stem from the fact that you're not sure how morphological image processing works, but we can put your doubts to rest. You can interpret the structuring element as the "base shape" to compare to. 1 in the structuring element corresponds to a pixel that you want to look at in this shape and 0 is one you want to ignore. There are different shapes, such as rectangular (as you have figured out with MORPH_RECT), ellipse, circular, etc.

您的许多问题源于您不确定形态学图像处理的工作原理,但我们可以消除您的疑虑。您可以将结构元素解释为要与之比较的“基本形状”。结构元素中的 1 对应于您要在此形状中查看的像素,而 0 是您要忽略的像素。有不同的形状,例如矩形(如您所想的那样MORPH_RECT)、椭圆形、圆形等。

As such, cv2.getStructuringElementreturns a structuring element for you. The first parameter specifies the type you want and the second parameter specifies the size you want. In your case, you want a 2 x 2 "rectangle"... which is really a square, but that's fine.

因此,cv2.getStructuringElement为您返回一个结构化元素。第一个参数指定您想要的类型,第二个参数指定您想要的大小。在你的情况下,你想要一个 2 x 2 的“矩形”......这真的是一个正方形,但这很好。

In a more bastardized sense, you use the structuring element and scan from left to right and top to bottom of your image and you grab pixel neighbourhoods. Each pixel neighbourhood has its centre exactly at the pixel of interest that you're looking at. The size of each pixel neighbourhood is the same size as the structuring element.

从更混杂的意义上讲,您使用结构元素并从左到右和从上到下扫描图像,然后获取像素邻域。每个像素邻域的中心都恰好位于您正在查看的感兴趣的像素处。每个像素邻域的大小与结构元素的大小相同。

Erosion

侵蚀

For an erosion, you examine all of the pixels in a pixel neighbourhood that are touching the structuring element. If every non-zero pixelis touching a structuring element pixel that is 1, then the output pixel in the corresponding centre position with respect to the input is 1. If there is at least one non-zero pixel that does nottouch a structuring pixel that is 1, then the output is 0.

对于侵蚀,您检查像素邻域中接触结构元素的所有像素。如果每个非零像素都接触到一个为 1 的结构元素像素,则相对于输入的相应中心位置的输出像素为 1。 如果至少有一个非零像素接触结构像素即 1,则输出为 0。

In terms of the rectangular structuring element, you need to make sure that every pixel in the structuring element is touching a non-zero pixel in your image for a pixel neighbourhood. If it isn't, then the output is 0, else 1. This effectively eliminates small spurious areas of noise and also decreases the area of objects slightly.

就矩形结构元素而言,您需要确保结构元素中的每个像素都接触到图像中像素邻域的非零像素。如果不是,则输出为 0,否则为 1。这有效地消除了小的杂散噪声区域,并略微减少了对象的面积。

The size factors in where the larger the rectangle, the more shrinking is performed. The size of the structuring element is a baseline where any objects that are smaller than this rectangular structuring element, you can consider them as being filtered and not appearing in the output. Basically, choosing a 1 x 1 rectangular structuring element is the same as the input image itself because that structuring element fits all pixels inside it as the pixel is the smallest representation of information possible in an image.

矩形越大,执行的收缩越多。结构元素的大小是基线,其中任何小于此矩形结构元素的对象,您都可以将它们视为已过滤而不出现在输出中。基本上,选择 1 x 1 矩形结构元素与输入图像本身相同,因为该结构元素适合其中的所有像素,因为像素是图像中信息的最小表示形式。

Dilation

扩张

Dilation is the opposite of erosion. If there is at least one non-zero pixel that touches a pixel in the structuring element that is 1, then the output is 1, else the output is 0. You can think of this as slightly enlarging object areas and making small islands bigger.

膨胀与腐蚀相反。如果至少有一个非零像素与结构元素中为 1 的像素相接触,则输出为 1,否则输出为 0。您可以将其视为稍微扩大对象区域并使小岛变大。

The implications with size here is that the larger the structuring element, the larger the areas of the objects will be and the larger the isolated islands become.

这里的大小含义是结构元素越大,对象的面积就越大,孤立的岛也越大。



What you're doing is an erosion first followed by a dilation. This is what is known as an openingoperation. The purpose of this operation is to remove small islands of noise while (trying to) maintain the areas of the larger objects in your image. The erosion removes those islands while the dilation grows back the larger objects to their original sizes.

你所做的是先腐蚀,然后膨胀。这就是所谓的开仓操作。此操作的目的是去除噪声的小岛,同时(尝试)保持图像中较大对象的区域。侵蚀去除了这些岛屿,而膨胀使较大的物体恢复到原来的尺寸。

You follow this with an erosion again for some reason, which I can't quite understand, but that's ok.

出于某种原因,您再次受到侵蚀,我不太明白,但这没关系。



What I would personally do is perform a closingoperation first which is a dilation followed by an erosion. Closing helps group areas that are close together into a single object. As such, you see that there are some larger areas that are close to each other that should probably be joined before we do anything else. As such, I would do a closing first, then do an openingafter so that we can remove the isolated noisy areas. Take note that I'm going to make the closing structuring element size largeras I want to make sure I get nearby pixels and the opening structuring element size smallerso that I don't want to mistakenly remove any of the larger areas.

我个人会做的是首先执行关闭操作,即膨胀然后腐蚀。闭合有助于将靠近的区域组合成单个对象。因此,您会看到一些较大的区域彼此靠近,可能应该在我们做任何其他事情之前将它们连接起来。因此,我会先关闭,然后再打开,以便我们可以去除孤立的嘈杂区域。请注意,我将增大关闭结构元素的大小,因为我想确保获得附近的像素,而打开结构元素的大小更小,这样我就不会错误地删除任何较大的区域。

Once you do this, I would mask out any extra information with the original image so that you leave the larger areas intact while the small islands go away.

一旦你这样做了,我会用原始图像掩盖任何额外的信息,这样你就可以在小岛消失的同时保持较大的区域完好无损。

Instead of chaining an erosion followed by a dilation, or a dilation followed by an erosion, use cv2.morphologyEx, where you can specify MORPH_OPENand MORPH_CLOSEas the flags.

而不是链接一个侵蚀后扩张,或扩张后侵蚀,使用cv2.morphologyEx,您可以在其中指定MORPH_OPENMORPH_CLOSE作为标志。

As such, I would personally do this, assuming your image is called spots.png:

因此,我个人会这样做,假设您的形象被称为spots.png

import cv2
import numpy as np

img = cv2.imread('spots.png')
img_bw = 255*(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) > 5).astype('uint8')

se1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
se2 = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
mask = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, se1)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, se2)

mask = np.dstack([mask, mask, mask]) / 255
out = img * mask

cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out)

The above code is pretty self-explanatory. First, I read in the image and then I convert the image to grayscale and threshold with an intensity of 5 to create a mask of what is considered object pixels. This is a rather clean image and so anything larger than 5 seems to have worked. For the morphology routines, I need to convert the image to uint8and scale the mask to 255. Next, we create two structuring elements - one that is a 5 x 5 rectangle for the closing operation and another that is 2 x 2 for the opening operation. I run cv2.morphologyExtwice for the opening and closing operations respectively on the thresholded image.

上面的代码是不言自明的。首先,我读入图像,然后将图像转换为灰度和强度为 5 的阈值,以创建被视为对象像素的蒙版。这是一个相当干净的图像,所以任何大于 5 的图像似乎都有效。对于形态例程,我需要将图像转换uint8为 255 并将蒙版缩放到 255。接下来,我们创建两个结构元素 - 一个是用于关闭操作的 5 x 5 矩形,另一个是用于打开操作的 2 x 2 矩形. 我cv2.morphologyEx分别在阈值图像上运行了两次打开和关闭操作。

Once I do that, I stack the mask so that it becomes a 3D matrix and divide by 255 so that it becomes a mask of [0,1]and then we multiply this mask with the original image so that we can grab the original pixels of the image back and maintaining what is considered a true object from the mask output.

一旦我这样做,我堆栈,使之成为一个三维矩阵,再除以255,使之成为一个面具的面具[0,1],然后我们乘这个面具与原始图像,使我们可以抓住图像回的原始像素和从掩码输出中维护被认为是真实对象的内容。

The rest is just for illustration. I show the image in a window, and I also save the image to a file called output.png, and its purpose is to show you what the image looks like in this post.

剩下的只是为了说明。我在一个窗口中显示图像,并将图像保存到一个名为 的文件中output.png,其目的是向您展示本文中图像的外观。

I get this:

我明白了:

enter image description here

在此处输入图片说明

Bear in mind that it isn't perfect, but it's much better than how you had it before. You'll have to play around with the structuring element sizes to get something that you consider as a good output, but this is certainly enough to get you started. Good luck!

请记住,它并不完美,但比您之前拥有的要好得多。您必须调整结构元素的大小才能获得您认为良好的输出,但这肯定足以让您入门。祝你好运!



C++ version

C++版本

There have been some requests to translate the code I wrote above into the C++ version using OpenCV. I have finally gotten around to writing a C++ version of the code and this has been tested on OpenCV 3.1.0. The code for this is below. As you can see, the code is very similar to that seen in the Python version. However, I used cv::Mat::setToon a copy of the original image and set whatever was not part of the final mask to 0. This is the same thing as performing an element-wise multiplication in Python.

有人要求使用 OpenCV 将我上面写的代码翻译成 C++ 版本。我终于开始编写代码的 C++ 版本,并且已经在 OpenCV 3.1.0 上进行了测试。代码如下。如您所见,代码与 Python 版本中的代码非常相似。但是,我使用cv::Mat::setTo了原始图像的副本,并将不属于最终掩码的任何内容设置为 0。这与在 Python 中执行逐元素乘法相同。

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char *argv[])
{
    // Read in the image
    Mat img = imread("spots.png", CV_LOAD_IMAGE_COLOR);

    // Convert to black and white
    Mat img_bw;
    cvtColor(img, img_bw, COLOR_BGR2GRAY);
    img_bw = img_bw > 5;

    // Define the structuring elements
    Mat se1 = getStructuringElement(MORPH_RECT, Size(5, 5));
    Mat se2 = getStructuringElement(MORPH_RECT, Size(2, 2));

    // Perform closing then opening
    Mat mask;
    morphologyEx(img_bw, mask, MORPH_CLOSE, se1);
    morphologyEx(mask, mask, MORPH_OPEN, se2);

    // Filter the output
    Mat out = img.clone();
    out.setTo(Scalar(0), mask == 0);

    // Show image and save
    namedWindow("Output", WINDOW_NORMAL);
    imshow("Output", out);
    waitKey(0);
    destroyWindow("Output");
    imwrite("output.png", out);
}

The results should be the same as what you get in the Python version.

结果应该与您在 Python 版本中得到的结果相同。

回答by duhaime

One can also remove small pixel clusters using the remove_small_objectsfunction in skimage:

还可以使用remove_small_objectsskimage 中的函数删除小像素簇:

import matplotlib.pyplot as plt
from skimage import morphology
import numpy as np
import skimage

# read the image, grayscale it, binarize it, then remove small pixel clusters
im = plt.imread('spots.png')
grayscale = skimage.color.rgb2gray(im)
binarized = np.where(grayscale>0.1, 1, 0)
processed = morphology.remove_small_objects(binarized.astype(bool), min_size=2, connectivity=2).astype(int)

# black out pixels
mask_x, mask_y = np.where(processed == 0)
im[mask_x, mask_y, :3] = 0

# plot the result
plt.figure(figsize=(10,10))
plt.imshow(im)

This displays:

这显示:

enter image description here

在此处输入图片说明

To retain only larger clusters, try increasing min_size(smallest size of retained clusters) and decreasing connectivity(size of pixel neighborhood when forming clusters). Using just those two parameters, one can retain only pixel clusters of an appropriate size.

要仅保留较大的簇,请尝试增加min_size(保留簇的最小大小)和减小connectivity(形成簇时像素邻域的大小)。仅使用这两个参数,就可以仅保留适当大小的像素簇。