删除图像中虚假的小噪声岛 - Python OpenCV [英] Remove spurious small islands of noise in an image - Python OpenCV

查看:204
本文介绍了删除图像中虚假的小噪声岛 - Python OpenCV的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正试图摆脱一些图像背景噪音。这是未经过滤的图片。





要过滤,我使用此代码生成应保留在图像中的掩码:

  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)

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



正如你所看到的,中间区域的所有小点都消失了,但很多来自密集区域的小点也消失了。为了减少过滤,我尝试将 getStructuringElement()的第二个参数更改为(1,1),但这样做会给我第一个图像,就像没有过滤任何内容一样。



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



此外,任何人都可以向我解释究竟是什么 getStructuringElement()呢?什么是结构元素?它做了什么以及它的大小(第二个参数)如何影响过滤水平?

解决方案

你的很多问题源于你不确定形态图像处理是如何工作的事实,但我们可以让你的疑虑得到休息。您可以将结构元素解释为要与之比较的基本形状。 structuring元素中的1对应于您想要在此形状中查看的像素,0是您要忽略的像素。有不同的形状,如矩形(如你已经想到 MORPH_RECT ),椭圆,圆形等。



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



在更加卑鄙的意义上,你使用的是构造元素并从图像的左侧到右侧以及从上到下扫描,您可以抓取像素邻域。每个像素邻域的中心都恰好位于您正在查看的感兴趣像素上。每个像素邻域的大小与结构元素的大小相同。



侵蚀



对于侵蚀,您检查像素邻域中接触结构元素的所有像素。如果每个非零像素正在触摸1的结构化元素像素,则相对于输入的相应中心位置的输出像素为1.如果存在至少一个非零像素触摸1的结构化像素,然后输出为0.



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



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



膨胀



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



这里对尺寸的影响是结构元素越大,对象的面积就越大,孤立的岛就越大。






你所做的是先侵蚀,然后是扩张。这就是所谓的开放操作。此操作的目的是在(尝试)维护图像中较大对象的区域时去除小的噪声岛。侵蚀消除了那些岛屿,而扩张使较大的物体恢复到原来的大小。



你因为某种原因再次受到侵蚀,我不能完全明白了,但没关系。






我个人会做的是首先执行关闭操作这是一种扩张,然后是侵蚀。结算有助于将靠近的区域分组为单个对象。因此,您会看到在我们执行任何其他操作之前,可能会有一些较大的区域彼此接近。因此,我会首先关闭,然后执行开启,以便我们可以移除孤立的噪音区域。请注意,我将使结束元素大小更大,因为我想确保我得到附近的像素和开放结构元素大小更小以便我不要我不想错误地删除任何较大的区域。



一旦你这样做,我会用原始图像掩盖任何额外的信息,这样你就可以离开更大的区域当小岛屿消失时,完好无损。



使用在原始图像的副本上,并将不是最终掩码的一部分设置为0.这与在Python中执行逐元素乘法相同。

  #include< opencv2 / opencv.hpp>使用命名空间cv 

;

int main(int argc,char * argv [])
{
//读入图片
Mat img = imread(spots.png, CV_LOAD_IMAGE_COLOR);

//转换为黑白
Mat img_bw;
cvtColor(img,img_bw,COLOR_BGR2GRAY);
img_bw = img_bw> 5;

//定义结构元素
Mat se1 = getStructuringElement(MORPH_RECT,Size(5,5));
Mat se2 = getStructuringElement(MORPH_RECT,Size(2,2));

//执行收盘然后开盘
垫面膜;
morphologyEx(img_bw,mask,MORPH_CLOSE,se1);
morphologyEx(mask,mask,MORPH_OPEN,se2);

//过滤输出
Mat out = img.clone();
out.setTo(Scalar(0),mask == 0);

//显示图像并保存
namedWindow(输出,WINDOW_NORMAL);
imshow(输出,out);
waitKey(0);
destroyWindow(输出);
imwrite(output.png,out);
}

结果应与Python版本中的结果相同。 / p>

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.

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?

解决方案

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.

As such, cv2.getStructuringElement returns 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.

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 pixel is 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 not touch a structuring pixel that is 1, then the output is 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.

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.

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.

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 opening operation. 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 closing operation 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 opening after so that we can remove the isolated noisy areas. Take note that I'm going to make the closing structuring element size larger as I want to make sure I get nearby pixels and the opening structuring element size smaller so 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_OPEN and MORPH_CLOSE as the flags.

As such, I would personally do this, assuming your image is called 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 uint8 and 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.morphologyEx twice for the opening and closing operations respectively on the thresholded image.

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.

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.

I get this:

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

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::setTo on 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.

#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 OpenCV的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆