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

Posted

技术标签:

【中文标题】去除图像中虚假的小噪声岛 - Python OpenCV【英文标题】:Remove spurious small islands of noise in an image - Python OpenCV 【发布时间】:2015-08-02 20:14:26 【问题描述】:

我正在尝试消除一些图像中的背景噪音。这是未经过滤的图像。

为了过滤,我使用此代码生成应保留在图像中的蒙版:

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

【问题讨论】:

请定义/识别什么是噪音,什么是数据。 【参考方案1】:

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

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

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

侵蚀

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

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

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

扩张

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

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


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

由于某种原因,您再次使用侵蚀进行此操作,我不太明白,但没关系。


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

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

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

因此,假设您的图片名为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 版本中得到的结果相同。

【讨论】:

@dvsaraiva - 不客气!感谢您的投票:) 你碰巧有一个与此等效的 c++ 吗? @Zypps987 不,但我可以写一个。我目前没有能力,所以你必须耐心等待。 这个答案治愈了我从阅读大量随意答案中得到的灼伤。 :) @rayryeng 我确实浏览了你的个人资料。我也会看看你的其他答案。 :)【参考方案2】:

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

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)

这显示:

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

【讨论】:

以上是关于去除图像中虚假的小噪声岛 - Python OpenCV的主要内容,如果未能解决你的问题,请参考以下文章

Opencv——图像添加椒盐噪声高斯滤波去除噪声原理及手写Python代码实现

小波变换后的图像如何去噪

OpenCV - 去除图像中的噪声

如何从 MATLAB 中的图像中去除高斯噪声?

从图像中去除高频垂直剪切噪声

使用傅里叶变换从图像中去除周期性噪声