去除图像中的虚假小噪声岛-Python OpenCV
image
image-processing
opencv
python
9
0

我试图消除某些图像的背景噪音。这是未过滤的图像。

为了进行过滤,我使用了以下代码来生成应保留在图像中的蒙版:

 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()确切作用吗?什么是“结构要素”?它是做什么的,它的大小(第二个参数)如何影响过滤级别?

参考资料:
Stack Overflow
收藏
评论
共 1 个回答
高赞 时间 活跃

您的许多问题都来自您不确定形态图像处理如何工作的事实,但是我们可以消除您的疑虑。您可以将结构元素解释为要比较的“基本形状”。结构元素中的1对应于您要在此形状中查看的像素,0是您要忽略的像素。有不同的形状,例如矩形(如您使用MORPH_RECT ),椭圆形,圆形等。

这样, cv2.getStructuringElement为您返回一个结构元素。第一个参数指定所需的类型,第二个参数指定所需的大小。在您的情况下,您需要一个2 x 2的“矩形” ...这实际上是一个正方形,但这很好。

从某种程度上讲,您使用结构化元素并从图像的左到右,从上到下扫描,从而获得像素邻域。每个像素邻域的中心都恰好在您要关注的像素处。每个像素邻域的大小与结构元素的大小相同。

侵蚀

对于腐蚀,可以检查像素附近的所有与结构元素接触的像素。如果每个非零像素都在触摸一个为1的结构元素像素,则相对于输入的中心位置处的输出像素为1。如果至少有一个非零像素不与一个结构像素相接触。即1,则输出为0。

对于矩形结构元素,您需要确保该结构元素中的每个像素都触摸到图像中的非零像素,以达到像素邻域的目的。如果不是,则输出为0,否则为1。这将有效地消除杂散噪声小区域,并稍微减小对象的区域。

矩形越大的尺寸因子,收缩越大。结构元素的大小是一个基线,所有小于此矩形结构元素的对象都可以视为基线,可以将其视为已过滤且未出现在输出中。基本上,选择1 x 1矩形结构元素与输入图像本身相同,因为该结构元素适合其内部的所有像素,因为像素是图像中可能的最小信息表示。

扩张

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

这里的尺寸含义是,结构元素越大,物体的面积就越大,孤立的孤岛也就越大。


您正在做的是先腐蚀然后膨胀。这就是所谓的打开操作。该操作的目的是在(试图)保持图像中较大对象的区域的同时,消除小干扰。侵蚀将这些岛移除,而膨胀使较大的物体重新生长回其原始大小。

您出于某种原因再次受到侵蚀,我不太了解,但是没关系。


我个人会做的是先执行闭合操作,先进行扩张,然后进行侵蚀。关闭有助于将彼此靠近的区域分组为一个对象。这样一来,您会发现在执行其他任何操作之前,应该将一些彼此靠近的较大区域合并。因此,我会先关闭,然后再打开 ,以便我们可以移除孤立的嘈杂区域。请注意,我要使闭合结构元素的尺寸更大,因为我要确保附近的像素变小 ,而开口结构元素的尺寸要较小,这样我就不会错误地删除任何较大的区域。

完成此操作后,我将用原始图像遮盖所有其他信息,以便您在较大的区域完好无损而小岛消失的情况下。

可以使用cv2.morphologyEx ,而不是将腐蚀后的膨胀链或膨胀后的腐蚀cv2.morphologyEx ,可以在其中指定MORPH_OPENMORPH_CLOSE作为标记。

因此,假设您的图片称为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)

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

完成此操作后,我将遮罩堆叠起来,使其成为3D矩阵并除以255,以使其成为[0,1]的遮罩,然后将该遮罩与原始图像相乘,以便可以抓取原始像素图像返回并维护遮罩输出中被视为真实对象的对象。

其余仅用于说明。我在窗口中显示图像,还将图像保存到名为output.png的文件中,其目的是向您展示该图像在本文中的外观。

我得到这个:

在此处输入图片说明

请记住,它并不完美,但是比您以前拥有的要好得多。您必须尝试使用结构元素的大小,才能获得一些您认为不错的输出,但这当然足以让您入门。祝好运!


C ++版本

有人要求使用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);
}

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

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号